Implement scoped IRPC reliefs and PT-MZ royalties with QA recovery evidence
tests / ci (push) Canceled after 0s
tests / ci (push) Canceled after 0s
This commit is contained in:
@@ -353,3 +353,20 @@ Fecho do delta privado na homologação: candidata `fb4e7d89` integrada em `65ed
|
||||
Actualização do mesmo requisito: os 28 rascunhos têm edição, leitura privada, prévia sem guardar, recuperação e publicação/retirada pela interface em QA, preservando originais, origem e revisões. Seis testes/1.000 asserções, TypeScript e build passaram; publicação positiva apenas em transacções revertidas. Pacote privado v2 preparado com 28 originais e proveniência; não transportado nem publicado na homologação. Guia, permissões existentes, lacunas de imagens, dependências da entrega e reversão na secção «Legado — edição e leitura dos 28 rascunhos em QA» do manual. Pedido funcional expresso da Tempo; hash de âmbito, prévia e controlo de versão são escolhas técnicas, sem novo papel/aprovador. Conserva o saldo de 20 públicos aceites e dois originais incompletos, sem repetir provas ou fechar a integração integral.
|
||||
|
||||
Referência do acervo legado, 22/10/2026: a lista única `v2/auditoria-portal/legado-correspondencia-integral.md` e o CSV existente reconciliam 50 identidades (20 publicados, 28 por integrar no destino, um vazio, um sem título). 20/20 páginas conferidas por HTTP anónimo/identidade/origem; cartões de Turismo observados no destino. Os 28 restantes avançaram para rascunhos internos QA, com ClamAV real e repetição idempotente, sem publicação. Recibo e limites no manual, secção «Legado — correspondência integral e avanço privado QA». As duas lacunas editoriais foram confirmadas no original. Não fecha a integração integral nem substitui revisão editorial por prova HTTP; nenhuma alteração activa nesta passagem.
|
||||
|
||||
|
||||
## 25/10/2026 — L5: dispensas e convenção demonstradas em QA
|
||||
|
||||
Base de código `2b33a1c3`, ramo `codex/irpc-l5-reliefs-qa-20261025`. O último relatório de instalação mantém o portal nessa entrega; este candidato não executa instalação. O inventário anterior de L3, ISPC, câmbio, fontes e provas aceites é reutilizado. Comparação do delta: sem alteração nesses módulos, em schema/migrações, configuração ou permissões; sem escrita na base activa. As correcções de codificação que tinham atingido trechos históricos durante a preparação foram retiradas do delta; os registos anteriores são conservados.
|
||||
|
||||
**Executável:** dispensa 68(d) para serviços/uso de equipamento 67(1)b/g com natureza por conta, titular sujeito a IRPC relativamente ao rendimento; dispensa 68(f) para rendas de sociedade de gestão de imóveis próprios fora da transparência fiscal 6(1)c; limite convencional de 10% sobre royalties/uso de equipamento qualificados PT–MZ, não digitais, titular residente convencional em Portugal, beneficiário efectivo, sem EP em Moçambique, devedor residente em Moçambique e montante sem excesso por relações especiais. As condições são dados documentados do caso, sem aprovador adicional. A selecção de regra não é substituída por texto livre ou taxa manual. Outros ramos continuam separados.
|
||||
|
||||
Reutilizados contraparte, residência/EP, documento, qualificação, montante e conversão da linha. Diploma, artigo, período, documentos e condições ficam no mesmo livro e no histórico. Alterar a prova invalida apenas a correspondência do resultado afectado. A dispensa produz retenção zero justificada, apresentada como dispensa, sem isentar o rendimento nem gerar crédito anual. Informação insuficiente, negativa, período incompatível ou artigo divergente fica indeterminada, sem zero/taxa reduzida presumidos. A existência de prova de entrega numa linha dispensada pede reconciliação sem apagar o pagamento.
|
||||
|
||||
**Demonstração:** Wayfinder `wayfinder:generate --with-form`, pelo wrapper QA guardado `tools/irpc-reliefs-qa.php routes`; TypeScript passou; compilação completa QA passou. Nove testes novos e cinco verificações dirigidas de compatibilidade IRPC passaram. API privada: um teste/17 verificações, base `simulator_fiscal_delta_20261021_test`, loopback55441, transacção com rollback. Resultados 68(d),68(f), convenção e informação insuficiente recuperados com documentos/histórico. Relatório de retenções, exportação de preparação e dossier financeiro conferidos a partir da recuperação da API. Corrigida a associação da secção «IRPC — retenções» à secção financeira do dossier: a linha existia, mas era omitida nessa apresentação. UI sintética do componente real demonstra cálculo, gravação/recuperação local e relatório; esta prova local é distinta da API privada.
|
||||
|
||||
**Reprodução:** gerar routes/actions com `php tools/irpc-reliefs-qa.php routes`; executar `node --test tests/frontend/irpc-reliefs.test.mjs` (produz a amostra sintética); executar `php tools/irpc-reliefs-qa.php test`. Usar PHP8.5/Node24 e dependências existentes. `tsc --noEmit`; compilação `INVESTMOZ_QA_BUILD=1 vp build` no ramo QA. Os módulos gerados são artefactos locais, sem edição manual ou commit.
|
||||
|
||||
**Saldo na mesma matriz:** restantes alíneas68, categorias digitais/residentes não correspondidas, outras convenções/Estados e casos com EP, dividendos/juros ou excesso por relações especiais mantêm correspondência própria. Juros de mora, rectificações, agregação, rubricas/CER e preenchimento do MRFTA permanecem discriminados. O cálculo convencional não declara cumpridas as formalidades de accionamento, recepção ou aceitação AT. Não são transportados formulários portugueses21-RFI para obrigações moçambicanas. Pendências condicionam apenas os casos dependentes. L5, IRPC e IRPS não estão integralmente concluídos. L3/ISPC/câmbio não foram retestados; as suas provas são reutilizadas. Entrega conjunta preparada apenas para os recortes demonstrados. Instalação futura segue o procedimento Git e os controlos de destino/backup/restauro já existentes.
|
||||
|
||||
Recibo: `docs/investmoz/simulations/irpc-l5-reliefs-qa-20261025.json`; provas locais em `outputs/irpc-reliefs/`.
|
||||
|
||||
@@ -0,0 +1,330 @@
|
||||
{
|
||||
"status": "QA_demonstrated_grouped_candidate_not_installed",
|
||||
"baseline_reported_installed": "2b33a1c3",
|
||||
"branch": "codex/irpc-l5-reliefs-qa-20261025",
|
||||
"implemented": [
|
||||
"CIRPC68d qualified on-account services/equipment",
|
||||
"CIRPC68f own-property management,not fiscal transparency",
|
||||
"PT-MZ12 qualified royalties/equipment10%gross definitive",
|
||||
"Insufficient/contradictory/out-of-period evidence remains indeterminate",
|
||||
"Private API persistence,recovery,history;standalone report and complete financial dossier",
|
||||
"IRPC dossier section association restored"
|
||||
],
|
||||
"runtime_delta": [
|
||||
{
|
||||
"path": "resources/js/lib/irpc-relief-preparation.ts",
|
||||
"sha256_LF": "e41a389ad0b5d3f33985e375b0c488eed8c2d185257eec1cf5f7d40fccd365d4"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/lib/irpc-withholdings.ts",
|
||||
"sha256_LF": "75bf1c5940595e90d7a27355aeae6f9fdca4841f3e9ce3cb02103376bbd4d91b"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/components/simulation-irpc-withholdings.tsx",
|
||||
"sha256_LF": "28dd35e40c5d8440adad01131714d324f0c7fcdfeb3f6ddd9d27750d563b5e4f"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/lib/project-dossier-report.ts",
|
||||
"sha256_LF": "4523365e2d4a2fcd014e13393deb33a5ec4b9396ff580d8a237aa8c4f02265e4"
|
||||
}
|
||||
],
|
||||
"verification": {
|
||||
"routes": "normal Wayfinder generate --with-form through guarded QA wrapper",
|
||||
"types": "tsc --noEmit passed",
|
||||
"build": "complete QA build passed; existing optional-font/chunk-size notices only",
|
||||
"frontend": "9 new tests passed",
|
||||
"compatibility": "5 directed IRPC cases passed;no repeated L3/ISPC/FX suites",
|
||||
"api": "1test/17assertions;private endpoints;55441/simulator_fiscal_delta_20261021_test;DatabaseTransactions rollback",
|
||||
"report": "retentions preparation and financial dossier from actual API recovered sample",
|
||||
"ui": {
|
||||
"calculation": true,
|
||||
"localSaveRecovery": true,
|
||||
"privateApiProof": "IrpcReliefsDraftTest, separate",
|
||||
"report": true,
|
||||
"insufficientInformation": true,
|
||||
"errors": []
|
||||
}
|
||||
},
|
||||
"preserved_FX_files": [
|
||||
{
|
||||
"path": "resources/js/lib/iva-fx-selection.ts",
|
||||
"sha256_LF": "9aa07ad443bc777b80510a3b5bd6aff9ddb9814d2450f3c690e531e74d71d998"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/components/simulation-fx-selection.tsx",
|
||||
"sha256_LF": "39bb6d87d404745b0b66349bc11626545a3672ae27321ce9b4658416592059bf"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/lib/business-financial-plan.ts",
|
||||
"sha256_LF": "c20274722d1ffcd0360f041a6cacb5d868fe0621cb27a1aca6e06826612c3ebb"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/lib/iva-currency-events.ts",
|
||||
"sha256_LF": "cc9f2045d302d7987310da4006cc250fc5c99a27397918d166d649bfc32fab08"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/lib/irpc-fx.ts",
|
||||
"sha256_LF": "5c4a7a477429f8d775acf6db3e117008468f5554e5731dec81f60fbb75e948fb"
|
||||
}
|
||||
],
|
||||
"sources_inventory": [
|
||||
{
|
||||
"path": "docs/investmoz/simulations/source-archive/AT-Comunicado-9-2026-ISPC.pdf",
|
||||
"sha256": "106f8a9ce9255c03549c70bf07fe999d32e6e55302d009b509862aa74ccb4973",
|
||||
"bytes": 308401
|
||||
},
|
||||
{
|
||||
"path": "docs/investmoz/simulations/source-archive/AT-Modelo-30-ISPC.pdf",
|
||||
"sha256": "6802edd06b6ff638a0b851a3ec7ecf69af8f07840faa47446becad67d35d445c",
|
||||
"bytes": 159748
|
||||
},
|
||||
{
|
||||
"path": "docs/investmoz/simulations/source-archive/AT-Modelo-B-Geral.pdf",
|
||||
"sha256": "bd977c7c59d4d98befd92e2c566b1bf168cb1343bb519f51b41a3807f431fc57",
|
||||
"bytes": 319577
|
||||
},
|
||||
{
|
||||
"path": "docs/investmoz/simulations/source-archive/AT-MRFTA-19-39.pdf",
|
||||
"sha256": "8ea62c53066b482f87078a7a7b1ecf15ca98a12a122e8d7abca63acfc993186b",
|
||||
"bytes": 256695
|
||||
},
|
||||
{
|
||||
"path": "docs/investmoz/simulations/source-archive/BR-16-I-Serie-2008-04-16.pdf",
|
||||
"sha256": "fc373ff5637edf75ec6a9595dec8763a8503d4ee786e3b7d8f1c742eae9d80ee",
|
||||
"bytes": 2267865
|
||||
},
|
||||
{
|
||||
"path": "docs/investmoz/simulations/source-archive/BR-248-I-Suplemento-1-2025.pdf",
|
||||
"sha256": "ccf2a8deeca820f2dd076b4b2141b1efe3f6a806568c3a587ca59a8e92f4c251",
|
||||
"bytes": 440018
|
||||
},
|
||||
{
|
||||
"path": "docs/investmoz/simulations/source-archive/BR-89-I-Serie-2017-06-08.pdf",
|
||||
"sha256": "11f0cab15eb6e51010952203ffa24fdc533a24fd642e19525fe4f46b17a1f5e5",
|
||||
"bytes": 9456358
|
||||
},
|
||||
{
|
||||
"path": "docs/investmoz/simulations/source-archive/BR-95-I-Suplemento-2025-05-21.pdf",
|
||||
"sha256": "e0e682e2c027c3cd9c1d46a73a3f48bc5f6bdbf05667fd97314d7a3f6a456358",
|
||||
"bytes": 329070
|
||||
},
|
||||
{
|
||||
"path": "docs/investmoz/simulations/source-archive/BR252-I-Serie-2022-Lei20-PESOE.pdf",
|
||||
"sha256": "1159fd3ce1ba5581671796d083534da48bd3c5578298819e896f1c2a8026318d",
|
||||
"bytes": 17966640
|
||||
},
|
||||
{
|
||||
"path": "docs/investmoz/simulations/source-archive/Decreto-22-2021-BR70.pdf",
|
||||
"sha256": "a8fac38dcfe08f9c7a8e46254cff10e1ec82fc4f7a8d88ad6e4fe7d91ffc21a5",
|
||||
"bytes": 905551
|
||||
},
|
||||
{
|
||||
"path": "docs/investmoz/simulations/source-archive/Decreto-27-2026-BR123.pdf",
|
||||
"sha256": "26249c3fa58f149c81de36e99d1aac2819a4771379d9e8833159660dabf539bb",
|
||||
"bytes": 665722
|
||||
},
|
||||
{
|
||||
"path": "docs/investmoz/simulations/source-archive/Decreto-29-1998-BR22-5-suplemento.pdf",
|
||||
"sha256": "6242cbe675faadf085f43f95ec8c7e8868aafe67c3b2ab2bb010992288f954f8",
|
||||
"bytes": 590418
|
||||
},
|
||||
{
|
||||
"path": "docs/investmoz/simulations/source-archive/Decreto-3-2011-BR-11-I-mineiros.pdf",
|
||||
"sha256": "5bca1133666ba9c69870b89823420175b7b5b8d0eccb667ecdf5c29716f3b6be",
|
||||
"bytes": 135471
|
||||
},
|
||||
{
|
||||
"path": "docs/investmoz/simulations/source-archive/Decreto-37-2023-BR124.pdf",
|
||||
"sha256": "7e680b191481faa0e2e9057fa5053587eb2a29d1d8c819e0eb9f02814255a04c",
|
||||
"bytes": 730017
|
||||
},
|
||||
{
|
||||
"path": "docs/investmoz/simulations/source-archive/Decreto-9-2008-BR16.pdf",
|
||||
"sha256": "96fccdecbfdc11c9eafb90fbc6c7bc259f88bbd4c7507d1ac3af1fa03f960697",
|
||||
"bytes": 900370
|
||||
},
|
||||
{
|
||||
"path": "docs/investmoz/simulations/source-archive/Lei-13-2016-CIVA-BR-156-10-suplemento.pdf",
|
||||
"sha256": "b90dc7c14ecea2e378c4a62e8e5a47d54a5644c457baeb8b8053b92fd2a5710b",
|
||||
"bytes": 2127554
|
||||
},
|
||||
{
|
||||
"path": "docs/investmoz/simulations/source-archive/Lei-14-1992-BR42.pdf",
|
||||
"sha256": "089fff7423e99a70a230a73ca379e4ea3a91d124703f91016e57ff82794b9e56",
|
||||
"bytes": 1604284
|
||||
},
|
||||
{
|
||||
"path": "docs/investmoz/simulations/source-archive/Lei-17-2022-BR251-exemplar-MCNet5.pdf",
|
||||
"sha256": "14eb34f0af91a8f5cb1770c7b52d6ff839c6818302d1ed96d79afbfb9fe999d4",
|
||||
"bytes": 1603184
|
||||
},
|
||||
{
|
||||
"path": "docs/investmoz/simulations/source-archive/Lei-17-2022-IPP-BR250-Parlamento.pdf",
|
||||
"sha256": "978cd22403ffd336a631704ecd7a5bbd814a427c815b9acdaca5515591dd9e2a",
|
||||
"bytes": 1990307
|
||||
},
|
||||
{
|
||||
"path": "docs/investmoz/simulations/source-archive/Lei-19-2013-BR76.pdf",
|
||||
"sha256": "bba96d645cc935ffc3d1a79b9f0f73ac1c693d6459c2ad41f56cd2841accae76",
|
||||
"bytes": 147521
|
||||
},
|
||||
{
|
||||
"path": "docs/investmoz/simulations/source-archive/Lei-19-2017-IRPS-BR-OCAM.pdf",
|
||||
"sha256": "c3a0186cb9ad090d7a9533006295dc9bb6a62ad0d6150173cbd49dba4ee4b52d",
|
||||
"bytes": 1894997
|
||||
},
|
||||
{
|
||||
"path": "docs/investmoz/simulations/source-archive/Lei-2-2011-BR-1-4-suplemento-MCNet.pdf",
|
||||
"sha256": "a7543a68cc3287a46a83c39a04821b40f649dd06c4e9b5dc51ff0b47fba08899",
|
||||
"bytes": 161625
|
||||
},
|
||||
{
|
||||
"path": "docs/investmoz/simulations/source-archive/Lei-22-2022-IVA-republicada-BR-250-2-suplemento.pdf",
|
||||
"sha256": "ea1b0dc6f5cf6e595f6d9a19e7042f398999e0087caef1785f6e8fac571b5f53",
|
||||
"bytes": 1834284
|
||||
},
|
||||
{
|
||||
"path": "docs/investmoz/simulations/source-archive/Lei-34-2007-BR52.pdf",
|
||||
"sha256": "cdbef875ad7ccc1517b12faf5de0e893a70ed2ed81e33913cd6dc4a674474e6e",
|
||||
"bytes": 1676884
|
||||
},
|
||||
{
|
||||
"path": "docs/investmoz/simulations/source-archive/Lei-4-1994-BR37-3-suplemento.pdf",
|
||||
"sha256": "210df9db8980d0a5690cd187a6eeec7e37a900d743e39062c16f4e0d12215c7e",
|
||||
"bytes": 186487
|
||||
},
|
||||
{
|
||||
"path": "docs/investmoz/simulations/source-archive/Lei-4-2012-BR3.pdf",
|
||||
"sha256": "b8845cef41dcbd9ba50c43dbfa743a56030dd3d348242a107e1ae30279eec121",
|
||||
"bytes": 187572
|
||||
},
|
||||
{
|
||||
"path": "docs/investmoz/simulations/source-archive/Lei-7-1991-Partidos-IFES.pdf",
|
||||
"sha256": "6b783a0bb3330688ef10660a58060d9724aa2da6a873d234fe902f0eaaeda42d",
|
||||
"bytes": 309350
|
||||
},
|
||||
{
|
||||
"path": "docs/investmoz/simulations/source-archive/PT-MZ-CDT-1992.pdf",
|
||||
"sha256": "d504e1024b28aee5c6acf4592acb96ec9c915a8414f67a61ab6984d6acf9df9d",
|
||||
"bytes": 199331
|
||||
},
|
||||
{
|
||||
"path": "docs/investmoz/simulations/source-archive/PT-MZ-Protocolo-2009.pdf",
|
||||
"sha256": "f353773dcf41f09f4daa6cf5ba5a5f7994f3e02615867c2dcffc61c3d901bf9b",
|
||||
"bytes": 197027
|
||||
}
|
||||
],
|
||||
"new_sources": [
|
||||
{
|
||||
"path": "docs/investmoz/simulations/source-archive/PT-MZ-CDT-1992.pdf",
|
||||
"sha256": "d504e1024b28aee5c6acf4592acb96ec9c915a8414f67a61ab6984d6acf9df9d",
|
||||
"bytes": 199331,
|
||||
"url": "https://dcjri.ministeriopublico.pt/sites/default/files/documentos/instrumentos/rar36-1992.pdf"
|
||||
},
|
||||
{
|
||||
"path": "docs/investmoz/simulations/source-archive/PT-MZ-Protocolo-2009.pdf",
|
||||
"sha256": "f353773dcf41f09f4daa6cf5ba5a5f7994f3e02615867c2dcffc61c3d901bf9b",
|
||||
"bytes": 197027,
|
||||
"url": "https://files.dre.pt/1s/2009/05/08900/0275202756.pdf"
|
||||
}
|
||||
],
|
||||
"remaining": [
|
||||
"Other68paragraphs and digital/resident-specific cases",
|
||||
"Other countries,treaties,EP-related and special-relationship excess cases",
|
||||
"Dividends,interest-specific treaty branches",
|
||||
"Late-payment interest,rectifications,aggregation,MRFTA/CER/form filling",
|
||||
"External treaty claiming formalities are not declared satisfied by this calculation"
|
||||
],
|
||||
"active_changes": false,
|
||||
"active_verification": "No fresh live inspection claimed;2b33a1c3 remains last reported installation",
|
||||
"schema_config_permissions_delta": false,
|
||||
"L5_integrally_completed": false,
|
||||
"L3_ISPC_FX": "Unchanged;accepted proofs reused;no repeated functional suites",
|
||||
"installation_remaining": "Existing Git release process,destination comparison and fresh backup/restore safeguards at installation",
|
||||
"proofs": [
|
||||
{
|
||||
"path": "outputs/irpc-reliefs/api.txt",
|
||||
"sha256": "5cccf4ee4e62ae00ef85276d48482fa8c13e202ddf68b6d79b1948977fb1f597"
|
||||
},
|
||||
{
|
||||
"path": "outputs/irpc-reliefs/build.txt",
|
||||
"sha256": "c60d4a5285f57a6458285163f64279d87558a8119f9b386cad8d12861d49e025"
|
||||
},
|
||||
{
|
||||
"path": "outputs/irpc-reliefs/calculated.json",
|
||||
"sha256": "563e05189c0a1255bf071fd1086aa8e67d6f0872891c5851dccbfc0d944752fe"
|
||||
},
|
||||
{
|
||||
"path": "outputs/irpc-reliefs/compatibility.txt",
|
||||
"sha256": "f37ad0ac9a0f2675fa4199f7600f3ae4e5a2df35a7294fe6e5aefd8024344051"
|
||||
},
|
||||
{
|
||||
"path": "outputs/irpc-reliefs/dossier.html",
|
||||
"sha256": "a34c2e9ef8b4d202e23412aaf6862ef9d603efeee0518a16adeb9d895f775641"
|
||||
},
|
||||
{
|
||||
"path": "outputs/irpc-reliefs/dossier.json",
|
||||
"sha256": "717561321d4b6c7659f0e6ab82c0e411ada0002a4f5436ba42092f8002f96076"
|
||||
},
|
||||
{
|
||||
"path": "outputs/irpc-reliefs/dossier.txt",
|
||||
"sha256": "a0473de355821758c4d320eea8e7af76a0d196a9cee652c71af723d6f69832f0"
|
||||
},
|
||||
{
|
||||
"path": "outputs/irpc-reliefs/frontend.txt",
|
||||
"sha256": "f86464b0d6a36ccfc39e22c0616d4a2e4f657126da134cb2014fcc199a11bdf2"
|
||||
},
|
||||
{
|
||||
"path": "outputs/irpc-reliefs/index.html",
|
||||
"sha256": "6bb76e37ac20a73cb9ffcda14f1ac9db701aa01bc2ef2f62fc5adaaffb640ca8"
|
||||
},
|
||||
{
|
||||
"path": "outputs/irpc-reliefs/Lei-19-2013-BR76.pdf-review.png",
|
||||
"sha256": "b62716ed89669622a33bde81a5ab6dcf0a6faafaf41726a09cd5b473b59abab1"
|
||||
},
|
||||
{
|
||||
"path": "outputs/irpc-reliefs/Lei-34-2007-BR52.pdf19.png",
|
||||
"sha256": "8355aa0b103439bb2b652b8499b2abf9bee1e4f3cc1af54b1be3ea059a2384f4"
|
||||
},
|
||||
{
|
||||
"path": "outputs/irpc-reliefs/Lei-4-2012-BR3.pdf-review.png",
|
||||
"sha256": "207224d8712713c9f4c5dda4e61539eba28f37af1ec70ef139cff0b450a62b2e"
|
||||
},
|
||||
{
|
||||
"path": "outputs/irpc-reliefs/preparation.html",
|
||||
"sha256": "2943b6cd21cc4ac043ae9b0605aee23c0fbbbed552842aeafef187ff963389a1"
|
||||
},
|
||||
{
|
||||
"path": "outputs/irpc-reliefs/protocol-page1.png",
|
||||
"sha256": "d981c72e254239c911554ff1289b578f7136bae7964a3d53f76a69094186cc04"
|
||||
},
|
||||
{
|
||||
"path": "outputs/irpc-reliefs/PT-MZ-CDT-1992.pdf13.png",
|
||||
"sha256": "2a57bc9924c0c826452bbc7f097b48c1899e6b642d6cdc247c244ace8993bb96"
|
||||
},
|
||||
{
|
||||
"path": "outputs/irpc-reliefs/PT-MZ-CDT-1992.pdf14.png",
|
||||
"sha256": "9fd31dcf17bdd1526390758a48a5afa8f9b98aebd83209b962ea97b7deef4006"
|
||||
},
|
||||
{
|
||||
"path": "outputs/irpc-reliefs/PT-MZ-Protocolo-2009.pdf-review.png",
|
||||
"sha256": "ba5ad799456a16959b83878c8935eb326ec3f0d87a2ba32e43217411e3f0dac9"
|
||||
},
|
||||
{
|
||||
"path": "outputs/irpc-reliefs/recovered.json",
|
||||
"sha256": "0b2378d1b8232b1424cf3d13e199146f4eabc330b9750a74b2b59f172741515e"
|
||||
},
|
||||
{
|
||||
"path": "outputs/irpc-reliefs/types.txt",
|
||||
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
},
|
||||
{
|
||||
"path": "outputs/irpc-reliefs/ui-proof.json",
|
||||
"sha256": "274ffe6c237da799aceff79df76087cf17f2a83505755aa6354d90b9d1f85abb"
|
||||
},
|
||||
{
|
||||
"path": "outputs/irpc-reliefs/ui.png",
|
||||
"sha256": "55df91c9dfc9a028eceafaf0b55b4ad2df892da491f8ac97d5d8edaacea73a4e"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -141,3 +141,18 @@ O novo exemplar integral do BR 252/I, de 30/12/2022, foi recuperado em arquivo p
|
||||
Origem: https://www.cipmoz.org/wp-content/uploads/2023/financas/17F%29%20Lei%2029_2022%20Plano%20Economico%20Social%20Or%C3%A7amento%20do%20Estado%202023.pdf
|
||||
|
||||
SHA256: `1159fd3ce1ba5581671796d083534da48bd3c5578298819e896f1c2a8026318d`. Exemplar: `docs/investmoz/simulations/source-archive/BR252-I-Serie-2022-Lei20-PESOE.pdf`. Lei19/2013 PDF2/p682(22) também conferida visualmente:62(3)liberatória20% e62(4)a–d10% específicos. Lei20/2022 mantém62(1/3/4) e termina medidas agrícolas em31/12/2025;Lei12/2025 estabelece62(5)digital/moeda electrónica. A fonte oficial permanece aceite; a nova leitura resolve uma lacuna de texto, sem revalidação genérica. Sem publicação/importação automática ou scan declarado. Recibo: `irpc-l5-grouped-qa-20261025.json`.
|
||||
|
||||
|
||||
## 25/10/2026 — L5 dispensas e convenção PT–MZ
|
||||
|
||||
Inventários anteriores reutilizados; inventário completo dos29 originais fiscais em `irpc-l5-reliefs-qa-20261025.json`, incluindo27 anteriores preservados e2 novos. CIRPC Lei34/2007 art68(d/f), PDF19/p768(73), conferido visualmente; listas de alterações Lei4/2012 PDF1 e Lei19/2013 PDF1 não alteram68. Lei20/2022 e Lei12/2025 já correspondidas no recibo agrupado. Sem reutilizar código revogado2002.
|
||||
|
||||
CDT PT–MZ, RAR36/1992: original oficial no arquivo do Ministério Público português, arts1/4/12;PDF13–14 conferidas visualmente. Protocolo/RAR36/2009, original DR,PDF1–2/p2752–2753:IRPC/residência e definição12(3);art15 aplicabilidade temporal confrontado. DR apresenta os diplomas em vigor; vigência do Protocolo07/06/2009 documentada na ficha oficial do Ministério Público, efeitos na fonte a partir do ano seguinte. Recorte2026 não estende automaticamente a outras categorias ou tratados. A condição de aplicação12(2) não é confundida com cumprimento de formalidades externas;21-RFI português não imposto em Moçambique.
|
||||
|
||||
Novos originais privados preservados, sem publicação/importação automáticas, sem scan ou certificação declarados:
|
||||
|
||||
- docs/investmoz/simulations/source-archive/PT-MZ-CDT-1992.pdf;SHA256`d504e1024b28aee5c6acf4592acb96ec9c915a8414f67a61ab6984d6acf9df9d`;origem:https://dcjri.ministeriopublico.pt/sites/default/files/documentos/instrumentos/rar36-1992.pdf.
|
||||
|
||||
- docs/investmoz/simulations/source-archive/PT-MZ-Protocolo-2009.pdf;SHA256`f353773dcf41f09f4daa6cf5ba5a5f7994f3e02615867c2dcffc61c3d901bf9b`;origem:https://files.dre.pt/1s/2009/05/08900/0275202756.pdf.
|
||||
|
||||
Referências oficiais adicionais: https://diariodarepublica.pt/dr/detalhe/resolucao-assembleia-republica/36-1992-408879 ; https://diariodarepublica.pt/dr/detalhe/resolucao-assembleia-republica/36-2009-607572 ; https://dcjri.ministeriopublico.pt/instrumento/protocolo-entre-republica-portuguesa-e-republica-de-mocambique-que-reve-convencao-para . A aceitação das fontes oficiais permanece, sem revalidação genérica.
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -5870,3 +5870,20 @@ Passaram 19 testes novos distintos, com casos dirigidos para natureza dos capita
|
||||
Permanecem separados: dispensas e casos residentes/EP que delas dependem, convenções invocadas, categorias/devedores/períodos fora deste recorte, juros, rectificações e correspondências/CER do MRFTA. Cada pendência identifica os dados e a condição de resolução no recibo `irpc-l5-grouped-qa-20261025.json`; não bloqueia casos independentes. Exportar a preparação não submete nem obtém aceitação AT. L5, IRPC e IRPS não estão integralmente concluídos.
|
||||
|
||||
O portal mantém `5c0cb25c`, com L3 e ISPC preservados. O saldo ISPC continua na mesma matriz e o IRPS conserva o caso anteriormente demonstrado. P001/P058 não bloqueiam; P084/P085 permanecem fechados; estatísticas e IA continuam adiadas. Foram conservados os 26 originais anteriores e acrescentado um novo original. A instalação futura exige a comparação autenticada do destino, backup/restauro fresco e conferência do delta pelo procedimento Git existente, sem repetir provas aceites.
|
||||
|
||||
|
||||
## 25/10/2026 — L5: dispensas e convenção demonstradas em QA
|
||||
|
||||
Base de código `2b33a1c3`, ramo `codex/irpc-l5-reliefs-qa-20261025`. O último relatório de instalação mantém o portal nessa entrega; este candidato não executa instalação. O inventário anterior de L3, ISPC, câmbio, fontes e provas aceites é reutilizado. Comparação do delta: sem alteração nesses módulos, em schema/migrações, configuração ou permissões; sem escrita na base activa. As correcções de codificação que tinham atingido trechos históricos durante a preparação foram retiradas do delta; os registos anteriores são conservados.
|
||||
|
||||
**Executável:** dispensa 68(d) para serviços/uso de equipamento 67(1)b/g com natureza por conta, titular sujeito a IRPC relativamente ao rendimento; dispensa 68(f) para rendas de sociedade de gestão de imóveis próprios fora da transparência fiscal 6(1)c; limite convencional de 10% sobre royalties/uso de equipamento qualificados PT–MZ, não digitais, titular residente convencional em Portugal, beneficiário efectivo, sem EP em Moçambique, devedor residente em Moçambique e montante sem excesso por relações especiais. As condições são dados documentados do caso, sem aprovador adicional. A selecção de regra não é substituída por texto livre ou taxa manual. Outros ramos continuam separados.
|
||||
|
||||
Reutilizados contraparte, residência/EP, documento, qualificação, montante e conversão da linha. Diploma, artigo, período, documentos e condições ficam no mesmo livro e no histórico. Alterar a prova invalida apenas a correspondência do resultado afectado. A dispensa produz retenção zero justificada, apresentada como dispensa, sem isentar o rendimento nem gerar crédito anual. Informação insuficiente, negativa, período incompatível ou artigo divergente fica indeterminada, sem zero/taxa reduzida presumidos. A existência de prova de entrega numa linha dispensada pede reconciliação sem apagar o pagamento.
|
||||
|
||||
**Demonstração:** Wayfinder `wayfinder:generate --with-form`, pelo wrapper QA guardado `tools/irpc-reliefs-qa.php routes`; TypeScript passou; compilação completa QA passou. Nove testes novos e cinco verificações dirigidas de compatibilidade IRPC passaram. API privada: um teste/17 verificações, base `simulator_fiscal_delta_20261021_test`, loopback55441, transacção com rollback. Resultados 68(d),68(f), convenção e informação insuficiente recuperados com documentos/histórico. Relatório de retenções, exportação de preparação e dossier financeiro conferidos a partir da recuperação da API. Corrigida a associação da secção «IRPC — retenções» à secção financeira do dossier: a linha existia, mas era omitida nessa apresentação. UI sintética do componente real demonstra cálculo, gravação/recuperação local e relatório; esta prova local é distinta da API privada.
|
||||
|
||||
**Reprodução:** gerar routes/actions com `php tools/irpc-reliefs-qa.php routes`; executar `node --test tests/frontend/irpc-reliefs.test.mjs` (produz a amostra sintética); executar `php tools/irpc-reliefs-qa.php test`. Usar PHP8.5/Node24 e dependências existentes. `tsc --noEmit`; compilação `INVESTMOZ_QA_BUILD=1 vp build` no ramo QA. Os módulos gerados são artefactos locais, sem edição manual ou commit.
|
||||
|
||||
**Saldo na mesma matriz:** restantes alíneas68, categorias digitais/residentes não correspondidas, outras convenções/Estados e casos com EP, dividendos/juros ou excesso por relações especiais mantêm correspondência própria. Juros de mora, rectificações, agregação, rubricas/CER e preenchimento do MRFTA permanecem discriminados. O cálculo convencional não declara cumpridas as formalidades de accionamento, recepção ou aceitação AT. Não são transportados formulários portugueses21-RFI para obrigações moçambicanas. Pendências condicionam apenas os casos dependentes. L5, IRPC e IRPS não estão integralmente concluídos. L3/ISPC/câmbio não foram retestados; as suas provas são reutilizadas. Entrega conjunta preparada apenas para os recortes demonstrados. Instalação futura segue o procedimento Git e os controlos de destino/backup/restauro já existentes.
|
||||
|
||||
Recibo: `docs/investmoz/simulations/irpc-l5-reliefs-qa-20261025.json`; provas locais em `outputs/irpc-reliefs/`.
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,3 +1,4 @@
|
||||
import {irpcReliefRules,newIrpcReliefPreparation,prepareIrpcRelief} from '@/lib/irpc-relief-preparation';
|
||||
import {SimulationFxSelection} from '@/components/simulation-fx-selection';
|
||||
import {irpcFxSources,irpcFxRow,updateIrpcFx,type IrpcFxAmount} from '@/lib/irpc-fx';
|
||||
import {useState} from 'react';
|
||||
@@ -13,10 +14,11 @@ export function SimulationIrpcWithholdings({draft,onChange}:{draft:Record<string
|
||||
const renderFx=(id:string,input:IrpcFxAmount,apply:(patch:Partial<IrpcFxAmount>)=>void)=><div className="space-y-2"><p>A base/comprovativo conserva a moeda e o montante originais. A conversão reutiliza o selector e a aritmética entregues; a data cambial fica distinta do evento da retenção.</p>{fxSources.map(src=><div key={src.id}><span>{src.label};data:{src.selection.applied?.date||'por confirmar'};selecção actual:{input.fxSelection?.applied?.rate||'nenhuma'}.</span><button type="button" className="rounded border p-2" onClick={()=>{if(window.confirm(`Substituir a selecção cambial desta linha (${input.fxSelection?.applied?.rate||'vazia'}) pela origem ${src.label},data ${src.selection.applied?.date||'por confirmar'}? O montante e a moeda originais são conservados;resultado anterior permanece no histórico até novo cálculo.`))apply({fxSelection:structuredClone(src.selection),fxLink:{id:src.id,fingerprint:src.fingerprint}});}}>Reutilizar selecção cambial {src.id}</button></div>)}<SimulationFxSelection row={irpcFxRow(id,input)} onChange={patch=>{if(patch.fxSelection)apply(updateIrpcFx(input,patch.fxSelection));}}/></div>;
|
||||
const ordinarySelect=(r:IrpcRow,k:keyof IrpcOrdinaryFacts,label:string,options:Array<[string,string]>)=><label className="block">{label}<select value={String((r.ordinary??emptyIrpcOrdinaryFacts())[k]??'')} onChange={e=>change(r.id,{ordinary:{...(r.ordinary??emptyIrpcOrdinaryFacts()),[k]:e.target.value}})}>{options.map(([v,l])=><option key={v} value={v}>{l}</option>)}</select></label>;
|
||||
const paymentChange=(id:string,p:Partial<IrpcPayment>)=>save({...book,payments:book.payments.map(v=>v.id===id?{...v,...p}:v)});
|
||||
return<section aria-label="Retenções de IRPC" className="space-y-4 rounded border p-4"><h4>IRPC — retenções e preparação documental</h4><p>Reutilize documentos e contrapartes existentes. Classifique o rendimento e o evento aplicável:facturar,pagar e reconhecer o custo são factos distintos. O conjunto calcula os casos digitais de 10%, as taxas normais de 20% e as taxas especiais do artigo 62(4) nos âmbitos identificados. As dispensas e convenções ficam separadas e só condicionam os rendimentos que delas dependem.</p>{error&&<p role="alert">{error}</p>}
|
||||
return<section aria-label="Retenções de IRPC" className="space-y-4 rounded border p-4"><h4>IRPC — retenções e preparação documental</h4><p>Reutilize documentos e contrapartes existentes. Classifique o rendimento e o evento aplicável:facturar,pagar e reconhecer o custo são factos distintos. O conjunto calcula os casos digitais de 10%, as taxas normais de 20% e as taxas especiais do artigo 62(4) nos âmbitos identificados. Dispensas dos artigos 68(d/f) e limite convencional de royalties Portugal–Moçambique são calculados nos casos documentados. Informação insuficiente condiciona apenas o respectivo rendimento.</p>{error&&<p role="alert">{error}</p>}
|
||||
<button type="button" className="rounded border p-2" onClick={()=>{try{save(reuseIrpcSources(draft,book));}catch(e){setError(String(e));}}}>Reutilizar contrapartes e documentos existentes</button><button type="button" className="rounded border p-2" onClick={()=>save({...book,rows:[...book.rows,newIrpcRow(uid())]})}>Acrescentar rendimento IRPC</button>
|
||||
{book.rows.map(r=><fieldset key={r.id} className="space-y-3 rounded border p-3"><legend>{r.description||r.documentRef||'Rendimento por identificar'}</legend>{r.source&&<p>Origem:{r.source.id}. Entradas deliberadamente editadas são conservadas. {sources.find(s=>s.id===r.source!.id)?.key!==r.source.key&&<><strong>Origem alterada ou indisponível.</strong>{sources.find(s=>s.id===r.source!.id)&&<><span> Base actual:{r.base};base na origem:{sources.find(s=>s.id===r.source!.id)?.amount||'desconhecida'};documento actual:{r.documentRef};origem:{sources.find(s=>s.id===r.source!.id)?.documentRef||'desconhecido'}.</span><button type="button" className="rounded border p-2" onClick={()=>{const s=sources.find(s=>s.id===r.source!.id)!;if(window.confirm('Confirma a reconciliação da origem indicada, conservando as entradas deliberadas e os resultados anteriores no histórico?'))change(r.id,{source:{...r.source!,key:s.key}});}}>Conservar edições e reconciliar origem</button></>}</>}</p>}
|
||||
<div className="grid gap-3 md:grid-cols-2">{select(r,'direction','Perspectiva',[['paid','Rendimento pago — retenção a entregar'],['received','Rendimento recebido — retenção sofrida']])}{field(r,'counterparty','Contraparte')}{field(r,'documentRef','Documento / operação / linha distinta')}{field(r,'description','Descrição')}{select(r,'origin','Origem',[['projected','Projecção com pressupostos'],['effective','Factos documentados']])}{select(r,'kind','Natureza do rendimento',irpcKinds)}{field(r,'base','Base do rendimento, separada do IVA')}{field(r,'currency','Moeda da base')}{select(r,'recipientIrpc','Titular sujeito a IRPC neste rendimento',yn)}{select(r,'payerIrpc','Devedor sujeito a IRPC',yn)}{select(r,'residence','Residência do titular',[['','Por esclarecer'],['resident','Residente'],['nonresident','Não residente']])}{r.residence==='nonresident'&&<>{select(r,'permanentEstablishment','Existe estabelecimento estável em Moçambique',yn)}{r.permanentEstablishment==='yes'&&select(r,'attributable','Rendimento imputável ao estabelecimento',yn)}</>}{select(r,'mozambique','Rendimento obtido em Moçambique, conforme qualificação legal',yn)}{field(r,'qualificationRef','Suporte / pressuposto da qualificação, residência e incidência')}{select(r,'specialRelief','Convenção, isenção ou dispensa aplicável',[['','Por esclarecer'],['none','Não invocada neste caso qualificado'],['claimed','Invocada — correspondência específica necessária']])}{r.specialRelief==='claimed'&&field(r,'reliefRef','Artigo, condições, período e prova invocados')}</div>
|
||||
<div className="grid gap-3 md:grid-cols-2">{select(r,'direction','Perspectiva',[['paid','Rendimento pago — retenção a entregar'],['received','Rendimento recebido — retenção sofrida']])}{field(r,'counterparty','Contraparte')}{field(r,'documentRef','Documento / operação / linha distinta')}{field(r,'description','Descrição')}{select(r,'origin','Origem',[['projected','Projecção com pressupostos'],['effective','Factos documentados']])}{select(r,'kind','Natureza do rendimento',irpcKinds)}{field(r,'base','Base do rendimento, separada do IVA')}{field(r,'currency','Moeda da base')}{select(r,'recipientIrpc','Titular sujeito a IRPC neste rendimento',yn)}{select(r,'payerIrpc','Devedor sujeito a IRPC',yn)}{select(r,'residence','Residência do titular',[['','Por esclarecer'],['resident','Residente'],['nonresident','Não residente']])}{r.residence==='nonresident'&&<>{select(r,'permanentEstablishment','Existe estabelecimento estável em Moçambique',yn)}{r.permanentEstablishment==='yes'&&select(r,'attributable','Rendimento imputável ao estabelecimento',yn)}</>}{select(r,'mozambique','Rendimento obtido em Moçambique, conforme qualificação legal',yn)}{field(r,'qualificationRef','Suporte / pressuposto da qualificação, residência e incidência')}{select(r,'specialRelief','Convenção, isenção ou dispensa aplicável',[['','Por esclarecer'],['none','Não invocada neste caso qualificado'],['claimed','Invocada — identificar regra e condições']])}{r.specialRelief==='claimed'&&field(r,'reliefRef','Artigo, condições, período e prova invocados')}</div>
|
||||
{r.specialRelief==='claimed'&&<fieldset className="space-y-2 rounded border p-2"><legend>Preparação de dispensa ou convenção</legend><p>Reutiliza contraparte, residência, estabelecimento estável e documento deste rendimento.</p><label>Enquadramento<select value={r.reliefPreparation?.kind??'dispensation'} onChange={e=>change(r.id,{reliefPreparation:{...(r.reliefPreparation??newIrpcReliefPreparation()),kind:e.target.value as 'dispensation'|'exemption'|'treaty'}})}><option value="dispensation">Dispensa de retenção</option><option value="exemption">Isenção</option><option value="treaty">Convenção</option></select></label>{(['instrument','article','country','residenceDocument','validFrom','validTo','conditions','evidence'] as const).filter(k=>!['country','residenceDocument'].includes(k)||r.reliefPreparation?.kind==='treaty').map(k=><label key={k} className="block">{({instrument:'Diploma / convenção',article:'Artigo',country:'Estado contratante',residenceDocument:'Referência do documento de residência fiscal',validFrom:'Aplicável desde',validTo:'Aplicável até',conditions:'Condições do benefício',evidence:'Referências dos documentos existentes'})[k]}<input type={k==='validFrom'||k==='validTo'?'date':'text'} value={r.reliefPreparation?.[k]??''} onChange={e=>change(r.id,{reliefPreparation:{...(r.reliefPreparation??newIrpcReliefPreparation()),[k]:e.target.value}})}/></label>)}<label>Regra aplicável<select value={r.reliefPreparation?.rule??''} onChange={e=>change(r.id,{reliefPreparation:{...(r.reliefPreparation??newIrpcReliefPreparation()),rule:e.target.value as NonNullable<IrpcRow['reliefPreparation']>['rule']}})}>{irpcReliefRules.map(([v,l])=><option key={v} value={v}>{l}</option>)}</select></label>{(r.reliefPreparation?.rule==='ptMz12'?(['treatyResident','beneficialOwner','payerResidentMz','armLength','royaltyDefinition'] as const):r.reliefPreparation?.rule==='cirpc68f'?(['subjectToIrpc','notTransparent'] as const):r.reliefPreparation?.rule==='cirpc68d'?(['subjectToIrpc'] as const):[]).map(k=><label key={k} className="block">{({subjectToIrpc:'Titular sujeito a IRPC relativamente a este rendimento (ainda que isento)',notTransparent:'Sociedade fora da transparência fiscal — 6(1)c',treatyResident:'Residência em Portugal nos termos do artigo 4 documentada',beneficialOwner:'Titular é beneficiário efectivo das royalties',payerResidentMz:'Devedor residente em Moçambique; origem do rendimento conforme 12(5)',armLength:'Montante sem excesso por relações especiais — 12(6)',royaltyDefinition:'Rendimento corresponde à definição de royalties — 12(3), versão 2009'})[k]}<select value={r.reliefPreparation?.facts?.[k]??''} onChange={e=>change(r.id,{reliefPreparation:{...(r.reliefPreparation??newIrpcReliefPreparation()),facts:{...r.reliefPreparation?.facts,[k]:e.target.value as ''|'yes'|'no'}}})}>{yn.map(([v,l])=><option key={v} value={v}>{l}</option>)}</select></label>)}<p>{prepareIrpcRelief(r,r.reliefPreparation??newIrpcReliefPreparation()).missing.length?'Faltam elementos documentais; confira artigo, período e provas.':'Documentos registados; calcule para conferir as condições da regra seleccionada.'}</p></fieldset>}
|
||||
{r.currency!=='MZN'&&renderFx(r.id,r,patch=>change(r.id,patch))}
|
||||
{r.kind&&!['digital_goods','digital_services','emoney_commission','other'].includes(r.kind)&&<fieldset className="space-y-2 rounded border p-2"><legend>Factos específicos do rendimento</legend>{ordinarySelect(r,'nonDigital','Rendimento fora do âmbito digital/moeda electrónica do artigo62(5)',yn)}{r.kind==='capital'&&ordinarySelect(r,'capitalType','Categoria dos capitais',[['','Por esclarecer'],['unlisted_interest','Juros não cotados, fora das permutas de liquidez'],['unlisted_dividend','Dividendos não cotados'],['listed_debt_mz','Juros de bilhetes do Tesouro/títulos de dívida cotados em bolsa —62(3)a'],['listed_other_mz','Outros títulos cotados na Bolsa de Valores de Moçambique —62(4)d'],['liquidity_repo','Juros de permutas de liquidez em bancos,com ou sem garantia —62(3)b'],['other','Outra categoria']])}{r.kind==='capital'&&r.residence==='resident'&&['listed_debt_mz','liquidity_repo'].includes(r.ordinary?.capitalType??'')&&ordinarySelect(r,'capitalSeatAndManagement','Titular com sede e direcção efectiva nacionais —62(3)',yn)}{r.kind==='rent'&&ordinarySelect(r,'rentOwnPropertyManagement','Titular é sociedade com objecto de gestão de imóveis próprios',yn)}{(['telecom','international_transport','connected_installation','aircraft_maintenance_charter','rural_public_electrification','maritime_fishing_cabotage'].includes(r.kind)||r.kind==='capital'&&r.ordinary?.capitalType==='listed_other_mz')&&ordinarySelect(r,'specialFacts','Factos exactos da categoria62(4) identificados no suporte — não apenas actividade semelhante',yn)}<label className="block">Suporte ou pressuposto específico da classificação<input value={r.ordinary?.reference??''} onChange={e=>change(r.id,{ordinary:{...(r.ordinary??emptyIrpcOrdinaryFacts()),reference:e.target.value}})}/></label></fieldset>}
|
||||
<h5>Eventos aplicáveis — artigo67(5), versão2026</h5>{r.events.map((e,n)=><div key={n} className="grid gap-2 md:grid-cols-3"><label>Evento<select value={e.kind} onChange={ev=>change(r.id,{events:r.events.map((v,j)=>j===n?{...v,kind:ev.target.value as IrpcEvent['kind']}:v)})}>{eventLabels.map(([k,l])=><option key={k} value={k}>{l}</option>)}</select></label><label>Data<input type="date" value={e.date} onChange={ev=>change(r.id,{events:r.events.map((v,j)=>j===n?{...v,date:ev.target.value}:v)})}/></label><label>Suporte / pressuposto<input value={e.reference} onChange={ev=>change(r.id,{events:r.events.map((v,j)=>j===n?{...v,reference:ev.target.value}:v)})}/></label></div>)}<button type="button" onClick={()=>change(r.id,{events:[...r.events,{kind:'cost',date:'',reference:''}]})}>Acrescentar evento aplicável</button>{select(r,'eventsComplete','Todos os eventos aplicáveis identificados, incluindo custo quando aplicável',yn)}
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
import type {IrpcRow} from './irpc-withholdings.ts';
|
||||
|
||||
export const irpcReliefVersion = 'IRPC-L5-reliefs/1';
|
||||
|
||||
export type IrpcReliefPreparation = {
|
||||
rule?: '' | 'cirpc68d' | 'cirpc68f' | 'ptMz12';
|
||||
facts?: Partial<Record<'subjectToIrpc' | 'notTransparent' | 'treatyResident' | 'beneficialOwner' | 'payerResidentMz' | 'armLength' | 'royaltyDefinition', '' | 'yes' | 'no'>>;
|
||||
kind: 'dispensation' | 'exemption' | 'treaty';
|
||||
instrument: string;
|
||||
article: string;
|
||||
country: string;
|
||||
residenceDocument: string;
|
||||
validFrom: string;
|
||||
validTo: string;
|
||||
conditions: string;
|
||||
evidence: string;
|
||||
};
|
||||
|
||||
export const irpcReliefRules = [
|
||||
['', 'Correspondência por identificar'],
|
||||
['cirpc68d', 'Dispensa — artigo 68(d), serviços / uso de equipamento'],
|
||||
['cirpc68f', 'Dispensa — artigo 68(f), gestão de imóveis próprios'],
|
||||
['ptMz12', 'Portugal–Moçambique — royalties, artigo 12'],
|
||||
] as const;
|
||||
|
||||
const documentLabels: Record<string,string> = {instrument:'diploma/convenção',article:'artigo',validFrom:'início do período',validTo:'fim do período',conditions:'condições',evidence:'documentos',country:'Estado contratante',residenceDocument:'prova de residência'};
|
||||
const factLabels: Record<string,string> = {subjectToIrpc:'Titular sujeito a IRPC neste rendimento',notTransparent:'Fora da transparência fiscal',treatyResident:'Residência convencional em Portugal',beneficialOwner:'Beneficiário efectivo',payerResidentMz:'Devedor residente em Moçambique',armLength:'Sem excesso por relações especiais',royaltyDefinition:'Definição convencional de royalties'};
|
||||
|
||||
export function evaluateIrpcRelief(row: IrpcRow, eventDate: string):
|
||||
{applied: false; reason: string} | {applied: true; treatment: 'dispensed' | 'treaty'; rate: number; reason: string; rule: string} {
|
||||
const e = row.reliefPreparation;
|
||||
if (!e) return {applied: false, reason: 'Identifique a regra da dispensa/convenção e os documentos aplicáveis.'};
|
||||
const preparation = prepareIrpcRelief(row, e);
|
||||
if (preparation.missing.length || !preparation.validPeriod || eventDate < e.validFrom || eventDate > e.validTo)
|
||||
return {applied: false, reason: 'Informação insuficiente ou período incompatível na dispensa/convenção: ' + preparation.missing.map(k=>documentLabels[k]).join(', ') + '. Confira o período e a prova aplicáveis ao primeiro evento.'};
|
||||
const f = e.facts ?? {};
|
||||
const article = e.article.toLowerCase().replace(/[^a-z0-9]/g, '');
|
||||
if (e.rule === 'cirpc68d' && article !== '68d' || e.rule === 'cirpc68f' && article !== '68f' || e.rule === 'ptMz12' && article !== '12')
|
||||
return {applied: false, reason: 'O artigo invocado diverge da regra seleccionada; reconcilie a referência sem apagar a prova.'};
|
||||
const onAccount = row.residence === 'resident' || row.residence === 'nonresident' && row.permanentEstablishment === 'yes' && row.attributable === 'yes';
|
||||
if (e.rule === 'cirpc68d' || e.rule === 'cirpc68f') {
|
||||
if (e.kind !== 'dispensation' || f.subjectToIrpc !== 'yes' || row.recipientIrpc !== 'yes' || !row.ordinary || row.ordinary.nonDigital !== 'yes' || !row.ordinary.reference.trim())
|
||||
return {applied: false, reason: 'Dispensa: identifique o titular sujeito a IRPC relativamente a este rendimento e a classificação não digital, com suporte.'};
|
||||
if (e.rule === 'cirpc68d' && (!onAccount || !['equipment', 'services'].includes(row.kind)))
|
||||
return {applied: false, reason: 'Artigo 68(d) exige rendimento 67(1)b/g com natureza de imposto por conta; não dispensa retenções definitivas.'};
|
||||
if (e.rule === 'cirpc68f' && (row.kind !== 'rent' || row.ordinary.rentOwnPropertyManagement !== 'yes' || f.notTransparent !== 'yes'))
|
||||
return {applied: false, reason: 'Artigo 68(f): faltam rendas de sociedade de gestão de imóveis próprios e exclusão documentada da transparência fiscal (6(1)c).'};
|
||||
return {applied: true, treatment: 'dispensed', rate: 0, rule: e.rule,
|
||||
reason: `Dispensa de retenção ${e.rule === 'cirpc68d' ? '68(d), rendimentos 67(1)b/g' : '68(f), rendimentos prediais 67(1)c e 6(1)c'}; CIRPC Lei 34/2007, PDF19/p768(73). Sem retenção nesta operação; não é isenção do rendimento nem crédito anual.`};
|
||||
}
|
||||
if (e.rule === 'ptMz12') {
|
||||
if (e.kind !== 'treaty' || !['PT', 'PORTUGAL'].includes(e.country.trim().toUpperCase()) || row.residence !== 'nonresident' || row.permanentEstablishment !== 'no' || !['royalty', 'equipment'].includes(row.kind) || row.ordinary?.nonDigital !== 'yes' || !row.ordinary.reference.trim())
|
||||
return {applied: false, reason: 'Convenção PT–MZ, artigo 12: este recorte exige titular residente em Portugal, sem EP em Moçambique e royalties/uso de equipamento qualificados não digitais. Outros casos conservam correspondência própria.'};
|
||||
if (['treatyResident', 'beneficialOwner', 'payerResidentMz', 'armLength', 'royaltyDefinition'].some(k => f[k as keyof typeof f] !== 'yes'))
|
||||
return {applied: false, reason: 'Informação insuficiente: documente residência convencional (art.4), beneficiário efectivo, devedor residente em Moçambique, definição de royalties e ausência de excesso por relações especiais (art.12(2–6)).'};
|
||||
return {applied: true, treatment: 'treaty', rate: 10, rule: e.rule,
|
||||
reason: 'Limite convencional de 10% sobre royalties brutas: CDT PT–MZ artigo12(2–6), RAR36/1992; definição12(3) e IRPC actualizados pelo Protocolo/RAR36/2009 arts1/7. Cálculo da retenção; formalidades de accionamento e MRFTA não equivalem a submissão ou aceitação AT.'};
|
||||
}
|
||||
return {applied: false, reason: 'Dispensa, isenção ou convenção sem correspondência executável neste recorte; conserve os documentos e identifique a regra específica.'};
|
||||
}
|
||||
|
||||
export const newIrpcReliefPreparation = (): IrpcReliefPreparation => ({
|
||||
kind: 'dispensation', instrument: '', article: '', country: '',
|
||||
residenceDocument: '', validFrom: '', validTo: '', conditions: '', evidence: '',
|
||||
});
|
||||
|
||||
// A preparation records an invocation; it does not activate a tax rule.
|
||||
export function prepareIrpcRelief(row: IrpcRow, evidence: IrpcReliefPreparation) {
|
||||
const missing = (['instrument', 'article', 'validFrom', 'validTo', 'conditions', 'evidence'] as const)
|
||||
.filter(key => !evidence[key].trim()).map(String);
|
||||
if (evidence.kind === 'treaty') {
|
||||
if (!evidence.country.trim()) missing.push('country');
|
||||
if (!evidence.residenceDocument.trim()) missing.push('residenceDocument');
|
||||
}
|
||||
const validDate = (s: string) => /^\d{4}-\d{2}-\d{2}$/.test(s) &&
|
||||
Number.isFinite(Date.parse(s)) && new Date(s).toISOString().slice(0, 10) === s;
|
||||
const validPeriod = validDate(evidence.validFrom) && validDate(evidence.validTo) && evidence.validFrom <= evidence.validTo;
|
||||
const outsidePeriod = validPeriod && row.events.some(event => validDate(event.date) &&
|
||||
(event.date < evidence.validFrom || event.date > evidence.validTo));
|
||||
return {
|
||||
counterparty: row.counterparty, residence: row.residence,
|
||||
permanentEstablishment: row.permanentEstablishment, attributable: row.attributable,
|
||||
documentRef: row.documentRef, qualificationRef: row.qualificationRef,
|
||||
previousReference: row.reliefRef, evidence: structuredClone(evidence),
|
||||
missing, validPeriod, outsidePeriod,
|
||||
status: 'preparation' as const,
|
||||
notice: 'Condições e documentos invocados; o cálculo verifica apenas a regra seleccionada e o período aplicável.',
|
||||
};
|
||||
}
|
||||
|
||||
export function irpcReliefEvidenceLabel(row: IrpcRow): string {
|
||||
const e = row.reliefPreparation;
|
||||
if (!e) return row.reliefRef || 'Sem benefício invocado';
|
||||
const p = prepareIrpcRelief(row, e);
|
||||
return `${irpcReliefRules.find(([rule])=>rule===e.rule)?.[1]??'Regra por corresponder'};${e.instrument};artigo:${e.article};período:${e.validFrom}–${e.validTo};Estado:${e.country};residência fiscal:${e.residenceDocument};condições:${e.conditions};provas:${e.evidence};${Object.entries(e.facts??{}).map(([k,v])=>`${factLabels[k]??k}:${v==='yes'?'Sim':v==='no'?'Não':'Por esclarecer'}`).join(';')};${p.missing.length?'Falta: '+p.missing.map(k=>documentLabels[k]).join(', '):'Elementos documentais registados'};${p.validPeriod?'Período válido':'Período por resolver'};referência anterior:${row.reliefRef}`;
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import {irpcReliefVersion,evaluateIrpcRelief,irpcReliefEvidenceLabel,type IrpcReliefPreparation} from './irpc-relief-preparation.ts';
|
||||
import {convertIrpcAmount,irpcFxDependency,irpcFxEvidenceLabel,type IrpcFxAmount,type IrpcFxEvidence} from './irpc-fx.ts';
|
||||
import {acquisitionBook,acquisitionScope} from './iva-acquisitions.ts';
|
||||
import {salesBook} from './iva-operations.ts';
|
||||
@@ -13,8 +14,8 @@ export type IrpcEvent={kind:'cost'|'payment'|'maturity'|'availability'|'liquidat
|
||||
export type IrpcPayment=IrpcFxAmount&{id:string;kind:'beneficiary'|'remittance'|'withholding_proof';reference:string;date:string;amount:string;currency:string};
|
||||
export type IrpcOrdinaryFacts={nonDigital:Choice;capitalType:''|'unlisted_interest'|'unlisted_dividend'|'listed_debt_mz'|'listed_other_mz'|'liquidity_repo'|'other';rentOwnPropertyManagement:Choice;specialFacts:Choice;capitalSeatAndManagement?:Choice;reference:string};
|
||||
export const emptyIrpcOrdinaryFacts=():IrpcOrdinaryFacts=>({nonDigital:'',capitalType:'',rentOwnPropertyManagement:'',specialFacts:'',capitalSeatAndManagement:'',reference:''});
|
||||
export type IrpcRow=IrpcFxAmount&{ordinary?:IrpcOrdinaryFacts;id:string;direction:'paid'|'received';counterparty:string;documentRef:string;description:string;base:string;currency:string;origin:'projected'|'effective';kind:IrpcKind;recipientIrpc:Choice;payerIrpc:Choice;residence:''|'resident'|'nonresident';permanentEstablishment:Choice;attributable:Choice;mozambique:Choice;qualificationRef:string;specialRelief:''|'none'|'claimed';reliefRef:string;eventsComplete:Choice;events:IrpcEvent[];settlement:''|'none'|'complete';settlementRef:string;allocations:Array<{paymentId:string;amount:string}>;source?:{id:string;key:string;fingerprint:string};};
|
||||
export type IrpcResult={id:string;status:'calculated'|'indeterminate'|'outside_scope';reason:string;period:string;eventDate:string;nature:'definitive'|'on_account'|null;rate:number|null;base:FiscalFraction|null;tax:FiscalFraction|null;settled:FiscalFraction|null;outstanding:FiscalFraction|null;direction:IrpcRow['direction'];conversion?:IrpcFxEvidence|null};
|
||||
export type IrpcRow=IrpcFxAmount&{reliefPreparation?:IrpcReliefPreparation;ordinary?:IrpcOrdinaryFacts;id:string;direction:'paid'|'received';counterparty:string;documentRef:string;description:string;base:string;currency:string;origin:'projected'|'effective';kind:IrpcKind;recipientIrpc:Choice;payerIrpc:Choice;residence:''|'resident'|'nonresident';permanentEstablishment:Choice;attributable:Choice;mozambique:Choice;qualificationRef:string;specialRelief:''|'none'|'claimed';reliefRef:string;eventsComplete:Choice;events:IrpcEvent[];settlement:''|'none'|'complete';settlementRef:string;allocations:Array<{paymentId:string;amount:string}>;source?:{id:string;key:string;fingerprint:string};};
|
||||
export type IrpcResult={treatment?:'dispensed'|'treaty';reliefRule?:string;id:string;status:'calculated'|'indeterminate'|'outside_scope';reason:string;period:string;eventDate:string;nature:'definitive'|'on_account'|null;rate:number|null;base:FiscalFraction|null;tax:FiscalFraction|null;settled:FiscalFraction|null;outstanding:FiscalFraction|null;direction:IrpcRow['direction'];conversion?:IrpcFxEvidence|null};
|
||||
export type IrpcBook={version:1;rows:IrpcRow[];payments:IrpcPayment[];result?:{version:string;key:string;calculatedAt:string;inputs:IrpcRow[];payments:IrpcPayment[];rows:IrpcResult[];paymentConversions?:Record<string,IrpcFxEvidence|null>};history?:Array<NonNullable<IrpcBook['result']>>};
|
||||
export const newIrpcRow=(id:string):IrpcRow=>({id,direction:'paid',counterparty:'',documentRef:'',description:'',base:'',currency:'MZN',origin:'projected',kind:'',recipientIrpc:'',payerIrpc:'',residence:'',permanentEstablishment:'',attributable:'',mozambique:'',qualificationRef:'',specialRelief:'',reliefRef:'',eventsComplete:'',events:[],settlement:'',settlementRef:'',allocations:[]});
|
||||
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 preservados.');return s;}
|
||||
@@ -22,6 +23,8 @@ function map(d:Record<string,unknown>):Record<string,IrpcBook>{const m=store(d).
|
||||
function validate(b:IrpcBook){
|
||||
if(!b||b.version!==1||!Array.isArray(b.rows)||!Array.isArray(b.payments)||b.history!==undefined&&!Array.isArray(b.history))throw Error('Livro IRPC não recuperável.');
|
||||
for(const r of b.rows){const template=newIrpcRow('');if(!r||Object.keys(template).filter(k=>typeof template[k as keyof IrpcRow]==='string').some(k=>typeof r[k as keyof IrpcRow]!=='string')||!['paid','received'].includes(r.direction)||!['projected','effective'].includes(r.origin)||!irpcKinds.some(([k])=>k===r.kind)||!['','resident','nonresident'].includes(r.residence)||!['','none','claimed'].includes(r.specialRelief)||!['','none','complete'].includes(r.settlement)||['recipientIrpc','payerIrpc','permanentEstablishment','attributable','mozambique','eventsComplete'].some(k=>!['','yes','no'].includes(r[k as keyof IrpcRow]as string))||!Array.isArray(r.events)||!Array.isArray(r.allocations)||r.events.some(e=>!e||!['cost','payment','maturity','availability','liquidation','quantification'].includes(e.kind)||typeof e.date!=='string'||typeof e.reference!=='string')||r.allocations.some(a=>!a||typeof a.paymentId!=='string'||typeof a.amount!=='string')||r.source!==undefined&&(!r.source||!['id','key','fingerprint'].every(k=>typeof r.source![k as keyof NonNullable<IrpcRow['source']>]==='string')))throw Error('Entradas IRPC não recuperáveis;sem apagar ou repor vazios.');}
|
||||
for(const r of b.rows)if(r.reliefPreparation&&(r.reliefPreparation.rule!==undefined&&!['','cirpc68d','cirpc68f','ptMz12'].includes(r.reliefPreparation.rule)||r.reliefPreparation.facts!==undefined&&(!r.reliefPreparation.facts||typeof r.reliefPreparation.facts!=='object'||Array.isArray(r.reliefPreparation.facts)||Object.values(r.reliefPreparation.facts).some(v=>!['','yes','no'].includes(v)))))throw Error('Condições da dispensa/convenção não recuperáveis.');
|
||||
for(const r of b.rows)if(r.reliefPreparation&&(!['dispensation','exemption','treaty'].includes(r.reliefPreparation.kind)||['instrument','article','country','residenceDocument','validFrom','validTo','conditions','evidence'].some(k=>typeof r.reliefPreparation![k as keyof IrpcReliefPreparation]!=='string')))throw Error('Preparação de dispensa/convenção não recuperável;original preservado.');
|
||||
for(const r of b.rows)if(r.ordinary&&(['nonDigital','rentOwnPropertyManagement','specialFacts'].some(k=>!['','yes','no'].includes(r.ordinary![k as 'nonDigital'|'rentOwnPropertyManagement'|'specialFacts']))||typeof r.ordinary.reference!=='string'||!['','unlisted_interest','unlisted_dividend','listed_debt_mz','listed_other_mz','liquidity_repo','other'].includes(r.ordinary.capitalType)))throw Error('Factos específicos IRPC não recuperáveis.');
|
||||
for(const r of b.rows)if(r.ordinary?.capitalSeatAndManagement!==undefined&&!['','yes','no'].includes(r.ordinary.capitalSeatAndManagement))throw Error('Factos de sede/direcção não recuperáveis.');
|
||||
for(const v of [...b.rows,...b.payments])if(v.fxSelection!==undefined&&(!v.fxSelection||typeof v.fxSelection!=='object'||Array.isArray(v.fxSelection))||v.fxLink!==undefined&&(!v.fxLink||typeof v.fxLink.id!=='string'||typeof v.fxLink.fingerprint!=='string'))throw Error('Vínculo/selecção cambial não recuperável;original preservado.');
|
||||
@@ -46,11 +49,16 @@ export function evaluateIrpc(d:Record<string,unknown>,r:IrpcRow):IrpcResult{
|
||||
if(r.recipientIrpc==='no')return{...v,status:'outside_scope',reason:'Titular fora de IRPC;sem converter em IRPS ou em isenção.'};
|
||||
if(!r.counterparty.trim()||!r.documentRef.trim()||r.recipientIrpc!=='yes'||r.payerIrpc!=='yes'||!r.residence||!r.kind||!r.qualificationRef.trim())return{...v,reason:'Faltam contraparte,documento,qualificação do rendimento,titular IRPC,devedor IRPC ou residência. A via devedor IRPS exige âmbito próprio.'};
|
||||
if(r.mozambique!=='yes')return{...v,reason:'Falta correspondência territorial aos arts5/67;ser estrangeiro não resolve incidência nem isenção.'};
|
||||
if(r.specialRelief!=='none')return{...v,reason:'Convenção,isenção ou dispensa invocada/desconhecida:identificar artigo,período,condições e prova;sem taxa reduzida ou zero automáticos.'};
|
||||
if(r.specialRelief!=='none'&&r.specialRelief!=='claimed')return{...v,reason:'Convenção,isenção ou dispensa invocada/desconhecida:identificar artigo,período,condições e prova;sem taxa reduzida ou zero automáticos.'};
|
||||
if(!r.eventsComplete||r.eventsComplete!=='yes'||!r.events.length||r.events.some(e=>!date(e.date)||!e.reference.trim()))return{...v,reason:'Identifique os eventos aplicáveis67(5),incluindo reconhecimento do custo,e confirme a sua completude com suporte;uma factura ou pagamento isolado não fixa o momento.'};
|
||||
v.eventDate=r.events.map(e=>e.date).sort()[0];v.period=v.eventDate.slice(0,7);
|
||||
if(!v.eventDate.startsWith('2026-'))return{...v,reason:'Primeiro evento aplicável fora de2026;corresponder versão/período próprio,sem deslocar para o pagamento posterior.'};
|
||||
try{const converted=convertIrpcAmount(d,r.base,r);v.base=converted.value;v.conversion=converted.evidence;}catch(e){return{...v,reason:'Base do rendimento/conversão por resolver: '+String(e)};}
|
||||
if(r.specialRelief==='claimed'){
|
||||
const relief=evaluateIrpcRelief(r,v.eventDate);if(!relief.applied)return{...v,reason:relief.reason};
|
||||
v.treatment=relief.treatment;v.reliefRule=relief.rule;v.rate=relief.rate;v.nature=relief.treatment==='treaty'?'definitive':'on_account';v.reason=relief.reason;
|
||||
v.tax=fraction(BigInt(v.base.numerator)*BigInt(relief.rate),BigInt(v.base.denominator)*100n);v.status='calculated';return v;
|
||||
}
|
||||
const digital=['digital_goods','digital_services','emoney_commission'].includes(r.kind);
|
||||
if(digital){
|
||||
if(r.residence==='nonresident'&&r.permanentEstablishment==='no'||r.residence==='nonresident'&&r.permanentEstablishment==='yes'&&r.attributable==='no')v.nature='definitive';
|
||||
@@ -84,13 +92,14 @@ export function evaluateIrpc(d:Record<string,unknown>,r:IrpcRow):IrpcResult{
|
||||
v.tax=fraction(BigInt(v.base.numerator)*BigInt(v.rate!),BigInt(v.base.denominator)*100n);v.status='calculated';v.reason+=' Base separada do IVA;entrega/prova e liquidação anual mantêm reconciliação própria.';return v;
|
||||
}
|
||||
function sharedPayments(d:Record<string,unknown>,b:IrpcBook){const used=b.payments.filter(p=>b.rows.some(r=>r.allocations.some(a=>a.paymentId===p.id)));return Object.entries(map(d)).filter(([ctx])=>ctx!==acquisitionScope(d)).flatMap(([ctx,other])=>{validate(other);const payments=other.payments.filter(q=>used.some(p=>p.reference.trim()===q.reference.trim()&&p.date===q.date&&p.currency===q.currency&&p.kind===q.kind));return payments.length?[[ctx,payments,other.rows.flatMap(r=>r.allocations.filter(a=>payments.some(p=>p.id===a.paymentId)))]]:[];});}
|
||||
function key(d:Record<string,unknown>,b:IrpcBook){const fx=[...b.rows,...b.payments].filter(r=>r.currency!=='MZN'&&r.fxLink).map(r=>irpcFxDependency(d,r));return JSON.stringify([irpcWithholdingVersion,acquisitionScope(d),b.rows,b.payments,b.rows.map(r=>sourceIssue(d,r)),sharedPayments(d,b),...(fx.length?[fx]:[])]);}
|
||||
function key(d:Record<string,unknown>,b:IrpcBook){const fx=[...b.rows,...b.payments].filter(r=>r.currency!=='MZN'&&r.fxLink).map(r=>irpcFxDependency(d,r));return JSON.stringify([irpcWithholdingVersion,acquisitionScope(d),b.rows,b.payments,b.rows.map(r=>sourceIssue(d,r)),sharedPayments(d,b),...(fx.length?[fx]:[]),...(b.rows.some(r=>r.specialRelief==='claimed')?[irpcReliefVersion]:[])]);}
|
||||
export function irpcCurrent(d:Record<string,unknown>,b:IrpcBook){return !!b.result&&b.result.key===key(d,b);}
|
||||
export function recordIrpc(d:Record<string,unknown>,b:IrpcBook,now=new Date().toISOString()):IrpcBook{
|
||||
validate(b);const rows=b.rows.map(r=>evaluateIrpc(d,r));const paymentConversions:Record<string,IrpcFxEvidence|null>={};const allocations=new Map<string,FiscalFraction[]>();
|
||||
const fail=(i:number,reason:string)=>{rows[i]={...rows[i],status:'indeterminate',reason,settled:null,outstanding:null};};
|
||||
b.rows.forEach((r,i)=>{
|
||||
if(rows[i].status!=='calculated')return;
|
||||
if(rows[i].treatment==='dispensed'){if(r.allocations.length){fail(i,'Dispensa com entrega/prova associada: reconcilie sem apagar o pagamento nem gerar crédito automático.');return;}rows[i].settled=fraction(0n,1n);rows[i].outstanding=fraction(0n,1n);return;}
|
||||
if(r.settlement==='none'&&r.allocations.length){fail(i,'Ausência de entrega/prova contradiz pagamentos associados;conserve e reconcilie.');return;}
|
||||
if(!r.settlement||!r.settlementRef.trim()){rows[i].reason+=' Entrega à AT/retenção sofrida ainda não reconciliada;não presumir zero pago.';return;}
|
||||
const parts:FiscalFraction[]=[];const seen=new Set<string>();
|
||||
@@ -107,5 +116,5 @@ export function recordIrpc(d:Record<string,unknown>,b:IrpcBook,now=new Date().to
|
||||
return{...b,history:b.result?[...(b.history??[]),structuredClone(b.result)]:b.history,result:{version:irpcWithholdingVersion,key:key(d,b),calculatedAt:now,inputs:structuredClone(b.rows),payments:structuredClone(b.payments),rows,paymentConversions}};
|
||||
}
|
||||
const amount=(v:FiscalFraction|null)=>{if(!v)return'Indeterminado';const p=fractionPresentation(v);return(p.approximate?'≈':'')+p.decimal+' MZN';};
|
||||
export function irpcReportRows(d:Record<string,unknown>):Array<[string,string,string,string]>{try{const b=irpcBook(d),r=b.result;if(!r)return[];return r.rows.map(v=>{const i=r.inputs.find(i=>i.id===v.id)!;return['IRPC — retenções',`${i.documentRef} — ${i.counterparty}`,`${({calculated:'Calculado',indeterminate:'Indeterminado',outside_scope:'Fora deste âmbito'})[v.status]};enquadramento: ${irpcKinds.find(([k])=>k===i.kind)?.[1]??i.kind};${i.direction==='paid'?'retida a entregar':'sofrida'};base:${amount(v.base)};taxa:${v.rate??'por corresponder'}%;retenção:${amount(v.tax)};entrega/prova:${amount(v.settled)};saldo:${amount(v.outstanding)};${v.reason}`,`${acquisitionScope(d)};${i.origin};${v.period};evento:${v.eventDate};${v.nature==='on_account'?'Imposto por conta (sem crédito anual automático)':v.nature==='definitive'?'Retenção definitiva':'Natureza por determinar'};${irpcCurrent(d,b)?'Entradas correspondentes':'Histórico;entradas/origem divergentes'};reconciliação:${i.settlementRef};comprovativos:${i.allocations.map(a=>{const p=r.payments.find(p=>p.id===a.paymentId);return p?`${p.reference}/${p.date}:parcela${a.amount}MZN de${p.amount}${p.currency};${r.paymentConversions?.[p.id]?irpcFxEvidenceLabel(r.paymentConversions[p.id]!):'sem conversão usada'}`:'referência por resolver';}).join(' | ')};${r.calculatedAt};${v.conversion?'Câmbio:'+irpcFxEvidenceLabel(v.conversion)+';':''}${irpcWithholdingSource}`];});}catch(e){return[['IRPC — retenções','Livro não recuperável',String(e),'Originais e histórico preservados.']];}}
|
||||
export function irpcReportRows(d:Record<string,unknown>):Array<[string,string,string,string]>{try{const b=irpcBook(d),r=b.result;if(!r)return[];return r.rows.map(v=>{const i=r.inputs.find(i=>i.id===v.id)!;return['IRPC — retenções',`${i.documentRef} — ${i.counterparty}`,`${({calculated:'Calculado',indeterminate:'Indeterminado',outside_scope:'Fora deste âmbito'})[v.status]};tratamento:${v.treatment==='dispensed'?'Dispensa de retenção':v.treatment==='treaty'?'Limite convencional':'Retenção interna'};enquadramento: ${irpcKinds.find(([k])=>k===i.kind)?.[1]??i.kind};${i.direction==='paid'?'retida a entregar':'sofrida'};base:${amount(v.base)};taxa:${v.treatment==='dispensed'?'Dispensa (sem retenção)':(v.rate??'por corresponder')+'%'};retenção:${amount(v.tax)};entrega/prova:${amount(v.settled)};saldo:${amount(v.outstanding)};${v.reason}`,`${acquisitionScope(d)};${i.origin};${v.period};evento:${v.eventDate};${v.nature==='on_account'?'Imposto por conta (sem crédito anual automático)':v.nature==='definitive'?'Retenção definitiva':'Natureza por determinar'};${irpcCurrent(d,b)?'Entradas correspondentes':'Histórico;entradas/origem divergentes'};dispensa/convenção:${irpcReliefEvidenceLabel(i)};reconciliação:${i.settlementRef};comprovativos:${i.allocations.map(a=>{const p=r.payments.find(p=>p.id===a.paymentId);return p?`${p.reference}/${p.date}:parcela${a.amount}MZN de${p.amount}${p.currency};${r.paymentConversions?.[p.id]?irpcFxEvidenceLabel(r.paymentConversions[p.id]!):'sem conversão usada'}`:'referência por resolver';}).join(' | ')};${r.calculatedAt};${v.conversion?'Câmbio:'+irpcFxEvidenceLabel(v.conversion)+';':''}${irpcWithholdingSource}`];});}catch(e){return[['IRPC — retenções','Livro não recuperável',String(e),'Originais e histórico preservados.']];}}
|
||||
export function irpcPreparationHtml(d:Record<string,unknown>):string{const b=irpcBook(d);if(!b.result||!irpcCurrent(d,b))throw Error('Calcule e guarde as entradas actuais antes de preparar.');const esc=(v:unknown)=>String(v).replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]!));return'<!doctype html><html lang="pt"><meta charset="utf-8"><title>IRPC — preparação das retenções</title><body><h1>Preparação IRPC — L5</h1><p>Mapa de trabalho;sem submissão,recepção ou aceitação AT. MRFTA19/39:quadro1 retenção;quadro2 mês/ano único por declaração;quadro7 base/taxa/imposto. CER e correspondência das rubricas digitais/moeda electrónica por esclarecer;67(7)remete62(6),mas taxa nova em62(5). Não preencher assinatura ou uso dos serviços. Não consolidar períodos diferentes nem transformar retenções sofridas em imposto a entregar.</p>'+irpcReportRows(d).map(r=>'<section><h2>'+esc(r[1])+'</h2><p>'+esc(r[2])+'</p><small>'+esc(r[3])+'</small></section>').join('')+'<p>Instruções do exemplar:prazo nominal dia20 do mês seguinte à dedução,sem ajuste de calendário/prorrogação ou procedimento2026 presumidos. Juros não calculados. GuiaM/39 anterior preservada,sem actualizar remissões do original.</p></body></html>';}
|
||||
|
||||
@@ -176,6 +176,7 @@ const rowSection: Record<string, DossierReportSectionId> = {
|
||||
Tesouraria: 'finance',
|
||||
'IVA mensal': 'finance',
|
||||
ISPC: 'finance',
|
||||
'IRPC — retenções': 'finance',
|
||||
Equipa: 'team',
|
||||
'Necessidade de negócio': 'resources',
|
||||
Financiamento: 'resources',
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
<?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 IrpcReliefsDraftTest extends TestCase
|
||||
{
|
||||
use DatabaseTransactions;
|
||||
|
||||
public function test_demonstrated_reliefs_and_insufficient_information_recover_through_private_api(): 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()]));
|
||||
$draft = json_decode(file_get_contents(base_path('outputs/irpc-reliefs/calculated.json')), true, flags: JSON_THROW_ON_ERROR);
|
||||
$personal = ['person_type'=>$draft['person_type']];
|
||||
unset($draft['person_type']);
|
||||
$id = $this->postJson('/api/v1/simulation-drafts', ['personal_snapshot'=>$personal, 'economic_snapshot'=>$draft], ['Idempotency-Key'=>(string) Str::uuid()])->assertCreated()->json('data.id');
|
||||
$recovered = $this->getJson('/api/v1/simulation-drafts/'.$id)->assertOk()->json('data.economic_snapshot');
|
||||
$this->assertSame($draft['modelo_a_snapshot'], $recovered['modelo_a_snapshot']);
|
||||
$store = json_decode($recovered['modelo_a_snapshot'], true, flags: JSON_THROW_ON_ERROR);
|
||||
$book = $store['irpcWithholdings'][$draft['context_opportunity']];
|
||||
[$dispensed, $rent, $treaty, $missing] = array_slice($book['result']['rows'], -4);
|
||||
$this->assertSame('dispensed', $dispensed['treatment']);
|
||||
$this->assertSame(['numerator'=>'0','denominator'=>'1'], $dispensed['tax']);
|
||||
$this->assertSame('dispensed', $rent['treatment']);
|
||||
$this->assertSame('cirpc68f', $rent['reliefRule']);
|
||||
$this->assertSame(['numerator'=>'0','denominator'=>'1'], $rent['tax']);
|
||||
$this->assertSame('treaty', $treaty['treatment']);
|
||||
$this->assertSame(10, $treaty['rate']);
|
||||
$this->assertSame(['numerator'=>'10000','denominator'=>'1'], $treaty['tax']);
|
||||
$this->assertSame('indeterminate', $missing['status']);
|
||||
$this->assertNull($missing['tax']);
|
||||
$this->assertSame('QA certificado fiscal Portugal 2026', $book['result']['inputs'][count($book['result']['inputs'])-2]['reliefPreparation']['residenceDocument']);
|
||||
$this->assertNotEmpty($book['history']);
|
||||
file_put_contents(base_path('outputs/irpc-reliefs/recovered.json'), json_encode($recovered, JSON_THROW_ON_ERROR|JSON_UNESCAPED_UNICODE));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs';
|
||||
import {newIrpcRow,emptyIrpcOrdinaryFacts,recordIrpc,saveIrpcBook,irpcBook,irpcCurrent,irpcReportRows,irpcPreparationHtml} from '../../resources/js/lib/irpc-withholdings.ts';
|
||||
import {newIrpcReliefPreparation} from '../../resources/js/lib/irpc-relief-preparation.ts';
|
||||
import {createProjectDossierReport,createProjectDossierPrintHtml} from '../../resources/js/lib/project-dossier-report.ts';
|
||||
const draft=()=>JSON.parse(fs.readFileSync(new URL('./fixtures/irpc-grouped-preserved.json',import.meta.url),'utf8'));
|
||||
const row=(id='dispensa')=>({...newIrpcRow(id),counterparty:'QA sociedade',documentRef:'QA-'+id,base:'100000',kind:'services',recipientIrpc:'yes',payerIrpc:'yes',residence:'resident',mozambique:'yes',qualificationRef:'QA contrato e cadastro do titular',specialRelief:'claimed',eventsComplete:'yes',events:[{kind:'cost',date:'2026-10-02',reference:'QA custo'}],ordinary:{...emptyIrpcOrdinaryFacts(),nonDigital:'yes',reference:'QA contrato de serviços'},reliefPreparation:{...newIrpcReliefPreparation(),rule:'cirpc68d',instrument:'CIRPC Lei34/2007',article:'68(d)',validFrom:'2026-01-01',validTo:'2026-12-31',conditions:'QA titular e rendimento sujeitos a IRPC',evidence:'QA cadastro e contrato existentes',facts:{subjectToIrpc:'yes'}}});
|
||||
const treaty=(id='convention')=>({...row(id),kind:'royalty',residence:'nonresident',permanentEstablishment:'no',reliefPreparation:{...row().reliefPreparation,kind:'treaty',rule:'ptMz12',instrument:'CDT PT-MZ RAR36/1992 e Protocolo2009',article:'12',country:'PT',residenceDocument:'QA certificado fiscal Portugal 2026',facts:{treatyResident:'yes',beneficialOwner:'yes',payerResidentMz:'yes',armLength:'yes',royaltyDefinition:'yes'}}});
|
||||
const calc=(r)=>recordIrpc(draft(),{version:1,rows:[r],payments:[]}).result.rows[0];
|
||||
|
||||
test('68d dispenses resident and attributable EP services/equipment without exempting the income',()=>{
|
||||
for(const kind of ['services','equipment'])for(const patch of [{},{residence:'nonresident',permanentEstablishment:'yes',attributable:'yes'}]){
|
||||
const v=calc({...row(),kind,...patch});assert.equal(v.status,'calculated');assert.equal(v.treatment,'dispensed');assert.equal(v.nature,'on_account');assert.equal(v.tax.numerator,'0');assert.equal(v.outstanding.numerator,'0');assert.match(v.reason,/não é isenção/);
|
||||
}
|
||||
});
|
||||
test('68f requires own-property management and absence of fiscal transparency',()=>{
|
||||
const r=row();r.kind='rent';r.ordinary.rentOwnPropertyManagement='yes';r.reliefPreparation.rule='cirpc68f';r.reliefPreparation.article='68(f)';r.reliefPreparation.facts.notTransparent='yes';assert.equal(calc(r).treatment,'dispensed');
|
||||
for(const value of ['', 'no']){r.reliefPreparation.facts.notTransparent=value;assert.equal(calc(r).tax,null);}r.reliefPreparation.facts.notTransparent='yes';r.ordinary.rentOwnPropertyManagement='no';assert.equal(calc(r).tax,null);
|
||||
});
|
||||
test('definitive nonresident equipment is never dispensed under68d',()=>{
|
||||
for(const patch of [{permanentEstablishment:'no'},{permanentEstablishment:'yes',attributable:'no'},{permanentEstablishment:''}])assert.equal(calc({...row(),kind:'equipment',residence:'nonresident',...patch}).tax,null);
|
||||
});
|
||||
test('Portugal royalties and qualified equipment use10% gross definitive limit',()=>{
|
||||
for(const kind of ['royalty','equipment']){const v=calc({...treaty(),kind});assert.equal(v.status,'calculated');assert.equal(v.treatment,'treaty');assert.equal(v.rate,10);assert.equal(v.tax.numerator,'10000');assert.equal(v.nature,'definitive');}
|
||||
});
|
||||
test('missing,negative,expired or contradictory evidence remains indeterminate,never zero',()=>{
|
||||
const samples=[r=>r.reliefPreparation.evidence='',r=>r.reliefPreparation.residenceDocument='',r=>r.reliefPreparation.validTo='2026-09-30',r=>r.reliefPreparation.validFrom='2026-02-30',r=>r.reliefPreparation.article='7',r=>r.reliefPreparation.country='ZA',r=>r.permanentEstablishment='yes',r=>r.kind='digital_services'];
|
||||
for(const fact of ['treatyResident','beneficialOwner','payerResidentMz','armLength','royaltyDefinition'])for(const value of ['', 'no'])samples.push(r=>r.reliefPreparation.facts[fact]=value);
|
||||
for(const mutate of samples){const r=treaty();mutate(r);const v=calc(r);assert.equal(v.status,'indeterminate');assert.equal(v.tax,null);assert.equal(v.rate,null);}
|
||||
});
|
||||
test('independent rows calculate beside insufficient information; save recover report export and history',()=>{
|
||||
const d=draft(),prior=irpcBook(d),insufficient=treaty('missing');insufficient.reliefPreparation.residenceDocument='';
|
||||
const rent=row('property-dispensation');rent.kind='rent';rent.ordinary.rentOwnPropertyManagement='yes';rent.reliefPreparation.rule='cirpc68f';rent.reliefPreparation.article='68(f)';rent.reliefPreparation.facts.notTransparent='yes';
|
||||
const b=recordIrpc(d,{...prior,rows:[...prior.rows,row(),rent,treaty(),insufficient]});d.modelo_a_snapshot=saveIrpcBook(d,b);
|
||||
const recovered=JSON.parse(JSON.stringify(d)),book=irpcBook(recovered);assert.deepEqual(book,b);assert.equal(irpcCurrent(recovered,book),true);assert.deepEqual(book.history.at(-1),prior.result);
|
||||
assert.deepEqual(book.result.rows.slice(-3).map(r=>r.status),['calculated','calculated','indeterminate']);
|
||||
const report=irpcReportRows(recovered).slice(-3);assert.match(report[0][2],/Dispensa de retenção/);assert.match(report[1][2],/Limite convencional/);assert.match(report[2][2],/Indeterminado/);assert.match(report[1][3],/QA certificado fiscal Portugal 2026/);
|
||||
assert.match(irpcPreparationHtml(recovered),/10000/);book.rows.at(-2).reliefPreparation.facts.beneficialOwner='';assert.equal(irpcCurrent(recovered,book),false);assert.equal(recordIrpc(recovered,book).result.rows.at(-2).tax,null);
|
||||
fs.mkdirSync('outputs/irpc-reliefs',{recursive:true});fs.writeFileSync('outputs/irpc-reliefs/calculated.json',JSON.stringify(recovered));fs.writeFileSync('outputs/irpc-reliefs/preparation.html',irpcPreparationHtml(recovered));
|
||||
});
|
||||
test('dispensa with payment attached requires reconciliation and retains the original proof',()=>{
|
||||
const r=row();r.allocations=[{paymentId:'p',amount:'100'}];const p={id:'p',kind:'remittance',reference:'QA prova',date:'2026-10-20',amount:'100',currency:'MZN'};const b=recordIrpc(draft(),{version:1,rows:[r],payments:[p]});assert.equal(b.result.rows[0].status,'indeterminate');assert.deepEqual(b.payments,[p]);
|
||||
});
|
||||
test('unsupported relief and malformed persisted conditions are not accepted as a benefit',()=>{
|
||||
const r=row();r.reliefPreparation.rule='';assert.equal(calc(r).tax,null);r.reliefPreparation.facts.subjectToIrpc='no';assert.equal(calc(r).tax,null);r.reliefPreparation.facts.subjectToIrpc=42;assert.throws(()=>saveIrpcBook(draft(),{version:1,rows:[r],payments:[]}));
|
||||
});
|
||||
test('financial dossier includes each IRPC result, evidence and insufficient information',async()=>{
|
||||
const d=draft(),missing=treaty('missing');missing.reliefPreparation.residenceDocument='';d.modelo_a_snapshot=saveIrpcBook(d,recordIrpc(d,{version:1,rows:[row(),treaty(),missing],payments:[]}));
|
||||
const report=createProjectDossierReport(d,{selectedSections:new Set(['finance'])}),text=JSON.stringify(report);
|
||||
for(const label of ['Dispensa de retenção','Limite convencional','Indeterminado','QA certificado fiscal Portugal 2026'])assert.ok(text.includes(label),label);
|
||||
const html=await createProjectDossierPrintHtml(report);assert.match(html,/Dispensa de retenção/);assert.match(html,/prova de residência/);
|
||||
});
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
$root=dirname(__DIR__);
|
||||
$operation=$argv[1]??'';
|
||||
if(!in_array($operation,['routes','test'],true)||basename($root)!=='portal-investidor-irpc-reliefs-qa-20261025')throw new RuntimeException('Explicit reliefs QA workspace and routes/test operation required.');
|
||||
foreach(['storage/framework/views','storage/framework/cache','storage/framework/sessions','bootstrap/cache','storage/logs','outputs/irpc-reliefs']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/IrpcReliefsDraftTest.php'];$argc=count($argv);$_SERVER['argv']=$argv;$_SERVER['argc']=$argc;require $root.'/vendor/phpunit/phpunit/phpunit';
|
||||
Reference in New Issue
Block a user