Compare commits

...
12 changed files with 795 additions and 1 deletions
@@ -1110,3 +1110,41 @@ Só serão demonstradas estas novas regras/efeitos,emQA isolada e com as guardas
### Esforço global:referência inicial não é previsão actual
As240–416horas deL8–L14 permanecem identificadas como referência inicial de planeamento,não saldo actual nem prazo assumido. Antes de estimar o conjunto:reconciliar por lote o que já está implementado/reutilizável e o que falta em código;descontar provas aceites sem as repetir;separar espera documental;incluir resíduosL1–L7,integrações/entregas eL15–L16,SISA/encargos imobiliários. Campos de entrada,fontes inventariadas ou percentagens isoladas não provam percurso executável completo. Contar apenas o delta necessário e não somar duas vezes componentes partilhados. Esta passagem entregaL7 e delimitaL8;gere uma estimativa reconciliada futura,sem converter os intervalos iniciais numa promessa global. **L7 parcial;saldo finalL8 ainda não implementado nesta entrega.**
## 26/10/2026 — L8: primeiro saldo anual simulado demonstrado em QA
**QA concluído em `11a6cee9`; ainda sem instalação.** `f8d57bdd` permanece a entrega comunicada no portal. Este incremento reutiliza a colecta corrente de L6 ou o total ordinário L7 aplicável, sem somar ambos nem contornar um tratamento pendente. Recorte: sujeito residente no regime geral, ano civil de 2026, MZN, sem PEC/créditos positivos ou imposto autónomo que exijam liquidação própria. L6 operacional no âmbito demonstrado; L7 mantém o saldo parcial já discriminado. Não há nova pesquisa ou reabertura das provas aceites.
**Percurso executável:** «Preparar saldo anual L8» reutiliza NUIT, exercício e chave/versão da colecta sem substituir edições deliberadas. Selecciona os comprovativos L5 recebidos, efectivos, por conta e reconciliados; confirma o próprio beneficiário e a inclusão do rendimento na base do exercício. Retenções entregues pela empresa por pagamentos a terceiros, definitivas ou projectadas não são abatimentos próprios. PPC exige recibo, emitente, data, titular, exercício, moeda, montante comprovado, parcela utilizada e confirmação do pagamento efectivo; não usa previsões do plano nem calcula a obrigação PPC. A reutilização não apaga provas ou consome saldos oficiais.
Conhecido, ausência declarada com suporte e informação em falta são estados distintos. Ausência contraditória, titular/exercício divergente, prova em falta ou origem desactualizada mantêm o saldo dependente indeterminado. A identidade normalizada do emitente/comprovativo detecta repetição mesmo com data alterada; a mesma prova não pode ser retenção e PPC. Controla limites do comprovativo e da afectação L5, incluindo parcelas em outros contextos do mesmo dossier. São simulações alternativas, sem reserva central ou consumo de crédito fiscal entre dossiers independentes.
| Caso demonstrado | Resultado L8 simulado |
| --- | --- |
| Colecta 1 408 − retenção sofrida 300 − PPC 500 | **608 MZN a pagar**. |
| Colecta 1 408 − retenção sofrida 1 000 − PPC 600 | Saldo aritmético −192: **192 MZN a recuperar**, sem afirmar reembolso recebido. |
| Colecta 1 408 − retenção 408 − PPC 1 000 | Saldo zero, com parcelas completas. |
| Ausência declarada de retenções/PPC, PEC ausente e outros créditos conhecidos zero | 1 408 MZN a pagar; estados conhecidos/ausentes conservados. |
| PPC por comprovar, prova repetida ou PEC positivo ainda sem regra própria | Saldo a pagar/recuperar indeterminado; entradas e subtotais conhecidos conservados. |
| Fronteiras do mínimo: saldos ±99,99 e ±100 | Aritmética exacta; abaixo de 100 MZN sem cobrança/reembolso neste recorte; em 100 aplica pagar/recuperar. Sem arredondamento ou transporte de crédito inventado. |
| Colecta anual L7 acelerada aceite de 1 996,80, sem abatimentos declarados | Reutilizada uma vez: saldo simulado 1 996,80, sem somar a referência L6 nem recalcular o benefício. |
**Fontes e limites:** CIRPC arts. 63/67, Lei 34/2007 e natureza da retenção na redacção aplicável da Lei 12/2025; Regulamento Decreto 9/2008 arts. 21(2–5) e 27(1–2/6). O mínimo de 100 MZN do art. 27(6) está ligado ao recorte ordinário de 2026; as alterações IRPC do acervo de 2025 não substituem essa regra. Exemplares, páginas e hashes constam do recibo, com aceitação oficial anterior preservada. Não generalizar este parâmetro a períodos/regimes não correspondidos. PEC positivo, crédito de investimento, dupla tributação e outros créditos não recebem abatimento, limite, transporte ou reembolso presumidos. Um crédito potencial L7 não passa automaticamente a crédito utilizável L8.
**Gravação e recuperação:** livro opcional `irpcAnnual` no snapshot existente, por contexto, sujeito e exercício; ligado à execução da colecta e às provas L5 seleccionadas. Guarda o input e o resultado simulado, acrescenta o resultado anterior ao histórico sem o recalcular, e invalida o resultado corrente quando mudam as fontes/entradas. HTML e dossier financeiro recuperam colecta, provas/parcelas, estados, saldo, lacunas e limites concordantes. Sem preparação L8, os relatórios anteriores não ganham linhas L8. Plano, originais, livros e históricos L5–L7 permanecem imutáveis. O saldo não é declaração submetida, pagamento efectuado ou reembolso recebido.
**Provas novas:** 19 testes frontend dirigidos; API privada QA, 1 teste/170 asserções, sete casos com cálculo → gravação → recuperação → HTML/dossier; base isolada `simulator_fiscal_delta_20261021_test`, loopback 55441, guarda de identidade e transacção com rollback. Interface real: preparar, preencher só dados em falta, seleccionar prova, impedir repetição, pagar/recuperar/zero/ausência/indeterminação, guardar/recarregar/recuperar/exportar, sem erros de página. Wayfinder `--with-form`, TypeScript e build QA passaram. Comparação de 2 832 ficheiros anteriores: só três pontos de integração alterados, mais os cinco ficheiros novos do incremento; motores L5/L6/L7, fontes, fixtures, schema, configuração e permissões preservados; 31 originais iguais. Não se repetiram suites aceites de L3, ISPC, câmbio ou L5–L7. SGF TEMPO separado.
### Saldo concreto L8 após este incremento
| Código ainda por implementar | Condição concreta de retoma |
| --- | --- |
| PEC e créditos positivos: investimento, dupla tributação e outros créditos próprios | Correspondência do crédito, titular/exercício, montante efectivamente disponível, ordem, limite e transporte aplicáveis; prova de cada saldo. Não presumir reembolsabilidade ou prazo de 3/5 anos. |
| Liquidação de imposto autónomo/61-A e articulação com o saldo anual | Segregar a parcela autónoma e corresponder quais abatimentos podem incidir sobre ela; não a ignorar nem compensar automaticamente com prejuízo/PPC. |
| Períodos especiais e comprovativos noutra moeda | Corresponder exercício/data de afectação e regra cambial fiscal do pagamento; reutilizar periodização/câmbio existentes sem confirmar pressupostos por omissão. |
| Cálculo da obrigação PPC/PEC, calendários, juros/multas e guias | Regra, parâmetros, factos e vencimentos próprios por período. O registo de PPC efectuado já demonstrado não calcula a obrigação. |
| Transporte oficial M20/M22, submissão e processamento de reembolso | Edição/campo, representação de fracções e fluxo de recepção correspondidos; sem preencher campos provisórios como oficiais. |
**Esclarecimentos documentais aguardados:** permanecem as dúvidas concretas L5/L6/L7 já registadas, sem nova pesquisa repetida. Exemplo: cooperativa com rendimentos mistos/retidos requer segregação antes de utilizar retenção positiva; excepção de cumulação exige disposição, período e limites correspondidos; campo M20/M22 divergente requer edição/campo exactos. Cada falta só bloqueia o seu caso. Ausência de documento num cenário não bloqueia o saldo normal independente acima.
**Próxima entrega:** compatibilidade conferida com `origin/main` em `f8d57bdd`, sem incorporar runtime do checkout raiz. Nenhum acesso ou operação no servidor nesta passagem. Os 4,14 GiB são o valor comunicado, não medição nova; antes de instalar, medir espaço/inodes, confirmar capacidade para backup/restauro fresco e compilação isolada, mantendo a versão activa até a nova estar pronta. Recibo: `docs/investmoz/simulations/irpc-l8-first-qa-20261026.json`; provas: `outputs/irpc-l8-settlement/`. Reprodução só do delta: `node --test tests/frontend/irpc-annual-settlement.test.mjs`; `php tools/irpc-annual-settlement-qa.php routes`; `php tools/irpc-annual-settlement-qa.php test`; `tsc --noEmit`; build com `INVESTMOZ_QA_BUILD=1`. **L8 parcial, QA pronto, sem instalação.** A referência inicial de 240–416 horas L8–L14 não é previsão actual; permanece necessária a reconciliação do trabalho reutilizado, resíduos e L15–L16 antes de assumir prazo global.
@@ -0,0 +1,382 @@
{
"status": "QA_complete_ready_for_grouped_delivery_not_installed",
"date": "2026-10-26",
"branch": "codex/irpc-l8-settlement-qa-20261026",
"runtime_revision": "11a6cee981f0a425b0fceefb6ed85485bb298245",
"installed_revision_per_user": "f8d57bddf96b4610604a013fcc5bdd703c7dc6d5",
"remote_main_checked": "f8d57bddf96b4610604a013fcc5bdd703c7dc6d5",
"version": "IRPC-annual-settlement/1",
"scope": "resident general IRPC,annualcivil2026,MZN,currentdetermined assessment,documented absence of positive PEC/unmappedcredits and autonomoustax needing ownsettlement",
"inputs_reused": [
"current L6 or applicable qualifiedL7 assessment key/version/subjectNUIT/period;never add both",
"L5 received/effective/on_account qualified and reconciled withholding proofs",
"actual documented PPC payments by subject/period/receipt",
"known/declaredabsence/missing states for every credit/paymentfamily"
],
"PEC_and_special_credits_separate": true,
"official_credit_consumption_or_central_reservation": false,
"duplicate_guards": [
"same L5 allocation/pair",
"normalised issuer/document even if date changed",
"same receipt as withholding and PPC",
"receipt total including partial allocations in other contexts in same dossier",
"same sourceKey L5 allocation cannot consume larger receipt twice",
"different entity/year or inconsistent proven amount"
],
"cases": {
"payable": {
"status": "payable",
"knownWithholdings": {
"numerator": "300",
"denominator": "1"
},
"knownPpc": {
"numerator": "500",
"denominator": "1"
},
"arithmeticBalance": {
"numerator": "608",
"denominator": "1"
},
"payable": {
"numerator": "608",
"denominator": "1"
},
"recoverable": {
"numerator": "0",
"denominator": "1"
},
"pending": []
},
"recoverable": {
"status": "recoverable",
"knownWithholdings": {
"numerator": "1000",
"denominator": "1"
},
"knownPpc": {
"numerator": "600",
"denominator": "1"
},
"arithmeticBalance": {
"numerator": "-192",
"denominator": "1"
},
"payable": {
"numerator": "0",
"denominator": "1"
},
"recoverable": {
"numerator": "192",
"denominator": "1"
},
"pending": []
},
"balanced": {
"status": "balanced",
"knownWithholdings": {
"numerator": "408",
"denominator": "1"
},
"knownPpc": {
"numerator": "1000",
"denominator": "1"
},
"arithmeticBalance": {
"numerator": "0",
"denominator": "1"
},
"payable": {
"numerator": "0",
"denominator": "1"
},
"recoverable": {
"numerator": "0",
"denominator": "1"
},
"pending": []
},
"absent": {
"status": "payable",
"knownWithholdings": {
"numerator": "0",
"denominator": "1"
},
"knownPpc": {
"numerator": "0",
"denominator": "1"
},
"arithmeticBalance": {
"numerator": "1408",
"denominator": "1"
},
"payable": {
"numerator": "1408",
"denominator": "1"
},
"recoverable": {
"numerator": "0",
"denominator": "1"
},
"pending": []
},
"missing": {
"status": "indeterminate",
"knownWithholdings": {
"numerator": "300",
"denominator": "1"
},
"knownPpc": {
"numerator": "0",
"denominator": "1"
},
"arithmeticBalance": null,
"payable": null,
"recoverable": null,
"pending": [
"QA-PPC-2026-1: Error: PPC exige comprovativo efectivo,MZN,data,entidade e exercício correspondentes;projecção/tesouraria não é pagamento"
]
},
"duplicate": {
"status": "indeterminate",
"knownWithholdings": {
"numerator": "300",
"denominator": "1"
},
"knownPpc": {
"numerator": "0",
"denominator": "1"
},
"arithmeticBalance": null,
"payable": null,
"recoverable": null,
"pending": [
"QA-PPC-2026-1: Comprovativo utilizado além do montante disponível ou com montantes divergentes neste/outro mapa."
]
},
"pec": {
"status": "indeterminate",
"knownWithholdings": {
"numerator": "300",
"denominator": "1"
},
"knownPpc": {
"numerator": "500",
"denominator": "1"
},
"arithmeticBalance": null,
"payable": null,
"recoverable": null,
"pending": [
"PEC: Error: montante positivo fora deste recorte:corresponder ordem,limite e transporte próprios;sem abatimento ou reembolso automático"
]
}
},
"checks": {
"frontend_tests": 19,
"frontend_pass": 19,
"API_tests": 1,
"API_assertions": 170,
"API_cases": 7,
"database": "simulator_fiscal_delta_20261021_test",
"host": "127.0.0.1",
"port": 55441,
"transaction_rollback": true,
"actual_components_UI": {
"actualComponents": true,
"explicitPreparation": true,
"onlyActualOnAccountProofSelectable": true,
"duplicateSelectionDisabled": true,
"missingNotZero": true,
"ownBeneficiaryAndIncludedIncomeConfirmed": true,
"actualPPCConfirmed": true,
"PECAndCreditsSeparate": true,
"payable608": true,
"recoverable192": true,
"balanced": true,
"duplicateIndeterminate": true,
"positivePECIndeterminate": true,
"oldBooksAndPlanUnchanged": true,
"saveReloadRecoverReportDownload": true,
"errors": []
},
"wayfinder_with_form": "passed",
"typescript": "passed",
"isolated_QA_build": "passed",
"build_existing_warnings": [
"chunk size over500kB;plugin timing diagnostic"
],
"prior_accepted_suites_not_repeated": true
},
"runtime_delta": [
"resources/js/components/simulation-form.tsx",
"resources/js/components/simulation-irpc-annual.tsx",
"resources/js/lib/irpc-annual-settlement.ts",
"resources/js/lib/project-dossier-report.ts",
"resources/js/lib/project-dossier.ts",
"tests/Feature/IrpcAnnualSettlementDraftTest.php",
"tests/frontend/irpc-annual-settlement.test.mjs",
"tools/irpc-annual-settlement-qa.php"
],
"preservation": {
"baseline": "f8d57bddf96b4610604a013fcc5bdd703c7dc6d5",
"documentation_base": "d5f11462",
"existing_tracked_files": 2832,
"changed_existing_files": [
"docs/investmoz/consolidated-review-matrix-2026-10-03.md",
"docs/investmoz/unification/traceability-and-manual.md",
"docs/investmoz/v2/auditoria-portal/matriz-requisitos.csv",
"resources/js/components/simulation-form.tsx",
"resources/js/lib/project-dossier-report.ts",
"resources/js/lib/project-dossier.ts"
],
"all_other_existing_files_byte_identical": true,
"schema_configuration_permissions_unchanged": true,
"L5_L6_L7_engines_and_fixtures_byte_identical": true,
"originals_count": 31,
"originals": {
"docs/investmoz/simulations/source-archive/AT-Comunicado-9-2026-ISPC.pdf": "106f8a9ce9255c03549c70bf07fe999d32e6e55302d009b509862aa74ccb4973",
"docs/investmoz/simulations/source-archive/AT-MRFTA-19-39.pdf": "8ea62c53066b482f87078a7a7b1ecf15ca98a12a122e8d7abca63acfc993186b",
"docs/investmoz/simulations/source-archive/AT-Modelo-30-ISPC.pdf": "6802edd06b6ff638a0b851a3ec7ecf69af8f07840faa47446becad67d35d445c",
"docs/investmoz/simulations/source-archive/AT-Modelo-B-Geral.pdf": "bd977c7c59d4d98befd92e2c566b1bf168cb1343bb519f51b41a3807f431fc57",
"docs/investmoz/simulations/source-archive/BM-Mercado-Monetario-MAIBOR-20261025.html": "00e8a7e0d3a1db4ca3beb56d5361b2084a0f1684f3d40fc1c203702a2190e063",
"docs/investmoz/simulations/source-archive/BR-16-I-Serie-2008-04-16.pdf": "fc373ff5637edf75ec6a9595dec8763a8503d4ee786e3b7d8f1c742eae9d80ee",
"docs/investmoz/simulations/source-archive/BR-248-I-Suplemento-1-2025.pdf": "ccf2a8deeca820f2dd076b4b2141b1efe3f6a806568c3a587ca59a8e92f4c251",
"docs/investmoz/simulations/source-archive/BR-89-I-Serie-2017-06-08.pdf": "11f0cab15eb6e51010952203ffa24fdc533a24fd642e19525fe4f46b17a1f5e5",
"docs/investmoz/simulations/source-archive/BR-95-I-Suplemento-2025-05-21.pdf": "e0e682e2c027c3cd9c1d46a73a3f48bc5f6bdbf05667fd97314d7a3f6a456358",
"docs/investmoz/simulations/source-archive/BR252-I-Serie-2022-Lei20-PESOE.pdf": "1159fd3ce1ba5581671796d083534da48bd3c5578298819e896f1c2a8026318d",
"docs/investmoz/simulations/source-archive/Decreto-22-2021-BR70.pdf": "a8fac38dcfe08f9c7a8e46254cff10e1ec82fc4f7a8d88ad6e4fe7d91ffc21a5",
"docs/investmoz/simulations/source-archive/Decreto-27-2026-BR123.pdf": "26249c3fa58f149c81de36e99d1aac2819a4771379d9e8833159660dabf539bb",
"docs/investmoz/simulations/source-archive/Decreto-29-1998-BR22-5-suplemento.pdf": "6242cbe675faadf085f43f95ec8c7e8868aafe67c3b2ab2bb010992288f954f8",
"docs/investmoz/simulations/source-archive/Decreto-3-2011-BR-11-I-mineiros.pdf": "5bca1133666ba9c69870b89823420175b7b5b8d0eccb667ecdf5c29716f3b6be",
"docs/investmoz/simulations/source-archive/Decreto-37-2023-BR124.pdf": "7e680b191481faa0e2e9057fa5053587eb2a29d1d8c819e0eb9f02814255a04c",
"docs/investmoz/simulations/source-archive/Decreto-9-2008-BR16.pdf": "96fccdecbfdc11c9eafb90fbc6c7bc259f88bbd4c7507d1ac3af1fa03f960697",
"docs/investmoz/simulations/source-archive/Lei-13-2016-CIVA-BR-156-10-suplemento.pdf": "b90dc7c14ecea2e378c4a62e8e5a47d54a5644c457baeb8b8053b92fd2a5710b",
"docs/investmoz/simulations/source-archive/Lei-14-1992-BR42.pdf": "089fff7423e99a70a230a73ca379e4ea3a91d124703f91016e57ff82794b9e56",
"docs/investmoz/simulations/source-archive/Lei-17-2022-BR251-exemplar-MCNet5.pdf": "14eb34f0af91a8f5cb1770c7b52d6ff839c6818302d1ed96d79afbfb9fe999d4",
"docs/investmoz/simulations/source-archive/Lei-17-2022-IPP-BR250-Parlamento.pdf": "978cd22403ffd336a631704ecd7a5bbd814a427c815b9acdaca5515591dd9e2a",
"docs/investmoz/simulations/source-archive/Lei-19-2013-BR76.pdf": "bba96d645cc935ffc3d1a79b9f0f73ac1c693d6459c2ad41f56cd2841accae76",
"docs/investmoz/simulations/source-archive/Lei-19-2017-IRPS-BR-OCAM.pdf": "c3a0186cb9ad090d7a9533006295dc9bb6a62ad0d6150173cbd49dba4ee4b52d",
"docs/investmoz/simulations/source-archive/Lei-2-2011-BR-1-4-suplemento-MCNet.pdf": "a7543a68cc3287a46a83c39a04821b40f649dd06c4e9b5dc51ff0b47fba08899",
"docs/investmoz/simulations/source-archive/Lei-20-2009-BR36-AT-exemplar19.pdf": "fc1c8144db42960cbf73e717e095896f511ece0f907f58f78819b27d6917a7ee",
"docs/investmoz/simulations/source-archive/Lei-22-2022-IVA-republicada-BR-250-2-suplemento.pdf": "ea1b0dc6f5cf6e595f6d9a19e7042f398999e0087caef1785f6e8fac571b5f53",
"docs/investmoz/simulations/source-archive/Lei-34-2007-BR52.pdf": "cdbef875ad7ccc1517b12faf5de0e893a70ed2ed81e33913cd6dc4a674474e6e",
"docs/investmoz/simulations/source-archive/Lei-4-1994-BR37-3-suplemento.pdf": "210df9db8980d0a5690cd187a6eeec7e37a900d743e39062c16f4e0d12215c7e",
"docs/investmoz/simulations/source-archive/Lei-4-2012-BR3.pdf": "b8845cef41dcbd9ba50c43dbfa743a56030dd3d348242a107e1ae30279eec121",
"docs/investmoz/simulations/source-archive/Lei-7-1991-Partidos-IFES.pdf": "6b783a0bb3330688ef10660a58060d9724aa2da6a873d234fe902f0eaaeda42d",
"docs/investmoz/simulations/source-archive/PT-MZ-CDT-1992.pdf": "d504e1024b28aee5c6acf4592acb96ec9c915a8414f67a61ab6984d6acf9df9d",
"docs/investmoz/simulations/source-archive/PT-MZ-Protocolo-2009.pdf": "f353773dcf41f09f4daa6cf5ba5a5f7994f3e02615867c2dcffc61c3d901bf9b"
},
"all_seven_recovered_cases_match_final_inputs": true,
"no_active_database_or_server_operations": true,
"no_SGF_TEMPO_operations": true,
"runtime_changed_existing_files": [
"resources/js/components/simulation-form.tsx",
"resources/js/lib/project-dossier-report.ts",
"resources/js/lib/project-dossier.ts"
],
"documentation_changed_existing_files": [
"docs/investmoz/consolidated-review-matrix-2026-10-03.md",
"docs/investmoz/unification/traceability-and-manual.md",
"docs/investmoz/v2/auditoria-portal/matriz-requisitos.csv"
],
"documentation_only_appends_and_target_AUD_record": true
},
"sources_reused": [
{
"file": "docs/investmoz/simulations/source-archive/Lei-34-2007-BR52.pdf",
"sha256": "cdbef875ad7ccc1517b12faf5de0e893a70ed2ed81e33913cd6dc4a674474e6e",
"articles": "CIRPC63/67",
"PDF_pages": [
18,
19
],
"official_source_acceptance_preserved": true
},
{
"file": "docs/investmoz/simulations/source-archive/Decreto-9-2008-BR16.pdf",
"sha256": "96fccdecbfdc11c9eafb90fbc6c7bc259f88bbd4c7507d1ac3af1fa03f960697",
"articles": "RIRPC21/27",
"PDF_pages": [
6,
7
],
"official_source_acceptance_preserved": true
},
{
"file": "docs/investmoz/simulations/source-archive/BR-248-I-Suplemento-1-2025.pdf",
"sha256": "ccf2a8deeca820f2dd076b4b2141b1efe3f6a806568c3a587ca59a8e92f4c251",
"articles": "Lei12/2025 IRPC67/version2026",
"PDF_pages": [
18,
19,
20
],
"official_source_acceptance_preserved": true
}
],
"minimum": {
"rule": "Decreto9/2008 art27(6)",
"scope": "2026 resident_general/calendar/MZN",
"less_than": "100 MZN",
"strict_boundary_tests": [
"+99.99",
"+100",
"-99.99",
"-100"
],
"arithmetic_exact": true,
"new_rounding": false,
"automatic_carry": false
},
"remaining_code": [
"automatic PPC/PEC obligationcalculation",
"positivePEC/creditorderlimitscarry",
"autonomoustaxsettlement",
"specialperiods/foreigncurrencypayments",
"interest/penalties/calendar/guideremittance",
"AT submission/refundprocessing",
"unmappedM20M22fields"
],
"documentary_dependencies": {
"previous_L5_L6_L7_matrix_items_preserved": true,
"examples": [
"mixed/withheld cooperativeincome needs L7 segregation",
"express cumulation exception requires disposition/period/limits",
"divergent M20M22 field needs exactedition and representation"
],
"only_dependent_case_blocked": true
},
"L6_operational_delimited": true,
"L7_partial": true,
"L8_partial": true,
"submitted_or_payment_or_refund_received": false,
"server_operations_this_turn": false,
"reported_server_free_GiB": 4.14,
"reported_capacity_not_measured_this_turn": true,
"capacity_blocks_future_install_not_QA": true,
"future_delivery": {
"fresh_main_compatibility": true,
"measure_space_and_inodes": true,
"confirm_backup_and_isolated_build_capacity": true,
"fresh_backup_restore": true,
"keep_active_until_new_ready": true,
"verify_served_revision_and_only_delta": true
},
"evidence_directory": "outputs/irpc-l8-settlement/",
"global_effort": {
"initial_L8_L14_reference_hours": [
240,
416
],
"is_current_remainingforecast": false,
"reconciliation_before_globalforecast": [
"implemented/reusable vs remainingcode perlot",
"reuseacceptedproofs;no duplicate sharedcomponentestimate",
"documentarywait separately",
"L1-L7residuals,integration/delivery,L15-L16 included"
]
},
"root_checkout_runtime_not_merged": true,
"SGF_TEMPO_untouched": true
}
@@ -6631,3 +6631,41 @@ Só serão demonstradas estas novas regras/efeitos,emQA isolada e com as guardas
### Esforço global:referência inicial não é previsão actual
As240–416horas deL8–L14 permanecem identificadas como referência inicial de planeamento,não saldo actual nem prazo assumido. Antes de estimar o conjunto:reconciliar por lote o que já está implementado/reutilizável e o que falta em código;descontar provas aceites sem as repetir;separar espera documental;incluir resíduosL1–L7,integrações/entregas eL15–L16,SISA/encargos imobiliários. Campos de entrada,fontes inventariadas ou percentagens isoladas não provam percurso executável completo. Contar apenas o delta necessário e não somar duas vezes componentes partilhados. Esta passagem entregaL7 e delimitaL8;gere uma estimativa reconciliada futura,sem converter os intervalos iniciais numa promessa global. **L7 parcial;saldo finalL8 ainda não implementado nesta entrega.**
## 26/10/2026 — L8: primeiro saldo anual simulado demonstrado em QA
**QA concluído em `11a6cee9`; ainda sem instalação.** `f8d57bdd` permanece a entrega comunicada no portal. Este incremento reutiliza a colecta corrente de L6 ou o total ordinário L7 aplicável, sem somar ambos nem contornar um tratamento pendente. Recorte: sujeito residente no regime geral, ano civil de 2026, MZN, sem PEC/créditos positivos ou imposto autónomo que exijam liquidação própria. L6 operacional no âmbito demonstrado; L7 mantém o saldo parcial já discriminado. Não há nova pesquisa ou reabertura das provas aceites.
**Percurso executável:** «Preparar saldo anual L8» reutiliza NUIT, exercício e chave/versão da colecta sem substituir edições deliberadas. Selecciona os comprovativos L5 recebidos, efectivos, por conta e reconciliados; confirma o próprio beneficiário e a inclusão do rendimento na base do exercício. Retenções entregues pela empresa por pagamentos a terceiros, definitivas ou projectadas não são abatimentos próprios. PPC exige recibo, emitente, data, titular, exercício, moeda, montante comprovado, parcela utilizada e confirmação do pagamento efectivo; não usa previsões do plano nem calcula a obrigação PPC. A reutilização não apaga provas ou consome saldos oficiais.
Conhecido, ausência declarada com suporte e informação em falta são estados distintos. Ausência contraditória, titular/exercício divergente, prova em falta ou origem desactualizada mantêm o saldo dependente indeterminado. A identidade normalizada do emitente/comprovativo detecta repetição mesmo com data alterada; a mesma prova não pode ser retenção e PPC. Controla limites do comprovativo e da afectação L5, incluindo parcelas em outros contextos do mesmo dossier. São simulações alternativas, sem reserva central ou consumo de crédito fiscal entre dossiers independentes.
| Caso demonstrado | Resultado L8 simulado |
| --- | --- |
| Colecta 1 408 − retenção sofrida 300 − PPC 500 | **608 MZN a pagar**. |
| Colecta 1 408 − retenção sofrida 1 000 − PPC 600 | Saldo aritmético −192: **192 MZN a recuperar**, sem afirmar reembolso recebido. |
| Colecta 1 408 − retenção 408 − PPC 1 000 | Saldo zero, com parcelas completas. |
| Ausência declarada de retenções/PPC, PEC ausente e outros créditos conhecidos zero | 1 408 MZN a pagar; estados conhecidos/ausentes conservados. |
| PPC por comprovar, prova repetida ou PEC positivo ainda sem regra própria | Saldo a pagar/recuperar indeterminado; entradas e subtotais conhecidos conservados. |
| Fronteiras do mínimo: saldos ±99,99 e ±100 | Aritmética exacta; abaixo de 100 MZN sem cobrança/reembolso neste recorte; em 100 aplica pagar/recuperar. Sem arredondamento ou transporte de crédito inventado. |
| Colecta anual L7 acelerada aceite de 1 996,80, sem abatimentos declarados | Reutilizada uma vez: saldo simulado 1 996,80, sem somar a referência L6 nem recalcular o benefício. |
**Fontes e limites:** CIRPC arts. 63/67, Lei 34/2007 e natureza da retenção na redacção aplicável da Lei 12/2025; Regulamento Decreto 9/2008 arts. 21(2–5) e 27(1–2/6). O mínimo de 100 MZN do art. 27(6) está ligado ao recorte ordinário de 2026; as alterações IRPC do acervo de 2025 não substituem essa regra. Exemplares, páginas e hashes constam do recibo, com aceitação oficial anterior preservada. Não generalizar este parâmetro a períodos/regimes não correspondidos. PEC positivo, crédito de investimento, dupla tributação e outros créditos não recebem abatimento, limite, transporte ou reembolso presumidos. Um crédito potencial L7 não passa automaticamente a crédito utilizável L8.
**Gravação e recuperação:** livro opcional `irpcAnnual` no snapshot existente, por contexto, sujeito e exercício; ligado à execução da colecta e às provas L5 seleccionadas. Guarda o input e o resultado simulado, acrescenta o resultado anterior ao histórico sem o recalcular, e invalida o resultado corrente quando mudam as fontes/entradas. HTML e dossier financeiro recuperam colecta, provas/parcelas, estados, saldo, lacunas e limites concordantes. Sem preparação L8, os relatórios anteriores não ganham linhas L8. Plano, originais, livros e históricos L5–L7 permanecem imutáveis. O saldo não é declaração submetida, pagamento efectuado ou reembolso recebido.
**Provas novas:** 19 testes frontend dirigidos; API privada QA, 1 teste/170 asserções, sete casos com cálculo → gravação → recuperação → HTML/dossier; base isolada `simulator_fiscal_delta_20261021_test`, loopback 55441, guarda de identidade e transacção com rollback. Interface real: preparar, preencher só dados em falta, seleccionar prova, impedir repetição, pagar/recuperar/zero/ausência/indeterminação, guardar/recarregar/recuperar/exportar, sem erros de página. Wayfinder `--with-form`, TypeScript e build QA passaram. Comparação de 2 832 ficheiros anteriores: só três pontos de integração alterados, mais os cinco ficheiros novos do incremento; motores L5/L6/L7, fontes, fixtures, schema, configuração e permissões preservados; 31 originais iguais. Não se repetiram suites aceites de L3, ISPC, câmbio ou L5–L7. SGF TEMPO separado.
### Saldo concreto L8 após este incremento
| Código ainda por implementar | Condição concreta de retoma |
| --- | --- |
| PEC e créditos positivos: investimento, dupla tributação e outros créditos próprios | Correspondência do crédito, titular/exercício, montante efectivamente disponível, ordem, limite e transporte aplicáveis; prova de cada saldo. Não presumir reembolsabilidade ou prazo de 3/5 anos. |
| Liquidação de imposto autónomo/61-A e articulação com o saldo anual | Segregar a parcela autónoma e corresponder quais abatimentos podem incidir sobre ela; não a ignorar nem compensar automaticamente com prejuízo/PPC. |
| Períodos especiais e comprovativos noutra moeda | Corresponder exercício/data de afectação e regra cambial fiscal do pagamento; reutilizar periodização/câmbio existentes sem confirmar pressupostos por omissão. |
| Cálculo da obrigação PPC/PEC, calendários, juros/multas e guias | Regra, parâmetros, factos e vencimentos próprios por período. O registo de PPC efectuado já demonstrado não calcula a obrigação. |
| Transporte oficial M20/M22, submissão e processamento de reembolso | Edição/campo, representação de fracções e fluxo de recepção correspondidos; sem preencher campos provisórios como oficiais. |
**Esclarecimentos documentais aguardados:** permanecem as dúvidas concretas L5/L6/L7 já registadas, sem nova pesquisa repetida. Exemplo: cooperativa com rendimentos mistos/retidos requer segregação antes de utilizar retenção positiva; excepção de cumulação exige disposição, período e limites correspondidos; campo M20/M22 divergente requer edição/campo exactos. Cada falta só bloqueia o seu caso. Ausência de documento num cenário não bloqueia o saldo normal independente acima.
**Próxima entrega:** compatibilidade conferida com `origin/main` em `f8d57bdd`, sem incorporar runtime do checkout raiz. Nenhum acesso ou operação no servidor nesta passagem. Os 4,14 GiB são o valor comunicado, não medição nova; antes de instalar, medir espaço/inodes, confirmar capacidade para backup/restauro fresco e compilação isolada, mantendo a versão activa até a nova estar pronta. Recibo: `docs/investmoz/simulations/irpc-l8-first-qa-20261026.json`; provas: `outputs/irpc-l8-settlement/`. Reprodução só do delta: `node --test tests/frontend/irpc-annual-settlement.test.mjs`; `php tools/irpc-annual-settlement-qa.php routes`; `php tools/irpc-annual-settlement-qa.php test`; `tsc --noEmit`; build com `INVESTMOZ_QA_BUILD=1`. **L8 parcial, QA pronto, sem instalação.** A referência inicial de 240–416 horas L8–L14 não é previsão actual; permanece necessária a reconciliação do trabalho reutilizado, resíduos e L15–L16 antes de assumir prazo global.
File diff suppressed because one or more lines are too long
@@ -1,3 +1,4 @@
import {SimulationIrpcAnnual} from '@/components/simulation-irpc-annual';
import {SimulationIrpcNormal} from '@/components/simulation-irpc-normal';
import {SimulationIrpcWithholdings} from '@/components/simulation-irpc-withholdings';
import {SimulationIspcResiduals} from '@/components/simulation-ispc-residuals';
@@ -3513,6 +3514,7 @@ export function SimulationForm() {
{(current.id === 'financial' || current.id === 'tax') && <SimulationIspcResiduals draft={draft} onChange={change} />}
{(current.id === 'financial' || current.id === 'tax') && <SimulationIrpcWithholdings draft={draft} onChange={change} />}
{(current.id === 'financial' || current.id === 'tax') && <SimulationIrpcNormal draft={draft} onChange={change} />}
{(current.id === 'financial' || current.id === 'tax') && <SimulationIrpcAnnual draft={draft} onChange={change} />}
{(current.id === 'financial' || current.id === 'tax') && <SimulationMonthlyIva draft={draft} busy={isCalculating} onCalculate={runSimulation} onChange={change} />}
{current.id === 'financial' &&
(draft.detail_level === 'advanced' ||
@@ -0,0 +1,25 @@
import {useState} from 'react';
import {annualBook,newAnnualBook,saveAnnualBook,reuseAnnualAssessment,annualAssessment,annualWithholdingOptions,reuseAnnualWithholding,newAnnualPpc,recordAnnual,annualReportRows,annualHtml,annualSettlementSource,type AnnualBook,type AnnualPpc} from '@/lib/irpc-annual-settlement';
type Props={draft:Record<string,unknown>;onChange:(field:string,value:string)=>void};
export function SimulationIrpcAnnual({draft,onChange}:Props){
const[error,setError]=useState('');if(draft.fiscal_component!=='irpc')return null;
try{
const b=annualBook(draft),save=(next:AnnualBook)=>{try{onChange('modelo_a_snapshot',saveAnnualBook(draft,next));setError('');}catch(e){setError(String(e));}};
if(!b)return<section aria-label="IRPC — L8 fecho anual" className="rounded border p-4"><h4>L8 — da colecta ao saldo anual simulado</h4><p>Reutiliza a colecta válida, retenções efectivamente sofridas por conta e PPC comprovados do próprio sujeito/exercício. PEC e créditos com regras próprias permanecem separados.</p><button type="button" onClick={()=>{try{save(reuseAnnualAssessment(draft,newAnnualBook()));}catch(e){setError(String(e));}}}>Preparar saldo anual L8</button>{error&&<p role="alert">{error}</p>}</section>;
const patch=(p:Partial<AnnualBook>)=>save({...b,...p}),a=annualAssessment(draft),options=annualWithholdingOptions(draft);
const field=(key:'subjectNuit'|'year'|'reference'|'assessmentReference',label:string)=><label className="block">{label}<input aria-label={label} value={b[key]} onChange={e=>patch({[key]:e.target.value})}/></label>;
const group=(key:'withholdings'|'ppc',label:string)=><><label className="block">Estado — {label}<select aria-label={'Estado L8 — '+label} value={b[key].state} onChange={e=>patch({[key]:{...b[key],state:e.target.value as ''|'known'|'absent'}})}><option value="">Informação em falta</option><option value="known">Valores conhecidos — seleccionar comprovativos</option><option value="absent">Ausência declarada com suporte</option></select></label><label className="block">Suporte — {label}<input aria-label={'Suporte L8 — '+label} value={b[key].reference} onChange={e=>patch({[key]:{...b[key],reference:e.target.value}})}/></label></>;
return<section aria-label="IRPC — L8 fecho anual" className="space-y-3 rounded border p-4"><h4>L8 — colecta, abatimentos e saldo anual simulado</h4><p>A colecta acima é anterior aos abatimentos. Este saldo tem cálculo e histórico próprios; retenção entregue pela empresa por pagamento a terceiros não é retenção sofrida. O resultado continua simulado, sem submissão, pagamento ou reembolso automático.</p><p>{annualSettlementSource}</p>{error&&<p role="alert">{error}</p>}
{field('subjectNuit','NUIT do sujeito do fecho L8')}{field('year','Exercício do fecho L8')}{field('reference','Suporte do fecho da entidade e exercício L8')}
<p>Colecta disponível:{a.colecta?a.colecta.numerator+'/'+a.colecta.denominator+' MZN':'Indeterminada'};origem:{a.kind==='special'?'L7 qualificado':'L6 normal'};NUIT:{a.subjectNuit||'Por identificar'};exercício:{a.year||'Por identificar'}.</p><button type="button" onClick={()=>{try{save(reuseAnnualAssessment(draft,b));}catch(e){setError(String(e));}}}>Reutilizar colecta corrente L6/L7</button>{field('assessmentReference','Suporte da colecta reutilizada L8')}
<fieldset className="space-y-2 rounded border p-3"><legend>Retenções efectivamente sofridas por conta</legend>{group('withholdings','Retenções sofridas')}
{options.length===0?<p>Não há afectações de comprovativosL5 neste contexto. Reutilize e reconcilie a retenção recebida emL5 ou declare ausência com suporte.</p>:options.map(o=><div key={o.rowId+'|'+o.paymentId} className="rounded border p-2"><p>{o.document||'Prova por identificar'};emitente:{o.issuer};data:{o.date};parcelaL5:{o.amount}MZN;{o.reason||'Retenção recebida/efectiva por conta reconciliada emL5.'}</p><button type="button" disabled={!o.ready||b.withholdings.rows.some(r=>r.rowId===o.rowId&&r.paymentId===o.paymentId)} onClick={()=>{try{save(reuseAnnualWithholding(draft,b,o.rowId,o.paymentId,crypto.randomUUID()));}catch(e){setError(String(e));}}}>Seleccionar prova L5 {o.document||o.paymentId}</button></div>)}
{b.withholdings.rows.map((r,index)=>{const change=(p:Partial<typeof r>)=>patch({withholdings:{...b.withholdings,rows:b.withholdings.rows.map(x=>x.id===r.id?{...x,...p}:x)}});return<fieldset key={r.id} className="rounded border p-2"><legend>Retenção seleccionada {index+1}</legend><p>Rendimento:{r.rowId};prova:{r.paymentId};NUIT:{r.subjectNuit};exercício:{r.year}. Fonte conservada. Se mudou, retire esta selecção e seleccione a prova actual. O histórico mantém-se.</p><label className="block">Parcela a abater<input aria-label={'Parcela da retenção L8 '+(index+1)} value={r.amount} onChange={e=>change({amount:e.target.value})}/></label><label className="block">O próprio sujeito é beneficiário e o rendimento está incluído na base deste exercício<select aria-label={'Rendimento incluído L8 '+(index+1)} value={r.incomeIncluded} onChange={e=>change({incomeIncluded:e.target.value as ''|'yes'|'no'})}><option value="">Informação em falta</option><option value="yes">Sim — documentado</option><option value="no">Não — crédito não aplicável neste fecho</option></select></label><label className="block">Suporte da titularidade e inclusão<input aria-label={'Suporte da retenção L8 '+(index+1)} value={r.reference} onChange={e=>change({reference:e.target.value})}/></label><button type="button" onClick={()=>patch({withholdings:{...b.withholdings,rows:b.withholdings.rows.filter(x=>x.id!==r.id)}})}>Retirar selecção L8 da retenção {index+1}</button></fieldset>;})}</fieldset>
<fieldset className="space-y-2 rounded border p-3"><legend>PPC efectivamente pagos</legend>{group('ppc','PPC efectuados')}<p>Regista comprovativos de pagamentos por conta; não calcula automaticamente a obrigação PPC nem usa pagamentos previstos no plano.</p><button type="button" onClick={()=>patch({ppc:{...b.ppc,rows:[...b.ppc.rows,{...newAnnualPpc(crypto.randomUUID()),subjectNuit:b.subjectNuit,year:b.year}]}})}>Registar comprovativo PPC</button>
{b.ppc.rows.map((r,index)=>{const change=(p:Partial<AnnualPpc>)=>patch({ppc:{...b.ppc,rows:b.ppc.rows.map(x=>x.id===r.id?{...x,...p}:x)}});return<fieldset key={r.id} className="rounded border p-2"><legend>PPC {index+1}</legend>{([['document','Comprovativo'],['issuer','Emitente'],['date','Data (AAAA-MM-DD)'],['subjectNuit','NUIT beneficiário'],['year','Exercício do comprovativo'],['currency','Moeda do comprovativo'],['proofAmount','Montante comprovado'],['amount','Parcela a abater'],['reference','Suporte do pagamento e afectação']]as const).map(([key,label])=><label key={key} className="block">{label}<input aria-label={'PPC L8 '+(index+1)+' — '+label} value={r[key]} onChange={e=>change({[key]:e.target.value})}/></label>)}<label className="block">Pagamento efectivamente comprovado<select aria-label={'PPC L8 '+(index+1)+' — Efectivo'} value={r.effective} onChange={e=>change({effective:e.target.value as ''|'yes'|'no'})}><option value="">Informação em falta</option><option value="yes">Sim — comprovativo reconciliado</option><option value="no">Não — apenas previsão/outro estado</option></select></label><button type="button" onClick={()=>patch({ppc:{...b.ppc,rows:b.ppc.rows.filter(x=>x.id!==r.id)}})}>Retirar PPC {index+1}</button></fieldset>;})}</fieldset>
{([['pec','PEC'],['credits','Créditos com regras próprias']]as const).map(([key,label])=><fieldset key={key} className="rounded border p-3"><legend>{label} — tratamento separado</legend><label className="block">Estado — {label}<select aria-label={'Estado L8 — '+label} value={b[key].state} onChange={e=>patch({[key]:{...b[key],state:e.target.value as ''|'known'|'absent'}})}><option value="">Informação em falta</option><option value="absent">Ausência declarada com suporte</option><option value="known">Valor conhecido — positivo exige regra própria</option></select></label>{(b[key].state==='known'||b[key].amount)&&<label className="block">Montante declarado<input aria-label={'Montante L8 — '+label} value={b[key].amount} onChange={e=>patch({[key]:{...b[key],amount:e.target.value}})}/></label>}<label className="block">Suporte<input aria-label={'Suporte L8 — '+label} value={b[key].reference} onChange={e=>patch({[key]:{...b[key],reference:e.target.value}})}/></label></fieldset>)}
<button type="button" onClick={()=>{try{save(recordAnnual(draft,b));}catch(e){setError(String(e));}}}>Calcular e guardar saldo anual L8</button><button type="button" onClick={()=>{try{const html=annualHtml(draft),url=URL.createObjectURL(new Blob([html],{type:'text/html;charset=utf-8'})),link=document.createElement('a');link.href=url;link.download='IRPC-L8-saldo-simulado.html';link.click();URL.revokeObjectURL(url);setError('');}catch(e){setError(String(e));}}}>Exportar saldo anual L8</button>
{annualReportRows(draft).map((r,n)=><p key={n}>{r[1]} — {r[2]} — {r[3]}</p>)}</section>;
}catch(e){return<p role="alert">PreparaçãoL8 não recuperável:{String(e)}. Originais e históricos conservados.</p>;}
}
+124
View File
@@ -0,0 +1,124 @@
import {normalBook,normalCurrent} from './irpc-normal.ts';
import {irpcBook,irpcCurrent} from './irpc-withholdings.ts';
import {acquisitionScope} from './iva-acquisitions.ts';
import {closureFingerprint} from './iva-closure.ts';
import {addFractions,subtractFractions,decimalFraction,fraction,fractionPresentation,type FiscalFraction} from './fiscal-rational.ts';
export const annualSettlementVersion='IRPC-annual-settlement/1';
export const annualSettlementSource='CIRPC, arts. 63 e 67 (Lei 34/2007 e natureza da retenção na redacção aplicável da Lei 12/2025); Regulamento, Decreto 9/2008, arts. 21(2–5) e 27(1–2/6). Recorte: 2026, residente no regime geral, ano civil e MZN. Colecta corrente válida, retenções efectivamente sofridas por conta e PPC comprovados. PEC/créditos positivos e tributação autónoma ficam fora deste fecho inicial. Art. 27(6): montante inferior a 100 MZN sem cobrança/reembolso; saldo aritmético exacto conservado. Saldo simulado por colecta projectada; não é declaração submetida, pagamento ou reembolso recebido.';
type State=''|'known'|'absent';
type Evidence={state:State;amount:string;reference:string};
type Group={state:State;reference:string};
export type AnnualWithholding={id:string;rowId:string;paymentId:string;sourceKey:string;subjectNuit:string;year:string;amount:string;incomeIncluded:''|'yes'|'no';reference:string};
export type AnnualPpc={id:string;document:string;issuer:string;date:string;subjectNuit:string;year:string;currency:string;proofAmount:string;amount:string;effective:''|'yes'|'no';reference:string};
type AnnualRecord={version:typeof annualSettlementVersion;key:string;createdAt:string;model:AnnualModel};
export type AnnualBook={version:1;subjectNuit:string;year:string;reference:string;assessment:''|'normal'|'special';assessmentKey:string;assessmentReference:string;withholdings:Group&{rows:AnnualWithholding[]};ppc:Group&{rows:AnnualPpc[]};pec:Evidence;credits:Evidence;result?:AnnualRecord;history:AnnualRecord[]};
const evidence=():Evidence=>({state:'',amount:'',reference:''});
export const newAnnualBook=():AnnualBook=>({version:1,subjectNuit:'',year:'',reference:'',assessment:'',assessmentKey:'',assessmentReference:'',withholdings:{state:'',reference:'',rows:[]},ppc:{state:'',reference:'',rows:[]},pec:evidence(),credits:evidence(),history:[]});
export const newAnnualPpc=(id:string):AnnualPpc=>({id,document:'',issuer:'',date:'',subjectNuit:'',year:'',currency:'MZN',proofAmount:'',amount:'',effective:'',reference:''});
const zero=()=>fraction(0n),cmp=(a:FiscalFraction,b:FiscalFraction)=>BigInt(a.numerator)*BigInt(b.denominator)-BigInt(b.numerator)*BigInt(a.denominator);
const iso=(s:string)=>/^20\d{2}-\d{2}-\d{2}$/.test(s)&&Number.isFinite(Date.parse(s))&&new Date(s).toISOString().slice(0,10)===s;
const moneyInput=(s:string)=>{if(!/^\d{1,18}(\.\d{1,2})?$/.test(s))throw Error('MontanteMZN em falta/inválido;máximo dois decimais,sem zero presumido.');return decimalFraction(s);};
const canonical=(s:string)=>s.normalize('NFKC').trim().toLocaleLowerCase('pt').replace(/\s+/g,' ');
const proofIdentity=(issuer:string,document:string,_date:string)=>JSON.stringify([canonical(issuer),canonical(document)]);
function store(d:Record<string,unknown>){const s=JSON.parse(String(d.modelo_a_snapshot||'{"version":1,"books":{}}'));if(s.version!==1||!s.books||typeof s.books!=='object'||Array.isArray(s.books))throw Error('Livros fiscais não recuperáveis;originais conservados.');return s;}
function books(d:Record<string,unknown>):Record<string,AnnualBook>{const m=store(d).irpcAnnual;if(m===undefined)return{};if(!m||typeof m!=='object'||Array.isArray(m))throw Error('MapaL8 não recuperável;originais conservados.');return m;}
export function validateAnnual(b:AnnualBook){
if(!b||b.version!==1||['subjectNuit','year','reference','assessmentKey','assessmentReference'].some(k=>typeof b[k as 'year']!=='string')||!['','normal','special'].includes(b.assessment)||!Array.isArray(b.history))throw Error('PreparaçãoL8 não recuperável.');
for(const g of[b.withholdings,b.ppc,b.pec,b.credits])if(!g||!['','known','absent'].includes(g.state)||typeof g.reference!=='string')throw Error('EstadoL8 não recuperável.');
for(const g of[b.pec,b.credits])if(typeof g.amount!=='string')throw Error('CréditoL8 não recuperável.');
const w=['id','rowId','paymentId','sourceKey','subjectNuit','year','amount','incomeIncluded','reference'],p=Object.keys(newAnnualPpc(''));
for(const[rows,keys]of[[b.withholdings.rows,w],[b.ppc.rows,p]]as const)if(!Array.isArray(rows)||new Set(rows.map(r=>r?.id)).size!==rows.length||rows.some(r=>!r||keys.some(k=>typeof(r as unknown as Record<string,unknown>)[k]!=='string')))throw Error('ComprovativosL8 não recuperáveis ou identificador repetido.');
if(b.withholdings.rows.some(r=>!['','yes','no'].includes(r.incomeIncluded))||b.ppc.rows.some(r=>!['','yes','no'].includes(r.effective)))throw Error('QualificaçãoL8 não recuperável.');
for(const r of[b.result,...b.history])if(r&&(r.version!==annualSettlementVersion||typeof r.key!=='string'||typeof r.createdAt!=='string'||!r.model||!Array.isArray(r.model.pending)))throw Error('HistóricoL8 não recuperável.');
}
export function annualBook(d:Record<string,unknown>){const b=books(d)[acquisitionScope(d)];if(b)validateAnnual(b);return b;}
export function saveAnnualBook(d:Record<string,unknown>,b:AnnualBook){validateAnnual(b);return JSON.stringify({...store(d),irpcAnnual:{...books(d),[acquisitionScope(d)]:b}});}
export function annualAssessment(d:Record<string,unknown>){
const b=normalBook(d),r=b.result,m=r?.model;
return{book:b,record:r,current:normalCurrent(d,b),key:r?closureFingerprint(r):'',subjectNuit:b.base?.subjectNuit??'',year:b.year,period:m?.periodsFx?{from:m.periodsFx.input.from,to:m.periodsFx.input.to}:m?.fiscalPeriod??m?.assessment?.period??null,kind:b.special?'special' as const:'normal' as const,colecta:b.special?m?.special?.colecta??null:m?.colecta??null,autonomous:b.special?m?.special?.expenses.autonomousTax??null:zero()};
}
export function reuseAnnualAssessment(d:Record<string,unknown>,b:AnnualBook):AnnualBook{const a=annualAssessment(d);return{...b,subjectNuit:b.subjectNuit||a.subjectNuit,year:b.year||a.year,assessment:a.kind,assessmentKey:a.key,assessmentReference:b.assessmentReference||a.book.assessment?.reference||''};}
export function annualWithholdingOptions(d:Record<string,unknown>){
const b=irpcBook(d),current=irpcCurrent(d,b);return b.rows.flatMap(r=>r.allocations.map(a=>{
const p=b.payments.find(p=>p.id===a.paymentId),v=b.result?.rows.find(v=>v.id===r.id),issues:string[]=[];
if(!current||!b.result)issues.push('Recalcule e guardeL5 com as entradas actuais.');
if(r.direction!=='received'||r.origin!=='effective'||v?.direction!=='received'||v.status!=='calculated'||v.nature!=='on_account'||r.settlement!=='complete'||v.settled===null)issues.push('Apenas retenção recebida/efectiva por conta,calculada e reconciliada emL5;remessa por terceiros,definitiva ou projecção não é crédito próprio.');
if(!p||p.kind!=='withholding_proof'||p.currency!=='MZN'||r.currency!=='MZN'||!iso(p.date)||!p.reference.trim())issues.push('Comprovativo de retenção sofrida emMZN em falta ou fora do recorte.');
return{rowId:r.id,paymentId:a.paymentId,document:p?.reference??'',issuer:r.counterparty,date:p?.date??'',eventDate:v?.eventDate??'',year:v?.eventDate.slice(0,4)??'',amount:a.amount,proofAmount:p?.amount??'',identity:proofIdentity(r.counterparty,p?.reference??'',p?.date??''),key:closureFingerprint([r,v,p,a]),ready:!issues.length,reason:issues.join(' ')};
}));
}
export function reuseAnnualWithholding(d:Record<string,unknown>,b:AnnualBook,rowId:string,paymentId:string,id:string):AnnualBook{
const o=annualWithholdingOptions(d).find(o=>o.rowId===rowId&&o.paymentId===paymentId);if(!o)throw Error('ComprovativoL5 não encontrado;originais conservados.');
if(b.withholdings.rows.some(r=>r.rowId===rowId&&r.paymentId===paymentId))throw Error('Retenção/prova já seleccionada;altere a parcela existente sem duplicar.');
return{...b,withholdings:{...b.withholdings,rows:[...b.withholdings.rows,{id,rowId,paymentId,sourceKey:o.key,subjectNuit:b.subjectNuit,year:o.year,amount:o.amount,incomeIncluded:'',reference:''}]}};
}
function dependencies(d:Record<string,unknown>){let normal:unknown,L5:unknown;try{const a=annualAssessment(d);normal=[a.record,a.current];}catch(e){normal=String(e);}try{const b=irpcBook(d);L5=[b.rows,b.payments,b.result,irpcCurrent(d,b)];}catch(e){L5=String(e);}return[normal,L5,Object.entries(books(d)).filter(([k])=>k!==acquisitionScope(d)).map(([k,b])=>[k,b.subjectNuit,b.year,b.withholdings,b.ppc])];}
function key(d:Record<string,unknown>,b:AnnualBook){const{result:_,history:__,...input}=b;return closureFingerprint([annualSettlementVersion,acquisitionScope(d),input,dependencies(d)]);}
export function annualCurrent(d:Record<string,unknown>,b:AnnualBook){return !!b.result&&b.result.key===key(d,b);}
export function calculateAnnual(d:Record<string,unknown>,b:AnnualBook){
validateAnnual(b);const{result:_result,history:_history,...input}=b;const pending:string[]=[],warn:string[]=[];let assessment:ReturnType<typeof annualAssessment>|null=null,colecta:FiscalFraction|null=null;let options:ReturnType<typeof annualWithholdingOptions>=[];
try{assessment=annualAssessment(d);const a=assessment,m=a.record?.model;
if(!a.record||!a.current||a.key!==b.assessmentKey||b.assessment!==a.kind)pending.push('Colecta de origem ausente,alterada ou não corrente;reutilize a execuçãoL6/L7 aplicável,não some nem contorne efeitosL7.');
if(!/^\d{9}$/.test(b.subjectNuit)||b.subjectNuit!==a.subjectNuit||b.year!=='2026'||b.year!==a.year||a.book.scope!=='resident_general'||m?.currency!=='MZN'||a.period?.from!=='2026-01-01'||a.period?.to!=='2026-12-31')pending.push('Entidade/NUIT,exercício ou âmbito fora do recorte residente/ano civil2026/MZN;períodos especiais permanecem separados.');
if(!b.reference.trim()||!b.assessmentReference.trim())pending.push('Falta suporte do fecho do sujeito/exercício e da colecta seleccionada.');
if(!a.colecta||cmp(a.colecta,zero())<0n)pending.push('Colecta aplicável indeterminada;uma parcelaL6 não substitui totalL7 pendente.');else colecta=structuredClone(a.colecta);
if(a.kind==='special'&&(!m?.special||!a.autonomous||cmp(a.autonomous,zero())!==0n))pending.push('Tributação autónoma positiva/indeterminada exige liquidação própria;este primeiro saldo não a ignora nem a compensa.');
}catch(e){pending.push('Origem da colecta não recuperável: '+String(e));}
try{options=annualWithholdingOptions(d);}catch(e){if(b.withholdings.state==='known'||b.withholdings.rows.length)pending.push('OrigemL5 não recuperável: '+String(e));}
const group=(g:Group,rows:unknown[],label:string)=>{if(!g.state||!g.reference.trim())pending.push(label+':identifique valor conhecido,ausência declarada ou informação em falta com suporte.');if(g.state==='absent'&&rows.length)pending.push(label+':ausência declarada contradiz parcelas presentes;conserve e reconcilie.');if(g.state==='known'&&!rows.length)pending.push(label+':não há comprovativos seleccionados;ausência exige declaração própria.');};
group(b.withholdings,b.withholdings.rows,'Retenções sofridas por conta');group(b.ppc,b.ppc.rows,'PPC efectuados');
try{if(b.withholdings.state==='absent'&&options.some(o=>o.ready&&o.year===b.year&&cmp(moneyInput(o.amount),zero())>0n))pending.push('Ausência de retenções sofridas contradiz provaL5 efectiva por conta no exercício;reconcilie sem a ignorar.');}catch(e){pending.push('Reconciliação da ausência de retenções: '+String(e));}
for(const[label,e]of[['PEC',b.pec],['Créditos com regras próprias',b.credits]]as const){try{if(!e.state||!e.reference.trim())throw Error('presença/ausência e suporte em falta');if(e.state==='absent'){if(e.amount.trim())throw Error('ausência contradiz montante preenchido');}else if(cmp(moneyInput(e.amount),zero())!==0n)throw Error('montante positivo fora deste recorte:corresponder ordem,limite e transporte próprios;sem abatimento ou reembolso automático');}catch(err){pending.push(label+': '+String(err));}}
const claims:Array<{id:string;kind:'withholding'|'ppc';identity:string;proofAmount:FiscalFraction|null;value:FiscalFraction|null;sourceKey:string;allocationAmount:FiscalFraction|null;document:string;issuer:string;date:string;subjectNuit:string;year:string;reason:string}>=[];
for(const r of b.withholdings.rows){const o=options.find(o=>o.rowId===r.rowId&&o.paymentId===r.paymentId),issues:string[]=[];let value:FiscalFraction|null=null,proofAmount:FiscalFraction|null=null;
try{if(!o||!o.ready||r.sourceKey!==o.key)throw Error(o?.reason||'provaL5 ausente ou alterada;reutilize sem duplicar');if(r.subjectNuit!==b.subjectNuit||r.year!==b.year||o.year!==b.year||!o.date.startsWith(b.year+'-'))throw Error('entidade/exercício da retenção não correspondem ao fecho');if(r.incomeIncluded!=='yes'||!r.reference.trim())throw Error('confirme o próprio beneficiário/NUIT e inclusão do rendimento na base do exercício com suporte');value=moneyInput(r.amount);proofAmount=moneyInput(o.proofAmount);if(cmp(value,moneyInput(o.amount))>0n)throw Error('parcela ultrapassa a retenção efectivamente comprovada e afectada emL5');if(b.withholdings.rows.filter(x=>x.rowId===r.rowId&&x.paymentId===r.paymentId).length!==1)throw Error('retenção/prova repetida;uma afectação por linhaL5');if(assessment?.record?.model.special?.cooperative.status==='calculated'&&cmp(value,zero())>0n)throw Error('cooperativa integralmente elegível declara ausência de rendimentos retidos;segregue o casoL7 antes do crédito anual');}catch(e){issues.push(String(e));value=null;}
claims.push({id:r.id,kind:'withholding',identity:o?.identity??'',proofAmount,value,sourceKey:r.sourceKey,allocationAmount:o&&value?moneyInput(o.amount):null,document:o?.document??r.paymentId,issuer:o?.issuer??'',date:o?.date??'',subjectNuit:r.subjectNuit,year:r.year,reason:issues.join(' ')});
}
for(const r of b.ppc.rows){const issues:string[]=[];let value:FiscalFraction|null=null,proofAmount:FiscalFraction|null=null;
try{if(!r.document.trim()||!r.issuer.trim()||!r.reference.trim()||r.effective!=='yes'||r.currency!=='MZN'||!iso(r.date)||!r.date.startsWith(b.year+'-')||r.subjectNuit!==b.subjectNuit||r.year!==b.year)throw Error('PPC exige comprovativo efectivo,MZN,data,entidade e exercício correspondentes;projecção/tesouraria não é pagamento');value=moneyInput(r.amount);proofAmount=moneyInput(r.proofAmount);if(cmp(value,proofAmount)>0n)throw Error('parcelaPPC ultrapassa o montante comprovado');}catch(e){issues.push(String(e));value=null;}
claims.push({id:r.id,kind:'ppc',identity:proofIdentity(r.issuer,r.document,r.date),proofAmount,value,sourceKey:'',allocationAmount:null,document:r.document,issuer:r.issuer,date:r.date,subjectNuit:r.subjectNuit,year:r.year,reason:issues.join(' ')});
}
const elsewhere:Array<{identity:string;subjectNuit:string;year:string;value:FiscalFraction;proofAmount:FiscalFraction;sourceKey:string}>=[];
for(const[scope,other]of Object.entries(books(d))){if(scope===acquisitionScope(d))continue;try{validateAnnual(other);for(const r of other.ppc.rows)elsewhere.push({identity:proofIdentity(r.issuer,r.document,r.date),subjectNuit:r.subjectNuit,year:r.year,value:moneyInput(r.amount),proofAmount:moneyInput(r.proofAmount),sourceKey:''});for(const r of other.withholdings.rows){const source=(store(d).irpcWithholdings??{})[scope];const row=source?.rows?.find((x:{id:string})=>x.id===r.rowId),p=source?.payments?.find((x:{id:string})=>x.id===r.paymentId);if(row&&p)elsewhere.push({identity:proofIdentity(row.counterparty,p.reference,p.date),subjectNuit:r.subjectNuit,year:r.year,value:moneyInput(r.amount),proofAmount:moneyInput(p.amount),sourceKey:r.sourceKey});}}catch(e){if(other?.subjectNuit===b.subjectNuit)pending.push('Outro mapa da mesma entidade não recuperável para conferir duplicações: '+scope+';'+String(e));}}
for(const c of claims){if(!c.value||!c.proofAmount)continue;const same=claims.filter(x=>x.identity===c.identity&&x.value),other=elsewhere.filter(x=>x.identity===c.identity);let issue='';
if(same.filter(x=>x.kind==='ppc').length>1||new Set(same.map(x=>x.kind)).size>1)issue='Mesmo comprovativo repetido ou usado como retenção ePPC;reconcilie sem dedução dupla.';
if(other.some(x=>x.subjectNuit!==b.subjectNuit||x.year!==b.year))issue='Mesmo comprovativo já associado a outra entidade/exercício;reconcilie a titularidade e a afectação.';
if([...same,...other].some(x=>x.proofAmount&&cmp(x.proofAmount,c.proofAmount!)!==0n)||cmp(addFractions([...same.flatMap(x=>x.value?[x.value]:[]),...other.map(x=>x.value)]),c.proofAmount)>0n)issue='Comprovativo utilizado além do montante disponível ou com montantes divergentes neste/outro mapa.';
if(c.kind==='withholding'&&c.sourceKey&&c.allocationAmount&&cmp(addFractions([...same.filter(x=>x.sourceKey===c.sourceKey).flatMap(x=>x.value?[x.value]:[]),...other.filter(x=>x.sourceKey===c.sourceKey).map(x=>x.value)]),c.allocationAmount)>0n)issue='Mesma afectação L5 reutilizada além da parcela disponível, ainda que o comprovativo global seja maior.';
if(issue)c.reason=[c.reason,issue].filter(Boolean).join(' ');
}
for(const c of claims)if(c.reason)pending.push((c.document||c.id)+': '+c.reason);
const knownWithholdings=addFractions(claims.filter(c=>c.kind==='withholding'&&!c.reason&&c.value).map(c=>c.value!)),knownPpc=addFractions(claims.filter(c=>c.kind==='ppc'&&!c.reason&&c.value).map(c=>c.value!));
const ready=pending.length===0&&colecta!==null,arithmeticBalance=ready?subtractFractions(subtractFractions(colecta!,knownWithholdings),knownPpc):null;
let payable:FiscalFraction|null=null,recoverable:FiscalFraction|null=null,belowMinimum:FiscalFraction|null=null;let status:'payable'|'recoverable'|'balanced'|'below_minimum'|'indeterminate'='indeterminate';
if(arithmeticBalance){const n=BigInt(arithmeticBalance.numerator),abs=fraction(n<0n?-n:n,BigInt(arithmeticBalance.denominator));payable=zero();recoverable=zero();belowMinimum=zero();if(!n)status='balanced';else if(cmp(abs,fraction(100n))<0n){status='below_minimum';belowMinimum=abs;warn.push('Art27(6):saldo inferior a100MZN,sem cobrança/reembolso neste recorte;aritmética conservada,não é transporte de crédito.');}else if(n>0n){status='payable';payable=abs;}else{status='recoverable';recoverable=abs;}}
return{version:annualSettlementVersion,input:structuredClone(input),subjectNuit:b.subjectNuit,year:b.year,assessment:assessment?.record?{key:b.assessmentKey,kind:b.assessment,version:assessment.record.model.version,createdAt:assessment.record.createdAt,colecta,period:assessment.period,origin:assessment.record.model.origin}:null,claims,knownWithholdings,knownPpc,arithmeticBalance,payable,recoverable,belowMinimum,status,pending:[...new Set(pending)],warnings:warn,source:annualSettlementSource,officialBalancesConsumed:false,submitted:false,refundReceived:false,limitations:['L8 parcial:PEC e créditos positivos,tributação autónoma,períodos especiais e pagamentos noutra moeda exigem regras próprias.','PPC aqui é pagamento comprovado;não cálculo automático da obrigação ou calendário.','Retenção sofrida não é retenção entregue pela empresa por conta de terceiros.','Valores racionais exactos;camposM20/M22 e representação não presumidos.','Saldo simulado por colecta projectada;sem pagamento,submissão ou reembolso automático.']};
}
export type AnnualModel=ReturnType<typeof calculateAnnual>;
export function recordAnnual(d:Record<string,unknown>,b:AnnualBook,now=new Date().toISOString()):AnnualBook{return{...b,history:b.result?[...b.history,structuredClone(b.result)]:b.history,result:{version:annualSettlementVersion,key:key(d,b),createdAt:now,model:calculateAnnual(d,b)}};}
const show=(v:FiscalFraction|null)=>v?(fractionPresentation(v).approximate?'≈':'')+fractionPresentation(v).decimal+' MZN (exacto '+v.numerator+'/'+v.denominator+')':'Indeterminado';
const stateLabel=(s:State)=>s==='known'?'Valor conhecido':s==='absent'?'Ausência declarada':'Informação em falta';
export function annualReportRows(d:Record<string,unknown>):Array<[string,string,string,string]>{
try{
const b=annualBook(d),r=b?.result;if(!b||!r)return[];const m=r.model;
const status={payable:'A pagar',recoverable:'A recuperar',balanced:'Saldo zero',below_minimum:'Inferior ao mínimo de cobrança/reembolso',indeterminate:'Indeterminado'}[m.status];
return[
['IRPC — L8','Fecho anual — âmbito e origem',
'NUIT: '+m.subjectNuit+'; exercício: '+m.year+'; colecta: '+show(m.assessment?.colecta??null)+'; origem: '+(m.assessment?.kind==='special'?'L7 qualificado':'L6 normal')+'. '+(annualCurrent(d,b)?'Entradas correspondentes.':'Histórico; origem/entradas alteradas: recalcule o saldo corrente.')+' Saldo simulado, não liquidação submetida.',
m.input.reference+'; '+m.input.assessmentReference+'; '+m.source],
...m.claims.map(c=>[
'IRPC — L8',(c.kind==='withholding'?'Retenção sofrida':'PPC')+' — '+(c.document||c.id),
'Entidade: '+c.subjectNuit+'; exercício: '+c.year+'; emitente: '+c.issuer+'; data: '+c.date+'; comprovado: '+show(c.proofAmount)+'; parcela: '+(c.reason?'Indeterminado':show(c.value))+'. '+(c.reason||'Reconciliada sem utilização duplicada.'),
c.kind==='withholding'?m.input.withholdings.reference+'; '+(m.input.withholdings.rows.find(r=>r.id===c.id)?.reference??''):m.input.ppc.reference+'; '+(m.input.ppc.rows.find(r=>r.id===c.id)?.reference??'')
]as[string,string,string,string]),
['IRPC — L8','Saldo anual simulado',
'Estado: '+status+'. Retenções conhecidas: '+show(m.knownWithholdings)+'; PPC conhecidos: '+show(m.knownPpc)+'; saldo aritmético: '+show(m.arithmeticBalance)+'; a pagar: '+show(m.payable)+'; a recuperar: '+show(m.recoverable)+'. '+m.pending.join(' | ')+' '+m.warnings.join(' '),m.source],
['IRPC — L8','Estados dos comprovativos',
'Retenções sofridas: '+stateLabel(m.input.withholdings.state)+'; PPC: '+stateLabel(m.input.ppc.state)+'. Subtotais conhecidos não substituem informação em falta.',m.input.withholdings.reference+'; '+m.input.ppc.reference],
['IRPC — L8','PEC e créditos separados',
'PEC: '+stateLabel(m.input.pec.state)+'; montante declarado: '+(m.input.pec.amount||'Não preenchido')+'; créditos: '+stateLabel(m.input.credits.state)+'; montante declarado: '+(m.input.credits.amount||'Não preenchido')+'. Sem dedução, transporte ou reembolso presumidos. '+m.limitations.join(' '),m.input.pec.reference+'; '+m.input.credits.reference]
];
}catch(e){return[['IRPC — L8','Não recuperável',String(e),'Originais e históricos conservados.']];}
}
export function annualHtml(d:Record<string,unknown>){const b=annualBook(d);if(!b?.result||!annualCurrent(d,b))throw Error('Calcule e guardeL8 com as fontes actuais antes de exportar.');const esc=(s:string)=>s.replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]!));return'<!doctype html><html lang="pt"><meta charset="utf-8"><title>IRPC — L8 saldo anual simulado</title><h1>IRPC — colecta e saldo anual simulado</h1><p>Não é declaração submetida,pagamento efectuado ou reembolso recebido.</p>'+annualReportRows(d).map(r=>'<section><h2>'+esc(r[1])+'</h2><p>'+esc(r[2])+'</p><small>'+esc(r[3])+'</small></section>').join('')+'</html>';}
@@ -179,6 +179,7 @@ const rowSection: Record<string, DossierReportSectionId> = {
'IRPC — retenções': 'finance',
'IRPC normal — L6': 'finance',
'IRPC — L7': 'finance',
'IRPC — L8': 'finance',
Equipa: 'team',
'Necessidade de negócio': 'resources',
Financiamento: 'resources',
+2
View File
@@ -1,3 +1,4 @@
import {annualReportRows} from './irpc-annual-settlement.ts';
import {normalReportRows} from './irpc-normal.ts';
import {irpcReportRows} from './irpc-withholdings.ts';
import {ispcResidualReportRows} from './ispc-residuals.ts';
@@ -697,6 +698,7 @@ export function buildProjectDossierRows(
rows.push(...ispcResidualReportRows(draft));
rows.push(...irpcReportRows(draft));
rows.push(...normalReportRows(draft));
rows.push(...annualReportRows(draft));
rows.push(...fiscalCalculationGapReportRows(draft));
rows.push(...l3FormPreparationReportRows(draft));
const operationRows = salesReportRows(draft);
@@ -0,0 +1,56 @@
<?php
namespace Tests\Feature;
use App\Models\User;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Tests\TestCase;
final class IrpcAnnualSettlementDraftTest extends TestCase
{
use DatabaseTransactions;
public function test_annual_simulated_balances_round_trip_with_proofs_states_and_preserved_books(): void
{
$target=DB::selectOne('select current_database() as name,inet_server_port() as port');
$this->assertSame('simulator_fiscal_delta_20261021_test',$target->name);
$this->assertSame(55441,(int)$target->port);
config(['investor-path.enabled'=>true,'suitecrm.enabled'=>false]);
$this->actingAs(User::factory()->create(['email_verified_at'=>now()]));
$id=null;$etag=null;
foreach(['payable','recoverable','balanced','absent','missing','duplicate','pec']as$case){
$d=json_decode(file_get_contents(base_path('outputs/irpc-l8-settlement/'.$case.'.json')),true,flags:JSON_THROW_ON_ERROR);
$personal=['person_type'=>$d['person_type']];unset($d['person_type']);
if($id===null)$id=$this->postJson('/api/v1/simulation-drafts',['personal_snapshot'=>$personal,'economic_snapshot'=>$d],['Idempotency-Key'=>(string)Str::uuid()])->assertCreated()->json('data.id');
else $this->putJson('/api/v1/simulation-drafts/'.$id,['personal_snapshot'=>$personal,'economic_snapshot'=>$d],['If-Match'=>$etag])->assertOk();
$r=$this->getJson('/api/v1/simulation-drafts/'.$id)->assertOk();$etag=$r->headers->get('ETag');$back=$r->json('data.economic_snapshot');
$this->assertSame($d['modelo_a_snapshot'],$back['modelo_a_snapshot']);
$this->assertSame($d['business_plan_snapshot'],$back['business_plan_snapshot']);
$before=json_decode($d['modelo_a_snapshot'],true,flags:JSON_THROW_ON_ERROR);
$s=json_decode($back['modelo_a_snapshot'],true,flags:JSON_THROW_ON_ERROR);$scope=$d['context_opportunity'];
$b=$s['irpcAnnual'][$scope];$m=$b['result']['model'];
foreach(['irpcNormal','irpcWithholdings','books','currencyEvents','ispcQuarters']as$map)$this->assertSame($before[$map],$s[$map]);
$this->assertSame('IRPC-annual-settlement/1',$m['version']);
$this->assertSame('123456789',$m['subjectNuit']);$this->assertSame('2026',$m['year']);
$this->assertSame(['numerator'=>'1408','denominator'=>'1'],$m['assessment']['colecta']);
$this->assertFalse($m['officialBalancesConsumed']);$this->assertFalse($m['submitted']);$this->assertFalse($m['refundReceived']);
$this->assertSame('known',$m['input']['credits']['state']);$this->assertSame('0',$m['input']['credits']['amount']);
if(in_array($case,['missing','duplicate','pec'],true)){
$this->assertSame('indeterminate',$m['status']);$this->assertNotEmpty($m['pending']);
$this->assertNull($m['arithmeticBalance']);$this->assertNull($m['payable']);$this->assertNull($m['recoverable']);
if($case==='missing')$this->assertSame('',$m['input']['ppc']['rows'][0]['effective']);
if($case==='pec')$this->assertSame('10',$m['input']['pec']['amount']);
if($case==='duplicate')$this->assertCount(2,$m['input']['ppc']['rows']);
}else{
$this->assertEmpty($m['pending']);
$this->assertSame(['payable'=>'payable','recoverable'=>'recoverable','balanced'=>'balanced','absent'=>'payable'][$case],$m['status']);
$this->assertSame(['numerator'=>['payable'=>'608','recoverable'=>'-192','balanced'=>'0','absent'=>'1408'][$case],'denominator'=>'1'],$m['arithmeticBalance']);
$this->assertSame(['numerator'=>$case==='recoverable'?'192':'0','denominator'=>'1'],$m['recoverable']);
if($case==='absent'){$this->assertSame('absent',$m['input']['withholdings']['state']);$this->assertSame('absent',$m['input']['ppc']['state']);}
else{$this->assertSame('yes',$m['input']['withholdings']['rows'][0]['incomeIncluded']);$this->assertSame('yes',$m['input']['ppc']['rows'][0]['effective']);}
}
file_put_contents(base_path('outputs/irpc-l8-settlement/recovered-'.$case.'.json'),json_encode($back,JSON_THROW_ON_ERROR|JSON_UNESCAPED_UNICODE));
}
}
}
@@ -0,0 +1,105 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs';
import {normalBook,normalCurrent,saveNormalBook} from '../../resources/js/lib/irpc-normal.ts';
import {irpcBook,newIrpcRow,saveIrpcBook,recordIrpc} from '../../resources/js/lib/irpc-withholdings.ts';
import {annualBook,newAnnualBook,newAnnualPpc,saveAnnualBook,reuseAnnualAssessment,annualAssessment,annualWithholdingOptions,reuseAnnualWithholding,calculateAnnual,recordAnnual,annualCurrent,annualReportRows,annualHtml} from '../../resources/js/lib/irpc-annual-settlement.ts';
import {createProjectDossierReport} from '../../resources/js/lib/project-dossier-report.ts';
const read=n=>JSON.parse(fs.readFileSync(new URL('./fixtures/'+n,import.meta.url),'utf8'));
const val=v=>v?Number(v.numerator)/Number(v.denominator):null;
const now='2026-10-26T18:00:00+02:00';
const original=()=>read('irpc-assessment-preserved.json');
function acceptedL7(expense=false){const d=read('irpc-l7-preserved.json')[expense?'expense':'acceleration'];if(!expense){const f=read('irpc-l7-continuation-preserved.json');d.business_plan_snapshot=f.plan;d.modelo_a_snapshot=saveNormalBook(d,f.book);}return d;}
function setup(rf='300',ppc='500'){
const d=original(),l5=irpcBook(d);
if(rf!==null){
const row={...newIrpcRow('L8-received'),direction:'received',origin:'effective',counterparty:'QA pagador A',documentRef:'QA rendimento próprio 2026',base:String(Number(rf)*5),currency:'MZN',kind:'royalty',recipientIrpc:'yes',payerIrpc:'yes',residence:'resident',mozambique:'yes',qualificationRef:'QA royalties próprios incluídos no resultado anual',specialRelief:'none',eventsComplete:'yes',events:[{kind:'payment',date:'2026-10-02',reference:'QA rendimento e primeiro evento 2026'}],settlement:'complete',settlementRef:'QA retenção efectivamente sofrida e reconciliada',allocations:[{paymentId:'RF-L8',amount:rf}],ordinary:{nonDigital:'yes',capitalType:'',rentOwnPropertyManagement:'',specialFacts:'no',capitalSeatAndManagement:'',reference:'QA royalties não digitais de residente'}};
l5.rows.push(row);l5.payments.push({id:'RF-L8',kind:'withholding_proof',reference:'QA-RF-2026-1',date:'2026-10-02',amount:rf,currency:'MZN'});
d.modelo_a_snapshot=saveIrpcBook(d,recordIrpc(d,l5,now));
}
let b=reuseAnnualAssessment(d,newAnnualBook());b.reference='QA entidade123456789,exercício2026,declaração integral dos abatimentos';b.assessmentReference='QA colecta válida já demonstrada1408';
b.withholdings={state:rf===null?'absent':'known',reference:'QA inventário completo das retenções efectivamente sofridas por este sujeito no exercício',rows:[]};
b.ppc={state:ppc===null?'absent':'known',reference:'QA inventário completo dos PPC efectuados no exercício',rows:[]};
b.pec={state:'absent',amount:'',reference:'QA ausência de PEC'};b.credits={state:'known',amount:'0',reference:'QA créditos com regras próprias conhecidos zero'};
if(rf!==null){b=reuseAnnualWithholding(d,b,'L8-received','RF-L8','claim-rf');Object.assign(b.withholdings.rows[0],{incomeIncluded:'yes',reference:'QA próprio beneficiário NUIT123456789;rendimento incluído na base anual2026'});}
if(ppc!==null)b.ppc.rows.push({...newAnnualPpc('claim-ppc'),document:'QA-PPC-2026-1',issuer:'QA AT',date:'2026-06-30',subjectNuit:b.subjectNuit,year:b.year,proofAmount:ppc,amount:ppc,effective:'yes',reference:'QA recibo de PPC efectivo e afectação ao sujeito/exercício'});
return{d,b};
}
const model=(d,b)=>calculateAnnual(d,b);
const pending=(d,b)=>{const m=model(d,b);assert.equal(m.status,'indeterminate',m.pending.join(' | '));assert.equal(m.arithmeticBalance,null);assert.equal(m.payable,null);assert.equal(m.recoverable,null);return m;};
test('optional L8 leaves accepted assessment, other books and report rows untouched',()=>{
const d=original(),before=JSON.stringify(d),b=normalBook(d);assert.equal(normalCurrent(d,b),true);assert.equal(val(annualAssessment(d).colecta),1408);assert.equal(annualBook(d),undefined);assert.deepEqual(annualReportRows(d),[]);assert.equal(JSON.stringify(d),before);
const old=acceptedL7();assert.deepEqual(annualReportRows(old),[]);assert.equal(normalBook(old).result.model.version,'IRPC-normal-adjustments/13');
});
test('1408 minus actual received300 and provenPPC500 yields608 payable without consuming official balances',()=>{
const{d,b}=setup(),before=d.modelo_a_snapshot,m=model(d,b);assert.deepEqual(m.pending,[]);assert.equal(m.status,'payable');assert.equal(val(m.knownWithholdings),300);assert.equal(val(m.knownPpc),500);assert.equal(val(m.payable),608);assert.equal(val(m.recoverable),0);assert.equal(m.officialBalancesConsumed,false);assert.equal(m.submitted,false);assert.equal(d.modelo_a_snapshot,before);
});
test('1408 minus1000 and600 yields192 recoverable;408 and1000 balances exactly',()=>{
for(const[rf,ppc,status,balance]of[['1000','600','recoverable',-192],['408','1000','balanced',0]]){const{d,b}=setup(rf,ppc),m=model(d,b);assert.deepEqual(m.pending,[]);assert.equal(m.status,status);assert.equal(val(m.arithmeticBalance),balance);assert.equal(val(m.recoverable),balance<0?-balance:0);}
});
test('declared absence yields1408;known zero has its own proof and missing never becomes zero',()=>{
const{d,b}=setup(null,null);assert.equal(val(model(d,b).payable),1408);b.credits.state='';assert.match(pending(d,b).pending.join(' '),/Créditos/);b.credits={state:'absent',amount:'',reference:'QA ausência'};b.ppc.state='';assert.match(pending(d,b).pending.join(' '),/PPC/);
const zero=setup('0','0');assert.equal(val(model(zero.d,zero.b).payable),1408);assert.equal(model(zero.d,zero.b).input.withholdings.state,'known');
});
test('effective received L5 proof is required, not a projection,remittance or definitive withholding',()=>{
for(const patch of[{origin:'projected'},{direction:'paid'},{residence:'nonresident',permanentEstablishment:'no'}]){const{d,b}=setup(),l5=irpcBook(d);Object.assign(l5.rows.find(r=>r.id==='L8-received'),patch);d.modelo_a_snapshot=saveIrpcBook(d,recordIrpc(d,l5,now));const option=annualWithholdingOptions(d).find(o=>o.rowId==='L8-received');assert.equal(option.ready,false);pending(d,b);}
const{d,b}=setup(),l5=irpcBook(d);l5.payments.find(p=>p.id==='RF-L8').kind='remittance';d.modelo_a_snapshot=saveIrpcBook(d,recordIrpc(d,l5,now));pending(d,b);
});
test('beneficiary,income inclusion,proof date,entity,exercise and actualPPC are mandatory',()=>{
for(const change of[b=>b.withholdings.rows[0].incomeIncluded='',b=>b.withholdings.rows[0].reference='',b=>b.withholdings.rows[0].subjectNuit='999999999',b=>b.year='2025',b=>b.ppc.rows[0].subjectNuit='999999999',b=>b.ppc.rows[0].year='2025',b=>b.ppc.rows[0].currency='USD',b=>b.ppc.rows[0].date='2026-02-30',b=>b.ppc.rows[0].date='2027-01-01',b=>b.ppc.rows[0].effective='no',b=>b.ppc.rows[0].reference='',b=>b.reference='']){const{d,b}=setup();change(b);pending(d,b);}
});
test('selected current source and reusing collection never overwrite deliberate entity/year edits',()=>{
const{d,b}=setup();b.subjectNuit='999999999';b.year='2025';const next=reuseAnnualAssessment(d,b);assert.equal(next.subjectNuit,'999999999');assert.equal(next.year,'2025');pending(d,next);
const other=setup();other.b.assessmentKey='old-source';pending(other.d,other.b);
});
test('absence contradiction and incomplete group inventories are indeterminate,not assumed totals',()=>{
for(const change of[b=>{b.withholdings={state:'absent',reference:'QA ausência contraditória',rows:[]};},b=>b.ppc.rows=[],b=>b.ppc.state='absent',b=>b.withholdings.reference='']){const{d,b}=setup();change(b);pending(d,b);}
});
test('duplicate pair,duplicatePPC,overclaim and cross-kind physical proof cannot be deducted twice',()=>{
for(const change of[b=>b.withholdings.rows.push({...b.withholdings.rows[0],id:'duplicate'}),b=>b.ppc.rows.push({...b.ppc.rows[0],id:'duplicate'}),b=>b.withholdings.rows[0].amount='301',b=>b.ppc.rows[0].amount='501',b=>Object.assign(b.ppc.rows[0],{document:' QA-RF-2026-1 ',issuer:'QA pagador A',date:'2026-10-02',amount:'100',proofAmount:'300'})]){const{d,b}=setup();change(b);pending(d,b);}
const{d,b}=setup();assert.throws(()=>reuseAnnualWithholding(d,b,'L8-received','RF-L8','again'),/já seleccionada/);
});
test('partial proof allocations allowed only within its available amount across context maps',()=>{
const{d,b}=setup(null,'500');b.ppc.rows[0].amount='300';const other=structuredClone(b);other.ppc.rows[0].id='other';other.ppc.rows[0].amount='200';const s=JSON.parse(d.modelo_a_snapshot);s.irpcAnnual={'opportunity:other':other};d.modelo_a_snapshot=JSON.stringify(s);assert.equal(val(model(d,b).payable),1108);other.ppc.rows[0].amount='201';d.modelo_a_snapshot=JSON.stringify(s);pending(d,b);
other.ppc.rows[0].amount='100';other.ppc.rows[0].subjectNuit='999999999';d.modelo_a_snapshot=JSON.stringify(s);pending(d,b);
other.ppc.rows[0].subjectNuit=b.subjectNuit;other.ppc.rows[0].year='2025';d.modelo_a_snapshot=JSON.stringify(s);pending(d,b);
});
test('positive PEC or specialcredit stays separated with null final balances,explicitzero permitted',()=>{
for(const key of['pec','credits']){const{d,b}=setup();b[key]={state:'known',amount:'10',reference:'QA saldo positivo exige regra própria'};const m=pending(d,b);assert.match(m.pending.join(' '),/fora deste recorte/);assert.equal(val(m.knownPpc),500);b[key].amount='0';assert.equal(model(d,b).status,'payable');b[key]={state:'absent',amount:'0',reference:'QA ausência contradiz montante'};pending(d,b);}
});
test('changing receipt date cannot evade identity checks;copied L5 allocation cannot consume larger proof twice',()=>{
const p=setup();p.b.ppc.rows.push({...p.b.ppc.rows[0],id:'other-date',date:'2026-07-01',amount:'100'});pending(p.d,p.b);
const{d,b}=setup();const l5=irpcBook(d);l5.payments.find(p=>p.id==='RF-L8').amount='1000';d.modelo_a_snapshot=saveIrpcBook(d,recordIrpc(d,l5,now));b.withholdings.rows=[];const fresh=reuseAnnualWithholding(d,b,'L8-received','RF-L8','rf');Object.assign(fresh.withholdings.rows[0],{amount:'200',incomeIncluded:'yes',reference:'QA próprio beneficiário e rendimento incluído'});
const other=structuredClone(fresh);other.withholdings.rows[0].amount='101';other.ppc.rows=[];other.ppc.state='absent';const s=JSON.parse(d.modelo_a_snapshot);s.irpcAnnual={'opportunity:other':other};s.irpcWithholdings['opportunity:other']=structuredClone(l5);d.modelo_a_snapshot=JSON.stringify(s);d.modelo_a_snapshot=saveIrpcBook(d,recordIrpc(d,irpcBook(d),now));fresh.withholdings.rows[0].sourceKey=annualWithholdingOptions(d).find(o=>o.rowId==='L8-received').key;other.withholdings.rows[0].sourceKey=fresh.withholdings.rows[0].sourceKey;const current=JSON.parse(d.modelo_a_snapshot);current.irpcAnnual['opportunity:other']=other;d.modelo_a_snapshot=JSON.stringify(current);assert.match(pending(d,fresh).pending.join(' '),/Mesma afectação L5/);
other.withholdings.rows[0].amount='100';d.modelo_a_snapshot=JSON.stringify(current);assert.equal(model(d,fresh).status,'payable');
});
test('annual accepted L7 accelerated assessment is reused once rather than summed with L6',()=>{
const d=read('irpc-l7-preserved.json').acceleration,b=reuseAnnualAssessment(d,newAnnualBook());b.reference='QA entidade/exercício';b.assessmentReference='QA colecta L7';for(const key of['withholdings','ppc','pec','credits']){b[key].state='absent';b[key].reference='QA ausência declarada';}
const m=model(d,b);assert.deepEqual(m.pending,[]);assert.equal(m.assessment.kind,'special');assert.equal(val(m.payable),1996.8);assert.notEqual(val(m.payable),val(normalBook(d).result.model.colecta)+1996.8);
});
test('100 minimum is applied symmetrically;99.99 kept exact without invented rounding or carry',()=>{
for(const[ppc,status,balance]of[['1308.01','below_minimum',99.99],['1308','payable',100],['1507.99','below_minimum',-99.99],['1508','recoverable',-100]]){const{d,b}=setup(null,ppc),m=model(d,b);assert.equal(m.status,status);assert.equal(val(m.arithmeticBalance),balance);if(status==='below_minimum'){assert.deepEqual(m.belowMinimum,{numerator:'9999',denominator:'100'});assert.equal(val(m.payable),0);assert.equal(val(m.recoverable),0);}}
const{d,b}=setup();b.ppc.rows[0].amount='0.001';pending(d,b);
});
test('existing L7 source cannot be bypassed by choosing L6 and positiveautonomous/shortperiod remain scoped',()=>{
for(const expense of[false,true]){const d=acceptedL7(expense);let b=reuseAnnualAssessment(d,newAnnualBook());b.reference='QA fecho';b.assessmentReference='QA colecta';for(const key of['withholdings','ppc','pec','credits']){b[key].state='absent';b[key].reference='QA sem movimentos';}assert.equal(b.assessment,'special');let m=pending(d,b);if(!expense)assert.match(m.pending.join(' '),/períodos especiais/);if(expense)assert.match(m.pending.join(' '),/Tributação autónoma/);b.assessment='normal';assert.match(pending(d,b).pending.join(' '),/não corrente/);}
});
test('record/recover/recalculate appends history unchanged,preserves all old books/plan,no double deduction',()=>{
const{d,b}=setup(),before=JSON.parse(d.modelo_a_snapshot),plan=d.business_plan_snapshot;d.modelo_a_snapshot=saveAnnualBook(d,recordAnnual(d,b,now));const once=annualBook(d),old=structuredClone(once.result);assert.equal(annualCurrent(d,once),true);d.modelo_a_snapshot=saveAnnualBook(d,recordAnnual(d,once,now));const back=annualBook(JSON.parse(JSON.stringify(d)));assert.deepEqual(back.history,[old]);assert.equal(val(back.result.model.payable),608);assert.equal(d.business_plan_snapshot,plan);const snapshot=JSON.parse(d.modelo_a_snapshot);delete snapshot.irpcAnnual;assert.deepEqual(snapshot,before);
b.ppc.rows[0].amount='400';assert.equal(annualCurrent(d,b),false);
});
test('changed L5 or assessment invalidates current L8 while retaining historical report and references',()=>{
const{d,b}=setup();d.modelo_a_snapshot=saveAnnualBook(d,recordAnnual(d,b,now));const back=annualBook(d),old=structuredClone(back.result);back.reference='NEW reference';d.modelo_a_snapshot=saveAnnualBook(d,back);assert.equal(annualCurrent(d,back),false);assert.match(annualReportRows(d).flat().join(' '),/Histórico/);assert.doesNotMatch(annualReportRows(d).flat().join(' '),/NEW reference/);assert.throws(()=>annualHtml(d),/fontes actuais/);assert.deepEqual(back.result,old);
const n=normalBook(d);n.base.identityReference+=' changed';d.modelo_a_snapshot=saveNormalBook(d,n);assert.equal(annualCurrent(d,back),false);pending(d,back);
const changed=setup();changed.d.modelo_a_snapshot=saveAnnualBook(changed.d,recordAnnual(changed.d,changed.b,now));const saved=annualBook(changed.d),prior=structuredClone(saved.result);const l5=irpcBook(changed.d);l5.payments.find(p=>p.id==='RF-L8').reference+=' corrected';changed.d.modelo_a_snapshot=saveIrpcBook(changed.d,recordIrpc(changed.d,l5,now));assert.equal(annualCurrent(changed.d,saved),false);pending(changed.d,saved);assert.deepEqual(saved.result,prior);
});
test('malformed optionalbook never resets accepted books;report explicitly preserves missing states',()=>{
const{d,b}=setup();b.credits.state='';d.modelo_a_snapshot=saveAnnualBook(d,recordAnnual(d,b,now));assert.match(annualHtml(d),/Indeterminado/);assert.match(annualHtml(d),/Informação em falta/);assert.match(JSON.stringify(createProjectDossierReport(d,{selectedSections:new Set(['finance'])})),/Saldo anual simulado/);
const old=d.modelo_a_snapshot;b.ppc.rows.push({...b.ppc.rows[0]});assert.throws(()=>saveAnnualBook(d,b),/identificador repetido/);assert.equal(d.modelo_a_snapshot,old);
});
test('prepare only L8 delta cases for guarded API and report recovery',()=>{
const cases={payable:setup(),recoverable:setup('1000','600'),balanced:setup('408','1000'),absent:setup(null,null),missing:setup(),duplicate:setup(),pec:setup()};cases.missing.b.ppc.rows[0].effective='';cases.duplicate.b.ppc.rows.push({...cases.duplicate.b.ppc.rows[0],id:'duplicate'});cases.pec.b.pec={state:'known',amount:'10',reference:'QA PEC separado'};
fs.mkdirSync('outputs/irpc-l8-settlement',{recursive:true});for(const[name,{d,b}]of Object.entries(cases)){d.modelo_a_snapshot=saveAnnualBook(d,recordAnnual(d,b,now));fs.writeFileSync('outputs/irpc-l8-settlement/'+name+'.json',JSON.stringify(d));fs.writeFileSync('outputs/irpc-l8-settlement/'+name+'.html',annualHtml(d));}
const{d}=setup();fs.writeFileSync('outputs/irpc-l8-settlement/initial.json',JSON.stringify(d));
});
+21
View File
@@ -0,0 +1,21 @@
<?php
$root=dirname(__DIR__);
$operation=$argv[1]??'';
if(!in_array($operation,['routes','test'],true)||basename($root)!=='portal-investidor-irpc-l7-common-qa-20261026')throw new RuntimeException('Explicit isolated QA workspace and routes/test operation required.');
foreach(['storage/framework/views','storage/framework/cache','storage/framework/sessions','bootstrap/cache','storage/logs','outputs/irpc-l8-settlement']as$dir)if(!is_dir($root.'/'.$dir))mkdir($root.'/'.$dir,0700,true);
foreach(['APP_BASE_PATH'=>$root,'APP_ENV'=>'testing','APP_KEY'=>'base64:'.base64_encode(str_repeat('Q',32)),'DB_CONNECTION'=>'pgsql','DB_HOST'=>'127.0.0.1','DB_PORT'=>'55441','DB_DATABASE'=>'simulator_fiscal_delta_20261021_test','DB_USERNAME'=>'postgres','DB_PASSWORD'=>'','DB_URL'=>'','CACHE_STORE'=>'array','SESSION_DRIVER'=>'array','QUEUE_CONNECTION'=>'sync','MAIL_MAILER'=>'array'] as $k=>$v){putenv($k.'='.$v);$_ENV[$k]=$_SERVER[$k]=$v;}
$loader=require $root.'/vendor/autoload.php';
spl_autoload_register(function($class)use($root){foreach(['App\\'=>'app/','Tests\\'=>'tests/','Database\\Factories\\'=>'database/factories/']as$prefix=>$dir){if(str_starts_with($class,$prefix)){$file=$root.'/'.$dir.str_replace('\\','/',substr($class,strlen($prefix))).'.php';if(is_file($file)){require $file;return;}}}},true,true);
$app=require $root.'/bootstrap/app.php';
(new Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables)->bootstrap($app);
(new Illuminate\Foundation\Bootstrap\LoadConfiguration)->bootstrap($app);
$c=config('database.connections.pgsql');
if($app->configurationIsCached()||$c['host']!=='127.0.0.1'||(int)$c['port']!==55441||$c['database']!=='simulator_fiscal_delta_20261021_test'||!empty($c['url']))throw new RuntimeException('QA guard');
$pdo=new PDO('pgsql:host=127.0.0.1;port=55441;dbname='.$c['database'],$c['username'],$c['password']);
if($pdo->query('select current_database()')->fetchColumn()!==$c['database'])throw new RuntimeException('QA identity');
if($operation==='routes'){
$app->make(Illuminate\Contracts\Console\Kernel::class)->bootstrap();
$status=Illuminate\Support\Facades\Artisan::call('wayfinder:generate',['--with-form'=>true]);
echo Illuminate\Support\Facades\Artisan::output();exit($status);
}
$argv=['phpunit','-c','phpunit.investmoz.xml','tests/Feature/IrpcAnnualSettlementDraftTest.php'];$argc=count($argv);$_SERVER['argv']=$argv;$_SERVER['argc']=$argc;require $root.'/vendor/phpunit/phpunit/phpunit';