feat: demonstrate scoped IVA operations L1 in QA
This commit is contained in:
@@ -2726,5 +2726,471 @@
|
||||
"IRPC_IRPS": "deferred, inventory preserved",
|
||||
"statistics_AI": "deferred",
|
||||
"protected_installer_or_database_audit": "not needed or performed; public served revision and delta verified; no synthetic private writes in homologation"
|
||||
},
|
||||
"fiscal_inventory_plan_20261024": {
|
||||
"status": "analysis_complete_plan_proposed_not_implemented",
|
||||
"tempo_decision": "Concluir simulador e inventario fiscal acordado antes de Imobiliario; preservar lotes fechados.",
|
||||
"single_plan": "docs/investmoz/unification/traceability-and-manual.md#24-10-2026--inventario-fiscal-reconciliado-e-proposta-de-fecho-antes-do-imobiliario",
|
||||
"matrix_id": "AUD-SIM-INT02",
|
||||
"source_inventory": [
|
||||
{
|
||||
"folder": "Modelos",
|
||||
"name": "Cópia de IRPS-IMOP.xlsx",
|
||||
"bytes": 277846,
|
||||
"sha256": "1ceca6f4c6d77853c1b2b445876599b42093175fa89eecf40590180956d83a9e",
|
||||
"temporary": false
|
||||
},
|
||||
{
|
||||
"folder": "Modelos",
|
||||
"name": "Escalões e Taxas do IRPS.docx",
|
||||
"bytes": 16692,
|
||||
"sha256": "ea9fdfaf8a8682645e0e3dad3e2e165520783e9f72e8d23acdf684d8359d690e",
|
||||
"temporary": false
|
||||
},
|
||||
{
|
||||
"folder": "Modelos",
|
||||
"name": "Impostos e seu tratamento.docx",
|
||||
"bytes": 64899,
|
||||
"sha256": "aade59537c6c19eb56b017cfc317407d5c6ae758c8a15a390508cd5f6e9ae3ed",
|
||||
"temporary": false
|
||||
},
|
||||
{
|
||||
"folder": "Modelos",
|
||||
"name": "IRPC M-22- DECLARACAO DE RENDIMENTOS.pdf",
|
||||
"bytes": 109727,
|
||||
"sha256": "4b61bd83d1fbcfd3b7b5c5523cea5923f7d0f65dc4c004eff58e5db38f763c88",
|
||||
"temporary": false
|
||||
},
|
||||
{
|
||||
"folder": "Modelos",
|
||||
"name": "IRPC_IRPS.xlsx",
|
||||
"bytes": 115701,
|
||||
"sha256": "fd5ddb07392bcad1313a76ae85603169a56eeecd9ac6fa4a1be12cee909c6260",
|
||||
"temporary": false
|
||||
},
|
||||
{
|
||||
"folder": "Modelos",
|
||||
"name": "O IRPC (Imposto sobre o Rendimento das Pessoas Coletivas) .docx",
|
||||
"bytes": 64489,
|
||||
"sha256": "4a4668c116c1abae26a334d55e3c9fe508c1ab31aa2a19fe7b88e374c4b34d16",
|
||||
"temporary": false
|
||||
},
|
||||
{
|
||||
"folder": "Modelos",
|
||||
"name": "VAT Return.pdf",
|
||||
"bytes": 285479,
|
||||
"sha256": "9c0b99a810c7db53eb75d63c22ee22fb86a50ba57bd5509041ed80ee10f75741",
|
||||
"temporary": false
|
||||
},
|
||||
{
|
||||
"folder": "Modelos",
|
||||
"name": "~$calões e Taxas do IRPS.docx",
|
||||
"bytes": 162,
|
||||
"sha256": "e33840a850226b6465e40b78318d597edf0ed399e574a33dd1638748ed2c99a3",
|
||||
"temporary": true
|
||||
},
|
||||
{
|
||||
"folder": "Modelos",
|
||||
"name": "~$IRPC_IRPS.xlsx",
|
||||
"bytes": 165,
|
||||
"sha256": "5a6f905aea475f25f054b0e13e4b2c8b955b7097789fcc3ca24ac2dd0b0bb284",
|
||||
"temporary": true
|
||||
},
|
||||
{
|
||||
"folder": "Modelos",
|
||||
"name": "~$postos e seu tratamento.docx",
|
||||
"bytes": 162,
|
||||
"sha256": "d96fe8461a43a2d40d28e3b247b43eb3d111906531354f9862523588d51ee69b",
|
||||
"temporary": true
|
||||
},
|
||||
{
|
||||
"folder": "Modelos",
|
||||
"name": "~WRL1681.tmp",
|
||||
"bytes": 32054,
|
||||
"sha256": "a3214a4b46c6c0b654e3621ff09ecfd2b777fa4e2b398571d91e754fdd074b06",
|
||||
"temporary": true
|
||||
},
|
||||
{
|
||||
"folder": "Notas",
|
||||
"name": "Estamos a gerar um problema.docx",
|
||||
"bytes": 28938,
|
||||
"sha256": "aa589574f7e581ca0e41df1a690a60379062879fed04ce765b827b9965047e44",
|
||||
"temporary": false
|
||||
},
|
||||
{
|
||||
"folder": "Notas",
|
||||
"name": "INVESTEMOZ Ibane.docx",
|
||||
"bytes": 144410,
|
||||
"sha256": "c5252af6dfa38558b638fafcf726658bb10edabe92939a2f97f5862003f6bed9",
|
||||
"temporary": false
|
||||
},
|
||||
{
|
||||
"folder": "Notas",
|
||||
"name": "investimentos nos corredores.docx",
|
||||
"bytes": 21595,
|
||||
"sha256": "1473b06c48aa234e771660e3f2a51b9d2e4e1943bd52d878e0ae854ee6657055",
|
||||
"temporary": false
|
||||
},
|
||||
{
|
||||
"folder": "Notas",
|
||||
"name": "Neste momento.docx",
|
||||
"bytes": 19639,
|
||||
"sha256": "e6166411122a12089da1cb4436c6a2322338467dbd46ea4e57aee0015a49902c",
|
||||
"temporary": false
|
||||
},
|
||||
{
|
||||
"folder": "Notas",
|
||||
"name": "nota 1.docx",
|
||||
"bytes": 25488,
|
||||
"sha256": "7037ff6d968aa8f756a74adad76758b64ba54d786726aac9e0c7a54ecdc225da",
|
||||
"temporary": false
|
||||
},
|
||||
{
|
||||
"folder": "Notas",
|
||||
"name": "Novo Modelo A v1.docx",
|
||||
"bytes": 532100,
|
||||
"sha256": "63d45fa80db17a60b43619d55a25813229d670d14c758273f86316285db8e02b",
|
||||
"temporary": false
|
||||
},
|
||||
{
|
||||
"folder": "Notas",
|
||||
"name": "Novo Modelo A.docx",
|
||||
"bytes": 532008,
|
||||
"sha256": "62c198e90d8c6792f7d5ccefc6386df2e4b6089573067d3a6e1eae7fc2b9572b",
|
||||
"temporary": false
|
||||
},
|
||||
{
|
||||
"folder": "Notas",
|
||||
"name": "PROVÍNCIA DE INHAMBANE v1.docx",
|
||||
"bytes": 109317,
|
||||
"sha256": "32c6ab160c5028f3084d23b2173ce3cfe6dddba0715d1228da6bd9380247f486",
|
||||
"temporary": false
|
||||
},
|
||||
{
|
||||
"folder": "Notas",
|
||||
"name": "PROVÍNCIA DE INHAMBANE.docx",
|
||||
"bytes": 108584,
|
||||
"sha256": "9f628cd57eb860433f36058b299660834e9052d07e1f7ca99f68f1d1cfdc9d72",
|
||||
"temporary": false
|
||||
},
|
||||
{
|
||||
"folder": "Notas",
|
||||
"name": "~$OVÍNCIA DE INHAMBANE.docx",
|
||||
"bytes": 162,
|
||||
"sha256": "7c2c7731b5ef1023fa980c582051296bf2b5e75a8645113368c2aa9bf34cf12f",
|
||||
"temporary": true
|
||||
},
|
||||
{
|
||||
"folder": "Notas",
|
||||
"name": "~$tamos a gerar um problema.docx",
|
||||
"bytes": 162,
|
||||
"sha256": "e726ad6723df9f664121d97e18854466b30efd24a1f8efab0a7945cbecf7059b",
|
||||
"temporary": true
|
||||
},
|
||||
{
|
||||
"folder": "Notas",
|
||||
"name": "~$VESTEMOZ Ibane.docx",
|
||||
"bytes": 162,
|
||||
"sha256": "9e71bcb8ded90236b2ca70f10937b758eacc0e924cf89d46ba73f1540d6f3132",
|
||||
"temporary": true
|
||||
},
|
||||
{
|
||||
"folder": "Notas",
|
||||
"name": "~$vestimentos nos corredores.docx",
|
||||
"bytes": 162,
|
||||
"sha256": "2b19e4a708c934710926f0dc5da74b785787d4818500e9c1d43359e06ffb9b57",
|
||||
"temporary": true
|
||||
},
|
||||
{
|
||||
"folder": "Notas",
|
||||
"name": "~WRL2857.tmp",
|
||||
"bytes": 143261,
|
||||
"sha256": "85ed72087b6b670292e81c83a455a264a9c4cf2b36ee12807379d475358b3577",
|
||||
"temporary": true
|
||||
}
|
||||
],
|
||||
"read_scope": [
|
||||
"All accessible originals inventoried in Modelos and Notas; editing temporaries distinguished",
|
||||
"Full OOXML text of both duplicate-content tax notes and IRPS scales",
|
||||
"Both XLSX sheet/cell/formula structures without executing or trusting cached formulas",
|
||||
"All three M22 PDF pages visually reviewed",
|
||||
"Accepted Modelo A and VAT Return comparison reused",
|
||||
"Current source consumer and persistence/report infrastructure reconciled"
|
||||
],
|
||||
"findings": [
|
||||
"Tax notes have distinct bytes but identical extracted text",
|
||||
"IRPC_IRPS contains M39/M19 guides and no formulas",
|
||||
"IRPS-IMOP has 102 formulas, 15 direct REF errors and 18 cached error cells; private exemplar data not investor inputs",
|
||||
"M22 PDF and workbook have differing field labels, numbering and statutory references",
|
||||
"2026 transitions/repeals in Laws9-12/2025 affect specific claims in notes",
|
||||
"No universal invoice stamp duty or annual-to-monthly IRPS inference admitted"
|
||||
],
|
||||
"estimated_person_hours": {
|
||||
"minimum": 544,
|
||||
"maximum": 912,
|
||||
"basis": "Engineering estimate including specific remaining source mapping, implementation, end-to-end QA, report and delivery delta; excludes external waiting/AT integration"
|
||||
},
|
||||
"lots": [
|
||||
{
|
||||
"id": "L1",
|
||||
"hours_min": 40,
|
||||
"hours_max": 64
|
||||
},
|
||||
{
|
||||
"id": "L2",
|
||||
"hours_min": 48,
|
||||
"hours_max": 72
|
||||
},
|
||||
{
|
||||
"id": "L3",
|
||||
"hours_min": 48,
|
||||
"hours_max": 80
|
||||
},
|
||||
{
|
||||
"id": "L4",
|
||||
"hours_min": 40,
|
||||
"hours_max": 64
|
||||
},
|
||||
{
|
||||
"id": "L5",
|
||||
"hours_min": 32,
|
||||
"hours_max": 56
|
||||
},
|
||||
{
|
||||
"id": "L6",
|
||||
"hours_min": 48,
|
||||
"hours_max": 80
|
||||
},
|
||||
{
|
||||
"id": "L7",
|
||||
"hours_min": 48,
|
||||
"hours_max": 80
|
||||
},
|
||||
{
|
||||
"id": "L8",
|
||||
"hours_min": 32,
|
||||
"hours_max": 56
|
||||
},
|
||||
{
|
||||
"id": "L9",
|
||||
"hours_min": 24,
|
||||
"hours_max": 40
|
||||
},
|
||||
{
|
||||
"id": "L10",
|
||||
"hours_min": 40,
|
||||
"hours_max": 64
|
||||
},
|
||||
{
|
||||
"id": "L11",
|
||||
"hours_min": 48,
|
||||
"hours_max": 80
|
||||
},
|
||||
{
|
||||
"id": "L12",
|
||||
"hours_min": 24,
|
||||
"hours_max": 48
|
||||
},
|
||||
{
|
||||
"id": "L13",
|
||||
"hours_min": 40,
|
||||
"hours_max": 72
|
||||
},
|
||||
{
|
||||
"id": "L14",
|
||||
"hours_min": 32,
|
||||
"hours_max": 56
|
||||
}
|
||||
],
|
||||
"implementation_changed": false,
|
||||
"active_data_changed": false,
|
||||
"new_tax_rules_activated": false,
|
||||
"accepted_tests_repeated": false,
|
||||
"closed_references_preserved": [
|
||||
"276d833a",
|
||||
"cd10cccb/355656b5",
|
||||
"1655f57e/e8c39a1b"
|
||||
],
|
||||
"documentation_limits": [
|
||||
"Detailed operative bases/rules not yet mapped for each proposed lot",
|
||||
"No entire historical 2000-2025 calculation coverage inferred",
|
||||
"No external AT submission, bank execution or approval inferred",
|
||||
"Six associations and two legacy originals affect only concretely dependent rules"
|
||||
]
|
||||
},
|
||||
"iva_l1_qa_20261024": {
|
||||
"status": "increment_executable_in_isolated_QA_not_full_L1_closure_not_deployed",
|
||||
"base": "e8c39a1b",
|
||||
"branch": "codex/iva-l1-20261024",
|
||||
"candidate": null,
|
||||
"manual": "docs/investmoz/unification/traceability-and-manual.md",
|
||||
"matrix_id": "AUD-SIM-INT02",
|
||||
"scope": {
|
||||
"period": "2026-01 through2026-12",
|
||||
"currency": "MZN",
|
||||
"calculated_treatments": 10,
|
||||
"indeterminate_categories": [
|
||||
"foreign_operation/model06 correspondence",
|
||||
"other/unclassified"
|
||||
],
|
||||
"included_price": "general statutory unit rounding; reduced only exact inverse at6decimal places"
|
||||
},
|
||||
"source_pages": {
|
||||
"CIVA2016": {
|
||||
"sha256": "b90dc7c14ecea2e378c4a62e8e5a47d54a5644c457baeb8b8053b92fd2a5710b",
|
||||
"pdf_pages": [
|
||||
9,
|
||||
10,
|
||||
12,
|
||||
13,
|
||||
14,
|
||||
15
|
||||
]
|
||||
},
|
||||
"Lei22_2022": {
|
||||
"pdf_pages": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4
|
||||
],
|
||||
"articles": [
|
||||
"1:9/10/15/17",
|
||||
"2:17-A",
|
||||
"6:entry2023-01-01"
|
||||
]
|
||||
},
|
||||
"Lei10_2025": {
|
||||
"pdf_pages": [
|
||||
13,
|
||||
14,
|
||||
15
|
||||
],
|
||||
"articles": [
|
||||
"1:19(2)/34",
|
||||
"2:25-A/53",
|
||||
"3:revocation",
|
||||
"5:entry2026-01-01"
|
||||
]
|
||||
},
|
||||
"prior_claim_IRPC": {
|
||||
"law": "12/2025",
|
||||
"pdf_page": 20,
|
||||
"articles": [
|
||||
3,
|
||||
4,
|
||||
6
|
||||
]
|
||||
},
|
||||
"prior_claim_IRPS": {
|
||||
"law": "11/2025",
|
||||
"pdf_page": 18,
|
||||
"articles": [
|
||||
3,
|
||||
4,
|
||||
6
|
||||
]
|
||||
}
|
||||
},
|
||||
"proofs": {
|
||||
"frontend": {
|
||||
"passed": 15,
|
||||
"new_L1": 7,
|
||||
"changed_acquisition_dependency": 8
|
||||
},
|
||||
"real_private_api": {
|
||||
"tests": 1,
|
||||
"assertions": 13,
|
||||
"input": "UI-calculated payload",
|
||||
"database": "simulator_fiscal_delta_20261021_test",
|
||||
"port": 55441,
|
||||
"transaction_rollback": true,
|
||||
"idempotency": true,
|
||||
"other_owner_refused": true,
|
||||
"rule_fingerprint_unchanged": true
|
||||
},
|
||||
"visual": "Actual component, synthetic inputs; insufficient-input version then general included and reduced exact included; report in browser",
|
||||
"types": "passed",
|
||||
"build": "passed",
|
||||
"exports": [
|
||||
"HTML",
|
||||
"CSV"
|
||||
],
|
||||
"native_pdf_new": "not_produced_or_verified; previous accepted PDFs untouched"
|
||||
},
|
||||
"implementation": "Frontend projected operations stored in existing ownership-protected private draft, not new published fiscal server rules",
|
||||
"remaining_L1": [
|
||||
"model06 exact correspondence and territorial cases",
|
||||
"reduced included price requiring rounding",
|
||||
"other exemptions9/12-14, special bases15(2), reverse charge/digital and non-enumerated cases"
|
||||
],
|
||||
"real_estate_inventory": {
|
||||
"omissions_confirmed": [
|
||||
"SISA",
|
||||
"IPRA"
|
||||
],
|
||||
"same_plan_lots": [
|
||||
"L15",
|
||||
"L16"
|
||||
],
|
||||
"state": "to_analyse_not_implemented_not_in_original_estimate",
|
||||
"related_existing_lots": [
|
||||
"L1",
|
||||
"L5",
|
||||
"L6",
|
||||
"L7",
|
||||
"L8",
|
||||
"L10",
|
||||
"L11",
|
||||
"L12"
|
||||
],
|
||||
"sources": [
|
||||
"https://cmcpemba.gov.mz/impostos-e-taxas/iasisa",
|
||||
"https://cmcpemba.gov.mz/impostos-e-taxas"
|
||||
],
|
||||
"limits": "Municipal family identification only; no universal rates or property/DUAT inference"
|
||||
},
|
||||
"effort": "Actual infrastructure reused; no reliable measured hours; original544-912 remains preliminary, excludes unanalyzed L15/L16; remaining L1 to reestimate by coverage",
|
||||
"active_data_changed": false,
|
||||
"schema_or_permissions_changed": false,
|
||||
"rules_activated": false,
|
||||
"accepted_unaffected_tests_repeated": false,
|
||||
"artifacts": {
|
||||
"ui-calculated.json": {
|
||||
"sha256": "5301e83cb77bcb8841e0d14a20e4995ff8eae5d27cd07212286a823fccca9c35"
|
||||
},
|
||||
"ui-api-recovered.json": {
|
||||
"sha256": "08af640394930de8cfa88ae8a2df25938218a05c2d61a951d31fdc694a74b113"
|
||||
},
|
||||
"report-model.json": {
|
||||
"sha256": "bfc494f507ba4ad628b378a26828744b3d51adbf9b85ef961b0f519fb239d020"
|
||||
},
|
||||
"l1-final.html": {
|
||||
"sha256": "8b3f08be41ea9e22dbab7baf8d8305a0884055498f6a66bfc708f235ad326e90"
|
||||
},
|
||||
"l1-final.csv": {
|
||||
"sha256": "88caa5674c70fe48c6ab8050a42a1d2f218bf2121fc0676a2665dcbe2142b6b7"
|
||||
},
|
||||
"frontend-tests.txt": {
|
||||
"sha256": "21a8ea44ca5b7a14f6b0ca969040d9485fc57581e77aa9558f36dc7ad6d20eee"
|
||||
},
|
||||
"api-tests.txt": {
|
||||
"sha256": "6a42799fdbd876ebaea333dbc4523dbff6baed05663803072c703af84b1aad2f"
|
||||
},
|
||||
"types.txt": {
|
||||
"sha256": "82962651e47f564de653ad2883f8fd34e7dae4b4468e4ac459cb3331962a3879"
|
||||
},
|
||||
"build.txt": {
|
||||
"sha256": "5548145b640658e388ff914d91a21afe783a0a8af4fce29c0efce8e316aba421"
|
||||
},
|
||||
"l1-final-tests.txt": {
|
||||
"sha256": "c09e7e21ce72064b8fc1a8c90525b1576a1d62bd760299c03dbe995f73409735"
|
||||
}
|
||||
},
|
||||
"preserved_ancestors": [
|
||||
"276d833a",
|
||||
"1655f57e"
|
||||
],
|
||||
"qa_url": "http://127.0.0.1:18624/"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,140 @@
|
||||
# Rastreabilidade, utilização e formação
|
||||
|
||||
## 24/10/2026 — L1 executável em QA: operações de IVA, cobertura e saldo explícitos
|
||||
|
||||
**Estado: incremento executado em QA, não entregue na homologação; não é fecho integral de L1 nem do IVA.** Base `e8c39a1b`, descendente da entrega `1655f57e`. Ramo `codex/iva-l1-20261024`. Preservados os fechos S1–S3, aquisições08–10/totais14–18 e referência `276d833a`. Mantém-se o plano único abaixo; o inventário admite omissões e recebe SISA/IPRA para análise antes do futuro Imobiliário. Nenhum imposto/regra publicado ou activado no destino, schema/configuração/permissões/dados activos alterados.
|
||||
|
||||
### Operações efectivamente executáveis
|
||||
|
||||
Período técnico deste incremento: Janeiro–Dezembro2026, MZN explícito; não é declaração de expiração legal das fontes. Cada linha mostra artigo, fonte, condições e fundamento/pressuposto; a escolha do sector ou da localização não selecciona automaticamente isenção/taxa.
|
||||
|
||||
| Operação incluída | Tratamento/campo | Fonte exacta e condições recolhidas |
|
||||
| --- | --- | --- |
|
||||
| Operação interna geral | 16%;01/02; preço líquido ou IVA incluído | CIVA arts.1,6,15(1),17(1–2); Lei22/2022 art.1, PDF3, p.2252(27); Lei10/2025 art.1, art.34, PDF14, p.2180(14). Confirmar localização tributável/regime normal e contraprestação completa sem base especial/isenção/taxa reduzida. |
|
||||
| Serviços médicos/sanitários privados e estreitamente conexos | 5%;03/04 | CIVA arts.15(10),17-A(a), Lei22/2022 arts.1/2, PDF3–4, pp.2252(27)–(28). Prestador hospitalar privado/clínica/dispensário/similar e natureza do serviço confirmados. |
|
||||
| Ensino privado reconhecido e operações conexas pelo estabelecimento | 5%;03/04 | CIVA arts.15(10),17-A(b), mesma fonte. Integração no Sistema Nacional de Ensino e reconhecimento ministerial; não aplicar a toda venda autónoma de material escolar. |
|
||||
| Formação profissional privada e fornecimentos conexos | 5%;03/04 | CIVA arts.15(10),17-A(c), mesma fonte. Confirmar formação/conexão e prestador privado. |
|
||||
| Lições ministradas a título pessoal | 5%;03/04 | CIVA arts.15(10),17-A(d), mesma fonte. Confirmar título pessoal e matérias do ensino escolar/superior. |
|
||||
| Exportação de bens pelo vendedor/terceiro por sua conta | Isenção com direito nas condições legais;05 | CIVA art.13(1)(a), republicação2016, PDF12, p.1426(270); art.19(1)(b)(i), PDF15, p.1426(273). Saída efectiva documentada ou pressuposto expresso da projecção; cliente estrangeiro por si só não basta. Não é dedução automática de compras nem classificação genérica «taxa zero». |
|
||||
| Locação de imóvel para habitação | Isenção sem direito;07 | CIVA art.9(5), Lei22/2022 art.1, PDF2, p.2252(26); condições do art.19(1), republicação2016 PDF15. Não aplicar a hospedagem nem locação comercial/industrial. |
|
||||
| Serviços médicos/sanitários públicos e estreitamente conexos | Isenção sem direito;07 | CIVA arts.9(1)(a),10(1), Lei22/2022 art.1, PDF1–2, pp.2252(25)–(26), conjugados com art.19(1). Confirmar serviço e natureza pública do prestador. |
|
||||
| Ensino público reconhecido e operações conexas pelo estabelecimento | Isenção sem direito;07 | CIVA arts.9(3)(a),10(1), mesma fonte. Confirmar integração/reconhecimento, natureza pública e operação efectivamente conexa. |
|
||||
| Formação profissional pública e fornecimentos conexos | Isenção sem direito;07 | CIVA arts.9(3)(b),10(1), mesma fonte. Confirmar formação/conexão e prestador público. |
|
||||
|
||||
Originais legais: [CIVA republicado2016](../simulations/source-archive/Lei-13-2016-CIVA-BR-156-10-suplemento.pdf), [Lei22/2022](../simulations/source-archive/Lei-22-2022-IVA-republicada-BR-250-2-suplemento.pdf), [BR248, Leis7–12/2025](../simulations/source-archive/BR-248-I-Suplemento-1-2025.pdf). As páginas PDF são contadas a partir de1; as páginas impressas estão indicadas separadamente. A leitura identificou que o art.17-A está na **página PDF4**, não3; a referência do novo catálogo foi corrigida antes do registo da candidata. Fontes aceites, sem nova validação genérica de credibilidade.
|
||||
|
||||
**Preço incluído:** geral — art.34 na redacção Lei10/2025: base=preço×100/116, arredondada à unidade mais próxima; IVA=preço−base. Escolha técnica explícita no empate: metade para cima. Exemplo QA1161MZN→base1001/IVA160/bruto1161. Reduzido — inversão aritmética dos arts.15(10)/17-A quando exacta até6casas:105MZN→base100/IVA5/bruto105. Não aplicar o divisor116 à taxa5%. Quando a inversão a5% exige arredondamento não delimitado, a linha permanece indeterminada; pode ser usada uma base líquida documentada, sem a inventar. Valores líquidos usam precisão técnica6casas/metade para cima, declarada no relatório, sem afirmar arredondamento final da AT.
|
||||
|
||||
### Dados reutilizados e únicos complementos pedidos
|
||||
|
||||
- Reutilizados: oportunidade em tratamento, moeda original, localização e rótulos, vendas e meses do plano/tesouraria, representação do preço quando já declarada e fonte do pressuposto. Reutilização não apaga edições deliberadas nem converte moeda.
|
||||
- Informação adicional por operação, só quando em falta: descrição discriminante, estabelecimento (não inferido do local do projecto), tratamento, condições específicas e documento/pressuposto. Período, valor e preço líquido/incluído são herdados; só são preenchidos quando inexistentes ou divergentes. Nos valores reutilizados, a fonte existente preenche o pressuposto, evitando reescrita.
|
||||
- Repartição explícita de vendas agregadas conserva total/período/moeda/representação da origem; parcelas devem reconciliar com esse total. Divergência, origem alterada, período fora de cobertura, preço desconhecido ou ausência de condição não gera zero. Uma lista vazia só constitui ausência após confirmação de cobertura do mês.
|
||||
- Mantidos o rascunho privado, API, idempotência, permissões, relatório/CSV e o armazenamento já existentes: extensão `salesOperations` por oportunidade dentro de `modelo_a_snapshot`, sem novo formulário obrigatório nem schema. Cada recálculo guarda a versão anterior. A execução mensal geral existente continua conservada como execução separada e não é somada novamente às linhas L1.
|
||||
|
||||
### Divergências delimitadas e saldo de L1
|
||||
|
||||
1. **Campo06:** o exemplar remete para art.18(1)(b), que na republicação2016 PDF14 é imposto devido na importação, enquanto o direito relativo a operações no estrangeiro surge no art.19(1)(b)(ii), PDF15. Não se troca silenciosamente a remissão nem se atribui esse campo a qualquer operação estrangeira. Entrada própria conserva os dados como indeterminados. A pendência não impede calcular as outras linhas; totais que possam depender dela continuam indeterminados.
|
||||
2. **Preço incluído5% com inversão não exacta:** falta delimitar o método de arredondamento dessa situação, não a existência da taxa ou o cálculo de uma base líquida conhecida. Apenas esse ramo permanece sem resultado.
|
||||
3. **Outras coberturas ainda por implementar em L1:** restantes isenções dos arts.9/12–14, bases especiais do art.15(2), territorialidade/autoliquidação/serviços digitais e outras operações não enumeradas nesta tabela; não presumir cobertura por seleccionar «Outra operação». O registo da venda é conservado, com informação insuficiente e próximo passo. As remissões temporárias do art.9(13)/(16) não são prorrogadas automaticamente. Importações/deduções parciais/regularizações mantêm os lotes próprios do plano.
|
||||
4. **Compras afectas a operações reduzidas/isentas/outras:** não reutilizar automaticamente a dedução exclusiva geral do lote08–10 em contexto novo misto. CIVA art.19(2), Lei10/2025 art.1, PDF13, p.2180(13), menciona as operações17-A. Neste incremento, essas parcelas exigem afectação específica em L2; resultados antigos são conservados, não convertidos em dedução universal. Quando a classificação L1 está actual, os seus campos01–07 alimentam o apuramento parcial existente; classificação alterada/incompleta não é substituída pela antiga execução geral nem por declarações de ausência.
|
||||
|
||||
### Referências exactas das incompatibilidades legislativas referidas no levantamento
|
||||
|
||||
- **IRPC:** Lei12/2025, arts.3/4/6, BR248 suplemento1 **PDF20, p.2180(20)**: revogação dos arts.39,47,75(2),76; transição dos regimes simplificados para contabilidade organizada; entrada01/01/2026. Daqui decorre a incompatibilidade da recomendação corrente dos coeficientes art.47 em2026. Não se presume prazo de transição adicional nem se elimina a possibilidade de regra histórica do respectivo período.
|
||||
- **IRPS:** Lei11/2025, arts.3/4/6, mesmo BR **PDF18, p.2180(18)**: revogações discriminadas e transição dos sujeitos da segunda categoria dos regimes simplificados; entrada01/01/2026. Não é regra de cálculo mensal derivada dos escalões anuais.
|
||||
- **IVA:** Lei10/2025, arts.2/3/5, mesmo BR **PDF15, p.2180(15)**: art.53 transitório, revogação das SubsecçõesI/II da SecçãoIV e do art.21(8), entrada01/01/2026. São estes trechos que impedem escolher automaticamente antigos limiares de isenção/simplificado do resumo. Não demonstram a extinção das isenções por operação do art.9, que continuam a ser analisadas separadamente.
|
||||
- **Taxa geral e reduzida:** Lei22/2022 arts.1/2/6, **PDF3–4, pp.2252(27)–(28)**, arts.17(1)/17-A e início01/01/2023. As condições operacionais não são substituídas pelo exemplo de uma empresa mista do documento descritivo.
|
||||
|
||||
### Provas dirigidas e limitações da demonstração
|
||||
|
||||
**15 testes frontend aprovados:** sete novos L1 e oito da ligação às aquisições directamente alterada; sem repetir suites S1–S3, mapas, projectos âncora ou outros fechos sem delta. Abrangem cada tratamento enumerado, preço incluído geral e reduzido exacto, preço desconhecido, condições em falta, zero explícito, divergência de moeda/período/origem, repartição, histórico, segunda oportunidade sem alterar a primeira, ligação aos totais e recusa de dedução universal.
|
||||
|
||||
**API real:1 teste/13 asserções aprovado**, executado sobre payload calculado na interface, base nominal isolada `simulator_fiscal_delta_20261021_test:55441`, identidade conferida e transacções revertidas. Gravação→mesmoID na repetição idempotente→recuperação exacta; outra conta recusada; fingerprint das regras inalterado. Não é teste de motor servidor L1: os valores projectados são calculados pelo componente frontend e conservados no rascunho privado existente, como no recorte de aquisições; nenhuma nova regra publicada/admitida no consumidor fiscal do servidor.
|
||||
|
||||
**Interface:** componente real num ensaio sintético isolado, com12 entradas (dez tratamentos e dois casos indeterminados). Retirar fundamento da linha geral→calcular→IVA indeterminado; repor fundamento→calcular→IVA160; linha privada105 com IVA incluído→base100/IVA5. Guardar estado da interface→API real QA→recuperação→relatório HTML/CSV, sem recálculo na exportação, com montantes, oportunidade309/Manica, estabelecimento, Janeiro2026, artigos/fontes e revisão preservados. Histórico da versão insuficiente permanece guardado. Não se confunde o armazenamento local do ecrã de ensaio com a prova API.
|
||||
|
||||
HTML/CSV novos em `outputs/iva-l1-qa-20261024/`, com modelo e payloads sintéticos. Relatório visualmente conferido no browser. Não foi produzido/conferido novo PDF nativo nesta passagem, nem se regeneraram os PDFs aceites. TypeScript e build do incremento conferidos; ficheiros de prova, limitações e estado exacto no recibo integrado `iva_l1_qa_20261024`.
|
||||
|
||||
**Esforço:** houve reaproveitamento efectivo do simulador, rascunho/API, permissões e exportador. Não se declara um novo motor ou implementação do orçamento completo40–64h; esse intervalo era preliminar para o âmbito mais amplo de L1. Não existe medição fiável de horas desta passagem. Reestimar o saldo por cobertura remanescente, sem transformar544–912h em prazo contratado nem acrescentar SISA/IPRA ao total sem análise.
|
||||
|
||||
|
||||
|
||||
## 24/10/2026 — inventário fiscal reconciliado e proposta de fecho antes do Imobiliário
|
||||
|
||||
**Estado: análise concluída; lotes abaixo propostos, não implementados nem activados nesta passagem.** Decisão expressa da Tempo: concluir o inventário fiscal acordado antes do Imobiliário. Estatísticas e IA continuam posteriores. A tabela abaixo é a referência única do plano, dentro do manual e da matriz existentes (AUD-SIM-INT02); não constitui uma segunda matriz. As secções anteriores que colocavam todo o IRPC/IRPS em desenvolvimento posterior ao Imobiliário ficam ultrapassadas por esta sequência, mantendo os respectivos factos históricos e fechos.
|
||||
|
||||
### O que foi efectivamente conferido
|
||||
|
||||
Inventário das pastas autorizadas `Modelos` e `portal do investidor/Notas`, distinguindo originais de temporários de edição; leitura integral do texto de `Impostos e seu tratamento.docx`, do documento IRPC, dos escalões IRPS, da estrutura/células/fórmulas dos dois XLSX e das três páginas do M/22 PDF. O cotejo Modelo A/VAT Return já aceite foi reutilizado. Inventário, hashes e extracções privadas em `outputs/fiscal-inventory-plan-20261024/`; sem importar, publicar, alterar ou recalcular os originais. Os dados preenchidos de terceiros nos exemplares Excel não são pressupostos do investidor e não são reproduzidos neste manual.
|
||||
|
||||
- `Impostos e seu tratamento.docx`: SHA256 `aade59537c6c19eb56b017cfc317407d5c6ae758c8a15a390508cd5f6e9ae3ed`. O documento intitulado `O IRPC (Imposto sobre o Rendimento das Pessoas Coletivas) .docx` tem bytes/hash distintos (`4a4668c116c1abae26a334d55e3c9fe508c1ab31aa2a19fe7b88e374c4b34d16`), mas texto extraído integralmente igual: não são duas confirmações independentes.
|
||||
- `Escalões e Taxas do IRPS.docx`: SHA256 `ea9fdfaf8a8682645e0e3dad3e2e165520783e9f72e8d23acdf684d8359d690e`; contém escalões anuais, não uma tabela mensal de retenção por trabalhador.
|
||||
- `IRPC_IRPS.xlsx`: guias M/39 IRPC e M/19 IRPS, sem fórmulas. A remissão CIRPS na guia intitulada IRPC exige correspondência própria; um formulário não demonstra um motor de cálculo.
|
||||
- `Cópia de IRPS-IMOP.xlsx`: sete folhas, uma oculta; 102 fórmulas, 15 referências directas `#REF!` e 18 erros em valores guardados. Inclui M/22, M/20 e anexos. Não serve de oráculo numérico sem reparar e cotejar cada correspondência; os erros não equivalem a zero.
|
||||
- M/22 PDF versus XLSX: quadro de cálculo 12 versus 10; campo 302 «colecta» e 309 «IRPC liquidado» no PDF aparecem com designações trocadas no XLSX; referências PEC art.71 versus art.66 e base da colecta também diferem. A edição/período e a semântica de cada campo precedem a ligação executável. O modelo antigo não prova que um regime continua disponível em 2026.
|
||||
|
||||
Código reconciliado na revisão corrente, descendente da entrega `1655f57e`: consumidor público de regra fiscal limitado ao IVA demonstrado; `custom_planning` é planeamento por pressupostos do utilizador. Cadastro de fontes, parâmetros, expressões, governação e campos de entrada IRPC/IRPS/ISPC/INSS/retenções/aduaneiro-ICE são reutilizáveis, mas não equivalem a consumidores fiscais completos. Não se declara ausência de regras no cadastro protegido a partir desta leitura de código. Não houve novas consultas protegidas, ensaios de aceitação ou alterações ao destino.
|
||||
|
||||
### Divergências concretas e efeito delimitado
|
||||
|
||||
1. **IRPC simplificado:** o texto recomenda coeficientes 20%/30% do art.47 como solução corrente. A Lei12/2025 revoga esse artigo e determina transição para contabilidade organizada, com entrada em vigor em 01/01/2026. Não implementar esses coeficientes como opção corrente de 2026. Eventual cálculo histórico exige a regra do respectivo período; não se inventa prazo de transição. A Lei11/2025 também altera o enquadramento simplificado da segunda categoria IRPS.
|
||||
2. **IVA:** o documento alterna limiares de 250.000 e 750.000 e recomenda isenção automática. A Lei10/2025 altera/revoga os antigos regimes e contém transição. Esses números não seleccionam automaticamente o regime em 2026. Operação a 5%, isenção com/sem direito à dedução e taxa zero são situações distintas; saúde/ensino não tornam todos os materiais ou vendas da entidade sujeitos a 5%.
|
||||
3. **ISPC:** o resumo 3%/4% é incompleto perante a Lei9/2025: há diferentes actividades, escalões, exclusões, opções e tratamentos, incluindo taxas de 5%, 12%, 15% e excesso de volume. Não transformar a descrição em taxa única nem presumir tributação marginal. A elegibilidade é anterior à comparação ISPC/IRPC/IVA.
|
||||
4. **Retenções e créditos:** nem todo serviço implica retenção uniforme de 20%; natureza do rendimento, residência, sujeito que retém, período, excepções e eventual convenção determinam o cálculo. Distinguir IRPC autónomo de mais-valias do tratamento de despesas não documentadas. Guia M/39, crédito estimado, crédito declarado, reconhecimento, compensação e dinheiro recebido são estados diferentes. Afirmações genéricas sobre original físico carimbado, caducidade de cinco anos, compensação automática ou cruzamento bancário da AT não se tornam restrições do portal sem disposição/procedimento concretamente aplicável.
|
||||
5. **Selo e encargos:** não foi demonstrada incidência universal de 0,4% sobre toda factura acrescida de IVA. Falta cotejar verba, base, isenções, interacção com IVA e período na tabela aplicável. Exemplos de juros «MIMO + margem», coimas e multas não fornecem parâmetros suficientes. A pendência afecta esses cálculos, não bloqueia os restantes impostos.
|
||||
6. **Trabalho:** escalões anuais IRPS não são convertidos numa tabela mensal dividindo por 12. As taxas INSS 4% empregador/3% trabalhador têm confirmação operacional oficial para TCO, mas base contributiva, exclusões, período e regime TCO/TCP precisam de correspondência própria. O exemplo de salário líquido que só desconta INSS não é líquido final quando há IRPS.
|
||||
|
||||
Fontes oficiais aceites reutilizadas: BR248, suplemento1 de29/12/2025 (Leis7–12/2025), CIVA2016/rectificação2017, republicação2022 e acervo fiscal existente. A origem oficial não requer nova aprovação jurídica genérica: aplicam-se as decisões Tempo de08/10 e22/10. Conferir edição, artigo, período, parâmetros e transições é execução da fonte aceite. Os antigos pedidos genéricos de «Fiscalista» constantes de registos históricos não criam um aprovador adicional para estes lotes.
|
||||
|
||||
### Tabela única — situação, lacunas, reutilização, dependências e esforço
|
||||
|
||||
Âmbito-base proposto: investimento e obrigações de 2026, com saldos/factos de anos anteriores necessários ao apuramento e respectiva proveniência. A possibilidade técnica de escolher outro ano não demonstra cobertura histórica: recalcular integralmente todos os impostos de2000–2025 ou activar anos futuros não integra implicitamente esta estimativa. Cada lote explicita os períodos que consegue calcular.
|
||||
|
||||
As estimativas são **horas de trabalho técnico**, não calendário nem medição já executada. Incluem cotejo específico restante, implementação, persistência/recuperação, relatório, provas dirigidas em QA e preparação/conferência do delta Git. Assumem reutilização da infra-estrutura existente; não incluem espera pela entrega de documentos efectivamente em falta, decisão organizacional nova ou integração externa da AT. Não se contabiliza novamente o IVA fechado. A sequência L1–L14 é proposta; dependências abaixo são concretas, não uma obrigação de bloquear todos os lotes em série.
|
||||
|
||||
| Lote / âmbito | Já disponível | Falta implementar de ponta a ponta | Dados reutilizáveis | Dependências concretas | Esforço estimado |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| Encerrado — planeamento, S1–S3 e IVA entregue | Planeamento financeiro; moeda original; recuperação por ID; IVA mensal geral líquido MZN/2026; aquisições08–10 no âmbito demonstrado e totais14–18 condicionados | Nenhum saldo nos lotes aceites. Não extrapolar para IVA integral | Vendas, compras, orçamento, custos, tesouraria, dossier por oportunidade, gravação e exportações | Preservar `cd10cccb`/`355656b5`, `1655f57e`/`e8c39a1b` e referência `276d833a` | 0 h de reimplementação |
|
||||
| L1 — IVA: operações e enquadramento | Vendas mensais e operação interna geral líquida | Classificar operações03–07 e outras situações aplicáveis; IVA incluído, isenções/direito à dedução, taxa reduzida e períodos por linha/estabelecimento | Produtos, vendas, entidade, localização e oportunidade | CIVA e alterações2025; incidência por operação, sem inferir regime de sector/forma jurídica | 40–64 h |
|
||||
| L2 — IVA: dedução restante e regularizações | Compras08–10, líquido/IVA/bruto, documento, mês e afectação explícita no recorte aceite | Importações documentadas, dedução parcial/pro-rata, exclusões, activos e regularizações12–13; condições por parcela | Compras, activos, fornecedor, documento, calendário, parcela não dedutível | L1 quando determina direito; artigos de dedução e regularização. Importação pode usar documento aduaneiro existente, sem esperar pelo cálculo de direitos de L13 | 48–72 h |
|
||||
| L3 — IVA: saldo e fecho do Modelo A | Totais14–18 quando todas as parcelas estão determinadas | Campos19–27 aplicáveis, transporte de crédito, rectificações/substituição, imposto a entregar e pedido de reembolso; estados documentados e relação com tesouraria | Execuções mensais, parcelas L1/L2, documentos e histórico | L1/L2 para apuramento integral; saldo anterior identificado e procedimento de reembolso aplicável. Pedido não significa deferimento/recebimento | 48–80 h |
|
||||
| L4 — ISPC: elegibilidade e apuramento | Campos de enquadramento e motor genérico | Regime/actividade/volume elegível, opções/exclusões, escalões/taxas aplicáveis, mínimo, trimestre e retenções específicas; comparação só entre regimes admissíveis | Vendas por actividade, custos, NUIT, natureza jurídica e residência confirmada | Lei9/2025 e regulamentação/período; distinguir receita anual, trimestre e excesso | 40–64 h |
|
||||
| L5 — retenções IRPC e guias | Documentos de compras/vendas e guia M/39 como exemplar | Rendimento pago/recebido, entidade que retém, residente/não residente/EP, evento temporal, excepções e casos digitais/e-money; guia e comprovativo ligados à operação | Contrapartes, facturas, pagamentos, período, moeda, oportunidade | Artigos aplicáveis por rendimento, taxa/convenção se invocada; corrigir remissões do exemplar. Sem submissão electrónica AT presumida | 32–56 h |
|
||||
| L6 — IRPC anual: regime normal | Projecções, resultados financeiros, activos e custos | Ponte resultado contabilístico→matéria colectável, custos admitidos/não admitidos, amortizações, perdas fiscais e colecta; correspondência M/20/M/22 | Plano financeiro e dados comuns; L5 para retenções sofridas | Código IRPC e Lei12/2025; exercício fiscal, residência/EP, saldos/perdas com ano e prova; resolver diferenças PDF/XLSX | 48–80 h |
|
||||
| L7 — IRPC: tratamentos específicos | Contexto sectorial/territorial e fontes cadastradas | Casos identificados: tributação autónoma de despesas e mais-valias, taxas reduzidas/cooperativas, isenções/benefícios e operações especiais com condições rastreadas | Base L6, despesas, activos, relação territorial documentada | Disposição e período por tratamento; localização por si só não concede benefício. Lista de tratamentos aplicáveis explicitada no lote | 48–80 h |
|
||||
| L8 — IRPC: liquidação e pagamentos | Parcelas L5–L7 e formulários originais | PPC/PEC, deduções/créditos, dupla tributação quando aplicável, saldo anual a pagar/recuperar; calendário e reconciliação com pagamentos | Retenções, colecta, saldos anteriores, pagamentos/comprovativos | Base anterior para PPC/PEC, regras de limitação/transporte; não usar cinco anos ou compensação automática sem cotejo específico | 32–56 h |
|
||||
| L9 — INSS: contribuição social | Massa salarial agregada e referência TCO3%+4% | Trabalhadores, remuneração/base contributiva, exclusões, empregador/trabalhador, TCO/TCP, declaração e encargos por período | Equipa/custos, entidade e calendário; cadastro de pessoa partilhado com L10 | Regulamento e alterações aplicáveis, natureza do vínculo; confirmar base antes da taxa. Contribuição social, distinta dos impostos | 24–40 h |
|
||||
| L10 — IRPS: retenção mensal e líquido | Entrada IRPS, custos salariais e escalões anuais documentais | Tabela mensal própria, remunerações/benefícios, situação pessoal relevante, retenção, salário líquido e contribuição ligada a L9 | Mesmo trabalhador/folha salarial, pagamentos e retenções | Tabela mensal do período, residência e condições familiares necessárias; não dividir escalões anuais por12 | 40–64 h |
|
||||
| L11 — IRPS: apuramento anual e outras categorias | Escalões anuais e exemplares M/19/M/20/anexos | Categorias aplicáveis aos rendimentos documentados, actividade independente, capitais/prediais/mais-valias, englobamento/tratamento autónomo, deduções, retenções e saldo | Dados pessoais autorizados, rendimentos/despesas, activos e L10 | Código IRPS/Lei11/2025, âmbito/categoria/período e deduções pessoais; diferenças de edição dos anexos. Não copiar regras IRPC | 48–80 h |
|
||||
| L12 — Imposto do Selo | Factos/contratos/documentos financeiros do projecto | Incidências por verba aplicável às operações documentadas, base, taxa/valor fixo, sujeito, isenção e obrigação; explicar não aplicação | Contratos, financiamento, garantias, recibos e contexto do negócio | Código/tabela e alterações da verba/período; resolver exemplo genérico0,4% sem o aplicar a todas as facturas | 24–48 h |
|
||||
| L13 — direitos aduaneiros | Entrada aduaneira/ICE e fontes no inventário anterior | Classificação pautal, origem, valor aduaneiro, despesas de composição, câmbio explícito e direitos/benefícios aplicáveis | Equipamentos/compras, transporte, seguro, documentos de importação e localização | Pauta/Lei8/2025, código/origem e data. Não inferir código pela descrição livre nem autorização de benefício pela localização | 40–72 h |
|
||||
| L14 — ICE | Componente de entrada e fonte Lei7/2025 inventariada | Produtos/operações abrangidos, tributação específica/ad valorem/mista quando prevista, base/unidade e período; ligação à importação ou produção interna | Quantidades, produtos, preços, origem e L13 quando importado | Código/tabelas ICE e alterações; ligação à base de IVA somente quando aplicável, sem bloquear produção interna por ausência de cálculo aduaneiro | 32–56 h |
|
||||
| L15 — SISA e delimitação territorial | Operações/localização do investimento; ainda sem consumidor fiscal específico | Analisar transmissão onerosa/direitos sobre imóveis, âmbito autárquico ou nacional, incidência/isenções/base/período e percurso completo | Imóvel, preço/documento, partes e localização; não equiparar DUAT a propriedade da terra | Cotejo do Código Tributário Autárquico/legislação nacional e alterações; fonte municipal confirma família, não taxa nacional uniforme | Por estimar após análise; não incluído em544–912h |
|
||||
| L16 — Imposto Predial Autárquico e encargos imobiliários aplicáveis | Localização e orçamento; sem cálculo específico demonstrado | Analisar posse/uso/valor patrimonial, município, exercício e isenções; discriminar taxas administrativas de impostos | Imóvel, uso, entidade e valores documentados | Base legal e tabela da autarquia/período; não converter preço de compra em valor patrimonial | Por estimar após análise; não incluído em544–912h |
|
||||
|
||||
|
||||
**Total preliminar original L1–L14 (L15/L16 ainda por analisar): 544–912 h (68–114 dias-pessoa de8h).** Não é promessa de prazo nem autorização automática de publicação. Rever a estimativa ao fechar o cotejo específico de cada lote, identificando exactamente a alteração. Não há um lote invisível adicional para relatórios ou persistência: fazem parte de cada linha.
|
||||
|
||||
Origem do âmbito: IVA, IRPC, ISPC, Selo e INSS constam de `Impostos e seu tratamento`; IRPS consta do documento de escalões e dos modelos. Direitos aduaneiros e ICE já constam do inventário fiscal/entrada existente e do BR248: são mantidos explicitamente na proposta, não atribuídos ao novo texto. Retenções, PPC/PEC, créditos, juros e obrigações são tratamentos das famílias respectivas, não impostos novos. Juros/encargos só são calculados no lote respectivo com facto, base, vencimento e parâmetro legal determinados; caso contrário ficam expressamente não calculados. Reclamações judiciais, minutas avulsas, execução bancária e ligação técnica de submissão à AT não se presumem requisitos de cálculo por aparecerem numa conversa do documento. Outros tributos não identificados neste acervo não são acrescentados silenciosamente ao significado de «terminar».
|
||||
|
||||
**Revisão para o futuro Imobiliário:** o inventário anterior era incompleto: faltavam SISA e IPRA explicitados. Acrescentados L15/L16 como recortes por analisar, mantendo a mesma tabela. A informação municipal oficial identifica [SISA imobiliária](https://cmcpemba.gov.mz/impostos-e-taxas/iasisa) e [IPRA](https://cmcpemba.gov.mz/impostos-e-taxas); serve aqui para confirmar a omissão, não para activar taxas. Há inconsistências concretas no índice municipal (descrição de veículos repetida em SISA), pelo que não se transportam essas descrições nem taxas para uma regra nacional. A delimitação legal/autárquica e temporal permanece por cotejar no ramo respectivo. IVA de locação habitacional está em L1; vendas sujeitas a SISA (art.9(12)(d)) e arrendamentos não habitacionais exigem enquadramento próprio; rendas/retenções e mais-valias permanecem L5–L8/L10–L11, contratos/financiamento em L12. Nenhuma destas ligações presume venda de terra ou transmissibilidade do DUAT. Outros encargos municipais efectivamente necessários serão inventariados com fonte e operação, sem acrescentar indiscriminadamente todos os tributos nacionais.
|
||||
|
||||
### Critério comum de fecho, aplicado separadamente a cada imposto/lote
|
||||
|
||||
1. **Dados:** entrada geral ou ficha herda a oportunidade em tratamento; reutiliza informação válida de perfil/organização/dossier/fontes, conservando autoria e edições. Projecto/localização não substitui residência fiscal. Pergunta apenas o que falta/diverge; valores sem moeda/origem não são convertidos ou reinterpretados silenciosamente.
|
||||
2. **Enquadramento:** cada ramo tem condição, artigo/fonte, edição e período; distingue aplicável, não aplicável com explicação e informação insuficiente com próxima acção concreta. Cobertura não implementada é pendência do produto, nunca «não aplicável» ao investidor. Uma divergência documental suspende apenas o ramo dependente.
|
||||
3. **Cálculo:** parcelas e total, arredondamento, limites, zero real e desconhecido são distintos. Caso normal e excepções delimitadas têm exemplos verificáveis, incluindo fronteiras de escalão/período. Crédito não é recebimento e compra não gera automaticamente dedução.
|
||||
4. **Gravação/recuperação:** mesma execução, ID, oportunidade, pressupostos, parcelas, fonte e versão; permissões conservadas e recuperação independente da admissão de novos cálculos. Alteração posterior cria versão/explica divergência sem reescrever resultado aceite.
|
||||
5. **Relatório:** ecrã, ficheiro final e recuperação apresentam os mesmos montantes/moeda/período/contexto/versão/fontes e pendências, incluindo paginação dos blocos novos. Dois investimentos: alterar apenas o segundo mantém o primeiro intacto. Provas anteriores sem delta são reutilizadas.
|
||||
6. **Entrega:** demonstrar o lote em QA isolada, integrar pelo Git preservando alterações concorrentes, conferir revisão servida e apenas o delta/dependências. Sem activar cálculos por importar/publicar fontes e sem simular reconhecimento/submissão da AT. Operações em dados activos mantêm backup/restauro e acção autenticada aplicável.
|
||||
|
||||
O fecho integral exige cobertura executável e demonstrada das linhas acordadas, com operações/regimes/períodos discriminados. A ausência de dados de um investidor pode gerar resultado indeterminado correctamente explicado; a ausência de uma regra necessária ou de implementação permanece saldo do lote. As seis associações e os dois originais documentais anteriores não bloqueiam globalmente este plano: só afectam uma regra quando a dependência efectiva for identificada.
|
||||
|
||||
**Saldo histórico do levantamento anterior, actualizado pelo incremento L1 acima:** fontes e implementação reconciliadas, divergências e proposta finita registadas; zero alterações funcionais, zero impostos activados e zero novas provas exigidas à Tempo. S1–S3, aquisições08–10/totais14–18 e demais fechos permanecem encerrados. L1 tem agora incremento executável em QA com saldo discriminado acima; L2–L16 não estão implementados por esta passagem.
|
||||
|
||||
|
||||
|
||||
## 24/10/2026 — aquisições Modelo A entregues na homologação, revisão1655f57e
|
||||
|
||||
Lote08–10/14–18 integrado pelo Git existente e instalado em `1655f57e`, candidata `d18ff0f0`. Saldo deste lote zero. Revisão servida, manifesto, quatro assets/nove marcadores, entrada do simulador e disponibilidade existente conferidos apenas no delta. Sem novos pedidos/cálculos privados na homologação ou repetição dos ensaios aceites. S1–S3 `cd10cccb`/`355656b5`, PDF aceite e referência `276d833a` preservados; schema/configuração/regras publicadas sem alteração. Recibo integrado `modelo_a_acquisitions_homologation_20261024`; cotejo, âmbito e condições em `../simulations/modelo-a-reconciliacao-20261021.md`. Não é apuramento integral do IVA; IRPC/IRPS posteriores, estatísticas/IA adiadas. As secções de espera abaixo são histórico.
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -5,6 +5,7 @@ import { fiscalQaRows } from '@/lib/fiscal-qa-preview';
|
||||
import { type ApiSimulationExecutionV1 } from '@/lib/simulation-contract';
|
||||
import { SimulationModeloA } from './simulation-modelo-a';
|
||||
import { SimulationAcquisitions } from './simulation-acquisitions';
|
||||
import { SimulationOperations } from './simulation-operations';
|
||||
|
||||
export function SimulationMonthlyIva({draft, busy, onCalculate, onChange}: {
|
||||
draft: Record<string, unknown>; busy: boolean;
|
||||
@@ -84,6 +85,7 @@ export function SimulationMonthlyIva({draft, busy, onCalculate, onChange}: {
|
||||
{input && <p className="text-sm">{input.monthly_sales.length} meses; moeda {input.currency || 'por indicar'}. Campo vazio significa informação em falta; zero declarado permanece zero. Depois da primeira execução, alterações das vendas actualizam o cálculo enquanto a actualização automática estiver ligada.</p>}
|
||||
{projection?.rows && <div className="overflow-x-auto" tabIndex={0}><table className="min-w-full text-sm"><caption>IVA mensal — {current ? 'versão correspondente às entradas' : 'versão histórica'}</caption><thead><tr>{['Mês','Vendas declaradas','Vendas tributáveis sem IVA','Taxa aplicada (%)','IVA calculado','Total com IVA','Estado'].map((label) => <th key={label} className="p-2 text-left">{label}</th>)}</tr></thead><tbody>{projection.rows.map((row) => <tr key={row.date}><th scope="row" className="p-2">{row.date.slice(0,7)}</th><td>{row.sales_declared ?? 'Sem informação'}</td><td>{row.net_sales ?? 'Indeterminado'}</td><td>{row.rate ?? 'Indeterminada'}</td><td>{row.vat ?? 'Indeterminado'}</td><td>{row.gross_sales ?? 'Indeterminado'}</td><td>{{calculated:'Calculado',missing_sales:'Sem informação de vendas',unsupported_basis:'Base líquida não determinada',indeterminate:'Enquadramento ou regra indeterminados'}[row.status] ?? 'Indeterminado'}</td></tr>)}</tbody></table></div>}
|
||||
{snapshot && <details><summary>Ver fundamento e relatório da versão guardada</summary><dl>{fiscalQaRows(snapshot).map(([label,value], index) => <div key={index}><dt className="font-semibold">{label}</dt><dd>{value}</dd></div>)}</dl></details>}
|
||||
<SimulationOperations draft={draft} onChange={onChange} />
|
||||
<SimulationAcquisitions draft={draft} onChange={onChange} />
|
||||
<SimulationModeloA draft={draft} />
|
||||
</section>;
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import { useState } from 'react';
|
||||
import { newSalesOperation, operationTreatments, recordSales, reuseSales, salesBook, salesCurrent, salesReportRows, saveSalesBook, splitSales, type SalesBook, type SalesOperation } from '@/lib/iva-operations';
|
||||
|
||||
export function SimulationOperations({draft,onChange}:{draft:Record<string,unknown>;onChange:(field:string,value:string)=>void}) {
|
||||
const [error,setError]=useState('');
|
||||
let book:SalesBook;
|
||||
try{book=salesBook(draft);}catch(cause){return <p role="alert">{String(cause)}</p>;}
|
||||
const run=(action:()=>void)=>{try{action();setError('');}catch(cause){setError(cause instanceof Error?cause.message:String(cause));}};
|
||||
const save=(next:SalesBook)=>onChange('modelo_a_snapshot',saveSalesBook(draft,next));
|
||||
const change=(id:string,patch:Partial<SalesOperation>)=>run(()=>save({...book,rows:book.rows.map(r=>r.id===id?{...r,...patch}:r)}));
|
||||
return <section aria-label="Operações IVA por oportunidade" className="space-y-3 rounded border p-3">
|
||||
<h4 className="font-semibold">Operações de IVA — enquadramento das vendas</h4>
|
||||
<p>Oportunidade {String(draft.context_opportunity||'entrada geral')}; moeda original {book.currency||'por indicar'}. Cobertura2026/MZN. Reutilize as vendas e complete apenas o enquadramento. Este bloco classifica as operações; não substitui nem soma novamente a execução mensal geral acima.</p>
|
||||
<p>Uma empresa pode ter operações com tratamentos diferentes. Os valores são projecções sob pressupostos declarados, sem certificar facturas, reconhecer créditos ou submeter uma declaração à AT.</p>
|
||||
{error&&<p role="alert">{error}</p>}
|
||||
<button type="button" className="underline" onClick={()=>run(()=>save(reuseSales(draft,book)))}>Reutilizar vendas e períodos existentes</button>
|
||||
<button type="button" className="ml-3 underline" disabled={!book.periods.length} onClick={()=>run(()=>save({...book,rows:[...book.rows,newSalesOperation(crypto.randomUUID(),book.periods[0].period,book.currency)]}))}>Acrescentar operação em falta</button>
|
||||
{book.rows.map(row=>{const rule=operationTreatments.find(r=>r.id===row.treatment);return <details className="rounded border p-3" key={row.id}><summary>{row.description||'Operação adicional'} — {row.period}</summary>
|
||||
<p>Origem: {row.source}; {row.sourceAmount??'sem valor de origem'} {row.sourceCurrency}; {row.sourcePeriod}. O estabelecimento não é inferido da localização do projecto.</p>
|
||||
<div className="grid gap-3 md:grid-cols-2">
|
||||
<label>Descrição da operação<input className="block rounded border p-2" value={row.description} onChange={e=>change(row.id,{description:e.target.value})}/></label>
|
||||
<label>Estabelecimento<input className="block rounded border p-2" value={row.establishment} onChange={e=>change(row.id,{establishment:e.target.value})}/></label>
|
||||
<label>Período<input type="month" className="block rounded border p-2" min="2026-01" max="2026-12" value={row.period} onChange={e=>change(row.id,{period:e.target.value})}/></label>
|
||||
<label>Valor declarado<input inputMode="decimal" className="block rounded border p-2" value={row.amount} onChange={e=>change(row.id,{amount:e.target.value})}/></label>
|
||||
<label>Representação do preço<select className="block rounded border p-2" value={row.basis} onChange={e=>change(row.id,{basis:e.target.value as SalesOperation['basis']})}><option value="">Ainda desconhecida</option><option value="net">Sem IVA</option><option value="included">IVA incluído</option></select></label>
|
||||
<label>Tratamento da operação<select className="block rounded border p-2" value={row.treatment} onChange={e=>change(row.id,{treatment:e.target.value,confirmed:[]})}>{operationTreatments.map(t=><option value={t.id} key={t.id}>{t.label}</option>)}</select></label>
|
||||
</div>
|
||||
<p>{rule?.article}; {rule?.source}. Aplicação nesta versão: Janeiro–Dezembro2026, sujeita às condições seguintes.</p>
|
||||
{rule?.conditions.map((condition,index)=><label className="block" key={index}><input type="checkbox" checked={row.confirmed.includes(index)} onChange={e=>change(row.id,{confirmed:e.target.checked?[...row.confirmed,index]:row.confirmed.filter(i=>i!==index)})}/> {condition}</label>)}
|
||||
<label className="block">Documento ou pressuposto que sustenta estas condições<textarea className="block w-full rounded border p-2" value={row.evidence} onChange={e=>change(row.id,{evidence:e.target.value})}/></label>
|
||||
<p>Se precisar de repartir uma venda agregada, conserve o valor de origem e prepare linhas discriminadas. Não altere silenciosamente a sua representação monetária.</p>
|
||||
<button type="button" className="mr-3 underline" onClick={()=>run(()=>save(splitSales(book,row.id,crypto.randomUUID())))}>Repartir explicitamente esta venda, conservando o total de origem</button>
|
||||
<button type="button" className="underline" onClick={()=>run(()=>save({...book,rows:book.rows.filter(r=>r.id!==row.id),periods:book.periods.map(p=>p.period===row.period?{...p,complete:false}:p)}))}>Remover esta linha do preenchimento (resultado anterior conservado)</button>
|
||||
</details>;})}
|
||||
{book.periods.map(p=><label className="block" key={p.period}><input type="checkbox" checked={p.complete} onChange={e=>run(()=>save({...book,periods:book.periods.map(v=>v.period===p.period?{...v,complete:e.target.checked}:v)}))}/> {p.period}: incluí todas as operações deste período; lista vazia significa ausência expressamente confirmada.</label>)}
|
||||
<p>Limites: outras isenções/bases especiais e correspondência do campo06 permanecem por delimitar. IVA incluído a5% só é calculado quando a inversão é exacta até6casas; nos restantes casos falta delimitar o arredondamento e não se usa divisor116. O desconhecido nunca é assumido como zero.</p>
|
||||
<button type="button" disabled={!book.periods.length} className="rounded bg-emerald-800 p-2 text-white disabled:opacity-50" onClick={()=>run(()=>save(recordSales(draft,book)))}>Calcular e guardar operações IVA</button>
|
||||
{book.result&&<><p role="status">{salesCurrent(draft,book)?'Resultado correspondente às entradas.':'Resultado histórico conservado; reveja as alterações antes de recalcular.'}</p><dl>{salesReportRows(draft).map(([,label,value,origin],index)=><div className="break-inside-avoid border-t py-2" key={index}><dt className="font-semibold">{label}</dt><dd>{value}<small className="block">{origin}</small></dd></div>)}</dl></>}
|
||||
</section>;
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { modeloAPurchaseReferences } from './iva-modelo-a.ts';
|
||||
import { monthlyIvaInput, monthlyIvaKey, readMonthlyIvaRecord, readMonthlyIvaSettings } from './monthly-iva.ts';
|
||||
import { restoreBusinessPlanSnapshot } from './business-financial-plan.ts';
|
||||
import { salesBook, salesCurrent as salesOperationsCurrent } from './iva-operations.ts';
|
||||
|
||||
export const acquisitionVersion = 'modelo-a-internal-acquisitions/1';
|
||||
export const acquisitionBasis = 'CIVA arts.18(1)(a),18(2) (Lei10/2025),18(3–5),19(1)(a),19(2),20 (Lei22/2022),21(1–3); âmbito interno, afectação exclusiva, mesmo período, sem exclusões ou dedução parcial.';
|
||||
@@ -104,7 +105,8 @@ function sourceKey(draft:Record<string,unknown>,book:AcquisitionBook):string {
|
||||
const raw=JSON.stringify([acquisitionScope(draft),draft.currency??'',draft.province_id??'',draft.district_id??'',draft.business_plan_snapshot,draft.monthly_iva_settings,draft.monthly_iva_snapshot,book.currency,book.taxpayerRegistered,book.rows,book.periods]);
|
||||
// Change detection only, never an authorization or authenticity token.
|
||||
let hash=14695981039346656037n;for(let i=0;i<raw.length;i++)hash=BigInt.asUintN(64,(hash^BigInt(raw.charCodeAt(i)))*1099511628211n);
|
||||
return `${raw.length}:${hash.toString(16)}`;
|
||||
const operations=(readAcquisitionStore(draft.modelo_a_snapshot) as Store & {salesOperations?:Record<string,unknown>}).salesOperations?.[acquisitionScope(draft)];
|
||||
return `${raw.length}:${hash.toString(16)}${operations?`:${JSON.stringify(operations)}`:''}`;
|
||||
}
|
||||
function location(draft:Record<string,unknown>):string {
|
||||
const labels=draft.selection_labels as Record<string,unknown>|undefined;
|
||||
@@ -139,9 +141,20 @@ export function calculateAcquisitions(draft:Record<string,unknown>,book:Acquisit
|
||||
fields['01']=sales?.net_sales??null;fields['02']=sales?.vat??null;
|
||||
if(!sales || sales.net_sales===null || sales.vat===null) reasons.push('01/02: vendas ou IVA mensal em falta, execução incompleta ou desactualizada.');
|
||||
for(const field of excluded) fields[field]=input?.absent.includes(field)?'0.00':null;
|
||||
const operations=salesBook(draft);
|
||||
if(operations.rows.length || operations.result) {
|
||||
const classified=salesOperationsCurrent(draft,operations)?operations.result?.periods.find(p=>p.period===period):null;
|
||||
for(const field of ['01','02','03','04','05','06','07'])fields[field]=classified?.fields[field]??null;
|
||||
if(!classified)reasons.push('Operações L1 alteradas ou sem resultado correspondente: recalcule a classificação; não usar01/02 antigos nem ausência presumida.');
|
||||
else reasons.push(...classified.reasons);
|
||||
}
|
||||
for(const field of ['08','09','10']) {
|
||||
const matching=book.rows.filter(r=>r.period===period && r.field===field);
|
||||
fields[field]=input?.complete ? sum(matching.map(r=>rows.find(x=>x.id===r.id)!.deductible)) : null;
|
||||
if((operations.rows.length || operations.result) && operations.rows.some(r=>r.period===period && !['general','export_goods'].includes(r.treatment)) && matching.some(r=>rows.find(x=>x.id===r.id)!.deductible!=='0.00')) {
|
||||
fields[field]=null;
|
||||
reasons.push(`Campo${field}: operações reduzidas/isentas/outras exigem afectar cada aquisição; não reutilizar dedução exclusiva geral como se fosse universal (L2).`);
|
||||
}
|
||||
}
|
||||
if(!input?.complete) reasons.push('Aquisições08–10: confirme que todas as compras do período foram incluídas, mesmo quando não existam.');
|
||||
for(const row of book.rows.filter(r=>r.period===period)) {const r=rows.find(x=>x.id===row.id)!;if(r.deductible===null) reasons.push(`${row.description||row.id}: ${r.reason}`);}
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
import { acquisitionScope, readAcquisitionStore } from './iva-acquisitions.ts';
|
||||
import { monthlySalesFromDraft, readMonthlyIvaSettings } from './monthly-iva.ts';
|
||||
|
||||
export const operationVersion = 'iva-operations-l1/1';
|
||||
const source2022 = 'Lei22/2022, BR250, 2.º suplemento, PDF1–4 (2252(25)–(28)); arts.1/2/6; efeitos01/01/2023';
|
||||
const source2016 = 'CIVA republicado Lei13/2016, BR156, 10.º suplemento, PDF9–15 (1426(267)–(273))';
|
||||
const source2025 = 'Lei10/2025, BR248, suplemento1, PDF14–15 (2180(14)–(15)); arts.1/2/3/5; efeitos01/01/2026';
|
||||
export type OperationTreatment = {id:string; label:string; field:string|null; rate:number|null; article:string; source:string; conditions:string[]};
|
||||
export const operationTreatments:OperationTreatment[] = [
|
||||
{id:'general',label:'Operação interna geral',field:'01',rate:16,article:'CIVA arts.1,6,15(1),17(1–2); art.34 (Lei10/2025)',source:`${source2016}; ${source2022}; ${source2025}`,conditions:['Operação localizada e tributável em Moçambique, no regime normal','Contraprestação completa; sem base especial do art.15(2), isenção ou taxa reduzida']},
|
||||
{id:'private_health',label:'Serviços médicos e sanitários privados',field:'03',rate:5,article:'CIVA arts.15(10),17-A(a)',source:source2022,conditions:['Serviço médico/sanitário ou operação estreitamente conexa','Prestador hospitalar privado, clínica, dispensário ou similar']},
|
||||
{id:'private_education',label:'Ensino privado reconhecido',field:'03',rate:5,article:'CIVA arts.15(10),17-A(b)',source:source2022,conditions:['Ensino ou bens/serviços conexos prestados pelo estabelecimento','Estabelecimento privado integrado no Sistema Nacional de Ensino e reconhecido pelo Ministério competente']},
|
||||
{id:'private_training',label:'Formação profissional privada',field:'03',rate:5,article:'CIVA arts.15(10),17-A(c)',source:source2022,conditions:['Formação profissional ou fornecimentos conexos pelo prestador, não venda autónoma de materiais','Prestador privado']},
|
||||
{id:'personal_lessons',label:'Lições ministradas a título pessoal',field:'03',rate:5,article:'CIVA arts.15(10),17-A(d)',source:source2022,conditions:['Lições ministradas a título pessoal','Matérias de ensino escolar ou superior']},
|
||||
{id:'export_goods',label:'Exportação de bens pelo vendedor',field:'05',rate:0,article:'CIVA arts.13(1)(a),19(1)(b)(i)',source:source2016,conditions:['Bens expedidos/transportados para o estrangeiro pelo vendedor ou terceiro por sua conta','Saída documentada, ou pressuposto expresso da projecção; não mera venda a cliente estrangeiro']},
|
||||
{id:'housing_rent',label:'Locação de imóvel para habitação',field:'07',rate:0,article:'CIVA art.9(5), redacção Lei22/2022; art.19(1)',source:source2022,conditions:['Locação de imóvel para fins de habitação','Não é hospedagem nem locação comercial/industrial ou serviço autónomo']},
|
||||
{id:'public_health',label:'Serviços médicos e sanitários públicos',field:'07',rate:0,article:'CIVA arts.9(1)(a),10(1),19(1)',source:source2022,conditions:['Serviço médico/sanitário ou operação estreitamente conexa','Estabelecimento hospitalar público, dispensário ou similar; natureza pública confirmada']},
|
||||
{id:'public_education',label:'Ensino público reconhecido',field:'07',rate:0,article:'CIVA arts.9(3)(a),10(1),19(1)',source:source2022,conditions:['Ensino ou fornecimentos conexos pelo estabelecimento','Estabelecimento público integrado no Sistema Nacional de Ensino e reconhecido pelo Ministério competente']},
|
||||
{id:'public_training',label:'Formação profissional pública',field:'07',rate:0,article:'CIVA arts.9(3)(b),10(1),19(1)',source:source2022,conditions:['Formação profissional ou fornecimentos conexos pelo prestador','Prestador público']},
|
||||
{id:'foreign_operation',label:'Operação no estrangeiro — correspondência documental pendente',field:null,rate:null,article:'CIVA arts.6 e19(1)(b)(ii); remissão do campo06 do exemplar para18(1)(b) divergente',source:source2016,conditions:[]},
|
||||
{id:'other',label:'Outra operação / enquadramento ainda desconhecido',field:null,rate:null,article:'Fundamento específico ainda necessário; não presumir taxa ou isenção',source:'Sem fonte operacional determinada para esta linha',conditions:[]},
|
||||
];
|
||||
export type SalesOperation = {id:string;period:string;description:string;establishment:string;amount:string;basis:''|'net'|'included';treatment:string;confirmed:number[];evidence:string;sourceAmount:string|null;sourcePeriod:string;sourceCurrency:string;source:string; splitSource?:{id:string;amount:string|null;period:string;currency:string;basis:string}};
|
||||
export type SalesBook = {currency:string;rows:SalesOperation[];periods:Array<{period:string;complete:boolean}>; result?:SalesResult; history?:SalesResult[]};
|
||||
export type SalesLineResult = {id:string;field:string|null;net:string|null;vat:string|null;gross:string|null;rate:number|null;status:'calculated'|'exempt'|'indeterminate';reason:string;article:string;source:string};
|
||||
export type SalesResult = {version:string;revision:number;calculatedAt:string;opportunity:string;location:string;currency:string;key:string;inputs:SalesOperation[];rows:SalesLineResult[];periods:Array<{period:string;fields:Record<string,string|null>;reasons:string[]}>;rounding:string};
|
||||
type Store = ReturnType<typeof readAcquisitionStore> & {salesOperations?:Record<string,SalesBook>};
|
||||
export function newSalesOperation(id:string,period:string,currency:string):SalesOperation {return {id,period,description:'',establishment:'',amount:'',basis:'',treatment:'other',confirmed:[],evidence:'',sourceAmount:null,sourcePeriod:period,sourceCurrency:currency,source:'Introdução directa; projecção, não declaração submetida'};}
|
||||
export function salesBook(draft:Record<string,unknown>):SalesBook {
|
||||
const store=readAcquisitionStore(draft.modelo_a_snapshot) as Store;
|
||||
if(store.salesOperations!==undefined&&(!store.salesOperations||typeof store.salesOperations!=='object'||Array.isArray(store.salesOperations)))throw new Error('Formato das operações IVA não recuperável; dados preservados, sem substituição.');
|
||||
const book=store.salesOperations?.[acquisitionScope(draft)];
|
||||
if(!book)return {currency:String(draft.currency??''),rows:[],periods:[]};
|
||||
if(typeof book.currency!=='string'||!Array.isArray(book.rows)||!Array.isArray(book.periods)||book.rows.some(r=>!r||Object.keys(newSalesOperation('','','')).some(k=>k==='confirmed'?!Array.isArray(r.confirmed)||r.confirmed.some(c=>!Number.isInteger(c)):k==='sourceAmount'?!(r.sourceAmount===null||typeof r.sourceAmount==='string'):typeof r[k as keyof SalesOperation]!=='string'))||book.periods.some(p=>!p||!/^\d{4}-\d{2}$/.test(p.period)||typeof p.complete!=='boolean'))throw new Error('Operações IVA não recuperáveis; os dados originais foram preservados.');
|
||||
return book;
|
||||
}
|
||||
export function saveSalesBook(draft:Record<string,unknown>,book:SalesBook):string {const store=readAcquisitionStore(draft.modelo_a_snapshot) as Store;return JSON.stringify({...store,salesOperations:{...store.salesOperations,[acquisitionScope(draft)]:book}});}
|
||||
export function reuseSales(draft:Record<string,unknown>,book:SalesBook):SalesBook {
|
||||
const source=monthlySalesFromDraft(draft),settings=readMonthlyIvaSettings(String(draft.monthly_iva_settings??''));
|
||||
if(!/^2026-(0[1-9]|1[0-2])$/.test(settings.start)||Number(settings.start.slice(5))+source.amounts.length>13)throw new Error('Defina o período da projecção dentro de2026 antes de reutilizar vendas.');
|
||||
if(book.currency!==draft.currency)throw new Error('Moeda da origem divergente; valores preservados, sem conversão.');
|
||||
const rows=[...book.rows],periods=[...book.periods];
|
||||
source.amounts.forEach((value,index)=>{const period=`2026-${String(Number(settings.start.slice(5))+index).padStart(2,'0')}`;
|
||||
if(!periods.some(p=>p.period===period))periods.push({period,complete:false});
|
||||
if(!rows.some(r=>r.sourcePeriod===period&&r.source!=='Introdução directa; projecção, não declaração submetida'))rows.push({...newSalesOperation(`sales-${period}`,period,book.currency),description:`Vendas do plano — ${period}`,amount:value??'',basis:settings.basis==='unknown'?'':settings.basis,sourceAmount:value,source:source.source,evidence:source.source});
|
||||
});return {...book,rows,periods};
|
||||
}
|
||||
export function splitSales(book:SalesBook,id:string,newId:string):SalesBook {
|
||||
const row=book.rows.find(r=>r.id===id);if(!row)throw new Error('Operação de origem não encontrada.');
|
||||
if(row.sourceAmount===null&&!row.splitSource)throw new Error('Reparta uma venda reutilizada com valor de origem determinado; para entradas directas acrescente as operações em falta.');
|
||||
const source=row.splitSource??{id:row.id,amount:row.sourceAmount??row.amount,period:row.period,currency:row.sourceCurrency,basis:row.basis};
|
||||
if(!source.basis||source.amount===null||amount(source.amount)===null)throw new Error('Determine o valor e a representação do preço antes de repartir a origem.');
|
||||
const first={...row,amount:'',sourceAmount:null,splitSource:source,source:`Repartição explícita de ${source.id}: ${source.amount} ${source.currency}; ${row.source}`};
|
||||
return {...book,rows:[...book.rows.map(r=>r.id===id?first:r),{...first,id:newId,description:'Parcela adicional da mesma venda',treatment:'other',confirmed:[],evidence:''}],periods:book.periods.map(p=>p.period===row.period?{...p,complete:false}:p)};
|
||||
}
|
||||
export function recordSales(draft:Record<string,unknown>,book:SalesBook):SalesBook {return {...book,history:book.result?[...(book.history??[]),book.result]:book.history,result:calculateSales(draft,book)};}
|
||||
function amount(raw:unknown):bigint|null {if(typeof raw!=='string'||!/^\d{1,15}(\.\d{1,6})?$/.test(raw.trim()))return null;const [n,d='']=raw.trim().split('.');return BigInt(n)*1000000n+BigInt(d.padEnd(6,'0'));}
|
||||
function money(n:bigint):string {return `${n/1000000n}.${(n%1000000n).toString().padStart(6,'0').replace(/0+$/,'').padEnd(2,'0')}`;}
|
||||
function sum(values:Array<string|null>):string|null {const parsed=values.map(amount);return parsed.some(v=>v===null)?null:money((parsed as bigint[]).reduce((a,b)=>a+b,0n));}
|
||||
export function evaluateSalesOperation(row:SalesOperation,currency:string):SalesLineResult {
|
||||
const rule=operationTreatments.find(r=>r.id===row.treatment),n=amount(row.amount);
|
||||
const result:SalesLineResult={id:row.id,field:rule?.field??null,net:null,vat:null,gross:null,rate:rule?.rate??null,status:'indeterminate',reason:'',article:rule?.article??'Tratamento desconhecido',source:rule?.source??'Fonte em falta'};
|
||||
const stop=(reason:string)=>({...result,reason});
|
||||
if(currency!=='MZN'||row.sourceCurrency!==currency)return stop('Moeda fora de MZN ou divergente da origem; sem conversão silenciosa.');
|
||||
if(!/^2026-(0[1-9]|1[0-2])$/.test(row.period))return stop('Período fora da cobertura de2026; sem cálculo histórico presumido.');
|
||||
if(!rule||rule.rate===null||!rule.field)return stop('Operação não calculada: delimite incidência e fundamento; campo06 mantém divergência de remissão, sem impedir outras linhas.');
|
||||
if(n===null||!['net','included'].includes(row.basis))return stop('Montante ou indicação de IVA incluído/sem IVA em falta; vazio não significa zero.');
|
||||
if(!row.description.trim()||!row.establishment.trim())return stop('Indique a operação e o estabelecimento a que pertence; não inferidos da localização do investimento.');
|
||||
if(rule.conditions.some((_,i)=>!row.confirmed.includes(i))||!row.evidence.trim())return stop('Condições da operação ou fundamento/pressuposto em falta; o sector não prova o tratamento fiscal.');
|
||||
if(row.sourceAmount!==null&&amount(row.sourceAmount)!==n)return stop('Divergência com o valor reutilizado: conserve a origem e prepare a repartição explicitamente; não substituir silenciosamente.');
|
||||
if(row.basis==='included'&&rule.rate===5&&(n*100n)%105n!==0n)return stop('Preço com IVA a5%: a inversão exacta excede6casas; falta delimitar o arredondamento aplicável. Art.34, redacção2026, explicita divisor116; não reutilizar esse divisor nem inventar arredondamento para105. Pode indicar base líquida documentada.');
|
||||
let net=n,vat=0n;
|
||||
if(rule.rate===16&&row.basis==='included') {net=((n*100n+58000000n)/116000000n)*1000000n;vat=n-net;if(vat<0n)return stop('Arredondamento à unidade produz IVA negativo para este montante; reveja a linha e a documentação.');}
|
||||
else if(rule.rate===5&&row.basis==='included'){net=n*100n/105n;vat=n-net;}
|
||||
else vat=(n*BigInt(rule.rate)+50n)/100n;
|
||||
return {...result,net:money(net),vat:money(vat),gross:money(row.basis==='included'?n:net+vat),status:rule.rate===0?'exempt':'calculated',reason:rule.rate===0?(rule.field==='05'?'Isenção com direito à dedução nas condições legais; não é taxa zero nem dedução automática de compras.':'Isenção sem direito à dedução para esta operação; não é isenção geral da entidade.'):'IVA da operação projectada; não é imposto final a pagar nem declaração submetida.'};
|
||||
}
|
||||
function key(draft:Record<string,unknown>,book:SalesBook):string {return JSON.stringify([operationVersion,operationTreatments,acquisitionScope(draft),draft.currency,draft.province_id,draft.district_id,draft.business_plan_snapshot,draft.cashflow_snapshot,draft.monthly_iva_settings,book.currency,book.rows,book.periods]);}
|
||||
export function salesCurrent(draft:Record<string,unknown>,book:SalesBook):boolean {return !!book.result&&book.result.version===operationVersion&&book.result.opportunity===acquisitionScope(draft)&&book.result.key===key(draft,book);}
|
||||
export function calculateSales(draft:Record<string,unknown>,book:SalesBook,now=new Date().toISOString()):SalesResult {
|
||||
if(book.currency!=='MZN'||draft.currency!==book.currency)throw new Error('Confirme a moeda original MZN; sem conversão ou reinterpretação.');
|
||||
if(new Set(book.rows.map(r=>r.id)).size!==book.rows.length||new Set(book.periods.map(p=>p.period)).size!==book.periods.length)throw new Error('Identificadores/períodos duplicados; corrija sem somar em duplicado.');
|
||||
if(!book.periods.length||book.rows.some(r=>!book.periods.some(p=>p.period===r.period))||book.periods.some(p=>!/^2026-(0[1-9]|1[0-2])$/.test(p.period)))throw new Error('Cada operação deve pertencer a um período válido de2026 incluído na cobertura.');
|
||||
let live:ReturnType<typeof monthlySalesFromDraft>|null=null;try {live=monthlySalesFromDraft(draft);}catch{ /* Explicitly block stale imported values below. */ }
|
||||
const start=readMonthlyIvaSettings(String(draft.monthly_iva_settings??'')).start;
|
||||
const rows=book.rows.map(r=>{
|
||||
const result=evaluateSalesOperation(r,book.currency);
|
||||
if(r.splitSource) {
|
||||
const source=r.splitSource,group=book.rows.filter(v=>v.splitSource?.id===source.id),index=Number(source.period.slice(5))-Number(start.slice(5));
|
||||
if(group.some(v=>v.period!==source.period||v.basis!==source.basis||v.sourceCurrency!==source.currency||JSON.stringify(v.splitSource)!==JSON.stringify(source))||sum(group.map(v=>v.amount))!==(amount(source.amount)===null?null:money(amount(source.amount)!))||!live||live.amounts[index]!==source.amount)return {...result,net:null,vat:null,gross:null,status:'indeterminate' as const,reason:'Repartição não reconciliada com o total, período, moeda e representação do preço da origem; nenhuma parcela foi presumida.'};
|
||||
}
|
||||
if(r.sourceAmount!==null||r.source.startsWith('Vendas por produto')||r.source.startsWith('Receita mensal')) {
|
||||
const index=Number(r.sourcePeriod.slice(5))-Number(start.slice(5));
|
||||
if(!live||!/^2026-/.test(start)||r.sourcePeriod!==r.period||live.amounts[index]!==r.sourceAmount)return {...result,net:null,vat:null,gross:null,status:'indeterminate' as const,reason:'A origem/período das vendas mudou. Reveja e confirme a nova origem; o resultado anterior foi conservado.'};
|
||||
}
|
||||
return result;
|
||||
});
|
||||
const periods=book.periods.map(p=>{
|
||||
const matching=book.rows.map((r,i)=>({input:r,result:rows[i]})).filter(r=>r.input.period===p.period),fields:Record<string,string|null>={},reasons=matching.filter(r=>r.result.status==='indeterminate').map(r=>`${r.input.description||r.input.id}: ${r.result.reason}`);
|
||||
const known=p.complete&&!matching.some(r=>r.result.status==='indeterminate');
|
||||
for(const f of ['01','03','05','07'])fields[f]=known?sum(matching.filter(r=>r.result.field===f).map(r=>r.result.net)):null;
|
||||
for(const [f,base] of [['02','01'],['04','03']])fields[f]=known?sum(matching.filter(r=>r.result.field===base).map(r=>r.result.vat)):null;
|
||||
fields['06']=known?'0.00':null;
|
||||
if(!p.complete)reasons.push('Confirme que todas as operações do período foram incluídas; uma lista vazia só é zero após essa confirmação.');
|
||||
return {period:p.period,fields,reasons};
|
||||
});
|
||||
const labels=draft.selection_labels as Record<string,unknown>|undefined;
|
||||
return {version:operationVersion,revision:(book.result?.revision??0)+1,calculatedAt:now,opportunity:acquisitionScope(draft),location:['province_id','district_id'].filter(k=>draft[k]).map(k=>String(labels?.[k]??draft[k])).join(' / '),currency:book.currency,key:key(draft,book),inputs:structuredClone(book.rows),rows,periods,rounding:'Base líquida: precisão técnica6decimais, metade para cima, sem arredondamento final da declaração. Preço geral com IVA: base=preço×100/116, arredondada à unidade mais próxima (metade para cima), IVA=preço−base, CIVA art.34 redacção Lei10/2025. Preço incluído5%: apenas inversão exacta da contraprestação×1,05 (arts.15(10)/17-A), sem arredondar; quando exige mais de6casas permanece indeterminado.'};
|
||||
}
|
||||
export function salesReportRows(draft:Record<string,unknown>):Array<[string,string,string,string]> {
|
||||
try {const book=salesBook(draft),r=book.result;if(!r)return [];
|
||||
const context=`${r.currency}; oportunidade ${r.opportunity}; ${r.location||'localização não indicada'}; ${r.version}; revisão ${r.revision}; ${r.calculatedAt}`;
|
||||
const out:Array<[string,string,string,string]>=[['IVA mensal','Operações IVA L1',salesCurrent(draft,book)?'Resultado correspondente às entradas':'Histórico desactualizado; não aplicar à projecção actual',context],['IVA mensal','Método e limites L1',r.rounding,'Sem apuramento integral, dedução automática ou submissão AT']];
|
||||
for(const row of r.inputs){const value=r.rows.find(v=>v.id===row.id)!;out.push(['IVA mensal',`${row.period} — ${row.description} — ${row.establishment}`,`Declarado ${row.amount||'em falta'} ${row.sourceCurrency}; ${row.basis==='included'?'IVA incluído':row.basis==='net'?'sem IVA':'base desconhecida'}; líquido ${value.net??'Indeterminado'}; IVA ${value.vat??'Indeterminado'}; bruto ${value.gross??'Indeterminado'}; campo ${value.field??'não determinado'}; ${value.reason}`,`${context}; ${value.article}; ${value.source}; origem: ${row.source}; pressuposto/documento: ${row.evidence||'em falta'}; condições confirmadas: ${row.confirmed.join(',')||'nenhuma'}`]);}
|
||||
for(const p of r.periods)out.push(['IVA mensal',`Operações L1 — ${p.period}`,Object.entries(p.fields).map(([f,v])=>`${f}: ${v??'Indeterminado'}`).join('; '),`${context}; ${p.reasons.join(' ')}`]);return out;
|
||||
}catch{return [['IVA mensal','Operações IVA L1','Não foi possível recuperar as operações guardadas','Originais preservados; sem substituição por zero']];}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import { investmentLandPreparation } from './investment-land-preparation.ts';
|
||||
import { monthlyIvaInput, monthlyIvaKey, readMonthlyIvaRecord, readMonthlyIvaSettings } from './monthly-iva.ts';
|
||||
import { modeloAPurchaseReferences } from './iva-modelo-a.ts';
|
||||
import { acquisitionReportRows } from './iva-acquisitions.ts';
|
||||
import { salesReportRows } from './iva-operations.ts';
|
||||
import { fiscalQaRows } from './fiscal-qa-preview.ts';
|
||||
import { csvCellWithDelimiter } from './csv.js';
|
||||
import {
|
||||
@@ -616,12 +617,14 @@ export function buildProjectDossierRows(
|
||||
]);
|
||||
}
|
||||
rows.push(...acquisitionReportRows(draft));
|
||||
const operationRows = salesReportRows(draft);
|
||||
rows.push(...operationRows);
|
||||
if (draft.monthly_iva_snapshot) {
|
||||
const saved = readMonthlyIvaRecord(String(draft.monthly_iva_snapshot));
|
||||
let current = false;
|
||||
try { current = !!saved && monthlyIvaKey(monthlyIvaInput(draft, readMonthlyIvaSettings(String(draft.monthly_iva_settings ?? '')))) === monthlyIvaKey(saved.input); } catch { /* An old fiscal version is never silently made current. */ }
|
||||
rows.push(['IVA mensal', 'Estado da versão', current ? 'Entradas correspondentes à projecção guardada' : 'Histórico desactualizado face às vendas ou pressupostos actuais; não aplicar como resultado actual', 'Execução versionada no servidor; sem apuramento final']);
|
||||
if (saved?.execution.result_snapshot) for (const [label,value] of fiscalQaRows(saved.execution.result_snapshot)) rows.push(['IVA mensal', label, value, 'Snapshot fiscal preservado; sem recálculo na exportação']);
|
||||
if (saved?.execution.result_snapshot) for (const [label,value] of fiscalQaRows(saved.execution.result_snapshot)) rows.push(['IVA mensal', operationRows.length ? `Execução geral separada — ${label}` : label, value, operationRows.length ? 'Snapshot geral conservado como execução separada; não somar nem substituir a classificação por operação L1. Sem recálculo na exportação.' : 'Snapshot fiscal preservado; sem recálculo na exportação']);
|
||||
else rows.push(['IVA mensal', 'Resultado', 'Indeterminado ou execução ainda não concluída', 'Sem substituição por zero']);
|
||||
try {
|
||||
for (const purchase of modeloAPurchaseReferences(draft)) rows.push(['IVA mensal', `Compras/encomendas — mês ${purchase.month}`, purchase.declared ?? 'Sem informação', 'Pressuposto actual do plano, não resultado fiscal histórico; base/IVA incluído por classificar; IVA suportado e dedutível indeterminados; não é data de factura ou pagamento']);
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
<?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 IvaOperationsDraftTest extends TestCase
|
||||
{
|
||||
use DatabaseTransactions;
|
||||
|
||||
public function test_l1_ui_payload_round_trips_without_duplicate_drafts_or_new_rules_and_other_owner_cannot_read(): void
|
||||
{
|
||||
$identity = DB::selectOne('select current_database() as name,inet_server_port() as port');
|
||||
$this->assertSame('simulator_fiscal_delta_20261021_test', $identity->name);
|
||||
$this->assertSame(55441, (int) $identity->port);
|
||||
config(['investor-path.enabled' => true, 'suitecrm.enabled' => false]);
|
||||
$fingerprint = DB::selectOne("select md5(string_agg(row_to_json(r)::text,E'\\n' order by id)) as hash from simulation_tax_rule_versions r")->hash;
|
||||
$this->actingAs(User::factory()->create(['email_verified_at' => now()]));
|
||||
$path = getenv('IVA_L1_UI_INPUT') ?: base_path('tests/frontend/fixtures/iva-operations.json');
|
||||
$draft = json_decode(file_get_contents($path), true, flags: JSON_THROW_ON_ERROR);
|
||||
$draft['purpose'] = 'simulate_only';
|
||||
$key = (string) Str::uuid();
|
||||
$payload = ['personal_snapshot' => [], 'economic_snapshot' => $draft];
|
||||
$created = $this->postJson('/api/v1/simulation-drafts', $payload, ['Idempotency-Key' => $key])->assertCreated();
|
||||
$id = $created->json('data.id');
|
||||
$retry = $this->postJson('/api/v1/simulation-drafts', $payload, ['Idempotency-Key' => $key]);
|
||||
$this->assertSame($id, $retry->json('data.id'));
|
||||
$saved = $this->getJson('/api/v1/simulation-drafts/'.$id)->assertOk();
|
||||
$economic = $saved->json('data.economic_snapshot');
|
||||
$this->assertSame($draft['modelo_a_snapshot'], $economic['modelo_a_snapshot']);
|
||||
$this->assertSame($draft['context_opportunity'], $economic['context_opportunity']);
|
||||
$store = json_decode($economic['modelo_a_snapshot'], true, flags: JSON_THROW_ON_ERROR);
|
||||
$book = $store['salesOperations'][$draft['context_opportunity']];
|
||||
$this->assertSame('iva-operations-l1/1', $book['result']['version']);
|
||||
$this->assertCount(12, $book['result']['rows']);
|
||||
$this->assertSame('160.00', $book['result']['rows'][0]['vat']);
|
||||
$this->assertNull($book['result']['rows'][10]['vat']);
|
||||
if (getenv('IVA_L1_RECOVERED_OUT')) file_put_contents(getenv('IVA_L1_RECOVERED_OUT'), json_encode($economic, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE));
|
||||
$this->actingAs(User::factory()->create(['email_verified_at' => now()]));
|
||||
$this->getJson('/api/v1/simulation-drafts/'.$id)->assertNotFound();
|
||||
$this->assertSame($fingerprint, DB::selectOne("select md5(string_agg(row_to_json(r)::text,E'\\n' order by id)) as hash from simulation_tax_rule_versions r")->hash);
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,74 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import fs from 'node:fs';
|
||||
import {closureFixture} from './fixtures/simulator-closure-fixture.mjs';
|
||||
import {calculateSales,evaluateSalesOperation,newSalesOperation,operationTreatments,recordSales,reuseSales,salesBook,salesCurrent,salesReportRows,saveSalesBook,splitSales} from '../../resources/js/lib/iva-operations.ts';
|
||||
import {acquisitionBook,calculateAcquisitions,newAcquisition,saveAcquisitionBook} from '../../resources/js/lib/iva-acquisitions.ts';
|
||||
import {createProjectDossierReport} from '../../resources/js/lib/project-dossier-report.ts';
|
||||
|
||||
export function operationsFixture(){
|
||||
const draft=closureFixture().draft;draft.project_title='SYNTHETIC QA — L1 operações IVA';draft.province_id='cf5e81f1-ffbc-535d-b1b6-b44b445c4421';draft.selection_labels={province_id:'Manica'};
|
||||
const rows=operationTreatments.map((t,i)=>({...newSalesOperation(`qa-${i}`,'2026-01','MZN'),description:`SYNTHETIC QA ${t.label}`,establishment:'SYNTHETIC QA estabelecimento Manica',amount:t.id==='general'?'1161':'100',basis:t.id==='general'?'included':'net',treatment:t.id,confirmed:t.conditions.map((_,i)=>i),evidence:'SYNTHETIC QA — pressupostos de ensaio, não documento real'}));
|
||||
const book={currency:'MZN',rows,periods:[{period:'2026-01',complete:true}]};return {draft,book};
|
||||
}
|
||||
test('each enumerated treatment calculates only with its conditions, article and source',()=>{
|
||||
const {book}=operationsFixture();
|
||||
for(const row of book.rows){const r=evaluateSalesOperation(row,'MZN');if(['other','foreign_operation'].includes(row.treatment)){assert.equal(r.vat,null);continue;}
|
||||
assert.notEqual(r.vat,null,row.treatment);assert.ok(r.article.includes('CIVA'));assert.ok(r.source.includes('Lei'));
|
||||
assert.equal(evaluateSalesOperation({...row,confirmed:[]},'MZN').vat,null,row.treatment);
|
||||
assert.equal(evaluateSalesOperation({...row,evidence:''},'MZN').vat,null);
|
||||
}
|
||||
const by=Object.fromEntries(book.rows.map(r=>[r.treatment,evaluateSalesOperation(r,'MZN')]));assert.equal(by.private_health.vat,'5.00');assert.equal(by.export_goods.field,'05');assert.match(by.export_goods.reason,/não é taxa zero/);assert.equal(by.housing_rent.field,'07');
|
||||
});
|
||||
test('VAT-included general uses statutory unit rounding, conserves gross; 5 percent included is explicitly unresolved',()=>{
|
||||
const row=operationsFixture().book.rows[0];const r=evaluateSalesOperation(row,'MZN');assert.equal(r.net,'1001.00');assert.equal(r.vat,'160.00');assert.equal(r.gross,'1161.00');
|
||||
assert.equal(evaluateSalesOperation({...row,amount:'1160'},'MZN').net,'1000.00');
|
||||
assert.equal(evaluateSalesOperation({...row,basis:'net',amount:'1000.03'},'MZN').vat,'160.0048');
|
||||
assert.equal(evaluateSalesOperation({...row,basis:'',amount:'0'},'MZN').vat,null);
|
||||
assert.equal(evaluateSalesOperation({...row,amount:'0'},'MZN').vat,'0.00');
|
||||
const reduced=operationsFixture().book.rows[1];assert.match(evaluateSalesOperation({...reduced,basis:'included'},'MZN').reason,/arredondamento/);
|
||||
assert.equal(evaluateSalesOperation({...reduced,basis:'included',amount:'105'},'MZN').net,'100.00');
|
||||
assert.equal(evaluateSalesOperation({...reduced,basis:'included',amount:'105'},'MZN').vat,'5.00');
|
||||
});
|
||||
test('missing information, invalid dates/currency and legal ambiguity never become zero',()=>{
|
||||
const row=operationsFixture().book.rows[0];for(const patch of [{amount:''},{amount:'-1'},{period:'2025-12'},{period:'2026-13'},{establishment:''},{sourceCurrency:'USD'},{treatment:'invented'}])assert.equal(evaluateSalesOperation({...row,...patch},'MZN').vat,null);
|
||||
const {draft,book}=operationsFixture();book.result=calculateSales(draft,book);assert.equal(book.result.rows[0].vat,'160.00');assert.equal(book.result.periods[0].fields['02'],null);assert.match(book.result.periods[0].reasons.join(' '),/campo06/);
|
||||
book.rows=book.rows.filter(r=>!['other','foreign_operation'].includes(r.treatment));book.result=calculateSales(draft,book);assert.equal(book.result.periods[0].fields['04'],'20.00');assert.equal(book.result.periods[0].fields['07'],'400.00');assert.equal(book.result.periods[0].fields['05'],'100.00');
|
||||
book.periods[0].complete=false;assert.equal(calculateSales(draft,book).periods[0].fields['02'],null);
|
||||
book.rows=[];book.periods[0].complete=true;assert.equal(calculateSales(draft,book).periods[0].fields['02'],'0.00');
|
||||
});
|
||||
test('reuse preserves original sales, intentional edits and period; drift makes new calculation indeterminate',()=>{
|
||||
const {draft}=operationsFixture();let book=reuseSales(draft,salesBook(draft));assert.equal(book.rows[0].amount,'1000.03');assert.equal(book.rows[3].amount,'');assert.equal(book.rows[0].treatment,'other');
|
||||
book.rows[0].description='Edited';book=reuseSales(draft,book);assert.equal(book.rows.length,4);assert.equal(book.rows[0].description,'Edited');
|
||||
const template=operationsFixture().book.rows[0];book.rows[0]={...template,...book.rows[0],treatment:'general',confirmed:[0,1],establishment:'QA',evidence:'QA'};assert.notEqual(calculateSales(draft,book).rows[0].vat,null);
|
||||
book.rows[0].amount='900';assert.equal(calculateSales(draft,book).rows[0].vat,null);
|
||||
assert.throws(()=>reuseSales({...draft,currency:'USD'},book),/moeda/i);
|
||||
});
|
||||
test('opportunity-specific persistence, report and stale history preserve the first when only the second changes',()=>{
|
||||
const {draft,book}=operationsFixture();draft.context_opportunity='opportunity:1';book.result=calculateSales(draft,book,'2026-10-24T15:00:00Z');draft.modelo_a_snapshot=saveSalesBook(draft,book);const first=JSON.stringify(salesBook(draft));
|
||||
draft.context_opportunity='opportunity:309';assert.equal(salesBook(draft).rows.length,0);const second=operationsFixture().book;second.rows[0].amount='2320';second.result=calculateSales(draft,second);draft.modelo_a_snapshot=saveSalesBook(draft,second);
|
||||
const recovered=JSON.parse(JSON.stringify(draft));assert.equal(salesCurrent(recovered,salesBook(recovered)),true);assert.equal(JSON.stringify(salesBook({...recovered,context_opportunity:'opportunity:1'})),first);
|
||||
assert.match(JSON.stringify(createProjectDossierReport(recovered,{selectedSections:new Set(['finance'])})),/2320/);assert.match(JSON.stringify(salesReportRows(recovered)),/Manica/);
|
||||
second.rows[0].amount='1';draft.modelo_a_snapshot=saveSalesBook(draft,second);assert.equal(salesCurrent(draft,second),false);assert.match(JSON.stringify(salesReportRows(draft)),/Histórico/);
|
||||
assert.throws(()=>salesBook({...draft,modelo_a_snapshot:'broken'}));
|
||||
assert.throws(()=>salesBook({...draft,modelo_a_snapshot:JSON.stringify({version:1,books:{},salesOperations:[]})}),/preservados/);
|
||||
});
|
||||
test('acquisitions use current classified sales, refuse stale absence and do not universalise deduction',()=>{
|
||||
const {draft,book}=operationsFixture();book.rows=book.rows.filter(r=>!['other','foreign_operation'].includes(r.treatment));book.result=calculateSales(draft,book);draft.modelo_a_snapshot=saveSalesBook(draft,book);
|
||||
const a={currency:'MZN',rows:[],periods:['2026-01','2026-02','2026-03','2026-04'].map(period=>({period,complete:true,absent:['03','04','05','06','07','11','12','13']}))};
|
||||
const result=calculateAcquisitions(draft,a);assert.equal(result.periods[0].fields['04'],'20.00');assert.equal(result.periods[0].fields['14'],'1901.00');assert.equal(result.periods[0].fields['16'],'180.00');
|
||||
a.result=result;draft.modelo_a_snapshot=saveAcquisitionBook(draft,a);assert.equal(salesCurrent(draft,salesBook(draft)),true);assert.equal(acquisitionBook(draft).result.periods[0].fields['04'],'20.00');
|
||||
a.rows=[{...newAcquisition('new-context-purchase','2026-01','MZN'),net:'100',vat:'16',gross:'116',documentType:'invoice',documentRef:'SYNTHETIC QA',documentDate:'2026-01-10',supplier:'SYNTHETIC QA',supplierRegistered:'yes',legalDocument:'yes',held:'yes',domestic:'yes',allocation:'taxable_only',exclusion:'none',integrity:'yes'}];a.taxpayerRegistered='yes';
|
||||
assert.equal(calculateAcquisitions(draft,a).periods[0].fields['09'],null);
|
||||
a.rows[0].allocation='no_right';assert.equal(calculateAcquisitions(draft,a).periods[0].fields['09'],'0.00');a.rows=[];
|
||||
book.rows[0].amount='116';draft.modelo_a_snapshot=saveSalesBook(draft,book);assert.equal(calculateAcquisitions(draft,a).periods[0].fields['02'],null);
|
||||
});
|
||||
test('split source reconciles every parcel and recalculation keeps the prior frozen version',()=>{
|
||||
const {draft}=operationsFixture();let book=reuseSales(draft,salesBook(draft));book=splitSales(book,book.rows[0].id,'second-part');
|
||||
const group=book.rows.filter(r=>r.splitSource);assert.equal(group.length,2);
|
||||
for(const [i,r] of group.entries())Object.assign(r,{amount:i?'500.03':'500',treatment:'general',confirmed:[0,1],establishment:'QA',evidence:'QA'});
|
||||
assert.notEqual(calculateSales(draft,book).rows[0].vat,null);
|
||||
group[1].amount='499';assert.equal(calculateSales(draft,book).rows[0].vat,null);
|
||||
group[1].amount='500.03';book=recordSales(draft,book);const frozen=JSON.stringify(book.result);book=recordSales(draft,book);assert.equal(JSON.stringify(book.history[0]),frozen);assert.equal(book.result.revision,2);
|
||||
});
|
||||
if(process.env.IVA_L1_FIXTURE_OUT){const {draft,book}=operationsFixture();book.result=calculateSales(draft,book,'2026-10-24T15:00:00Z');draft.modelo_a_snapshot=saveSalesBook(draft,book);fs.writeFileSync(process.env.IVA_L1_FIXTURE_OUT,JSON.stringify(draft,null,2));}
|
||||
Reference in New Issue
Block a user