Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
355656b5c8 | ||
|
|
cd10cccbf4 | ||
|
|
d044ef1efc | ||
|
|
65b87819a0 | ||
|
|
be62783b98 |
@@ -2001,5 +2001,379 @@
|
||||
"qa_fixtures_copied": false,
|
||||
"accepted_scenarios_repeated_manually": false,
|
||||
"limitations": "No new scenario executed in homologation and no real dossier or personal data created. QA functional evidence reused; destination verification limited to revision, configuration and entry routes. Sector/fiscal exclusions remain explicit."
|
||||
},
|
||||
"closure_scope_reconciliation_20261024": {
|
||||
"kind": "analysis_and_proposed_batch_only",
|
||||
"reference_commit": "276d833a4b4f9071db328b653f48529b2dfac4f9",
|
||||
"scope": [
|
||||
"Financial planning consistency",
|
||||
"Existing published IVA on net monthly sales",
|
||||
"Data/calculation/save/recovery/report continuity",
|
||||
"Screen/export values/context/source/version agreement",
|
||||
"Explicit unavailable functionality without presumed results"
|
||||
],
|
||||
"accepted_proofs_reused": [
|
||||
"Independent financial controls and cashflow recovery",
|
||||
"Published IVA evaluator, server selection and frozen versions",
|
||||
"Monthly sales reuse, zero versus missing, stale history",
|
||||
"General CSV/Word/ZIP and native footer",
|
||||
"Selected opportunity native PDF, 8036b9b0/cdf8fcfb"
|
||||
],
|
||||
"code_presence_reconciled": [
|
||||
"3f5430c3 is ancestor of reference/stabilization-20261024",
|
||||
"0b0cf761 is ancestor of reference/stabilization-20261024"
|
||||
],
|
||||
"public_availability_read": {
|
||||
"url": "https://teste-investidor.tempo.co.mz/api/v1/fiscal-consumer",
|
||||
"enabled": true,
|
||||
"coverage_start": "2026-01-01",
|
||||
"coverage_end": "2026-12-31",
|
||||
"not_a_fresh_private_rule_audit": true
|
||||
},
|
||||
"items": [
|
||||
{
|
||||
"id": "S1",
|
||||
"classification": "code_gap_new_isolated_diagnostic",
|
||||
"consumer": "Saved cashflow to IVA fallback and report",
|
||||
"cause": "Cashflow snapshot v1 has no original currency; consumers use current project currency",
|
||||
"evidence": "outputs/simulator-scope-20261024/currency-gap.json",
|
||||
"observed": "Saved amounts with original project USD become monthly IVA 123 MZN and MZN report after project currency changes; original currency absent from snapshot",
|
||||
"proposal": "Version currency provenance; common mismatch/reconciliation guard; retain v1 bytes and require only missing currency where origin cannot be recovered",
|
||||
"delta_proof": "Currency change, saved recovery and matching panel/IVA/report without conversion or loss"
|
||||
},
|
||||
{
|
||||
"id": "S2",
|
||||
"classification": "code_gap_directed_UI_proof_pending",
|
||||
"consumer": "Monthly IVA pending execution recovery",
|
||||
"cause": "Availability guard returns before pending-ID GET; button disabled when new admissions disabled",
|
||||
"existing_server_contract": "Admitted execution/version and authenticated history remain readable",
|
||||
"proposal": "Separate creation from read/resume by ID; do not reactivate consumer or create another execution",
|
||||
"delta_proof": "Already admitted queued/running execution, new admissions unavailable, same ID recovered completed and reported without duplicate"
|
||||
},
|
||||
{
|
||||
"id": "S3",
|
||||
"classification": "specific_evidence_gap_not_presumed_defect",
|
||||
"consumer": "Monthly IVA section in final dossier CSV/Word/native PDF",
|
||||
"proposal": "One representative existing synthetic snapshot; compare exact values, zero/missing, selected context, source/version and current/history label; inspect only new monthly block pagination",
|
||||
"reuse": "General exporters, ZIP, native footer and other accepted pagination proofs"
|
||||
}
|
||||
],
|
||||
"proposed_order": [
|
||||
"Fix S1 and S2 together",
|
||||
"Check only changed dependencies",
|
||||
"Complete S3 on resulting candidate, fixing only observed failures",
|
||||
"Integrate and install through existing Git process after directed QA; verify installed delta"
|
||||
],
|
||||
"documentary_dependencies_identified": [],
|
||||
"six_legacy_associations_and_two_incomplete_originals": "Remain documentary; no dependency identified for S1-S3",
|
||||
"excluded": [
|
||||
"Other taxes",
|
||||
"New IVA rates/regimes/eligibility or incentives",
|
||||
"Deductions/credits/refunds/final settlement",
|
||||
"Full Modelo A",
|
||||
"Extension beyond demonstrated 2026 coverage",
|
||||
"Statistics and AI"
|
||||
],
|
||||
"functional_fixes_executed": false,
|
||||
"deployment_executed": false,
|
||||
"active_business_data_writes": 0,
|
||||
"accepted_tests_reexecuted": false,
|
||||
"historical_output_files_missing_in_this_workspace_do_not_revoke_acceptance": true,
|
||||
"matrix_ids": [
|
||||
"AUD-SIM-01",
|
||||
"AUD-SIM-INT02",
|
||||
"AUD-SIM-INT03",
|
||||
"AUD-SIM-INT04",
|
||||
"AUD-SIM-INT05",
|
||||
"AUD-SIM-INT06",
|
||||
"AUD-SIM-INT07",
|
||||
"AUD-SIM-INT08"
|
||||
]
|
||||
},
|
||||
"closure_qa_20261024": {
|
||||
"status": "S1_S2_S3_passed_QA_ready_for_integration",
|
||||
"authorization": "Tempo execute S1/S2 jointly in QA, then S3; no fiscal rule changes; candidate for existing Git integration",
|
||||
"functional_candidate": "65b87819a0629f79f415788a6aa1a16fe031e37e",
|
||||
"branch": "codex/simulator-closure-qa-20261024",
|
||||
"preserved_reference": "276d833a4b4f9071db328b653f48529b2dfac4f9",
|
||||
"integrated_main": false,
|
||||
"homologation_deployed": false,
|
||||
"S1": {
|
||||
"status": "corrected_and_demonstrated_QA",
|
||||
"snapshot_version": 2,
|
||||
"currency_origin_preserved": true,
|
||||
"original_USD_to_project_MZN": "guarded; report/panel retain USD, IVA refuses reuse as MZN",
|
||||
"explicit_resolution": "Restore original currency on project; unknown legacy origin requires explicit currency confirmation only",
|
||||
"legacy_v1_inputs_preserved": true,
|
||||
"private_draft_roundtrip_passed": true
|
||||
},
|
||||
"S2": {
|
||||
"status": "corrected_and_demonstrated_QA",
|
||||
"method": "GET same admitted execution ID, credentials same-origin; wrong response ID rejected",
|
||||
"new_admissions_disabled": true,
|
||||
"visual_read_requests": 1,
|
||||
"visual_create_requests": 0,
|
||||
"real_API_owner_read_and_wrong_owner_denial": true,
|
||||
"execution_count_preserved": true,
|
||||
"rule_version_fingerprint_unchanged": true
|
||||
},
|
||||
"S3": {
|
||||
"status": "final_CSV_Word_and_Edge_PDF_verified",
|
||||
"artifact_inspection": {
|
||||
"files": {
|
||||
"monthly-final.csv": {
|
||||
"bytes": 10639,
|
||||
"sha256": "0b5fc670b0c252391aa99c0bff4e21af1e7a7b9c3a0e4ac6e2e2359a647d8a0e"
|
||||
},
|
||||
"monthly-final.docx": {
|
||||
"bytes": 14101,
|
||||
"sha256": "f83629e6ed3090a0d99d4843b11d4388048a6c993d2e1f7fabd87282bf0c2b38"
|
||||
},
|
||||
"monthly-final.html": {
|
||||
"bytes": 11530,
|
||||
"sha256": "09d40095550cae9bfdd29fcb5c6c7e0ad825a26170997a23347a8320a352b3a7"
|
||||
}
|
||||
},
|
||||
"same_frozen_monthly_fields_verified": [
|
||||
"160.0048",
|
||||
"320.0096",
|
||||
"1160.0348",
|
||||
"2320.0696",
|
||||
"MZN",
|
||||
"opportunity:309",
|
||||
"Manica",
|
||||
"2026-01-01",
|
||||
"2026-04-01",
|
||||
"Fonte sintética QA",
|
||||
"IVA-GERAL-BASE-LIQUIDA"
|
||||
],
|
||||
"csv_rows": 73,
|
||||
"first_opportunity_not_included": true,
|
||||
"fiscal_calculation_repeated": false,
|
||||
"native_pdf_status": "awaiting_saved_file; request already sent"
|
||||
},
|
||||
"native_pdf_expected_path": "C:/laragon/www/portal-investidor/outputs/simulator-closure-qa-20261024/monthly-final.pdf",
|
||||
"user_response": "Will save at requested path",
|
||||
"HTML_is_not_native_pdf_proof": true,
|
||||
"fixture": "Explicit synthetic QA, frozen accepted monthly numbers; not legal validation or real-rule publication",
|
||||
"pdf_generation": {
|
||||
"renderer": "Installed Microsoft Edge, isolated headless print engine",
|
||||
"manual_print_dialog_used": false,
|
||||
"source": "C:\\laragon\\www\\portal-investidor\\outputs\\simulator-closure-qa-20261024\\ui\\monthly-review.html",
|
||||
"source_sha256": "b44ee5fc573f8deb8d5427f9f3b6a667c1d60fc23de4509d383b23acb9df2779",
|
||||
"pdf": "C:\\laragon\\www\\portal-investidor\\outputs\\simulator-closure-qa-20261024\\monthly-final.pdf",
|
||||
"bytes": 111910,
|
||||
"sha256": "7719a05136f6dba668b621f551626060d10db5fcbb84815cdad112a1eab9079b",
|
||||
"exit_code": 0
|
||||
},
|
||||
"pdf_inspection": {
|
||||
"status": "native_file_read_and_rendered",
|
||||
"exact_path": "C:\\laragon\\www\\portal-investidor\\outputs\\simulator-closure-qa-20261024\\monthly-final.pdf",
|
||||
"sha256": "7719a05136f6dba668b621f551626060d10db5fcbb84815cdad112a1eab9079b",
|
||||
"pages": [
|
||||
{
|
||||
"page": 1,
|
||||
"size": [
|
||||
594.9599609375,
|
||||
841.9199829101562
|
||||
],
|
||||
"characters": 1633,
|
||||
"image": "C:\\laragon\\www\\portal-investidor\\outputs\\simulator-closure-qa-20261024\\monthly-pdf-pages\\page-01.png",
|
||||
"footer_boxes": [
|
||||
[
|
||||
48.0,
|
||||
807.8525390625,
|
||||
257.5710144042969,
|
||||
817.9072265625
|
||||
]
|
||||
],
|
||||
"footer_separated": true
|
||||
},
|
||||
{
|
||||
"page": 2,
|
||||
"size": [
|
||||
594.9599609375,
|
||||
841.9199829101562
|
||||
],
|
||||
"characters": 2299,
|
||||
"image": "C:\\laragon\\www\\portal-investidor\\outputs\\simulator-closure-qa-20261024\\monthly-pdf-pages\\page-02.png",
|
||||
"footer_boxes": [
|
||||
[
|
||||
48.0,
|
||||
807.8525390625,
|
||||
257.5710144042969,
|
||||
817.9072265625
|
||||
]
|
||||
],
|
||||
"footer_separated": true
|
||||
},
|
||||
{
|
||||
"page": 3,
|
||||
"size": [
|
||||
594.9599609375,
|
||||
841.9199829101562
|
||||
],
|
||||
"characters": 2047,
|
||||
"image": "C:\\laragon\\www\\portal-investidor\\outputs\\simulator-closure-qa-20261024\\monthly-pdf-pages\\page-03.png",
|
||||
"footer_boxes": [
|
||||
[
|
||||
48.0,
|
||||
807.8525390625,
|
||||
257.5710144042969,
|
||||
817.9072265625
|
||||
]
|
||||
],
|
||||
"footer_separated": true
|
||||
},
|
||||
{
|
||||
"page": 4,
|
||||
"size": [
|
||||
594.9599609375,
|
||||
841.9199829101562
|
||||
],
|
||||
"characters": 1832,
|
||||
"image": "C:\\laragon\\www\\portal-investidor\\outputs\\simulator-closure-qa-20261024\\monthly-pdf-pages\\page-04.png",
|
||||
"footer_boxes": [
|
||||
[
|
||||
48.0,
|
||||
807.8525390625,
|
||||
257.5710144042969,
|
||||
817.9072265625
|
||||
]
|
||||
],
|
||||
"footer_separated": true
|
||||
}
|
||||
],
|
||||
"checks": {
|
||||
"160.0048": true,
|
||||
"320.0096": true,
|
||||
"1160.0348": true,
|
||||
"2320.0696": true,
|
||||
"MZN": true,
|
||||
"opportunity:309": true,
|
||||
"Manica": true,
|
||||
"2026-01-01": true,
|
||||
"2026-04-01": true,
|
||||
"Fonte sintética QA": true,
|
||||
"IVA-GERAL-BASE-LIQUIDA": true,
|
||||
"first_opportunity_absent": true,
|
||||
"rule_version_4": true,
|
||||
"zero_and_missing": true
|
||||
},
|
||||
"content_checks_passed": true,
|
||||
"visual_review": "passed: all four rendered pages inspected; repeated headers, intact rows, no clipping/overlap, footers separated, numbering 1/4-4/4",
|
||||
"regenerated": false,
|
||||
"render_method": "Installed Microsoft Edge headless print engine, isolated profile; not manual print-dialog save",
|
||||
"generation_authorized_by_user": true
|
||||
},
|
||||
"manual_print_dialog_used": false,
|
||||
"regeneration_authorization": "User explicitly requested generating the file again to complete this step"
|
||||
},
|
||||
"checks": {
|
||||
"frontend_new_cases": 4,
|
||||
"frontend_total_direct_dependencies": 12,
|
||||
"frontend_passed": 12,
|
||||
"API_tests": 2,
|
||||
"API_assertions": 23,
|
||||
"QA_database": "simulator_fiscal_delta_20261021_test",
|
||||
"QA_port": 55441,
|
||||
"QA_transactions_rolled_back": true,
|
||||
"typescript": "passed",
|
||||
"build": "passed",
|
||||
"diff_check": "passed"
|
||||
},
|
||||
"unchanged": [
|
||||
"Fiscal server/evaluator/rules/sources",
|
||||
"Schema/configuration/permissions/routes",
|
||||
"Active business data",
|
||||
"Accepted stabilization and opportunity-context closures"
|
||||
],
|
||||
"remaining": [],
|
||||
"ready_for_integration": true,
|
||||
"new_taxes_added": false,
|
||||
"integration_note": "Candidate only; existing Git process and installed-delta verification still to execute after integration authorization; no homologation delivery claimed"
|
||||
},
|
||||
"closure_homologation_20261024": {
|
||||
"status": "installed_delta_verified",
|
||||
"authorization": "Tempo current conversation: integrate S1-S3 through existing Git process, install homologation and verify only delta",
|
||||
"served_commit": "cd10cccbf45f53836d8bf7b2bd90794d99602cb9",
|
||||
"functional_candidate": "65b87819",
|
||||
"QA_closure_record": "cd10cccb",
|
||||
"previous_main": "276d833a4b4f9071db328b653f48529b2dfac4f9",
|
||||
"stabilization_tag": "reference/stabilization-20261024",
|
||||
"Git_integration": "fast-forward after fetch; no conflicting/concurrent main changes found; no force",
|
||||
"installed_delta": {
|
||||
"served_revision": {
|
||||
"commit": "cd10cccbf45f53836d8bf7b2bd90794d99602cb9",
|
||||
"branch": "main",
|
||||
"built_at": "2026-10-24T09:39:42.304316+00:00",
|
||||
"manifest_sha256": "7957c776ab2c2eab516bcc6caedcdeb9943184645b4a21a89a4f709a4ba974f6"
|
||||
},
|
||||
"manifest_hash_matches": true,
|
||||
"compiled_delta_markers": {
|
||||
"currency_origin_guard": "assets/project-dossier-DNSA8_QR.js",
|
||||
"original_currency_report": "assets/project-dossier-DNSA8_QR.js",
|
||||
"same_id_recovery": "assets/services-BXcNtigY.js",
|
||||
"explicit_currency_resolution": "assets/services-BXcNtigY.js"
|
||||
},
|
||||
"assets": [
|
||||
{
|
||||
"file": "assets/project-dossier-DNSA8_QR.js",
|
||||
"status": 200,
|
||||
"content_type": "application/javascript",
|
||||
"sha256": "8cfe6743dddc5f35ddfe7ccdd6bb4d859719c1e263c6e51d9ac94b30a5fe1b76"
|
||||
},
|
||||
{
|
||||
"file": "assets/simulation-form-ClLDsvMf.js",
|
||||
"status": 200,
|
||||
"content_type": "application/javascript",
|
||||
"sha256": "fc829b447dcb1eec1cc550edcbfaa28f4db7b8973b4f86dab8a065cbbd8f0421"
|
||||
},
|
||||
{
|
||||
"file": "assets/project-dossier-document-CJ_IVxDn.js",
|
||||
"status": 200,
|
||||
"content_type": "application/javascript",
|
||||
"sha256": "ac4013924c4a78427c3e134654f331bf6fce313733532e7084ed85cf344106b2"
|
||||
},
|
||||
{
|
||||
"file": "assets/services-BXcNtigY.js",
|
||||
"status": 200,
|
||||
"content_type": "application/javascript",
|
||||
"sha256": "66d8af6670347a19e4006323f6c8b84e625a2dbaad6b4de777b4d979faa67093"
|
||||
}
|
||||
],
|
||||
"simulator_public_status": 200,
|
||||
"simulator_uses_installed_app_entry": "assets/app-Ds1F0RnS.js",
|
||||
"existing_fiscal_availability": {
|
||||
"data": {
|
||||
"enabled": true,
|
||||
"coverage_start": "2026-01-01",
|
||||
"coverage_end": "2026-12-31",
|
||||
"message": "IVA geral sobre base líquida disponível; enquadramento declarado."
|
||||
}
|
||||
},
|
||||
"synthetic_dossiers_requests_or_calculations_created": 0,
|
||||
"QA_acceptance_tests_repeated": false,
|
||||
"method": "Read-only public installed delta: revision, manifest, compiled changed features, simulator page and existing availability; no authenticated DB audit claimed",
|
||||
"shared_guard_asset_matches_QA_bytes": true,
|
||||
"reference_276d833a_is_ancestor": true,
|
||||
"fiscal_server_schema_config_routes_deploy_diff": [],
|
||||
"cross_environment_asset_validation": "Served build checked against its own revision manifest and compiled changed features; not a claim of identical complete bundles across Windows QA and Linux production"
|
||||
},
|
||||
"functional_files": [
|
||||
"resources/js/components/simulation-cashflow-planner.tsx",
|
||||
"resources/js/components/simulation-monthly-iva.tsx",
|
||||
"resources/js/lib/monthly-cashflow.ts",
|
||||
"resources/js/lib/monthly-iva.ts",
|
||||
"resources/js/lib/project-dossier.ts"
|
||||
],
|
||||
"preservation": "Stabilization ancestry and accepted proofs preserved; no fiscal server/schema/config/routes/deploy diff. No new authenticated database count audit claimed. No destination business-data mutation or new synthetic calculation requested.",
|
||||
"accepted_QA_tests_repeated": false,
|
||||
"PDF_regenerated_for_delivery": false,
|
||||
"remaining_in_S1_S2_S3": [],
|
||||
"legacy_snapshot_limit": "Original currency absent in v1 requires explicit confirmation; amounts preserved, MZN not presumed",
|
||||
"not_full_all_tax_completion": true,
|
||||
"next_fiscal_batch": "Scope from attached models; no automatic new tax/rule activation",
|
||||
"statistics_and_AI": "remain deferred"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,59 @@
|
||||
# Rastreabilidade, utilização e formação
|
||||
|
||||
## Simulador S1–S3 — entregue na homologação em `cd10cccb`, 24/10/2026
|
||||
|
||||
Por autorização expressa da Tempo após o fecho QA, integrado pelo Git existente, de `276d833a` para **`cd10cccbf45f53836d8bf7b2bd90794d99602cb9`**, que contém a correcção funcional `65b87819`. `git fetch` não encontrou alterações concorrentes em main; avanço normal, sem force. A referência `276d833a`/`reference/stabilization-20261024` permanece ancestral. Não houve edição da pasta servida nem migração/importação solicitada.
|
||||
|
||||
**Delta instalado conferido por leitura pública:** `/build/revision.json` identifica `cd10cccb`; SHA256 do manifesto servido coincide com o da revisão (`7957c776ab2c2eab516bcc6caedcdeb9943184645b4a21a89a4f709a4ba974f6`). Quatro assets do simulador/relatório responderam 200; os bundles contêm a guarda da moeda original, a confirmação explícita e a recuperação por mesmo ID. O asset comum da guarda de moeda coincide por bytes com o build QA. O simulador responde 200 e referencia a entrada app do manifesto instalado. O consumidor IVA mantém disponibilidade `enabled=true` e cobertura de 2026. Não se exige identidade dos bundles completos entre Windows QA e Linux; a conferência usa o manifesto do próprio build servido, os assets e o delta de código.
|
||||
|
||||
O diff de `app`, `database`, `config`, `routes` e `deploy` contra `276d833a` é vazio: sem alteração de motor, regras/fontes fiscais, schema, permissões, configuração ou processo de entrega. Cinco fontes frontend constituem o delta funcional de moeda/recuperação/relatório. Não foram criados dossiers, pedidos ou cálculos sintéticos na homologação, nem repetidos os ensaios aceites. Provas QA 12 frontend, 2 API/23 asserções, TypeScript/build e PDF de quatro páginas são reutilizadas, sem nova impressão. Não se apresenta esta leitura pública como auditoria autenticada nova dos dados protegidos.
|
||||
|
||||
**Saldo S1–S3: entregue, sem pendências deste lote.** Nos snapshots antigos sem moeda, a confirmação explícita da origem continua necessária, preservando os montantes; não se presume MZN nem se convertem valores. Este fecho abrange moeda da tesouraria, recuperação do IVA mensal e exportações; **não conclui todos os impostos**. Os modelos anexados delimitam o próximo lote fiscal, sem activação implícita. As pendências documentais anteriores mantêm-se no seu âmbito. Recibo integrado existente: chave `closure_homologation_20261024`; prova do delta em `outputs/simulator-delivery-20261024/installed-delta.json`. Os estados QA/pendentes abaixo são histórico superado por esta entrega.
|
||||
|
||||
## Simulador S1/S2/S3 — candidata QA `65b87819`, 24/10/2026
|
||||
|
||||
**Actualização de fecho S3:** a Tempo pediu expressamente voltar a gerar o ficheiro. O PDF foi gravado directamente em `outputs/simulator-closure-qa-20261024/monthly-final.pdf` pelo motor de impressão do Microsoft Edge em modo headless, com perfil isolado, a partir da mesma saída HTML final; não foi gravação manual pelo diálogo de impressão. Ficheiro de 111 910 bytes, SHA256 `7719a05136f6dba668b621f551626060d10db5fcbb84815cdad112a1eab9079b`. As **quatro páginas A4** foram extraídas, renderizadas e inspeccionadas visualmente: valores exactos, MZN, oportunidade `309`/Manica, Janeiro–Abril/2026, fonte sintética QA e versão 4 conservados; zero distinto de falta, cabeçalhos repetidos, linhas intactas, rodapés separados e numeração 1/4–4/4. CSV e Word previamente conferidos mantidos, sem recálculo fiscal. **S1, S2 e S3 concluídos em QA; candidata pronta para integração pelo Git.** Saldo QA deste lote: zero; integração, disponibilização e conferência do delta instalado ainda não executadas. Os estados de espera abaixo são histórico anterior a esta geração/conferência e não reabrem S3. Recibo integrado e mesmas oito linhas da matriz actualizados; referência `276d833a` e regras fiscais preservadas.
|
||||
|
||||
Execução autorizada pela Tempo após `be62783b`, preservando `276d833a` e a etiqueta `reference/stabilization-20261024`. Ramo `codex/simulator-closure-qa-20261024`; **não integrado em main nem entregue na homologação**. Cinco fontes frontend alteradas; sem alterações em motor/regras/fontes fiscais, schema, configuração, permissões, rotas ou dados activos. Sem impostos adicionais.
|
||||
|
||||
| Recorte | Correcção e resultado dirigido |
|
||||
| --- | --- |
|
||||
| **S1 — corrigido e demonstrado em QA** | Novos snapshots de tesouraria v2 conservam moeda de origem. Painel, CSV próprio e relatório usam essa moeda; o fallback de vendas para IVA recusa divergência. USD → MZN conserva `123 USD`, explica a diferença e permite restaurar USD no projecto por acção explícita. Os montantes não são convertidos nem reinterpretados. Snapshots v1 continuam legíveis; moeda sem origem recuperável exige confirmação única, conservando valores/modo/meses vazios. Não se presume MZN. A observação visual detectou um rótulo residual de saldo na moeda actual; corrigido para a moeda de origem. O rótulo do selector passou a «Moeda do projecto», distinguindo-o dos pressupostos guardados. |
|
||||
| **S2 — corrigido e demonstrado em QA** | Recuperação mensal usa GET no mesmo ID, com credenciais e verificação do identificador devolvido; não depende da disponibilidade para novas admissões. Botão específico continua disponível com consumidor desligado. Resposta não substitui snapshot alterado entretanto ou componente desmontado. Interface isolada: consumidor indisponível, uma consulta GET, zero criações POST, mesma execução concluída. API real de QA: execução admitida → consumidor desligado só na configuração do teste → mesmo ID em fila/concluído; outra conta continua recusada e contagem de execuções não aumenta. Regras existentes reutilizadas, sem criar/publicar/suspender regra; fingerprint integral das versões igual. |
|
||||
| **S3 — CSV/Word e fonte de impressão conferidos; PDF nativo ainda pendente** | Mesmo bloco mensal sintético congelado, sem recalcular: Janeiro `160.0048`, Fevereiro `320.0096`, Março zero, Abril sem informação; MZN, oportunidade `309`/Manica, período Janeiro–Abril/2026, fonte identificada como sintética QA e versão 4. CSV e Word finais lidos e comparados, incluindo ausência da primeira oportunidade/Sofala. A fonte HTML conserva os mesmos dados. O relatório está aberto no Edge e a Tempo indicou que guardará o PDF em `outputs/simulator-closure-qa-20261024/monthly-final.pdf`; falta ler e renderizar esse ficheiro para conferir apenas a paginação do bloco. HTML não substitui essa prova. |
|
||||
|
||||
Provas: quatro testes novos em `simulator-closure-scope.test.mjs`, mais oito casos das dependências directamente afectadas, **12/12 aprovados**; `SimulatorClosureRecoveryTest`, **2 casos/23 asserções**, transacções revertidas no clone nominal `simulator_fiscal_delta_20261021_test:55441`. TypeScript e build aprovados. As suites de aceitação não afectadas, ZIP, cálculos fiscais, PDFs/rodapé anteriores e contextos por oportunidade são reutilizados; não se exigiu à Tempo repetir essas provas. O ensaio visual usa componentes reais com transporte simulado explicitamente identificado, complementado pelo teste API de permissões; não é apresentado como servidor real ou processo na homologação.
|
||||
|
||||
Artefactos novos: `outputs/simulator-closure-qa-20261024/` — CSV/Word/HTML finais, snapshot congelado, inspecção/hash dos ficheiros, captura de recuperação e resultados dos testes. O estado executado fica no recibo integrado existente, chave `closure_qa_20261024`, e nas mesmas oito linhas da matriz. **Limite corrente:** S1/S2 em QA; S3 aguarda somente o PDF nativo guardado. A candidata não é declarada pronta para integração enquanto faltar essa conferência. Os seis pares legados e os dois originais incompletos não têm dependência efectiva neste lote.
|
||||
|
||||
## Simulador — âmbito de fecho reconciliado e lote proposto, 24/10/2026
|
||||
|
||||
Pedido expresso da Tempo após a referência `276d833a`/`reference/stabilization-20261024`, confirmada segundo o relatório, sem lhe atribuir leitura do recibo local. Esta passagem é **análise e proposta**, sem correcção funcional, integração, nova publicação/activação fiscal ou alteração de dados. A ordem de referência mantém-se. Não se acrescentam impostos nem se reabre o lote de estabilização.
|
||||
|
||||
### O que já existe e permanece aceite
|
||||
|
||||
| Frente | Implementação e provas reutilizadas |
|
||||
| --- | --- |
|
||||
| Planeamento financeiro | Plano empresarial por produto/mês, tesouraria, pressupostos editáveis, moeda e cenários de planeamento; controlos independentes anteriores de saldos positivo/negativo/nulo/incompleto, crédito e projecções. `business-financial-plan`, `monthly-cashflow` e recibos integrados de 20–22/10. Exemplos demonstrativos e imposto manual continuam identificados como planeamento, sem serem taxas legais ou valores do investidor. |
|
||||
| IVA implementado | Consumidor publicado, selecção e versão congeladas no servidor, avaliador existente; vendas mensais do plano ou receita constante já guardada, indicação explícita de base líquida, zero distinto de falta e IVA separado da caixa. `SimulationPublishedFiscalTest`, quatro casos de `monthly-iva.test.mjs` e ensaio Edge mensal anteriores reutilizados. `3f5430c3` e `0b0cf761` pertencem à referência instalada; as notas históricas «só QA» não significam hoje ausência deste código. A disponibilidade pública consultada nesta passagem devolveu `enabled=true`, cobertura 2026. Isso não é uma nova auditoria dos parâmetros protegidos; o cadastro/publicação real v1 mantém o recibo de `d3fe8a02`. |
|
||||
| Continuidade | Dois dossiers/nova sessão, isolamento, recuperação, erro e nova tentativa, históricos e hashes já provados. A escolha da oportunidade e o PDF correspondente estão entregues em `8036b9b0`/`cdf8fcfb`; não repetir. Inputs, rascunho e snapshots usam os mecanismos existentes, sem voltar a preencher vendas no IVA. |
|
||||
| Resultados/exportações | Resumo/detalhe, CSV/Word, erro e nova tentativa de exportação, ZIP e rodapé do PDF nativo têm provas aceites. O rodapé nativo de tesouraria foi corrigido e conferido em 21/10; o PDF guardado por oportunidade foi conferido em 23/10. O bloco mensal usa o mesmo snapshot fiscal e não recalcula imposto ao exportar. A prova específica restante desse bloco consta de S3 abaixo; não reabrir os restantes exportadores ou PDFs. |
|
||||
| Indisponibilidade | Fora da cobertura demonstrada de 2026, moeda diferente de MZN, preços com IVA incluído/base desconhecida, enquadramento não declarado e vendas em falta permanecem explícitos ou indeterminados, sem substituir por zero. Deduções, créditos, reembolsos, regimes especiais, apuramento final/Modelo A completo e outros impostos não são implementados por este lote. Cobertura técnica não é uma data legal de revogação. |
|
||||
|
||||
Reutilizados os recibos versionados `integrated-review-continuation-20261020-receipt.json`, `treatment-scope-installation-receipt-20261023.json` e os fechos posteriores do manual. Alguns caminhos históricos `outputs/` não existem nesta cópia do workspace: essa ausência não revoga provas aceites nem autoriza regenerá-las. Nenhuma suite de aceitação anterior foi reexecutada nesta análise.
|
||||
|
||||
### Saldo concreto — duas lacunas de código e uma prova específica
|
||||
|
||||
| ID / situação | Ecrã, causa e efeito delimitado | Lote proposto e critério de fecho |
|
||||
| --- | --- | --- |
|
||||
| **S1 — lacuna de código confirmada por diagnóstico isolado novo** | **Tesouraria → vendas reutilizadas pelo IVA → relatório.** `CashflowDraftSnapshot` v1 guarda montantes/modo, mas não a moeda de origem. O painel e o relatório usam a moeda actual do projecto; o fallback `monthlySalesFromDraft` não tem moeda guardada para comparar. Serializar receita `123` enquanto o projecto está em USD e depois mudar o projecto para MZN produz uma entrada IVA `123 MZN` e relatório MZN, sem conservar USD. O plano empresarial já tem protecção própria de moeda; a lacuna é a tesouraria guardada, não todos os planeadores. | Versionar a moeda dos pressupostos de tesouraria e partilhar a guarda entre painel, IVA e relatório. Divergência exige reconciliação explícita, sem conversão/reclassificação silenciosa nem perda dos montantes. Snapshots v1 continuam legíveis e intactos; moeda sem origem recuperável fica por confirmar, nunca presumida como MZN. Prova nova só deste delta: USD → MZN, gravação/recuperação, ecrã/IVA/exportação coerentes; valores e edições conservados. Diagnóstico sem DB/rede em `outputs/simulator-scope-20261024/currency-gap.json`. |
|
||||
| **S2 — lacuna identificada no código; prova de interface dirigida a fazer com a correcção** | **IVA mensal, execução já admitida ainda em fila/processamento.** Em `SimulationMonthlyIva`, o efeito regressa por `!available` antes de consultar o ID pendente e o botão também fica desactivado. A suspensão do consumidor, que deve impedir novas admissões, bloqueia assim a recuperação pela componente mensal. O servidor já preserva a versão admitida e permite consultar o histórico; não há falha de cálculo ou perda da execução demonstrada. | Separar nova admissão de consulta/retoma por ID. Execução admitida deve ser recuperável com consumidor suspenso, respeitando autenticação/dono, sem novo POST nem nova regra. Prova dirigida em QA: mesma execução pendente → indisponibilidade para novas admissões → conclusão/recuperação por mesmo ID → rascunho/relatório com o snapshot correcto e sem duplicação. Não repetir os testes aceites do avaliador ou publicar/suspender a regra real de homologação. |
|
||||
| **S3 — ainda sem prova específica, não defeito presumido** | **Bloco mensal de IVA nos ficheiros finais.** Há prova de reutilização das vendas, snapshots mensais no servidor e composição do relatório, e provas aceites dos exportadores gerais. Não há neste inventário uma conferência explícita de ficheiros finais com o bloco mensal completo — valores exactos, estado actual/histórico, oportunidade, fonte e versão — incluindo a paginação desse bloco. Os PDFs anteriores cobriam outros deltas. | Conferir um cenário mensal representativo por ecrã e CSV/Word, e apenas a paginação do bloco mensal no PDF nativo final. Reutilizar execução/snapshot sintético aceite, se recuperável, sem recalcular para testar exportação. Comparar montantes exactos, zero/falta, contexto, fonte, versão e aviso de ausência de apuramento final. ZIP e restante paginação/rodapé reutilizam as provas aceites. Corrigir apenas divergência efectivamente observada; não presumir falha de exportador. |
|
||||
|
||||
**Proposta de execução:** fechar S1 e S2 juntos, pelos consumidores de pressupostos/snapshots; verificar apenas os seus deltas e executar S3 sobre a candidata resultante. Integrar e disponibilizar somente depois dessas provas, pelo Git existente e preservando alterações concorrentes; conferir a revisão e o delta instalado. Não migrar dados activos nem publicar outra regra por esta proposta. As guardas, mensagens e compatibilidade de snapshots devem ser tratadas no mesmo lote, sem criar motor, formulários ou aprovações novos.
|
||||
|
||||
**Dependências reais:** nenhuma identificada entre S1–S3 e as seis associações legadas ou os dois originais incompletos. As fontes da regra IVA já cadastrada e os recibos fiscais existentes são reutilizados. Declaração Modelo A completa, outros impostos, elegibilidade/incentivos novos e extensão temporal não entram neste fecho. O resultado esperado é o ciclo **dados → cálculo disponível → gravação → recuperação → relatório** coerente neste âmbito, não a certificação de um simulador fiscal integral. A proposta está registada nas linhas existentes `AUD-SIM-01` e `AUD-SIM-INT02`–`08`; as notas e estimativas históricas ficam subordinadas a este saldo de três itens, sem outro backlog.
|
||||
|
||||
## Fecho do lote de estabilização e sequência de referência — 24/10/2026
|
||||
|
||||
**Ordem expressa da Tempo:** (1) correcções e estabilização; (2) simulador fiscal com âmbito definido; (3) Imobiliário; (4) Bens de consumo e franquias; (5) Estatística e painéis; (6) IA. A preservação técnica é responsabilidade do Codex e dos programadores. A Tempo avalia utilidade, clareza e apresentação; não tem de redescobrir regressões para comprovar preservação.
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -5,6 +5,7 @@ import {
|
||||
defaultCashflowDraftInputs,
|
||||
restoreCashflowDraftSnapshot,
|
||||
serializeCashflowDraftSnapshot,
|
||||
cashflowCurrency,
|
||||
type CashflowDraftSnapshot,
|
||||
} from '@/lib/monthly-cashflow';
|
||||
import {
|
||||
@@ -24,6 +25,9 @@ export function SimulationCashflowPlanner({
|
||||
onSnapshotChange?: (snapshot: string) => void;
|
||||
}) {
|
||||
const restored = restoreCashflowDraftSnapshot(snapshot);
|
||||
const [confirmedCurrency, setConfirmedCurrency] = useState('');
|
||||
const originCurrency = restored?.version === 2 ? restored.currency : null;
|
||||
const calculationCurrency = snapshot ? originCurrency ?? '' : currency;
|
||||
const [taxMode, setTaxMode] = useState<'fixed' | 'schedule'>(
|
||||
restored?.taxMode ?? 'fixed',
|
||||
);
|
||||
@@ -41,7 +45,7 @@ export function SimulationCashflowPlanner({
|
||||
) {
|
||||
setInputs(next);
|
||||
setTaxMode(mode);
|
||||
onSnapshotChange?.(serializeCashflowDraftSnapshot(mode, next));
|
||||
onSnapshotChange?.(serializeCashflowDraftSnapshot(mode, next, snapshot ? originCurrency ?? null : currency || null));
|
||||
}
|
||||
const set = (
|
||||
key: Exclude<keyof typeof inputs, 'monthlyTaxSchedule'>,
|
||||
@@ -63,7 +67,8 @@ export function SimulationCashflowPlanner({
|
||||
throw new Error(
|
||||
'Não foi possível recuperar os pressupostos guardados de tesouraria. Reveja os campos antes de voltar a guardar; nenhum resultado foi calculado com dados alternativos.',
|
||||
);
|
||||
projection = projectMonthlyCashflow({ ...cashflowInput, currency });
|
||||
if (restored) cashflowCurrency(restored, currency);
|
||||
projection = projectMonthlyCashflow({ ...cashflowInput, currency: calculationCurrency });
|
||||
} catch (cause) {
|
||||
error =
|
||||
cause instanceof Error
|
||||
@@ -76,7 +81,7 @@ export function SimulationCashflowPlanner({
|
||||
aria-labelledby="cashflow-title"
|
||||
>
|
||||
<label className="block max-w-xs text-sm font-medium">
|
||||
Moeda dos valores (código ISO)
|
||||
Moeda do projecto (código ISO)
|
||||
<input
|
||||
list="cashflow-currency-options"
|
||||
inputMode="text"
|
||||
@@ -103,6 +108,16 @@ export function SimulationCashflowPlanner({
|
||||
<h3 id="cashflow-title" className="text-lg font-semibold">
|
||||
Projecção de tesouraria — 12 meses
|
||||
</h3>
|
||||
{restored && !originCurrency && <div className="space-y-2 rounded border border-amber-600 p-3">
|
||||
<p>A moeda de origem dos montantes guardados não foi registada. Os valores foram conservados. Confirme apenas essa moeda; não é necessário preencher os montantes novamente.</p>
|
||||
<label>Moeda de origem da tesouraria guardada<select className="ml-2 rounded border p-2" value={confirmedCurrency} onChange={event => setConfirmedCurrency(event.target.value)}><option value="">Por confirmar</option>{simulationCurrencyOptions.map(code => <option key={code} value={code}>{code}</option>)}</select></label>
|
||||
<button type="button" className="block underline" disabled={!isSimulationCurrency(confirmedCurrency)} onClick={() => onSnapshotChange?.(serializeCashflowDraftSnapshot(taxMode, inputs, confirmedCurrency))}>Confirmar moeda de origem e conservar montantes</button>
|
||||
</div>}
|
||||
{originCurrency && originCurrency !== currency && <div role="alert" className="rounded border border-amber-600 p-3">
|
||||
<p>{cashflowCurrency(restored!, currency).notice}</p>
|
||||
<button type="button" className="underline" onClick={() => onCurrencyChange(originCurrency)}>Usar {originCurrency} no projecto e conservar montantes</button>
|
||||
</div>}
|
||||
{originCurrency && <p>Moeda de origem dos pressupostos: {originCurrency}.</p>}
|
||||
<p className="text-sm text-slate-700">
|
||||
Introduza pressupostos na mesma moeda. O imposto indicado é um
|
||||
pressuposto do utilizador; esta ferramenta não calcula nem
|
||||
@@ -257,12 +272,12 @@ export function SimulationCashflowPlanner({
|
||||
{error}
|
||||
</p>
|
||||
)}
|
||||
{projection && isSimulationCurrency(currency) && (
|
||||
{projection && isSimulationCurrency(calculationCurrency) && (
|
||||
<>
|
||||
<p aria-live="polite" className="font-semibold">
|
||||
{projection.endingCash === null
|
||||
? `Caixa conhecida antes de imposto em falta, ao fim de 12 meses (${currency}):`
|
||||
: `Saldo projectado ao fim de 12 meses (${currency}):`}{' '}
|
||||
? `Caixa conhecida antes de imposto em falta, ao fim de 12 meses (${calculationCurrency}):`
|
||||
: `Saldo projectado ao fim de 12 meses (${calculationCurrency}):`}{' '}
|
||||
{projection.endingCash ??
|
||||
projection.endingCashBeforeUnassessedTax}
|
||||
</p>
|
||||
@@ -274,7 +289,7 @@ export function SimulationCashflowPlanner({
|
||||
[
|
||||
exportMonthlyCashflowCsv({
|
||||
...cashflowInput,
|
||||
currency,
|
||||
currency: calculationCurrency,
|
||||
periods: 12,
|
||||
}),
|
||||
],
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { InvestmentContextAssessment } from './investment-context-assessment';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { monthlyIvaInput, monthlyIvaKey, monthlySalesFromDraft, readMonthlyIvaRecord, readMonthlyIvaSettings, monthlyIvaCashTreatment, type MonthlyIvaInput, type MonthlyIvaSettings } from '@/lib/monthly-iva';
|
||||
import { monthlyIvaInput, monthlyIvaKey, monthlySalesFromDraft, readMonthlyIvaRecord, readMonthlyIvaSettings, recoverMonthlyIvaRecord, monthlyIvaCashTreatment, type MonthlyIvaInput, type MonthlyIvaSettings } from '@/lib/monthly-iva';
|
||||
import { fiscalQaRows } from '@/lib/fiscal-qa-preview';
|
||||
import { isApiSimulationReadResponseV1, type ApiSimulationExecutionV1 } from '@/lib/simulation-contract';
|
||||
import { type ApiSimulationExecutionV1 } from '@/lib/simulation-contract';
|
||||
import { SimulationModeloA } from './simulation-modelo-a';
|
||||
|
||||
export function SimulationMonthlyIva({draft, busy, onCalculate, onChange}: {
|
||||
@@ -16,6 +16,9 @@ export function SimulationMonthlyIva({draft, busy, onCalculate, onChange}: {
|
||||
const [error, setError] = useState('');
|
||||
const [available, setAvailable] = useState<boolean | null>(null);
|
||||
const attempt = useRef('');
|
||||
const mounted = useRef(true);
|
||||
const latestSnapshot = useRef(draft.monthly_iva_snapshot); latestSnapshot.current = draft.monthly_iva_snapshot;
|
||||
useEffect(() => { mounted.current = true; return () => { mounted.current = false; }; }, []);
|
||||
const callbacks = useRef({onCalculate, onChange}); callbacks.current = {onCalculate, onChange};
|
||||
let input: MonthlyIvaInput | null = null; let inputError = ''; let source = '';
|
||||
try { source = monthlySalesFromDraft(draft).source; input = monthlyIvaInput(draft, settings); }
|
||||
@@ -23,6 +26,8 @@ export function SimulationMonthlyIva({draft, busy, onCalculate, onChange}: {
|
||||
const key = input ? monthlyIvaKey(input) : '';
|
||||
const current = !!(input && record && monthlyIvaKey(record.input) === key);
|
||||
const completed = current && record?.execution.status === 'completed';
|
||||
const pendingRecord = record && ['queued','running'].includes(record.execution.status) ? record : null;
|
||||
const actionKey = pendingRecord ? `recover:${pendingRecord.execution.id}` : key;
|
||||
const saveSettings = (patch: Partial<MonthlyIvaSettings>) => onChange('monthly_iva_settings', JSON.stringify({...settings, ...patch}));
|
||||
useEffect(() => {
|
||||
let active = true;
|
||||
@@ -32,27 +37,26 @@ export function SimulationMonthlyIva({draft, busy, onCalculate, onChange}: {
|
||||
return () => { active = false; };
|
||||
}, [retry]);
|
||||
useEffect(() => {
|
||||
if (!settings.auto || !input || input.currency !== 'MZN' || busy || !available || completed || attempt.current === key) return;
|
||||
const request = input;
|
||||
const pending = current && record && ['queued','running'].includes(record.execution.status) ? record.execution.id : null;
|
||||
if (!settings.auto || busy || attempt.current === actionKey) return;
|
||||
if (!pendingRecord && (!input || input.currency !== 'MZN' || !available || completed)) return;
|
||||
const request = pendingRecord?.input ?? input!;
|
||||
const savedSnapshot = draft.monthly_iva_snapshot;
|
||||
const timer = window.setTimeout(() => {
|
||||
attempt.current = key; setError('');
|
||||
attempt.current = actionKey; setError('');
|
||||
void (async () => {
|
||||
try {
|
||||
let execution;
|
||||
if (pending) {
|
||||
const response = await fetch(`/api/v1/simulations/${pending}`, {credentials:'same-origin', headers:{Accept:'application/json'}});
|
||||
const value: unknown = await response.json();
|
||||
if (!response.ok || !isApiSimulationReadResponseV1(value)) throw new Error('Não foi possível recuperar a execução mensal guardada. Não foi criada uma execução alternativa.');
|
||||
execution = value.data;
|
||||
if (pendingRecord) {
|
||||
execution = (await recoverMonthlyIvaRecord(pendingRecord)).execution;
|
||||
} else execution = await callbacks.current.onCalculate(request);
|
||||
if (!execution) throw new Error('O cálculo mensal não foi concluído. As vendas e a versão anterior foram preservadas; tente novamente.');
|
||||
if (!mounted.current || (pendingRecord && latestSnapshot.current !== savedSnapshot)) return;
|
||||
callbacks.current.onChange('monthly_iva_snapshot', JSON.stringify({input:request, execution}));
|
||||
} catch (cause) {setError(cause instanceof Error ? cause.message : 'Não foi possível calcular o IVA mensal.');}
|
||||
} catch (cause) {if (mounted.current) setError(cause instanceof Error ? cause.message : 'Não foi possível calcular o IVA mensal.');}
|
||||
})();
|
||||
}, 700);
|
||||
return () => window.clearTimeout(timer);
|
||||
}, [key, settings.auto, busy, available, completed, retry]);
|
||||
}, [actionKey, key, settings.auto, busy, available, completed, retry]);
|
||||
|
||||
const snapshot = record?.execution.result_snapshot;
|
||||
const projection = snapshot?.monthly_projection as {rows?: Array<{date:string; sales_declared:string|null; net_sales:string|null; rate:string|null; vat:string|null; gross_sales:string|null; status:string}>} | undefined;
|
||||
@@ -73,7 +77,8 @@ export function SimulationMonthlyIva({draft, busy, onCalculate, onChange}: {
|
||||
{settings.auto && !completed && current && <p role="status">Execução mensal guardada: {record?.execution.status === 'queued' ? 'em fila' : record?.execution.status === 'running' ? 'em processamento' : 'não concluída'}. Pode voltar a consultar sem duplicar.</p>}
|
||||
{busy && <p role="status">A actualizar os resultados dependentes das vendas…</p>}
|
||||
{error && <p role="alert">{error}</p>}
|
||||
<button type="button" disabled={busy || !available || !input || input.currency !== 'MZN'} onClick={() => {attempt.current=''; setRetry((value) => value+1); saveSettings({auto:true});}} className="rounded bg-emerald-800 p-2 text-white disabled:opacity-50">Calcular / actualizar IVA mensal e guardar versão</button>
|
||||
<button type="button" disabled={busy || (!pendingRecord && (!available || !input || input.currency !== 'MZN'))} onClick={() => {attempt.current=''; setRetry((value) => value+1); saveSettings({auto:true});}} className="rounded bg-emerald-800 p-2 text-white disabled:opacity-50">{pendingRecord ? 'Recuperar execução mensal admitida pelo mesmo ID' : 'Calcular / actualizar IVA mensal e guardar versão'}</button>
|
||||
{pendingRecord && <p>Consulta da execução {pendingRecord.execution.id}. A suspensão de novos cálculos não impede esta recuperação; não é criado outro pedido.</p>}
|
||||
{settings.auto && <button type="button" className="ml-3 underline" onClick={() => saveSettings({auto:false})}>Pausar actualização automática</button>}
|
||||
{input && <p className="text-sm">{input.monthly_sales.length} meses; moeda {input.currency || 'por indicar'}. Campo vazio significa informação em falta; zero declarado permanece zero. Depois da primeira execução, alterações das vendas actualizam o cálculo enquanto a actualização automática estiver ligada.</p>}
|
||||
{projection?.rows && <div className="overflow-x-auto" tabIndex={0}><table className="min-w-full text-sm"><caption>IVA mensal — {current ? 'versão correspondente às entradas' : 'versão histórica'}</caption><thead><tr>{['Mês','Vendas declaradas','Vendas tributáveis sem IVA','Taxa aplicada (%)','IVA calculado','Total com IVA','Estado'].map((label) => <th key={label} className="p-2 text-left">{label}</th>)}</tr></thead><tbody>{projection.rows.map((row) => <tr key={row.date}><th scope="row" className="p-2">{row.date.slice(0,7)}</th><td>{row.sales_declared ?? 'Sem informação'}</td><td>{row.net_sales ?? 'Indeterminado'}</td><td>{row.rate ?? 'Indeterminada'}</td><td>{row.vat ?? 'Indeterminado'}</td><td>{row.gross_sales ?? 'Indeterminado'}</td><td>{{calculated:'Calculado',missing_sales:'Sem informação de vendas',unsupported_basis:'Base líquida não determinada',indeterminate:'Enquadramento ou regra indeterminados'}[row.status] ?? 'Indeterminado'}</td></tr>)}</tbody></table></div>}
|
||||
|
||||
@@ -17,7 +17,9 @@ export function defaultCashflowDraftInputs() {
|
||||
}
|
||||
|
||||
export type CashflowDraftSnapshot = {
|
||||
version: 1;
|
||||
version: 1 | 2;
|
||||
/** Missing in legacy v1: never infer it from the current project. */
|
||||
currency?: string | null;
|
||||
taxMode: 'fixed' | 'schedule';
|
||||
inputs: ReturnType<typeof defaultCashflowDraftInputs>;
|
||||
};
|
||||
@@ -31,7 +33,8 @@ export function restoreCashflowDraftSnapshot(
|
||||
const parsed = JSON.parse(snapshot);
|
||||
if (
|
||||
!parsed ||
|
||||
parsed.version !== 1 ||
|
||||
![1, 2].includes(parsed.version) ||
|
||||
(parsed.version === 2 && parsed.currency !== null && (typeof parsed.currency !== 'string' || !/^[A-Z]{3}$/.test(parsed.currency))) ||
|
||||
!['fixed', 'schedule'].includes(parsed.taxMode) ||
|
||||
!parsed.inputs ||
|
||||
typeof parsed.inputs !== 'object' ||
|
||||
@@ -55,7 +58,7 @@ export function restoreCashflowDraftSnapshot(
|
||||
} else if (typeof value !== 'string' || value.length > 160)
|
||||
return null;
|
||||
}
|
||||
return { version: 1, taxMode: parsed.taxMode, inputs: parsed.inputs };
|
||||
return { version: parsed.version, ...(parsed.version === 2 ? {currency: parsed.currency} : {}), taxMode: parsed.taxMode, inputs: parsed.inputs };
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
@@ -64,8 +67,16 @@ export function restoreCashflowDraftSnapshot(
|
||||
export function serializeCashflowDraftSnapshot(
|
||||
taxMode: CashflowDraftSnapshot['taxMode'],
|
||||
inputs: CashflowDraftSnapshot['inputs'],
|
||||
currency?: string | null,
|
||||
): string {
|
||||
return JSON.stringify({ version: 1, taxMode, inputs });
|
||||
return JSON.stringify({ version: currency === undefined ? 1 : 2, ...(currency !== undefined ? {currency} : {}), taxMode, inputs });
|
||||
}
|
||||
|
||||
export function cashflowCurrency(saved: CashflowDraftSnapshot, projectCurrency: string): {currency: string; matchesProject: boolean; notice: string} {
|
||||
const currency = saved.version === 2 ? saved.currency : null;
|
||||
if (!currency) throw new Error('A moeda de origem da tesouraria guardada está por confirmar. Os montantes foram preservados; confirme a moeda na ferramenta de tesouraria antes de calcular ou reutilizar as vendas.');
|
||||
const matchesProject = currency === projectCurrency;
|
||||
return {currency, matchesProject, notice: matchesProject ? '' : `A tesouraria foi guardada em ${currency}, mas o projecto está em ${projectCurrency || 'moeda por indicar'}. Os montantes continuam em ${currency}; não foram convertidos nem reinterpretados. Para os reutilizar no projecto, restaure explicitamente a moeda de origem. Uma projecção noutra moeda exige pressupostos próprios.`};
|
||||
}
|
||||
|
||||
export type MonthlyCashflowInput = {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { monthlyProjectedSales, restoreBusinessPlanSnapshot } from './business-financial-plan.ts';
|
||||
import { restoreCashflowDraftSnapshot } from './monthly-cashflow.ts';
|
||||
import { cashflowCurrency, restoreCashflowDraftSnapshot } from './monthly-cashflow.ts';
|
||||
import { isApiSimulationReadResponseV1, type ApiSimulationExecutionV1 } from './simulation-contract.ts';
|
||||
|
||||
export type MonthlyIvaInput = {
|
||||
@@ -27,6 +27,8 @@ export function monthlySalesFromDraft(draft: Record<string, unknown>): { amounts
|
||||
if (draft.cashflow_snapshot) {
|
||||
const saved = restoreCashflowDraftSnapshot(String(draft.cashflow_snapshot));
|
||||
if (!saved) throw new Error('Não foi possível recuperar a receita projectada.');
|
||||
const origin = cashflowCurrency(saved, String(draft.currency ?? ''));
|
||||
if (!origin.matchesProject) throw new Error(origin.notice);
|
||||
return {amounts: Array.from({length: 12}, () => saved.inputs.monthlyRevenue.trim() === '' ? null : saved.inputs.monthlyRevenue), source: 'Receita mensal constante já declarada na projecção de tesouraria; confirme que corresponde a vendas tributáveis.'};
|
||||
}
|
||||
return {amounts: Array.from({length: 12}, () => null), source: 'Sem projecção de vendas guardada. Prepare as vendas no plano empresarial; não é necessário voltar a introduzi-las aqui.'};
|
||||
@@ -48,4 +50,11 @@ export function readMonthlyIvaRecord(raw?: string): MonthlyIvaRecord | null {
|
||||
} catch { /* Keep the stored value intact; no fallback fiscal result. */ }
|
||||
return null;
|
||||
}
|
||||
/** Read an admitted execution through the existing ownership-protected endpoint; never create one. */
|
||||
export async function recoverMonthlyIvaRecord(record: MonthlyIvaRecord, request: typeof fetch = fetch): Promise<MonthlyIvaRecord> {
|
||||
const response = await request(`/api/v1/simulations/${encodeURIComponent(record.execution.id)}`, {credentials:'same-origin', headers:{Accept:'application/json'}});
|
||||
const value: unknown = await response.json();
|
||||
if (!response.ok || !isApiSimulationReadResponseV1(value) || value.data.id !== record.execution.id) throw new Error('Não foi possível recuperar a execução mensal guardada. Os dados foram preservados e não foi criada uma execução alternativa.');
|
||||
return {input: record.input, execution: value.data};
|
||||
}
|
||||
export const monthlyIvaCashTreatment = 'IVA apresentado separadamente: não é somado novamente aos recebimentos declarados nem deduzido como custo ou imposto final a pagar. A tesouraria conserva os recebimentos e pagamentos introduzidos, antes de uma liquidação fiscal não apurada; não se presume cobrança integral das vendas no mesmo mês.';
|
||||
|
||||
@@ -7,6 +7,7 @@ import { fiscalQaRows } from './fiscal-qa-preview.ts';
|
||||
import { csvCellWithDelimiter } from './csv.js';
|
||||
import {
|
||||
restoreCashflowDraftSnapshot,
|
||||
cashflowCurrency,
|
||||
projectMonthlyCashflow,
|
||||
} from './monthly-cashflow.ts';
|
||||
import {
|
||||
@@ -637,7 +638,9 @@ export function buildProjectDossierRows(
|
||||
throw new Error(
|
||||
'Pressupostos de tesouraria não recuperáveis; não foram substituídos por valores alternativos.',
|
||||
);
|
||||
const currency = String(draft.currency ?? '');
|
||||
const originCurrency = cashflowCurrency(saved, String(draft.currency ?? ''));
|
||||
const currency = originCurrency.currency;
|
||||
if (originCurrency.notice) rows.push(['Tesouraria', 'Divergência de moeda', originCurrency.notice, 'Moeda original preservada; sem conversão']);
|
||||
if (!currency)
|
||||
throw new Error(
|
||||
'Indique uma moeda para interpretar os montantes de tesouraria.',
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Jobs\RunSimulation;
|
||||
use App\Models\Simulation;
|
||||
use App\Models\SimulationTaxRuleVersion;
|
||||
use App\Models\User;
|
||||
use App\Services\SimulationExecution;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Queue;
|
||||
use Illuminate\Support\Str;
|
||||
use Tests\TestCase;
|
||||
|
||||
final class SimulatorClosureRecoveryTest extends TestCase
|
||||
{
|
||||
use DatabaseTransactions;
|
||||
|
||||
public function test_existing_monthly_admission_can_be_read_and_finished_when_new_admissions_are_off(): void
|
||||
{
|
||||
$identity = DB::selectOne('select current_database() as name,inet_server_port() as port');
|
||||
$this->assertSame('simulator_fiscal_delta_20261021_test', $identity->name);
|
||||
$this->assertSame(55441, (int) $identity->port);
|
||||
Queue::fake();
|
||||
// Reuse the existing isolated synthetic rule. No create/publish/suspend of any rule.
|
||||
$rule = SimulationTaxRuleVersion::findOrFail('01a15e50-7972-72e0-8991-82ca28df9b49');
|
||||
$before = $this->ruleFingerprint();
|
||||
config(['simulation-fiscal-consumer.enabled' => true, 'simulation-fiscal-consumer.rule_id' => $rule->id, 'simulation-fiscal-consumer.rule_version' => $rule->rule_version]);
|
||||
$actor = User::factory()->create(['email_verified_at' => now()]);
|
||||
$input = ['scenario_kind' => 'fiscal_rule_published', 'currency' => 'MZN', 'scope_declared' => true, 'sales_vat_basis' => 'net', 'monthly_sales' => [['date' => '2026-01-01', 'amount' => '1000.03']]];
|
||||
$id = $this->actingAs($actor)->postJson('/api/v1/simulations', $input, ['Idempotency-Key' => (string) Str::uuid()])->assertAccepted()->json('data.id');
|
||||
$count = Simulation::count();
|
||||
config(['simulation-fiscal-consumer.enabled' => false]);
|
||||
$this->getJson('/api/v1/fiscal-consumer')->assertOk()->assertJsonPath('data.enabled', false);
|
||||
$this->getJson('/api/v1/simulations/'.$id)->assertOk()->assertJsonPath('data.id', $id)->assertJsonPath('data.status', 'queued');
|
||||
$this->postJson('/api/v1/simulations', $input, ['Idempotency-Key' => (string) Str::uuid()])->assertStatus(503);
|
||||
(new RunSimulation($id, $actor->id, null))->handle(app(SimulationExecution::class));
|
||||
$this->getJson('/api/v1/simulations/'.$id)->assertOk()->assertJsonPath('data.id', $id)->assertJsonPath('data.status', 'completed');
|
||||
$this->assertSame($count, Simulation::count());
|
||||
$this->actingAs(User::factory()->create(['email_verified_at' => now()]))->getJson('/api/v1/simulations/'.$id)->assertNotFound();
|
||||
$this->assertSame($before, $this->ruleFingerprint());
|
||||
}
|
||||
|
||||
private function ruleFingerprint(): string
|
||||
{
|
||||
return DB::selectOne("select md5(string_agg(row_to_json(r)::text,E'\\n' order by id)) as hash from simulation_tax_rule_versions r")->hash;
|
||||
}
|
||||
|
||||
public function test_cashflow_original_currency_and_amounts_round_trip_in_the_existing_private_draft(): void
|
||||
{
|
||||
$identity = DB::selectOne('select current_database() as name,inet_server_port() as port');
|
||||
$this->assertSame('simulator_fiscal_delta_20261021_test', $identity->name);
|
||||
$this->assertSame(55441, (int) $identity->port);
|
||||
config(['investor-path.enabled' => true, 'suitecrm.enabled' => false]);
|
||||
$actor = User::factory()->create(['email_verified_at' => now()]);
|
||||
$this->actingAs($actor);
|
||||
$id = $this->postJson('/investimentos', ['title' => 'SYNTHETIC QA — moeda de tesouraria', 'client_key' => (string) Str::uuid(), 'reuse_consent' => true])->assertCreated()->json('id');
|
||||
$snapshot = json_encode(['version' => 2, 'currency' => 'USD', 'taxMode' => 'fixed', 'inputs' => ['monthlyRevenue' => '123', 'monthlyTaxSchedule' => ['0', '', '0']]], JSON_THROW_ON_ERROR);
|
||||
$this->postJson('/api/v1/simulation-drafts', ['context' => $id, 'personal_snapshot' => [], 'economic_snapshot' => ['purpose' => 'simulate_only', 'project_title' => 'SYNTHETIC QA — moeda', 'currency' => 'MZN', 'cashflow_snapshot' => $snapshot]], ['Idempotency-Key' => (string) Str::uuid()])->assertCreated();
|
||||
$this->actingAs($actor->fresh());
|
||||
$saved = $this->getJson('/api/v1/simulation-drafts/current?context='.$id)->assertOk()->json('data.economic_snapshot');
|
||||
$this->assertSame('MZN', $saved['currency']);
|
||||
$this->assertSame($snapshot, $saved['cashflow_snapshot']);
|
||||
$this->assertSame('USD', json_decode($saved['cashflow_snapshot'], true)['currency']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import {serializeBusinessPlanSnapshot} from '../../../resources/js/lib/business-financial-plan.ts';
|
||||
|
||||
export function closureFixture(status='completed') {
|
||||
const fields=['openingCash','monthlyCollections','monthlyVariableCostPayments','monthlyInventoryPurchases','monthlyOperatingExpenses','monthlyOperatingPayments','monthlyPayrollExpense','monthlyPayrollPayments','assetInvestment','inventory','receivables','payables','equity','shareholderLoans','bankLoanProceeds','grants','assetDepreciation','monthlyInterestExpense','monthlyInterestPayment','monthlyPrincipalRepayment'];
|
||||
const plan={...Object.fromEntries(fields.map(k=>[k,'0'])),currency:'MZN',periods:4,depreciationLifeMonths:60,products:[{label:'SYNTHETIC QA',monthlyUnits:'1',unitPrice:'1000.03',unitVariableCost:'0',monthlyUnitsByPeriod:['1','2','0','']}]};
|
||||
const input={scenario_kind:'fiscal_rule_published',monthly_sales:[{date:'2026-01-01',amount:'1000.03'},{date:'2026-02-01',amount:'2000.06'},{date:'2026-03-01',amount:'0.00'},{date:'2026-04-01',amount:null}],sales_vat_basis:'net',currency:'MZN',scope_declared:true};
|
||||
// Frozen numbers already accepted in the monthly QA receipt; no tax calculation here.
|
||||
const rows=[{date:'2026-01-01',sales_declared:'1000.03',net_sales:'1000.03',rate:'16',vat:'160.0048',gross_sales:'1160.0348',status:'calculated'},{date:'2026-02-01',sales_declared:'2000.06',net_sales:'2000.06',rate:'16',vat:'320.0096',gross_sales:'2320.0696',status:'calculated'},{date:'2026-03-01',sales_declared:'0.00',net_sales:'0.00',rate:'16',vat:'0',gross_sales:'0',status:'calculated'},{date:'2026-04-01',sales_declared:null,net_sales:null,rate:null,vat:null,gross_sales:null,status:'missing_sales'}];
|
||||
const snapshot={contract_version:1,scenario_kind:'fiscal_rule_published',base:null,currency:'MZN',indicative_total:null,legal_interpretation:false,formula_version:'investmoz.tax.v1',context:{reference_date:'2026-01-01',currency:'MZN',currency_fraction_digits:2,scenario_id:'61000100-0000-4000-8000-000000000099',scenario_version:1,engine_version:'fiscal-published-v1',ruleset_version:'IVA-GERAL-BASE-LIQUIDA:4'},result:{status:'indeterminate',value:null,unit:'MZN',explanation:'IVA das vendas projectadas; sem apuramento final.',missing_inputs:['monthly_sales'],source_ids:['synthetic-qa-source']},presentation:{value:null,rounding_applied:false},fiscal_provenance:{rule:{rule_code:'IVA-GERAL-BASE-LIQUIDA',rule_version:4,valid_from:'2023-01-01',valid_until:null,source_sha256:'synthetic-qa-source-hash',source:{diploma:'Fonte sintética QA — regra já ensaiada',article:'Cópia do snapshot aceite'}},scope_status:'user_declared'},monthly_projection:{input,rows,cash_treatment:'IVA separado da tesouraria; não é imposto final a pagar.'}};
|
||||
const execution={id:'06566910-9672-4176-804d-aad8350417cb',status,version_no:2,result_snapshot:status==='completed'?snapshot:null};
|
||||
const context={version:1,context_revision:13,consumer_opportunity:'opportunity:309',opportunity_choices:[{opportunity:{key:'opportunity:309',title:'SYNTHETIC QA — segunda oportunidade Manica'}}],path:{steps:[]}};
|
||||
return {input,snapshot,execution,draft:{project_title:'SYNTHETIC QA — fecho do simulador S1 S2 S3',currency:'MZN',context_opportunity:'opportunity:309',context_applicability_snapshot:JSON.stringify(context),business_plan_snapshot:serializeBusinessPlanSnapshot(plan),monthly_iva_settings:JSON.stringify({start:'2026-01',basis:'net',declared:true,auto:false}),monthly_iva_snapshot:JSON.stringify({input,execution})}};
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import {mkdir,writeFile} from 'node:fs/promises';
|
||||
import JSZip from 'jszip';
|
||||
import {defaultCashflowDraftInputs,serializeCashflowDraftSnapshot,restoreCashflowDraftSnapshot,cashflowCurrency} from '../../resources/js/lib/monthly-cashflow.ts';
|
||||
import {monthlyIvaInput,recoverMonthlyIvaRecord} from '../../resources/js/lib/monthly-iva.ts';
|
||||
import {exportProjectDossierCsv,buildProjectDossierRows} from '../../resources/js/lib/project-dossier.ts';
|
||||
import {createProjectDossierReport,generateProjectDossierDocx,createProjectDossierPrintHtml} from '../../resources/js/lib/project-dossier-report.ts';
|
||||
import {closureFixture} from './fixtures/simulator-closure-fixture.mjs';
|
||||
|
||||
test('S1 saved USD stays USD in report and cannot be reused as MZN for IVA',()=>{
|
||||
const inputs={...defaultCashflowDraftInputs(),monthlyRevenue:'123',monthlyTaxAssessed:'0'};
|
||||
const snapshot=serializeCashflowDraftSnapshot('fixed',inputs,'USD');
|
||||
const draft={currency:'MZN',cashflow_snapshot:snapshot};
|
||||
const saved=restoreCashflowDraftSnapshot(snapshot);
|
||||
assert.equal(saved.currency,'USD');assert.deepEqual(saved.inputs,inputs);
|
||||
assert.equal(cashflowCurrency(saved,'MZN').matchesProject,false);
|
||||
assert.throws(()=>monthlyIvaInput(draft,{start:'2026-01',basis:'net',declared:true,auto:false}),/continuam em USD/);
|
||||
const rows=buildProjectDossierRows(draft).filter(r=>r[0]==='Tesouraria');
|
||||
assert.match(rows.find(r=>r[1]==='Pressupostos e período')[2],/moeda USD/);
|
||||
assert.match(rows.find(r=>r[1]==='Divergência de moeda')[2],/não foram convertidos/);
|
||||
const restoredProject={...draft,currency:'USD'};
|
||||
assert.equal(cashflowCurrency(restoreCashflowDraftSnapshot(restoredProject.cashflow_snapshot),restoredProject.currency).matchesProject,true);
|
||||
assert.equal(restoredProject.cashflow_snapshot,snapshot);
|
||||
});
|
||||
test('S1 legacy amounts remain readable but require explicit origin currency before reuse',()=>{
|
||||
const inputs={...defaultCashflowDraftInputs(),monthlyRevenue:'123'};
|
||||
const legacy=serializeCashflowDraftSnapshot('fixed',inputs);const saved=restoreCashflowDraftSnapshot(legacy);
|
||||
assert.equal(saved.version,1);assert.throws(()=>cashflowCurrency(saved,'MZN'),/por confirmar/);
|
||||
const confirmed=restoreCashflowDraftSnapshot(serializeCashflowDraftSnapshot(saved.taxMode,saved.inputs,'USD'));
|
||||
assert.deepEqual(confirmed.inputs,saved.inputs);assert.equal(confirmed.currency,'USD');
|
||||
assert.equal(restoreCashflowDraftSnapshot(legacy).version,1);
|
||||
});
|
||||
test('S2 recovery is a same-ID authenticated GET without new admission and preserves denied records',async()=>{
|
||||
const pending=closureFixture('queued');const completed=closureFixture();const calls=[];
|
||||
const record={input:pending.input,execution:pending.execution};const before=JSON.stringify(record);
|
||||
const request=async(url,options)=>{calls.push({url,options});return new Response(JSON.stringify({data:completed.execution}),{status:200});};
|
||||
const recovered=await recoverMonthlyIvaRecord(record,request);
|
||||
assert.equal(recovered.execution.id,pending.execution.id);assert.equal(recovered.execution.status,'completed');assert.deepEqual(recovered.input,pending.input);
|
||||
assert.equal(calls.length,1);assert.equal(calls[0].options.credentials,'same-origin');assert.equal(calls[0].options.method,undefined);assert.match(calls[0].url,/simulations\/06566910/);
|
||||
await assert.rejects(()=>recoverMonthlyIvaRecord(record,async()=>new Response('{}',{status:403})),/preservados/);
|
||||
await assert.rejects(()=>recoverMonthlyIvaRecord(record,async()=>new Response(JSON.stringify({data:{...completed.execution,id:'00000000-0000-4000-8000-000000000000'}}),{status:200})),/preservados/);
|
||||
assert.equal(JSON.stringify(record),before);
|
||||
});
|
||||
test('S3 final CSV Word and print source retain frozen monthly values context source and version',async()=>{
|
||||
const {draft}=closureFixture();
|
||||
const report=createProjectDossierReport(draft,{selectedSections:new Set(['sources','finance']),version:'SYNTHETIC QA S3'});
|
||||
const csv=exportProjectDossierCsv(draft);const html=await createProjectDossierPrintHtml(report,[],false);
|
||||
const docx=await generateProjectDossierDocx(report,[],true);const bytes=new Uint8Array(await docx.arrayBuffer());const word=await JSZip.loadAsync(bytes);const xml=await word.file('word/document.xml').async('string');
|
||||
for(const text of [csv,html,xml]) for(const expected of ['160.0048','320.0096','1160.0348','MZN','2026-01','2026-04','Fonte sintética QA','IVA-GERAL-BASE-LIQUIDA','segunda oportunidade Manica','opportunity:309']) assert.ok(text.includes(expected),expected);
|
||||
for(const text of [csv,html,xml]) assert.match(text,/IVA-GERAL-BASE-LIQUIDA[^\n<]{0,30}4/);
|
||||
assert.match(html,/segunda oportunidade Manica/);assert.match(xml,/segunda oportunidade Manica/);
|
||||
const out='outputs/simulator-closure-qa-20261024';await mkdir(out,{recursive:true});
|
||||
await Promise.all([writeFile(`${out}/monthly-final.csv`,csv),writeFile(`${out}/monthly-final.docx`,bytes),writeFile(`${out}/monthly-final.html`,html),writeFile(`${out}/frozen-fixture.json`,JSON.stringify(draft,null,2))]);
|
||||
});
|
||||
@@ -17,7 +17,7 @@ test('cashflow draft recovery preserves unanswered months and invalid editable v
|
||||
openingCash: '77.01',
|
||||
monthlyTaxSchedule: ['10', '', '20', ...Array(9).fill('0')],
|
||||
};
|
||||
const stored = serializeCashflowDraftSnapshot('schedule', inputs);
|
||||
const stored = serializeCashflowDraftSnapshot('schedule', inputs, 'MZN');
|
||||
const restored = restoreCashflowDraftSnapshot(stored);
|
||||
assert.equal(restored.taxMode, 'schedule');
|
||||
assert.deepEqual(restored.inputs, inputs);
|
||||
@@ -65,7 +65,7 @@ test('cashflow draft recovery preserves unanswered months and invalid editable v
|
||||
assert.equal(restoreCashflowDraftSnapshot('{"version":2}'), null);
|
||||
assert.equal(
|
||||
restoreCashflowDraftSnapshot(
|
||||
stored.replace('"version":1', '"version":2'),
|
||||
stored.replace('"version":2', '"version":3'),
|
||||
),
|
||||
null,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user