feat(irpc): qualify supported benefit combinations and prevent duplicate deductions in QA

This commit is contained in:
2026-10-26 16:04:05 +02:00
parent f33d99b4d5
commit 245549f9e8
12 changed files with 802 additions and 12 deletions
@@ -1021,3 +1021,31 @@ Backup fresco `2026-10-26T13:29:24Z`:174tabelas e369ficheiros/configuração con
Reutilizadas as provas aceites:início12 (9testes/API97) e cessação/quotas13 (11testes/API123),UI real,gravação/recuperação/relatórios,tipos/build. Não se repetiram as respectivas suites por iniciativa desta entrega;apenas os controlos automáticos obrigatórios do procedimentoGit e a conferência do delta instalado. Os motoresL6 de amortizações,periodização e câmbio mantêm-se;L3,ISPC,L5 e demais provas aceites não foram reabertos. Recibo:`docs/investmoz/simulations/irpc-l7-grouped-delivery-20261026.json`;provas locais:`outputs/irpc-l7-grouped-delivery-20261026/`.
**SaldoL7 e próxima retoma:**continuam próprios as cumulações (por exemplo cooperativa+CBF16:identificar disposição,parcela elegível,ordem e limites antes de combinar),outras origens/segregações de despesas autónomas,cooperativas mistas/retidas e demais entidades,extensõesCBF16fora dos anos/períodos/métodos/categorias demonstrados,mais-valias61-A/operações54–56/41-A,CBF17–19/direito potencial15,regimes sectoriais/territoriais e transporteM20/M22. Mantém-se a separação entre código por implementar e documentos por caso:natureza/parcela/período,reconhecimento ou excepção exigida,saldos anteriores temporariamente tributados,norma/ordem/limites de cumulação e edição/campo/fracção. Uma falta limita só o caso/campo dependente;sem pesquisa transversal repetida ou aprovador genérico. **L6 operacional delimitado;L7 parcial;L8 conserva retenções,pagamentos,créditos anuais e saldo final.** Cada futura entrega volta a medir capacidade e executar backup/restauro fresco.
## 26/10/2026 — Cumulações L7 demonstradas em QA e saldo do simulador
**QA pronto,sem instalação.** `632dc069`,incluindo `ba83afe7`,está entregue e deixa de constituir pendência de instalação. A próxima candidata acrescenta apenas a qualificação e o cálculo das combinações abaixo;mantém resultados/entradas/históricos10–13. L6 continua operacional no âmbito demonstrado;L7 parcial;L8 conserva a liquidação final.
**Regra aplicada e fontes reutilizadas:**CBF4,APIEXp66,veda a cumulação dos benefícios específicos entre si ou com os genéricos,salvo previsão expressa. CBF13,APIEXp70,delimita os genéricos a investimentos não destinatários de específicos;CBF16,APIEXp72,é genérico. CIRPC12/14,PDF5/p768(59),reduz50% da taxa geral nas cooperativas abrangidas,excluindo rendimentos sujeitos a retenção. A correspondência agora implementada trata a reduçãoCIRPC12 e o efeitoCBF16 genérico em fases distintas,no mesmo sujeito/projecto/período,quando ambos estão qualificados e não há benefício específico nem outro benefício por corresponder. Não transforma o artigo4 numa proibição genérica de todos os tratamentosCIRPC, nem numa autorização de qualquer outra combinação. Não há novo parecer/aprovador genérico como condição;as condições dos artigos e documentos concretos continuam necessárias.
A opção «Preparar cumulações dos tratamentosL7 registados» reutiliza cooperativa,activos,períodos,despesas e suportes existentes. Discrimina presença/ausência de específicosCBF,excepção expressa invocada e outros benefícios,em lugar da antiga confirmação global de ausência de outros benefícios;esta entrada anterior permanece conservada,sem alteração automática das escolhas. Não selecciona benefício pela localização. Uma incompatibilidade não escolhe o tratamento a manter,não apaga registos e não aplica o benefício específico sem motor próprio. A nova preparação é opcional14;sem ela,as chaves/modelos anteriores permanecem exactos.
| Caso novo | Efeito demonstrado |
| --- | --- |
| Cooperativa agrária abrangida +CBF16 genérico,períodoJan–Jun2026 e activo2025/quotas anteriores já entregues | Lucro/baseL6 3130;CBF16 deduz só25adicionais e obtém3105;depois taxa16% sobre3105:colecta simulada496,80MZN. Os15recuperados já estão nos75deduzidos,sem outra dedução. Não se somam reduções monetárias calculadas sobre bases diferentes. |
| Mesmo caso com despesa36g100 e incidência61(4) qualificada | Acréscimo100 já emL6:lucro3230→3205 após25CBF16;colecta ordinária512,80MZN à taxa16%;imposto autónomo35MZN separado,sem redução cooperativa;colecta combinada547,80MZN. Nenhum novo acréscimo da despesa. |
| EspecíficoCBF22 declarado no mesmo projecto +genérico16,sem excepção expressa | Combinação incompatívelCBF4/13;totalL7indeterminado e entradas conservadas. ReferênciaL6 conhecida mantida. O artigo/taxa do benefício específico não é aplicado por presunção. |
| Excepção invocada/outro benefício/presença de específico ou suporte em falta | Estado indeterminado até corresponder artigo,âmbito,período,ordem e limites. Não tratar desconhecido como ausência. |
| Duplicação de activo ou dedução positiva não correspondida já registada na baseL6 | Impede reaplicar a mesma origem;registo e valor original conservados,sem preencher outro benefício por aproximação. |
| Saldo fiscal20 /base tributável zero | Dedução respeita saldo e contabilização,sem saldo negativo. Numa base zero a colecta ordinária é zero,mas o imposto autónomo35 permanece,sem compensação pelo prejuízo. |
| Condição cooperativa negativa /ausênciaCBF16 declarada | Apenas o tratamento efectivamente aplicável permanece;estados separados,sem forçar acumulação. |
**Ordem e limites:**diferença fiscalCBF16 uma vez→mesmas deduções/perdasL6→matéria colectável→taxa cooperativa16% na base integralmente elegível→imposto autónomo35% separado. Mantêm-se limites de contabilização,parcela anterior recuperável e saldo fiscal do activo,sem alterar os saldos declarados ou consumir saldo oficial. A redução não se estende a rendimentos mistos/retidos nem ao imposto autónomo. O relatório apresenta a origem documental/activo,a dedução normal,a acelerada,a recuperação já incluída e a única diferença adicional.
**Demonstração nova:**12testes dirigidos,API privada1teste/133asserções e sete casos cálculo→gravação→recuperação→HTML/dossier financeiro. BaseQA `simulator_fiscal_delta_20261021_test`,127.0.0.1:55441,guarda de identidade e transacção/rollback. Resultado13 aceite reproduzido exactamente e histórico integral,activos,saldos,plano e outros livros conservados;recalcular não acumula valores. Interface real demonstrou preparar/cancelar,preencher só as declarações novas,combinação permitida,incompatibilidade,excepção não correspondida,falta de informação,imposto autónomo separado,guardar/recuperar e exportar,sem erros. Wayfinder`--with-form`,TypeScript ebuildQA passaram. Sem repetir suites aceites,migração,importação ou escrita no activo;SGF TEMPO separado. MotoresL6 de amortizações/periodização/câmbio e31originais preservados.
**SaldoL7:**este recorte sai do grupo «cumulação ainda não implementada» apenas paraCIRPC12+CBF16 e a coexistência separada61(4),com as condições acima. Permanecem por implementar outras cumulações/excepções expressas,CBF17–19 e direito potencial15,regimes específicos,parcelas cooperativas mistas/retidas e demais entidades,outras origens de despesas,extensõesCBF16,mais-valias/operações especiais e transporteM20/M22. Retoma por disposição/artigo/ordem/limite e parcela identificados,implementando os tratamentos ainda ausentes;documento individual em falta limita só o caso/campo dependente. Não reabrir as provas aceites ou pesquisar outra vez dúvidas sem informação nova.
**Quanto falta no simulador:**a matriz conserva estimativas iniciais,sem uma reestimativa actual do esforço integral restante. SóL8–L14 somavam inicialmente240–416horas (30–52dias de8h);esta referência não é um saldo actualizado nem inclui resíduosL1–L7,L15–L16 ou espera documental. L8 contém liquidação/pagamentos/créditos/saldo final;L9–L14 contêm INSS,IRPS,Selo,direitos aduaneiros eICE;L15–L16,SISA/encargos imobiliários,ainda por dimensionar. L6 mantém5grupos de código e5documentais;L5 mantém as dez dúvidasAT só para os casos dependentes. Os lotes e grupos não são unidades iguais,não sustentam uma percentagem global de conclusão. A cobertura já entregue continua utilizável com limites explícitos;simulador fiscal integral permanece um trabalho de dezenas de dias na referência de planeamento,sem prazo fechado.
Entrega agrupada preparada com os sete casos novos compatíveis;incompatibilidade e informação insuficiente acompanham o mesmo candidato como estados demonstrados. Não houve instalação nesta passagem. A futura entrega requer main compatível,capacidade do servidor medida novamente ebackup/restauro fresco,mantendo a versão activa até a nova ficar pronta. Recibo:`docs/investmoz/simulations/irpc-l7-combinations-qa-20261026.json`;provas:`outputs/irpc-l7-combinations/`. Reprodução só do delta:`node --test tests/frontend/irpc-special-combinations.test.mjs`;`php tools/irpc-special-combinations-qa.php routes`;`php tools/irpc-special-combinations-qa.php test`;`tsc --noEmit`;build com `INVESTMOZ_QA_BUILD=1`. **L7 parcial;colecta simulada distinta do saldo finalL8.**
@@ -0,0 +1,596 @@
{
"status": "combinations_QA_ready_not_installed",
"date": "2026-10-26",
"branch": "codex/irpc-l7-combinations-qa-20261026",
"installed_baseline": "632dc06930d6b4f1b012feb3b4daa3b1cfc53f39",
"documentation_baseline": "f33d99b4",
"version": "IRPC-normal-adjustments/14",
"optional_input": "special.combinations version1;document presence/absence of specific/other benefits and express exception;reuse existing qualification/period/assets",
"scope": "CIRPC12 qualified pure cooperative + genericCBF16;separate expense61(4);CBF4/13 incompatibility of specific+generic without express exception;unmapped exception/other benefit remain indeterminate",
"calculation_order": [
"CBF16 net difference once",
"existing taxablebase deductions/losses",
"16percent on fullyeligible cooperativebase",
"35percent autonomous expenses separately",
"L8 final remains unknown"
],
"positive_unmapped_L6_benefit_not_reapplied": true,
"runtime_delta": {
"resources/js/lib/irpc-special.ts": "bc72c0c045fa7d7fc2a6b98ca95205479aa448f0c2a9819b930e72f7c93f6ade",
"resources/js/lib/irpc-normal.ts": "4741c2bdecb8570fe786e51f777c633da34dcb0052bb000f9b63ebfde48b1f4a",
"resources/js/components/simulation-irpc-special.tsx": "bb567f06cf2c844419341fa298751752ef94a2f493972d28f68b5f2245b95e00",
"resources/js/lib/irpc-special-combinations.ts": "b638fdf96e33a27c1c73492fb5690c7b170ecbe97dd4ad9881465b2c4eaa6a22"
},
"L6_depreciation_period_fx_engines_unchanged": true,
"cases_recovered": {
"combined": {
"L6_profit": {
"numerator": "3130",
"denominator": "1"
},
"L6_base": {
"numerator": "3130",
"denominator": "1"
},
"L6_colecta": {
"numerator": "5008",
"denominator": "5"
},
"status": "compatible",
"order": [
"Diferença adicionalCBF16 aplicada uma vez ao lucroL6",
"Matéria colectável recalculada com as mesmas deduções/perdasL6",
"Cooperativa12:50% da taxa geral32%=16% apenas na base integralmente elegível",
"Imposto autónomo61(4) separado a35%,sem redução cooperativa",
"Saldo final a pagar/recuperar permaneceL8"
],
"asset_claims": [
{
"assetId": "server",
"document": "QA-activo-servidor",
"line": "1",
"normalDeduction": {
"numerator": "50",
"denominator": "1"
},
"acceleratedDeduction": {
"numerator": "75",
"denominator": "1"
},
"priorRecoveredIncluded": {
"numerator": "15",
"denominator": "1"
},
"additionalDeduction": {
"numerator": "25",
"denominator": "1"
},
"remainingFiscal": {
"numerator": "825",
"denominator": "1"
}
}
],
"L7_profit": {
"numerator": "3105",
"denominator": "1"
},
"L7_base": {
"numerator": "3105",
"denominator": "1"
},
"L7_colecta": {
"numerator": "2484",
"denominator": "5"
},
"autonomous_tax": {
"numerator": "0",
"denominator": "1"
},
"combined_colecta": {
"numerator": "2484",
"denominator": "5"
},
"pending": [],
"L8_final": {
"scope": "L8",
"payable": null,
"recoverable": null
}
},
"autonomous": {
"L6_profit": {
"numerator": "3230",
"denominator": "1"
},
"L6_base": {
"numerator": "3230",
"denominator": "1"
},
"L6_colecta": {
"numerator": "5168",
"denominator": "5"
},
"status": "compatible",
"order": [
"Diferença adicionalCBF16 aplicada uma vez ao lucroL6",
"Matéria colectável recalculada com as mesmas deduções/perdasL6",
"Cooperativa12:50% da taxa geral32%=16% apenas na base integralmente elegível",
"Imposto autónomo61(4) separado a35%,sem redução cooperativa",
"Saldo final a pagar/recuperar permaneceL8"
],
"asset_claims": [
{
"assetId": "server",
"document": "QA-activo-servidor",
"line": "1",
"normalDeduction": {
"numerator": "50",
"denominator": "1"
},
"acceleratedDeduction": {
"numerator": "75",
"denominator": "1"
},
"priorRecoveredIncluded": {
"numerator": "15",
"denominator": "1"
},
"additionalDeduction": {
"numerator": "25",
"denominator": "1"
},
"remainingFiscal": {
"numerator": "825",
"denominator": "1"
}
}
],
"L7_profit": {
"numerator": "3205",
"denominator": "1"
},
"L7_base": {
"numerator": "3205",
"denominator": "1"
},
"L7_colecta": {
"numerator": "2564",
"denominator": "5"
},
"autonomous_tax": {
"numerator": "35",
"denominator": "1"
},
"combined_colecta": {
"numerator": "2739",
"denominator": "5"
},
"pending": [],
"L8_final": {
"scope": "L8",
"payable": null,
"recoverable": null
}
},
"incompatible": {
"L6_profit": {
"numerator": "3130",
"denominator": "1"
},
"L6_base": {
"numerator": "3130",
"denominator": "1"
},
"L6_colecta": {
"numerator": "5008",
"denominator": "5"
},
"status": "incompatible",
"order": [
"Diferença adicionalCBF16 aplicada uma vez ao lucroL6",
"Matéria colectável recalculada com as mesmas deduções/perdasL6",
"Cooperativa12:50% da taxa geral32%=16% apenas na base integralmente elegível",
"Imposto autónomo61(4) separado a35%,sem redução cooperativa",
"Saldo final a pagar/recuperar permaneceL8"
],
"asset_claims": [
{
"assetId": "server",
"document": "QA-activo-servidor",
"line": "1",
"normalDeduction": {
"numerator": "50",
"denominator": "1"
},
"acceleratedDeduction": null,
"priorRecoveredIncluded": null,
"additionalDeduction": null,
"remainingFiscal": null
}
],
"L7_profit": null,
"L7_base": null,
"L7_colecta": null,
"autonomous_tax": {
"numerator": "0",
"denominator": "1"
},
"combined_colecta": null,
"pending": [
"IncompatibilidadeCBF4/13:benefício específico declarado e genéricoCBF16 sem excepção expressa;nenhum benefício é escolhido ou removido automaticamente."
],
"L8_final": {
"scope": "L8",
"payable": null,
"recoverable": null
}
},
"missing": {
"L6_profit": {
"numerator": "3130",
"denominator": "1"
},
"L6_base": {
"numerator": "3130",
"denominator": "1"
},
"L6_colecta": {
"numerator": "5008",
"denominator": "5"
},
"status": "indeterminate",
"order": [
"Diferença adicionalCBF16 aplicada uma vez ao lucroL6",
"Matéria colectável recalculada com as mesmas deduções/perdasL6",
"Cooperativa12:50% da taxa geral32%=16% apenas na base integralmente elegível",
"Imposto autónomo61(4) separado a35%,sem redução cooperativa",
"Saldo final a pagar/recuperar permaneceL8"
],
"asset_claims": [
{
"assetId": "server",
"document": "QA-activo-servidor",
"line": "1",
"normalDeduction": {
"numerator": "50",
"denominator": "1"
},
"acceleratedDeduction": null,
"priorRecoveredIncluded": null,
"additionalDeduction": null,
"remainingFiscal": null
}
],
"L7_profit": null,
"L7_base": null,
"L7_colecta": null,
"autonomous_tax": {
"numerator": "0",
"denominator": "1"
},
"combined_colecta": null,
"pending": [
"Benefícios específicosCBF:identifique presença ou ausência documentada;localização não prova estatuto."
],
"L8_final": {
"scope": "L8",
"payable": null,
"recoverable": null
}
},
"exception": {
"L6_profit": {
"numerator": "3130",
"denominator": "1"
},
"L6_base": {
"numerator": "3130",
"denominator": "1"
},
"L6_colecta": {
"numerator": "5008",
"denominator": "5"
},
"status": "indeterminate",
"order": [
"Diferença adicionalCBF16 aplicada uma vez ao lucroL6",
"Matéria colectável recalculada com as mesmas deduções/perdasL6",
"Cooperativa12:50% da taxa geral32%=16% apenas na base integralmente elegível",
"Imposto autónomo61(4) separado a35%,sem redução cooperativa",
"Saldo final a pagar/recuperar permaneceL8"
],
"asset_claims": [
{
"assetId": "server",
"document": "QA-activo-servidor",
"line": "1",
"normalDeduction": {
"numerator": "50",
"denominator": "1"
},
"acceleratedDeduction": null,
"priorRecoveredIncluded": null,
"additionalDeduction": null,
"remainingFiscal": null
}
],
"L7_profit": null,
"L7_base": null,
"L7_colecta": null,
"autonomous_tax": {
"numerator": "0",
"denominator": "1"
},
"combined_colecta": null,
"pending": [
"Excepção expressa invocada:ligar artigo,âmbito,período e limites à combinação antes de calcular;esta excepção ainda não está correspondida."
],
"L8_final": {
"scope": "L8",
"payable": null,
"recoverable": null
}
},
"negative": {
"L6_profit": {
"numerator": "3130",
"denominator": "1"
},
"L6_base": {
"numerator": "3130",
"denominator": "1"
},
"L6_colecta": {
"numerator": "5008",
"denominator": "5"
},
"status": "compatible",
"order": [
"Diferença adicionalCBF16 aplicada uma vez ao lucroL6",
"Matéria colectável recalculada com as mesmas deduções/perdasL6",
"Cooperativa12:50% da taxa geral32%=16% apenas na base integralmente elegível",
"Imposto autónomo61(4) separado a35%,sem redução cooperativa",
"Saldo final a pagar/recuperar permaneceL8"
],
"asset_claims": [
{
"assetId": "server",
"document": "QA-activo-servidor",
"line": "1",
"normalDeduction": {
"numerator": "50",
"denominator": "1"
},
"acceleratedDeduction": {
"numerator": "75",
"denominator": "1"
},
"priorRecoveredIncluded": {
"numerator": "15",
"denominator": "1"
},
"additionalDeduction": {
"numerator": "25",
"denominator": "1"
},
"remainingFiscal": {
"numerator": "825",
"denominator": "1"
}
}
],
"L7_profit": {
"numerator": "3105",
"denominator": "1"
},
"L7_base": {
"numerator": "3105",
"denominator": "1"
},
"L7_colecta": {
"numerator": "4968",
"denominator": "5"
},
"autonomous_tax": {
"numerator": "0",
"denominator": "1"
},
"combined_colecta": {
"numerator": "4968",
"denominator": "5"
},
"pending": [],
"L8_final": {
"scope": "L8",
"payable": null,
"recoverable": null
}
},
"absent": {
"L6_profit": {
"numerator": "3130",
"denominator": "1"
},
"L6_base": {
"numerator": "3130",
"denominator": "1"
},
"L6_colecta": {
"numerator": "5008",
"denominator": "5"
},
"status": "compatible",
"order": [
"Diferença adicionalCBF16 aplicada uma vez ao lucroL6",
"Matéria colectável recalculada com as mesmas deduções/perdasL6",
"Cooperativa12:50% da taxa geral32%=16% apenas na base integralmente elegível",
"Imposto autónomo61(4) separado a35%,sem redução cooperativa",
"Saldo final a pagar/recuperar permaneceL8"
],
"asset_claims": [],
"L7_profit": {
"numerator": "3130",
"denominator": "1"
},
"L7_base": {
"numerator": "3130",
"denominator": "1"
},
"L7_colecta": {
"numerator": "2504",
"denominator": "5"
},
"autonomous_tax": {
"numerator": "0",
"denominator": "1"
},
"combined_colecta": {
"numerator": "2504",
"denominator": "5"
},
"pending": [],
"L8_final": {
"scope": "L8",
"payable": null,
"recoverable": null
}
}
},
"checks": {
"new_frontend_tests": 12,
"API_tests": 1,
"API_assertions": 133,
"QA_database": "simulator_fiscal_delta_20261021_test",
"QA_host": "127.0.0.1",
"QA_port": 55441,
"QA_transaction_rollback": true,
"wayfinder_with_form": true,
"typescript": true,
"build": true,
"recovered_HTML_and_financial_dossier": 7,
"UI": {
"actualComponent": true,
"explicitPrepareCancel": true,
"assetsPeriodsBenefitsNotEdited": true,
"missingOnlyDependentL7": true,
"CBF16Additional25ThenCooperative16": true,
"prior15AlreadyIncluded": true,
"specificGenericIncompatibilityNoAutomaticChoice": true,
"unmappedExceptionIndeterminate": true,
"autonomous35NotHalved": true,
"saveRecoverDownload": true,
"accepted13InputPreserved": true,
"L8FinalUnknown": true,
"errors": []
},
"accepted13_exact_model_key_history_comparison": true,
"accepted_suites_not_rerun": true
},
"preservation": {
"tracked_files_compared_before_docs": 2822,
"unchanged": 2819,
"modified": [
"resources/js/components/simulation-irpc-special.tsx",
"resources/js/lib/irpc-normal.ts",
"resources/js/lib/irpc-special.ts"
],
"new_runtime_module": "resources/js/lib/irpc-special-combinations.ts",
"originals": {
"docs/investmoz/simulations/source-archive/AT-Comunicado-9-2026-ISPC.pdf": "106f8a9ce9255c03549c70bf07fe999d32e6e55302d009b509862aa74ccb4973",
"docs/investmoz/simulations/source-archive/AT-MRFTA-19-39.pdf": "8ea62c53066b482f87078a7a7b1ecf15ca98a12a122e8d7abca63acfc993186b",
"docs/investmoz/simulations/source-archive/AT-Modelo-30-ISPC.pdf": "6802edd06b6ff638a0b851a3ec7ecf69af8f07840faa47446becad67d35d445c",
"docs/investmoz/simulations/source-archive/AT-Modelo-B-Geral.pdf": "bd977c7c59d4d98befd92e2c566b1bf168cb1343bb519f51b41a3807f431fc57",
"docs/investmoz/simulations/source-archive/BM-Mercado-Monetario-MAIBOR-20261025.html": "00e8a7e0d3a1db4ca3beb56d5361b2084a0f1684f3d40fc1c203702a2190e063",
"docs/investmoz/simulations/source-archive/BR-16-I-Serie-2008-04-16.pdf": "fc373ff5637edf75ec6a9595dec8763a8503d4ee786e3b7d8f1c742eae9d80ee",
"docs/investmoz/simulations/source-archive/BR-248-I-Suplemento-1-2025.pdf": "ccf2a8deeca820f2dd076b4b2141b1efe3f6a806568c3a587ca59a8e92f4c251",
"docs/investmoz/simulations/source-archive/BR-89-I-Serie-2017-06-08.pdf": "11f0cab15eb6e51010952203ffa24fdc533a24fd642e19525fe4f46b17a1f5e5",
"docs/investmoz/simulations/source-archive/BR-95-I-Suplemento-2025-05-21.pdf": "e0e682e2c027c3cd9c1d46a73a3f48bc5f6bdbf05667fd97314d7a3f6a456358",
"docs/investmoz/simulations/source-archive/BR252-I-Serie-2022-Lei20-PESOE.pdf": "1159fd3ce1ba5581671796d083534da48bd3c5578298819e896f1c2a8026318d",
"docs/investmoz/simulations/source-archive/Decreto-22-2021-BR70.pdf": "a8fac38dcfe08f9c7a8e46254cff10e1ec82fc4f7a8d88ad6e4fe7d91ffc21a5",
"docs/investmoz/simulations/source-archive/Decreto-27-2026-BR123.pdf": "26249c3fa58f149c81de36e99d1aac2819a4771379d9e8833159660dabf539bb",
"docs/investmoz/simulations/source-archive/Decreto-29-1998-BR22-5-suplemento.pdf": "6242cbe675faadf085f43f95ec8c7e8868aafe67c3b2ab2bb010992288f954f8",
"docs/investmoz/simulations/source-archive/Decreto-3-2011-BR-11-I-mineiros.pdf": "5bca1133666ba9c69870b89823420175b7b5b8d0eccb667ecdf5c29716f3b6be",
"docs/investmoz/simulations/source-archive/Decreto-37-2023-BR124.pdf": "7e680b191481faa0e2e9057fa5053587eb2a29d1d8c819e0eb9f02814255a04c",
"docs/investmoz/simulations/source-archive/Decreto-9-2008-BR16.pdf": "96fccdecbfdc11c9eafb90fbc6c7bc259f88bbd4c7507d1ac3af1fa03f960697",
"docs/investmoz/simulations/source-archive/Lei-13-2016-CIVA-BR-156-10-suplemento.pdf": "b90dc7c14ecea2e378c4a62e8e5a47d54a5644c457baeb8b8053b92fd2a5710b",
"docs/investmoz/simulations/source-archive/Lei-14-1992-BR42.pdf": "089fff7423e99a70a230a73ca379e4ea3a91d124703f91016e57ff82794b9e56",
"docs/investmoz/simulations/source-archive/Lei-17-2022-BR251-exemplar-MCNet5.pdf": "14eb34f0af91a8f5cb1770c7b52d6ff839c6818302d1ed96d79afbfb9fe999d4",
"docs/investmoz/simulations/source-archive/Lei-17-2022-IPP-BR250-Parlamento.pdf": "978cd22403ffd336a631704ecd7a5bbd814a427c815b9acdaca5515591dd9e2a",
"docs/investmoz/simulations/source-archive/Lei-19-2013-BR76.pdf": "bba96d645cc935ffc3d1a79b9f0f73ac1c693d6459c2ad41f56cd2841accae76",
"docs/investmoz/simulations/source-archive/Lei-19-2017-IRPS-BR-OCAM.pdf": "c3a0186cb9ad090d7a9533006295dc9bb6a62ad0d6150173cbd49dba4ee4b52d",
"docs/investmoz/simulations/source-archive/Lei-2-2011-BR-1-4-suplemento-MCNet.pdf": "a7543a68cc3287a46a83c39a04821b40f649dd06c4e9b5dc51ff0b47fba08899",
"docs/investmoz/simulations/source-archive/Lei-20-2009-BR36-AT-exemplar19.pdf": "fc1c8144db42960cbf73e717e095896f511ece0f907f58f78819b27d6917a7ee",
"docs/investmoz/simulations/source-archive/Lei-22-2022-IVA-republicada-BR-250-2-suplemento.pdf": "ea1b0dc6f5cf6e595f6d9a19e7042f398999e0087caef1785f6e8fac571b5f53",
"docs/investmoz/simulations/source-archive/Lei-34-2007-BR52.pdf": "cdbef875ad7ccc1517b12faf5de0e893a70ed2ed81e33913cd6dc4a674474e6e",
"docs/investmoz/simulations/source-archive/Lei-4-1994-BR37-3-suplemento.pdf": "210df9db8980d0a5690cd187a6eeec7e37a900d743e39062c16f4e0d12215c7e",
"docs/investmoz/simulations/source-archive/Lei-4-2012-BR3.pdf": "b8845cef41dcbd9ba50c43dbfa743a56030dd3d348242a107e1ae30279eec121",
"docs/investmoz/simulations/source-archive/Lei-7-1991-Partidos-IFES.pdf": "6b783a0bb3330688ef10660a58060d9724aa2da6a873d234fe902f0eaaeda42d",
"docs/investmoz/simulations/source-archive/PT-MZ-CDT-1992.pdf": "d504e1024b28aee5c6acf4592acb96ec9c915a8414f67a61ab6984d6acf9df9d",
"docs/investmoz/simulations/source-archive/PT-MZ-Protocolo-2009.pdf": "f353773dcf41f09f4daa6cf5ba5a5f7994f3e02615867c2dcffc61c3d901bf9b"
},
"histories_assets_balances_plan_otherbooks_preserved": true,
"official_balances_not_consumed": true,
"active_writes": 0,
"migrations": 0,
"SGF_TEMPO_untouched": true
},
"sources_reused": [
{
"path": "C:/laragon/www/portal-investidor-irpc-l7-scope-20261026/outputs/irpc-l7-scope/APIEX-legislacao-investimentos-2025.pdf",
"sha256": "ba1e9eaabfec5d0c4cb2d9f2444a6860f7a90f222034764197829be402d408ed",
"pages": [
66,
70,
72
],
"articles": "CBF4/13/16"
},
{
"path": "C:\\laragon\\www\\portal-investidor-irpc-l7-common-qa-20261026\\docs\\investmoz\\simulations\\source-archive\\Lei-34-2007-BR52.pdf",
"sha256": "cdbef875ad7ccc1517b12faf5de0e893a70ed2ed81e33913cd6dc4a674474e6e",
"pages": [
5
],
"articles": "CIRPC12/14"
}
],
"inventory_reused": 8143,
"remaining_code": [
"other combinations and express exceptions",
"CBF17-19 and potential15 entitlement",
"specific sector/territorial regimes",
"mixed/withheld cooperativeincome and otherentities",
"other autonome expense origins",
"furtherCBF16 scopes",
"capitalgains/specialoperations",
"officialL7M20M22transport"
],
"resume_conditions": [
"disposition/article/scope/period/order/limit and segregatedparcel for combination",
"implement not-yet-supported treatment before adding its effect",
"specific document only for dependentcase/field"
],
"effort_reference": {
"type": "initial planning figures,not updated remainingestimate",
"L8_to_L14_hours": [
240,
416
],
"eight_hour_days": [
30,
52
],
"excludes": [
"L1-L7residuals",
"L15-L16unestimated",
"documentarywait"
],
"global_completion_percentage_not_established": true
},
"future_delivery_requires_fresh_main_capacity_backup_restore": true,
"L6_operational_delimited": true,
"L7_partial": true,
"L8_final_balance_separate": true,
"evidence_directory": "outputs/irpc-l7-combinations"
}
@@ -2523,7 +2523,7 @@ As estimativas são **horas de trabalho técnico**, não calendário nem mediç
| 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 | Disponível5aaf1a56:percurso normal demonstrado atécolecta,períodos/moedas e preparação parcialM20/M22 | Operacional no âmbito demonstrado por decisãoTempo26/10;limitações explícitas;5grupos código+5documentais preservados;sem conclusão integral | Plano financeiro,dados comuns,activos/livros e histórico;retenções/pagamentos/créditos anuaisL8 | Pendências bloqueiam só os casos dependentes;L7/L8 próprios | Estimativa inicial48–80h;saldo não reestimado |
| L7 — IRPC: tratamentos específicos | 632dc069 instalado,incluindo início12/ba83afe7 e cessação/quotas13;0bd5d495 preservado | L7parcial;cumulações e restantes casos/dependências concretos mantidos | Activos/períodos/saldosL6 reutilizados;75−50=25 adicionais,15 recuperados já nos75;sem consumo oficial | Retomar combinação por norma/parcela/ordem/limites;futuras entregas com capacidade/backup/restauro frescos;L8 conserva saldo final | Estimativa inicial48–80h;saldo não reestimado |
| L7 — IRPC: tratamentos específicos | 632dc069 instalado;cumulações14 prontas emQA,sem instalação | CIRPC12+CBF16 qualificados e61(4) separado;incompatibilidadeCBF4/13 e lacunas explícitas;L7parcial | Activos/períodos/saldos/benefícios reutilizados;diferença25 uma vez e15 já nos75;taxa16% só sobre base elegível | Outras combinações/excepções e restantes tratamentos dependem de regra/parcela/ordem/limites;L8 conserva saldo final | Estimativa inicial48–80h;saldo integral não reestimado |
| 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 |
@@ -6542,3 +6542,31 @@ Backup fresco `2026-10-26T13:29:24Z`:174tabelas e369ficheiros/configuração con
Reutilizadas as provas aceites:início12 (9testes/API97) e cessação/quotas13 (11testes/API123),UI real,gravação/recuperação/relatórios,tipos/build. Não se repetiram as respectivas suites por iniciativa desta entrega;apenas os controlos automáticos obrigatórios do procedimentoGit e a conferência do delta instalado. Os motoresL6 de amortizações,periodização e câmbio mantêm-se;L3,ISPC,L5 e demais provas aceites não foram reabertos. Recibo:`docs/investmoz/simulations/irpc-l7-grouped-delivery-20261026.json`;provas locais:`outputs/irpc-l7-grouped-delivery-20261026/`.
**SaldoL7 e próxima retoma:**continuam próprios as cumulações (por exemplo cooperativa+CBF16:identificar disposição,parcela elegível,ordem e limites antes de combinar),outras origens/segregações de despesas autónomas,cooperativas mistas/retidas e demais entidades,extensõesCBF16fora dos anos/períodos/métodos/categorias demonstrados,mais-valias61-A/operações54–56/41-A,CBF17–19/direito potencial15,regimes sectoriais/territoriais e transporteM20/M22. Mantém-se a separação entre código por implementar e documentos por caso:natureza/parcela/período,reconhecimento ou excepção exigida,saldos anteriores temporariamente tributados,norma/ordem/limites de cumulação e edição/campo/fracção. Uma falta limita só o caso/campo dependente;sem pesquisa transversal repetida ou aprovador genérico. **L6 operacional delimitado;L7 parcial;L8 conserva retenções,pagamentos,créditos anuais e saldo final.** Cada futura entrega volta a medir capacidade e executar backup/restauro fresco.
## 26/10/2026 — Cumulações L7 demonstradas em QA e saldo do simulador
**QA pronto,sem instalação.** `632dc069`,incluindo `ba83afe7`,está entregue e deixa de constituir pendência de instalação. A próxima candidata acrescenta apenas a qualificação e o cálculo das combinações abaixo;mantém resultados/entradas/históricos10–13. L6 continua operacional no âmbito demonstrado;L7 parcial;L8 conserva a liquidação final.
**Regra aplicada e fontes reutilizadas:**CBF4,APIEXp66,veda a cumulação dos benefícios específicos entre si ou com os genéricos,salvo previsão expressa. CBF13,APIEXp70,delimita os genéricos a investimentos não destinatários de específicos;CBF16,APIEXp72,é genérico. CIRPC12/14,PDF5/p768(59),reduz50% da taxa geral nas cooperativas abrangidas,excluindo rendimentos sujeitos a retenção. A correspondência agora implementada trata a reduçãoCIRPC12 e o efeitoCBF16 genérico em fases distintas,no mesmo sujeito/projecto/período,quando ambos estão qualificados e não há benefício específico nem outro benefício por corresponder. Não transforma o artigo4 numa proibição genérica de todos os tratamentosCIRPC, nem numa autorização de qualquer outra combinação. Não há novo parecer/aprovador genérico como condição;as condições dos artigos e documentos concretos continuam necessárias.
A opção «Preparar cumulações dos tratamentosL7 registados» reutiliza cooperativa,activos,períodos,despesas e suportes existentes. Discrimina presença/ausência de específicosCBF,excepção expressa invocada e outros benefícios,em lugar da antiga confirmação global de ausência de outros benefícios;esta entrada anterior permanece conservada,sem alteração automática das escolhas. Não selecciona benefício pela localização. Uma incompatibilidade não escolhe o tratamento a manter,não apaga registos e não aplica o benefício específico sem motor próprio. A nova preparação é opcional14;sem ela,as chaves/modelos anteriores permanecem exactos.
| Caso novo | Efeito demonstrado |
| --- | --- |
| Cooperativa agrária abrangida +CBF16 genérico,períodoJan–Jun2026 e activo2025/quotas anteriores já entregues | Lucro/baseL6 3130;CBF16 deduz só25adicionais e obtém3105;depois taxa16% sobre3105:colecta simulada496,80MZN. Os15recuperados já estão nos75deduzidos,sem outra dedução. Não se somam reduções monetárias calculadas sobre bases diferentes. |
| Mesmo caso com despesa36g100 e incidência61(4) qualificada | Acréscimo100 já emL6:lucro3230→3205 após25CBF16;colecta ordinária512,80MZN à taxa16%;imposto autónomo35MZN separado,sem redução cooperativa;colecta combinada547,80MZN. Nenhum novo acréscimo da despesa. |
| EspecíficoCBF22 declarado no mesmo projecto +genérico16,sem excepção expressa | Combinação incompatívelCBF4/13;totalL7indeterminado e entradas conservadas. ReferênciaL6 conhecida mantida. O artigo/taxa do benefício específico não é aplicado por presunção. |
| Excepção invocada/outro benefício/presença de específico ou suporte em falta | Estado indeterminado até corresponder artigo,âmbito,período,ordem e limites. Não tratar desconhecido como ausência. |
| Duplicação de activo ou dedução positiva não correspondida já registada na baseL6 | Impede reaplicar a mesma origem;registo e valor original conservados,sem preencher outro benefício por aproximação. |
| Saldo fiscal20 /base tributável zero | Dedução respeita saldo e contabilização,sem saldo negativo. Numa base zero a colecta ordinária é zero,mas o imposto autónomo35 permanece,sem compensação pelo prejuízo. |
| Condição cooperativa negativa /ausênciaCBF16 declarada | Apenas o tratamento efectivamente aplicável permanece;estados separados,sem forçar acumulação. |
**Ordem e limites:**diferença fiscalCBF16 uma vez→mesmas deduções/perdasL6→matéria colectável→taxa cooperativa16% na base integralmente elegível→imposto autónomo35% separado. Mantêm-se limites de contabilização,parcela anterior recuperável e saldo fiscal do activo,sem alterar os saldos declarados ou consumir saldo oficial. A redução não se estende a rendimentos mistos/retidos nem ao imposto autónomo. O relatório apresenta a origem documental/activo,a dedução normal,a acelerada,a recuperação já incluída e a única diferença adicional.
**Demonstração nova:**12testes dirigidos,API privada1teste/133asserções e sete casos cálculo→gravação→recuperação→HTML/dossier financeiro. BaseQA `simulator_fiscal_delta_20261021_test`,127.0.0.1:55441,guarda de identidade e transacção/rollback. Resultado13 aceite reproduzido exactamente e histórico integral,activos,saldos,plano e outros livros conservados;recalcular não acumula valores. Interface real demonstrou preparar/cancelar,preencher só as declarações novas,combinação permitida,incompatibilidade,excepção não correspondida,falta de informação,imposto autónomo separado,guardar/recuperar e exportar,sem erros. Wayfinder`--with-form`,TypeScript ebuildQA passaram. Sem repetir suites aceites,migração,importação ou escrita no activo;SGF TEMPO separado. MotoresL6 de amortizações/periodização/câmbio e31originais preservados.
**SaldoL7:**este recorte sai do grupo «cumulação ainda não implementada» apenas paraCIRPC12+CBF16 e a coexistência separada61(4),com as condições acima. Permanecem por implementar outras cumulações/excepções expressas,CBF17–19 e direito potencial15,regimes específicos,parcelas cooperativas mistas/retidas e demais entidades,outras origens de despesas,extensõesCBF16,mais-valias/operações especiais e transporteM20/M22. Retoma por disposição/artigo/ordem/limite e parcela identificados,implementando os tratamentos ainda ausentes;documento individual em falta limita só o caso/campo dependente. Não reabrir as provas aceites ou pesquisar outra vez dúvidas sem informação nova.
**Quanto falta no simulador:**a matriz conserva estimativas iniciais,sem uma reestimativa actual do esforço integral restante. SóL8–L14 somavam inicialmente240–416horas (30–52dias de8h);esta referência não é um saldo actualizado nem inclui resíduosL1–L7,L15–L16 ou espera documental. L8 contém liquidação/pagamentos/créditos/saldo final;L9–L14 contêm INSS,IRPS,Selo,direitos aduaneiros eICE;L15–L16,SISA/encargos imobiliários,ainda por dimensionar. L6 mantém5grupos de código e5documentais;L5 mantém as dez dúvidasAT só para os casos dependentes. Os lotes e grupos não são unidades iguais,não sustentam uma percentagem global de conclusão. A cobertura já entregue continua utilizável com limites explícitos;simulador fiscal integral permanece um trabalho de dezenas de dias na referência de planeamento,sem prazo fechado.
Entrega agrupada preparada com os sete casos novos compatíveis;incompatibilidade e informação insuficiente acompanham o mesmo candidato como estados demonstrados. Não houve instalação nesta passagem. A futura entrega requer main compatível,capacidade do servidor medida novamente ebackup/restauro fresco,mantendo a versão activa até a nova ficar pronta. Recibo:`docs/investmoz/simulations/irpc-l7-combinations-qa-20261026.json`;provas:`outputs/irpc-l7-combinations/`. Reprodução só do delta:`node --test tests/frontend/irpc-special-combinations.test.mjs`;`php tools/irpc-special-combinations-qa.php routes`;`php tools/irpc-special-combinations-qa.php test`;`tsc --noEmit`;build com `INVESTMOZ_QA_BUILD=1`. **L7 parcial;colecta simulada distinta do saldo finalL8.**
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2 -1
View File
@@ -1,3 +1,4 @@
import {irpcSpecialCombinationsVersion} from './irpc-special-combinations.ts';
import {irpcSpecialVersion,irpcSpecialMonthlyVersion,irpcSpecialStartPeriodVersion,irpcSpecialCessationPriorVersion,specialCalculationVersion,validateSpecial,calculateSpecial,specialReportRows,specialDependency,type SpecialInput,type SpecialModel} from './irpc-special.ts';
import {normalFormPreparationVersion,validateNormalFormPreparation,prepareNormalForms,normalFormPreparationReport,type NormalFormPreparationInput,type NormalFormPreparationModel} from './irpc-normal-form-preparation.ts';
import {normalPeriodFxVersion,validateNormalPeriodFx,mapNormalPeriodFx,normalPeriodFxDependency,normalPeriodFxReport,type NormalPeriodFxInput,type NormalPeriodFxModel} from './irpc-normal-period-fx.ts';
@@ -41,7 +42,7 @@ export function validateNormal(b:NormalBook){
if(!b||b.version!==1||['year','mappingReference','scopeReference'].some(k=>typeof b[k as 'year']!=='string')||!['','resident_general','ep_general','other'].includes(b.scope)||!Array.isArray(b.rows)||b.history!==undefined&&!Array.isArray(b.history))throw Error('Preparação IRPC normal não recuperável.');
for(const r of b.rows)if(!r||Object.keys(newNormalAdjustment('')).some(k=>typeof r[k as keyof NormalAdjustment]!=='string')||!['','noncontractual_fine','compensatory_interest','other'].includes(r.rule)||!['','yes','no'].includes(r.included))throw Error('Ajustamento IRPC não recuperável;original conservado.');
if(new Set(b.rows.map(r=>r.id)).size!==b.rows.length)throw Error('Identificador de ajustamento duplicado.');
for(const r of [b.result,...(b.history??[])])if(r&&(!r.model||![irpcSpecialStartPeriodVersion,irpcSpecialCessationPriorVersion,irpcSpecialMonthlyVersion,irpcSpecialVersion,irpcNormalVersion,normalProfitVersion,normalBaseVersion,normalAssessmentVersion,normalSchedulesVersion,normalCostsVersion,normalDepreciationVersion,normalPeriodFxVersion,normalFormPreparationVersion].includes(r.model.version)||typeof r.key!=='string'||typeof r.createdAt!=='string'||!Array.isArray(r.model.rows)||!Array.isArray(r.model.pending)))throw Error('Histórico IRPC normal não recuperável.');
for(const r of [b.result,...(b.history??[])])if(r&&(!r.model||![irpcSpecialCombinationsVersion,irpcSpecialStartPeriodVersion,irpcSpecialCessationPriorVersion,irpcSpecialMonthlyVersion,irpcSpecialVersion,irpcNormalVersion,normalProfitVersion,normalBaseVersion,normalAssessmentVersion,normalSchedulesVersion,normalCostsVersion,normalDepreciationVersion,normalPeriodFxVersion,normalFormPreparationVersion].includes(r.model.version)||typeof r.key!=='string'||typeof r.createdAt!=='string'||!Array.isArray(r.model.rows)||!Array.isArray(r.model.pending)))throw Error('Histórico IRPC normal não recuperável.');
}
export function normalBook(d:Record<string,unknown>):NormalBook{
const m=store(d).irpcNormal;if(m===undefined)return newNormalBook();if(!m||typeof m!=='object'||Array.isArray(m))throw Error('Mapa IRPC normal não recuperável.');const b=m[acquisitionScope(d)]??newNormalBook();validateNormal(b);return b;
@@ -0,0 +1,20 @@
export const irpcSpecialCombinationsVersion='IRPC-normal-adjustments/14';
export const specialCombinationsSource='CBF4/13,compilação oficial APIEX pp66/70:benefícios específicos não cumuláveis entre si ou com genéricos,salvo excepção expressa;CBF16 é genérico. CIRPC12/14 e61(1):cooperativa abrangida,redução50% da taxa geral32%=16%,base integralmente elegível,sem rendimentos sujeitos a retenção. Recorte de combinação destes tratamentos já qualificados:primeiro a diferença fiscalCBF16,depois a matéria colectável e a taxa16%;imposto autónomo61(4) separado a35%,sem redução cooperativa. Não se aplica automaticamente benefício específico,outro benefício ou excepção sem correspondência.';
export type SpecialCombinationsInput={version:1;reference:string;specificState:''|'absent'|'known';specificReference:string;specificException:''|'absent'|'claimed';otherState:''|'absent'|'known';otherReference:string};
export const newSpecialCombinations=():SpecialCombinationsInput=>({version:1,reference:'',specificState:'',specificReference:'',specificException:'',otherState:'',otherReference:''});
export function validateSpecialCombinations(i:SpecialCombinationsInput){
if(!i||i.version!==1||['reference','specificReference','otherReference'].some(k=>typeof i[k as 'reference']!=='string')||!['','absent','known'].includes(i.specificState)||!['','absent','claimed'].includes(i.specificException)||!['','absent','known'].includes(i.otherState))throw Error('Mapa de cumulações não recuperável;históricos conservados.');
}
export function qualifySpecialCombinations(i:SpecialCombinationsInput,genericRequested=true){
validateSpecialCombinations(i);const pending:string[]=[];let incompatible=false;
if(!i.reference.trim())pending.push('Falta suporte da combinação dos tratamentos registados no mesmo sujeito/projecto/período.');
if(!i.specificState||!i.specificReference.trim())pending.push('Benefícios específicosCBF:identifique presença ou ausência documentada;localização não prova estatuto.');
if(!i.otherState||!i.otherReference.trim())pending.push('Outros benefícios além dos tratamentosL7 registados:identifique presença ou ausência com suporte.');
if(i.specificState==='absent'&&i.specificException)pending.push('Ausência de benefício específico contradiz excepção preenchida;reconcilie a declaração.');
if(i.specificState==='known'){
if(i.specificException==='absent'&&genericRequested){incompatible=true;pending.push('IncompatibilidadeCBF4/13:benefício específico declarado e genéricoCBF16 sem excepção expressa;nenhum benefício é escolhido ou removido automaticamente.');}
else pending.push(i.specificException==='absent'?'Benefício específico declarado sem cálculo próprio correspondido;não é ignorado na totalização.':i.specificException==='claimed'?'Excepção expressa invocada:ligar artigo,âmbito,período e limites à combinação antes de calcular;esta excepção ainda não está correspondida.':'Benefício específico declarado:identifique excepção expressa ou ausência;sem presumir acumulação.');
}
if(i.otherState==='known')pending.push('Outro benefício declarado ainda sem cálculo/ordem/limite correspondidos;sem o aplicar ou ignorar por presunção.');
return{status:incompatible?'incompatible' as const:pending.length?'indeterminate' as const:'compatible' as const,ready:pending.length===0,pending,source:specialCombinationsSource};
}
+21 -6
View File
@@ -1,3 +1,4 @@
import {irpcSpecialCombinationsVersion,qualifySpecialCombinations,validateSpecialCombinations,specialCombinationsSource,type SpecialCombinationsInput} from './irpc-special-combinations.ts';
import type {NormalBook,NormalModel} from './irpc-normal.ts';
import {calculateNormalBase} from './irpc-normal-base.ts';
import {calculateNormalAssessment} from './irpc-normal-assessment.ts';
@@ -18,14 +19,16 @@ export type SpecialExpense={id:string;scheduleId:string;nature:''|'undocumented'
export type AcceleratedAsset={id:string;assetId:string;kind:''|'new_project_building'|'rehabilitated_project_building'|'industrial_equipment'|'agroindustrial_equipment'|'other';eligible:''|'yes'|'no';reference:string};
export type SpecialContinuationInput={version:1;period:''|'annual'|'cessation';reference:string;historyReference:string;monthlyFromStart:''|'yes'|'no'};
export type SpecialMonthlyInput={version:1;reference:string;startPeriod?:{version:1;reference:string};continuation?:SpecialContinuationInput};
export type SpecialInput={version:1;expenses:{qualification:Qualification;rows:SpecialExpense[]};cooperative:{qualification:Qualification;kind:''|'agrarian'|'craft'|'cultural'|'other';allIncomeEligible:''|'yes'|'no';withheldIncome:''|'absent'|'known'};acceleration:{qualification:Qualification;cbfRequirements:''|'yes'|'no';recognition:''|'documented'|'not_required';recognitionReference:string;noOtherBenefits:''|'yes'|'no';rows:AcceleratedAsset[];monthly?:SpecialMonthlyInput}};
export const specialCalculationVersion=(i:SpecialInput)=>i.acceleration.monthly?.continuation?irpcSpecialCessationPriorVersion:i.acceleration.monthly?.startPeriod?irpcSpecialStartPeriodVersion:i.acceleration.monthly?irpcSpecialMonthlyVersion:irpcSpecialVersion;
export type SpecialInput={version:1;combinations?:SpecialCombinationsInput;expenses:{qualification:Qualification;rows:SpecialExpense[]};cooperative:{qualification:Qualification;kind:''|'agrarian'|'craft'|'cultural'|'other';allIncomeEligible:''|'yes'|'no';withheldIncome:''|'absent'|'known'};acceleration:{qualification:Qualification;cbfRequirements:''|'yes'|'no';recognition:''|'documented'|'not_required';recognitionReference:string;noOtherBenefits:''|'yes'|'no';rows:AcceleratedAsset[];monthly?:SpecialMonthlyInput}};
export const specialCalculationVersion=(i:SpecialInput)=>i.combinations?irpcSpecialCombinationsVersion:i.acceleration.monthly?.continuation?irpcSpecialCessationPriorVersion:i.acceleration.monthly?.startPeriod?irpcSpecialStartPeriodVersion:i.acceleration.monthly?irpcSpecialMonthlyVersion:irpcSpecialVersion;
export function specialAccelerationSource(i:SpecialInput){const source=i.acceleration.monthly?.continuation?specialCessationPriorSource:i.acceleration.monthly?.startPeriod?specialStartPeriodSource:i.acceleration.monthly?specialMonthlySource:specialSources.acceleration;return i.combinations?source.replace('/cumulação','').replace('Sem cumulação.','Cumulação qualificada no mapa próprio.').replace('sem cumulação presumida.','cumulação qualificada no mapa próprio.')+' '+specialCombinationsSource:source;}
export const newSpecialInput=():SpecialInput=>({version:1,expenses:{qualification:newQualification(),rows:[]},cooperative:{qualification:newQualification(),kind:'',allIncomeEligible:'',withheldIncome:''},acceleration:{qualification:newQualification(),cbfRequirements:'',recognition:'',recognitionReference:'',noOtherBenefits:'',rows:[]}});
export const newSpecialExpense=(id:string):SpecialExpense=>({id,scheduleId:'',nature:'',eligible:'',reference:''});
export const newAcceleratedAsset=(id:string):AcceleratedAsset=>({id,assetId:'',kind:'',eligible:'',reference:''});
const choices=(v:unknown,allowed:string[])=>typeof v==='string'&&allowed.includes(v);
export function validateSpecial(i:SpecialInput){
if(!i||i.version!==1||!i.expenses||!i.cooperative||!i.acceleration)throw Error('Qualificação L7 não recuperável;originais conservados.');
if(i.combinations!==undefined)validateSpecialCombinations(i.combinations);
if(i.acceleration.monthly!==undefined&&(!i.acceleration.monthly||i.acceleration.monthly.version!==1||typeof i.acceleration.monthly.reference!=='string'))throw Error('Preparação CBF16 por duodécimos não recuperável;históricos conservados.');
if(i.acceleration.monthly?.startPeriod!==undefined&&(!i.acceleration.monthly.startPeriod||i.acceleration.monthly.startPeriod.version!==1||typeof i.acceleration.monthly.startPeriod.reference!=='string'))throw Error('Preparação CBF16 para início de actividade não recuperável;históricos conservados.');
const continuation=i.acceleration.monthly?.continuation;
@@ -58,6 +61,8 @@ function qualify(q:Qualification,b:NormalBook,m:NormalModel):Decision{
function fail(d:Decision,reason:string){d.status='indeterminate';d.pending.push(reason);d.reason=d.pending.join(' ');}
export function calculateSpecial(draft:Record<string,unknown>,b:NormalBook,m:NormalModel,source:Parameters<typeof calculateNormalDepreciation>[1]){
const i=b.special!;validateSpecial(i);const expenseDecision=qualify(i.expenses.qualification,b,m),coopDecision=qualify(i.cooperative.qualification,b,m),accDecision=qualify(i.acceleration.qualification,b,m);
const combinations=i.combinations?qualifySpecialCombinations(i.combinations,accDecision.status==='calculated'&&i.acceleration.rows.some(r=>r.eligible==='yes')):undefined;
if(combinations&&b.base?.benefits.state==='known'){let extra=true;try{extra=cmp(decimalFraction(b.base.benefits.amount),z())!==0n;}catch{/*L6 conserva a própria pendência.*/}if(extra){combinations.ready=false;if(combinations.status!=='incompatible')combinations.status='indeterminate';combinations.pending.push('Dedução/benefício à matéria colectável já registado emL6 sem correspondência a esta combinação:identifique parcela,origem e limite antes de reaplicarCBF16 ou reduzir novamente;valor original conservado.');}}
const existing=(b.schedules?.rows??[]).filter(r=>r.rule==='undocumented');
if(expenseDecision.status==='absent'&&(existing.length||i.expenses.rows.length))fail(expenseDecision,'Ausência declarada contradiz despesas36g ou linhasL7 presentes;reconcilie sem apagar o ajustamento.');
if(expenseDecision.status!=='calculated'&&i.expenses.rows.length&&expenseDecision.status!=='indeterminate')fail(expenseDecision,'Estado global e parcelas da despesa contraditórios;qualifique as parcelas ou conserve apenas a declaração.');
@@ -84,14 +89,15 @@ export function calculateSpecial(draft:Record<string,unknown>,b:NormalBook,m:Nor
if(accDecision.status==='absent'&&i.acceleration.rows.length)fail(accDecision,'Ausência de amortização acelerada contradiz activos seleccionados.');
if(accDecision.status!=='calculated'&&i.acceleration.rows.length&&accDecision.status!=='indeterminate')fail(accDecision,'Não aplicação global com activos seleccionados:reconcilie o estado e as parcelas.');
if(accDecision.status==='calculated'){
if(combinations&&!combinations.ready)fail(accDecision,combinations.pending.join(' '));
if(i.acceleration.monthly&&!i.acceleration.monthly.reference.trim())fail(accDecision,'Falta suporte da reutilização dos meses e da opção por duodécimosCBF16 no mapaL6.');
if(i.acceleration.monthly?.startPeriod&&!i.acceleration.monthly.startPeriod.reference.trim())fail(accDecision,'Falta suporte do período curto por início de actividadeCBF16;período e activosL6 conservados.');
const continuation=i.acceleration.monthly?.continuation;
if(continuation?.monthlyFromStart==='no')fail(accDecision,'Duodécimos desde a entrada emuso não aplicados:esta combinação de quotas/saída exige correspondência própria;L6 e os saldos declarados ficam preservados.');
if(continuation&&(!continuation.period||!continuation.reference.trim()||!continuation.historyReference.trim()||!continuation.monthlyFromStart))fail(accDecision,'CBF16 cessação/quotas anteriores:identifique período,suporte da vigência/condições e das quotas anteriores ou ausência;duodécimos desde a entrada emuso por documentar. Sem recalcular ou presumir saldos anteriores.');
if(i.acceleration.cbfRequirements!=='yes'||!i.acceleration.recognition||!i.acceleration.recognitionReference.trim())fail(accDecision,'Falta enquadramentoCBF3/9 e reconhecimento10 quando exigido;excepção deve ter norma/suporte,não localização.');
if(i.acceleration.noOtherBenefits!=='yes')fail(accDecision,'CumulaçãoCBF4 por corresponder;este recorte não combina outros benefícios positivos.');
if(coopDecision.status==='calculated')fail(accDecision,'Combinação de redução cooperativa comCBF16 ainda não correspondida;cada caso independente continua demonstrável.');
if(!i.combinations&&i.acceleration.noOtherBenefits!=='yes')fail(accDecision,'CumulaçãoCBF4 por corresponder;este recorte não combina outros benefícios positivos.');
if(!i.combinations&&coopDecision.status==='calculated')fail(accDecision,'Combinação de redução cooperativa comCBF16 ainda não correspondida;cada caso independente continua demonstrável.');
if(!b.depreciation||!m.depreciation||!i.acceleration.rows.length)fail(accDecision,'Seleccione activos do mapaL6 já reconciliado;sem criar activo ou quota paralelos.');
}
const accelerations=i.acceleration.rows.map(r=>{
@@ -125,6 +131,7 @@ export function calculateSpecial(draft:Record<string,unknown>,b:NormalBook,m:Nor
const startPeriod=!!i.acceleration.monthly?.startPeriod,continuation=i.acceleration.monthly?.continuation;
acceleratedDepreciation=calculateNormalDepreciation(b.depreciation,source,startPeriod||continuation?specialPeriod(b,m):{from:'2026-01-01',to:'2026-12-31'},[...b.rows,...(b.schedules?.rows??[]),...(b.costs?.rows??[])],startPeriod||continuation?.period==='cessation',new Set(accelerations.filter(r=>r.status==='calculated').map(r=>r.assetId)));
if(startPeriod||continuation)acceleratedDepreciation={...acceleratedDepreciation,source:continuation?specialCessationPriorSource:specialStartPeriodSource};
if(i.combinations)acceleratedDepreciation={...acceleratedDepreciation,source:specialAccelerationSource(i)};
if(acceleratedDepreciation.adjustment===null)fail(accDecision,acceleratedDepreciation.pending.join(' '));
else additionalDeduction=subtractFractions(m.depreciation.adjustment,acceleratedDepreciation.adjustment);
}else if(accDecision.status==='calculated')fail(accDecision,'Reconciliação da amortização normal em falta.');
@@ -136,12 +143,14 @@ export function calculateSpecial(draft:Record<string,unknown>,b:NormalBook,m:Nor
const assessment=assessmentInput?calculateNormalAssessment(assessmentInput,base?.taxableBase??null,{year:b.year,scope:b.scope,currency:m.currency,extended:!!b.schedules||!!b.periodsFx,short:!!m.periodsFx?.short,period:specialPeriod(b,m)}):null;
const normalColecta=assessment?.colecta??null,colecta=coopDecision.status==='indeterminate'?null:normalColecta?(coopDecision.status==='calculated'?mul(normalColecta,1n,2n):normalColecta):null;
const combinedColecta=colecta&&autonomousTax?addFractions([colecta,autonomousTax]):null;
const monthlySource=i.acceleration.monthly?.continuation?specialCessationPriorSource:i.acceleration.monthly?.startPeriod?specialStartPeriodSource:specialMonthlySource;
const monthlySource=i.combinations?specialAccelerationSource(i):i.acceleration.monthly?.continuation?specialCessationPriorSource:i.acceleration.monthly?.startPeriod?specialStartPeriodSource:specialMonthlySource;
const monthlyModel=i.acceleration.monthly?{input:structuredClone(i.acceleration.monthly),period:specialPeriod(b,m),source:monthlySource,assets:accelerations.map(r=>{
const input=b.depreciation?.assets.find(a=>a.id===r.assetId),normal=m.depreciation?.assets.find(a=>a.id===r.assetId),accelerated=acceleratedDepreciation?.assets.find(a=>a.id===r.assetId),applied=accDecision.status==='calculated'&&r.status==='calculated'&&accelerated?.status==='calculated',rate=normal?.status==='calculated'&&input?decimalFraction(input.rate):null;
return{...(i.acceleration.monthly?.continuation?{prior:{previousAccounting:input?.previousAccounting??'',previousFiscal:input?.previousFiscal??'',available:input?.priorAvailable??'',reference:input?.previousReference??'',normalUsed:normal?.priorUsed??null,normalRemaining:normal?.priorRemaining??null,acceleratedUsed:applied?accelerated.priorUsed:null,acceleratedRemaining:applied?accelerated.priorRemaining:null,normalFiscalRemaining:normal?.remainingFiscal??null,acceleratedFiscalRemaining:applied?accelerated.remainingFiscal:null},exit:{state:input?.exitState??'',date:input?.exitDate??''}}:{}),id:r.id,assetId:r.assetId,assetKey:r.assetKey,description:r.description,start:input?.start??'',months:normal?.months??null,taxBasis:normal?.taxBasis??null,normalRate:rate,acceleratedRate:applied&&rate?mul(rate,3n,2n):null,normalQuota:normal?.quota??null,acceleratedQuota:applied?accelerated.quota:null,normalDeduction:normal?.deduction??null,acceleratedDeduction:applied?accelerated.deduction:null,additionalDeduction:applied&&normal?.deduction&&accelerated.deduction?subtractFractions(accelerated.deduction,normal.deduction):r.status==='not_applicable'?z():null,applied,reason:r.reason};
})}:undefined;
return{input:structuredClone(i),context:structuredClone(specialContext(draft)),subjectNuit:b.base?.subjectNuit??'',period:specialPeriod(b,m),expenses:{...expenseDecision,rows:expenses,knownAutonomous,autonomousTax,source:specialSources.expenses},cooperative:{...coopDecision,rate:coopDecision.status==='calculated'?fraction(16n):null,source:specialSources.cooperative},acceleration:{...accDecision,rows:accelerations,additionalDeduction,normal:m.depreciation??null,accelerated:acceleratedDepreciation,...(monthlyModel?{monthly:monthlyModel}:{}),source:i.acceleration.monthly?.continuation?specialCessationPriorSource:i.acceleration.monthly?.startPeriod?specialStartPeriodSource:i.acceleration.monthly?specialSources.acceleration+' '+specialMonthlySource:specialSources.acceleration},reference:{taxableProfit:m.taxableProfit,taxableBase:m.taxableBase,colecta:m.colecta??null},taxableProfit,taxableBase:base?.taxableBase??null,colecta,combinedColecta,pending:[...expenseDecision.pending,...coopDecision.pending,...accDecision.pending,...(base?.pending??[]),...(assessment?.pending??[])],limitations:['L7 parcial:apenas61(4),cooperativa integralmente elegível12 eCBF16 no recorte documentado.','Localização/sector só contextualizam;não activam benefícios.','Parcelas mistas,cumulação,mais-valias e demais famílias têm correspondência própria.','M20/M22 permanecem preparaçãoL6 de referência;efeitosL7 sem campo oficial presumido.','Colecta combinada não é saldo final a pagar/recuperar;retenções,pagamentos e créditos anuais emL8.'],finalBalance:{scope:'L8',payable:null,recoverable:null}};
const combinationsModel=combinations?{...combinations,input:structuredClone(i.combinations!),period:specialPeriod(b,m),subjectNuit:b.base?.subjectNuit??'',reusedL6Benefits:b.base?structuredClone(b.base.benefits):null,order:['Diferença adicionalCBF16 aplicada uma vez ao lucroL6','Matéria colectável recalculada com as mesmas deduções/perdasL6','Cooperativa12:50% da taxa geral32%=16% apenas na base integralmente elegível','Imposto autónomo61(4) separado a35%,sem redução cooperativa','Saldo final a pagar/recuperar permaneceL8'],assetClaims:accelerations.map(r=>{const normal=m.depreciation?.assets.find(a=>a.id===r.assetId),accelerated=acceleratedDepreciation?.assets.find(a=>a.id===r.assetId);return{assetId:r.assetId,document:normal?.document??'',line:normal?.line??'',normalDeduction:normal?.deduction??null,acceleratedDeduction:accelerated?.deduction??null,priorRecoveredIncluded:accelerated?.priorUsed??null,additionalDeduction:normal?.deduction&&accelerated?.deduction?subtractFractions(accelerated.deduction,normal.deduction):null,remainingFiscal:accelerated?.remainingFiscal??null};}),limits:{additionalDeduction,cooperativeRate:coopDecision.status==='calculated'?fraction(16n):null,autonomousRate:fraction(35n),autonomousTax,ordinaryColecta:colecta,combinedColecta}}:undefined;
const combinationsReady=combinations?.ready??true;
return{...(combinationsModel?{combinations:combinationsModel}:{}),input:structuredClone(i),context:structuredClone(specialContext(draft)),subjectNuit:b.base?.subjectNuit??'',period:specialPeriod(b,m),expenses:{...expenseDecision,rows:expenses,knownAutonomous,autonomousTax,source:specialSources.expenses},cooperative:{...coopDecision,rate:coopDecision.status==='calculated'?fraction(16n):null,source:specialSources.cooperative},acceleration:{...accDecision,rows:accelerations,additionalDeduction,normal:m.depreciation??null,accelerated:acceleratedDepreciation,...(monthlyModel?{monthly:monthlyModel}:{}),source:i.combinations?specialAccelerationSource(i):i.acceleration.monthly?.continuation?specialCessationPriorSource:i.acceleration.monthly?.startPeriod?specialStartPeriodSource:i.acceleration.monthly?specialSources.acceleration+' '+specialMonthlySource:specialSources.acceleration},reference:{taxableProfit:m.taxableProfit,taxableBase:m.taxableBase,colecta:m.colecta??null},taxableProfit:combinationsReady?taxableProfit:null,taxableBase:combinationsReady?base?.taxableBase??null:null,colecta:combinationsReady?colecta:null,combinedColecta:combinationsReady?combinedColecta:null,pending:[...(combinations?.pending??[]),...expenseDecision.pending,...coopDecision.pending,...accDecision.pending,...(base?.pending??[]),...(assessment?.pending??[])],limitations:['L7 parcial:apenas61(4),cooperativa integralmente elegível12 eCBF16 no recorte documentado.','Localização/sector só contextualizam;não activam benefícios.',i.combinations?'Parcelas mistas,outras cumulações/excepções,mais-valias e demais famílias têm correspondência própria.':'Parcelas mistas,cumulação,mais-valias e demais famílias têm correspondência própria.','M20/M22 permanecem preparaçãoL6 de referência;efeitosL7 sem campo oficial presumido.','Colecta combinada não é saldo final a pagar/recuperar;retenções,pagamentos e créditos anuais emL8.'],finalBalance:{scope:'L8',payable:null,recoverable:null}};
}
export type SpecialModel=ReturnType<typeof calculateSpecial>;
const money=(v:FiscalFraction|null)=>{if(!v)return'Indeterminado';const p=fractionPresentation(v);return(p.approximate?'≈':'')+p.decimal+' MZN (exacto '+v.numerator+'/'+v.denominator+')';};
@@ -155,9 +164,15 @@ export function specialReportRows(m:SpecialModel):Array<[string,string,string,st
['IRPC — L7','Amortizações aceleradas',`${status(m.acceleration.status)};dedução adicional conciliada:${money(m.acceleration.additionalDeduction)};${m.acceleration.reason}`,`${m.input.acceleration.qualification.reference};${m.input.acceleration.qualification.periodReference};${m.input.acceleration.recognitionReference};${m.acceleration.source}`],
...m.acceleration.rows.map(r=>{const a=m.acceleration.accelerated?.assets.find(a=>a.id===r.assetId),n=m.acceleration.normal?.assets.find(a=>a.id===r.assetId);return['IRPC — L7',r.description||r.assetId,`${status(r.status)};quota normal:${money(n?.quota??null)};quota acelerada:${money(a?.quota??null)};dedução normal:${money(n?.deduction??null)};dedução acelerada:${money(a?.deduction??null)};${r.reason}`,r.reference]as[string,string,string,string];}),
['IRPC — L7','Efeitos nos totais',`ReferênciaL6:lucro:${money(m.reference.taxableProfit)};base:${money(m.reference.taxableBase)};colecta:${money(m.reference.colecta)}. Após tratamentosL7:lucro:${money(m.taxableProfit)};base:${money(m.taxableBase)};colecta sobre a base:${money(m.colecta)};imposto autónomo:${money(m.expenses.autonomousTax)};colecta combinada:${money(m.combinedColecta)}. Saldo final a pagar/recuperar:Indeterminado (L8).`,m.pending.join(' | ')||'Regras/parcelas reconciliadas neste recorte;simulação,não liquidação final.'],
...(m.combinations?specialCombinationsReport(m):[]),
...(m.acceleration.monthly?specialMonthlyReport(m.acceleration.monthly,m.acceleration.additionalDeduction):[]),
];}
function specialMonthlyReport(m:NonNullable<SpecialModel['acceleration']['monthly']>,deduction:FiscalFraction|null):Array<[string,string,string,string]>{
return[['IRPC — L7','CBF16 — duodécimos reutilizados de L6','Período:'+m.period.from+'–'+m.period.to+';dedução adicional conciliada:'+money(deduction)+';sem nova dedução do activo de origem.',m.input.reference+(m.input.startPeriod?';início de actividade:'+m.input.startPeriod.reference:'')+(m.input.continuation?';período:'+(m.input.continuation.period==='cessation'?'Cessação da actividade':m.input.continuation.period==='annual'?'Ano civil2026':'Informação em falta')+';suporte:'+m.input.continuation.reference+';histórico:'+m.input.continuation.historyReference:'')+';'+m.source],...m.assets.map(a=>['IRPC — L7','CBF16 por duodécimos — '+a.description,'Entrada emuso:'+(a.start||'Informação em falta')+';duodécimosL6:'+(a.months??'Indeterminados')+';base fiscal:'+money(a.taxBasis)+';taxa normal:'+(a.normalRate?fractionPresentation(a.normalRate).decimal+'%':'Indeterminada')+';taxa CBF16:'+(a.acceleratedRate?fractionPresentation(a.acceleratedRate).decimal+'%':'Não aplicada')+';quota normal:'+money(a.normalQuota)+';quota CBF16:'+money(a.acceleratedQuota)+';dedução normal:'+money(a.normalDeduction)+';dedução CBF16:'+money(a.acceleratedDeduction)+';diferença adicional:'+money(a.additionalDeduction)+(a.prior?';quotas contabilísticas anteriores:'+a.prior.previousAccounting+';quotas fiscais anteriores:'+a.prior.previousFiscal+';parcela anterior recuperável declarada:'+a.prior.available+';recuperada normal:'+money(a.prior.normalUsed)+';recuperada CBF16:'+money(a.prior.acceleratedUsed)+';saldo anterior aritmético normal:'+money(a.prior.normalRemaining)+';saldo anterior aritmético CBF16:'+money(a.prior.acceleratedRemaining)+';saldo fiscal normal:'+money(a.prior.normalFiscalRemaining)+';saldo fiscal CBF16:'+money(a.prior.acceleratedFiscalRemaining)+';saída:'+(a.exit?.state==='absent'?'Ausência declarada':a.exit?.state==='known'?'Evento documentado':'Informação em falta')+' '+(a.exit?.date??'')+';sem consumo de saldos oficiais':'')+';'+a.reason,m.source+(a.prior?';suporte das quotas anteriores:'+a.prior.reference:'')]as[string,string,string,string])];
}
function specialCombinationsReport(m:SpecialModel):Array<[string,string,string,string]>{
const c=m.combinations!;
return[['IRPC — L7','Cumulações — qualificação e ordem','Estado:'+(c.status==='compatible'?'Combinação permitida no recorte':c.status==='incompatible'?'Combinação incompatível':'Informação em falta')+';mesmo sujeito:'+c.subjectNuit+';período:'+c.period.from+'–'+c.period.to+';'+c.order.join(' → ')+';'+c.pending.join(' | '),c.input.reference+';benefícios específicos:'+c.input.specificReference+';outros benefícios:'+c.input.otherReference+';'+c.source],...c.assetClaims.map(a=>['IRPC — L7','Cumulações — activo '+a.assetId,'Documento/linha:'+a.document+'/'+a.line+';dedução normal:'+money(a.normalDeduction)+';dedução CBF16:'+money(a.acceleratedDeduction)+';parcela anterior já incluída na dedução CBF16:'+money(a.priorRecoveredIncluded)+';única diferença adicional:'+money(a.additionalDeduction)+';saldo fiscal aritmético:'+money(a.remainingFiscal)+';sem nova dedução do custo nem consumo de saldo oficial.',c.source]as[string,string,string,string]),['IRPC — L7','Cumulações — limites e totais','Dedução adicional conciliada:'+money(c.limits.additionalDeduction)+';taxa cooperativa:'+(c.limits.cooperativeRate?'16%':'Não aplicada')+';imposto autónomo independente:'+money(c.limits.autonomousTax)+';colecta sobre a base:'+money(m.colecta)+';colecta combinada:'+money(m.combinedColecta)+';saldo final a pagar/recuperar:Indeterminado (L8).',c.source]];
}
@@ -0,0 +1,44 @@
<?php
namespace Tests\Feature;
use App\Models\User;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Tests\TestCase;
final class IrpcSpecialCombinationsDraftTest extends TestCase
{
use DatabaseTransactions;
public function test_qualified_combinations_and_incompatibilities_round_trip_without_rewriting_inputs(): void
{
$target=DB::selectOne('select current_database() as name,inet_server_port() as port');
$this->assertSame('simulator_fiscal_delta_20261021_test',$target->name);$this->assertSame(55441,(int)$target->port);
config(['investor-path.enabled'=>true,'suitecrm.enabled'=>false]);$this->actingAs(User::factory()->create(['email_verified_at'=>now()]));$id=null;$etag=null;
foreach(['combined','autonomous','incompatible','missing','exception','negative','absent']as$case){
$d=json_decode(file_get_contents(base_path('outputs/irpc-l7-combinations/'.$case.'.json')),true,flags:JSON_THROW_ON_ERROR);
$personal=['person_type'=>$d['person_type']];unset($d['person_type']);
if($id===null)$id=$this->postJson('/api/v1/simulation-drafts',['personal_snapshot'=>$personal,'economic_snapshot'=>$d],['Idempotency-Key'=>(string)Str::uuid()])->assertCreated()->json('data.id');
else $this->putJson('/api/v1/simulation-drafts/'.$id,['personal_snapshot'=>$personal,'economic_snapshot'=>$d],['If-Match'=>$etag])->assertOk();
$r=$this->getJson('/api/v1/simulation-drafts/'.$id)->assertOk();$etag=$r->headers->get('ETag');$back=$r->json('data.economic_snapshot');
$this->assertSame($d['modelo_a_snapshot'],$back['modelo_a_snapshot']);$this->assertSame($d['business_plan_snapshot'],$back['business_plan_snapshot']);
$s=json_decode($back['modelo_a_snapshot'],true,flags:JSON_THROW_ON_ERROR);$b=$s['irpcNormal'][$d['context_opportunity']];$m=$b['result']['model'];$v=$m['special'];$c=$v['combinations'];
$this->assertSame('IRPC-normal-adjustments/14',$m['version']);$this->assertSame($b['special']['combinations'],$c['input']);
$this->assertContains('IRPC-normal-adjustments/13',array_column(array_column($b['history'],'model'),'version'));
$this->assertSame('150',$b['depreciation']['assets'][0]['previousAccounting']);$this->assertSame('100',$b['depreciation']['assets'][0]['previousFiscal']);$this->assertSame('50',$b['depreciation']['assets'][0]['priorAvailable']);
$this->assertSame($b['depreciation'],$v['acceleration']['normal']['input']);$this->assertSame(['scope'=>'L8','payable'=>null,'recoverable'=>null],$v['finalBalance']);$this->assertNull($m['tax']);$this->assertArrayHasKey('irpcWithholdings',$s);
if(in_array($case,['combined','autonomous'],true)){
$this->assertSame('compatible',$c['status']);$this->assertSame('25',$v['acceleration']['additionalDeduction']['numerator']);$this->assertSame('15',$c['assetClaims'][0]['priorRecoveredIncluded']['numerator']);$this->assertSame('75',$c['assetClaims'][0]['acceleratedDeduction']['numerator']);
$this->assertSame('16',$v['cooperative']['rate']['numerator']);$this->assertSame($case==='combined'?'3105':'3205',$v['taxableBase']['numerator']);
$this->assertSame(['numerator'=>$case==='combined'?'2484':'2739','denominator'=>'5'],$v['combinedColecta']);
if($case==='autonomous'){$this->assertSame('35',$v['expenses']['autonomousTax']['numerator']);$this->assertSame('0',$v['expenses']['rows'][0]['additionalProfitAdjustment']['numerator']);}
}elseif(in_array($case,['incompatible','missing','exception'],true)){
$this->assertSame($case==='incompatible'?'incompatible':'indeterminate',$c['status']);$this->assertNull($v['taxableBase']);$this->assertNull($v['combinedColecta']);$this->assertSame('3130',$m['taxableProfit']['numerator']);
}elseif($case==='negative'){
$this->assertSame('not_applicable',$v['cooperative']['status']);$this->assertSame(['numerator'=>'4968','denominator'=>'5'],$v['colecta']);
}else{
$this->assertSame('absent',$v['acceleration']['status']);$this->assertSame('0',$v['acceleration']['additionalDeduction']['numerator']);$this->assertSame(['numerator'=>'2504','denominator'=>'5'],$v['colecta']);
}
file_put_contents(base_path('outputs/irpc-l7-combinations/recovered-'.$case.'.json'),json_encode($back,JSON_THROW_ON_ERROR|JSON_UNESCAPED_UNICODE));
}
}
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,34 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs';
import {normalBook,normalCurrent,calculateNormal,recordNormal,saveNormalBook,normalHtml} from '../../resources/js/lib/irpc-normal.ts';
import {validateSpecial} from '../../resources/js/lib/irpc-special.ts';
import {createProjectDossierReport} from '../../resources/js/lib/project-dossier-report.ts';
const read=n=>JSON.parse(fs.readFileSync(new URL('./fixtures/'+n,import.meta.url),'utf8'));
const val=v=>v?Number(v.numerator)/Number(v.denominator):null;
function accepted(){const d=read('irpc-l7-preserved.json').acceleration,f=read('irpc-l7-continuation-preserved.json');d.business_plan_snapshot=f.plan;d.modelo_a_snapshot=saveNormalBook(d,f.book);return{d,b:normalBook(d)};}
function setup(expense=false){const{d,b}=accepted();const i=b.special;Object.assign(i.cooperative,{kind:'agrarian',allIncomeEligible:'yes',withheldIncome:'absent'});Object.assign(i.cooperative.qualification,{state:'known',eligible:'yes',reference:'QA cooperativa agrária12,base ordinária integralmente elegível;sem rendimentos sujeitos a retenção'});i.acceleration.rows[0].kind='agroindustrial_equipment';i.combinations={version:1,reference:'QA mesmo sujeito/projecto/período;reduçãoCIRPC12 eCBF16 genérico,efeitos em etapas distintas',specificState:'absent',specificReference:'QA sem estatuto/benefício específicoCBF20–48 neste projecto/período;CBF13',specificException:'',otherState:'absent',otherReference:'QA sem outros benefícios além dos tratamentosL7 registados'};if(expense){const old=normalBook(read('irpc-l7-preserved.json').expense);b.schedules.rows.push(structuredClone(old.schedules.rows[0]));b.profit.topics.expenseCorrections=structuredClone(old.profit.topics.expenseCorrections);i.expenses=structuredClone(old.special.expenses);Object.assign(i.expenses.qualification,{from:b.periodsFx.from,to:b.periodsFx.to});}return{d,b};}
test('accepted13 model,key and allhistorical balances reproduce exactly without optionalcombination',()=>{const{d,b}=accepted();assert.equal(normalCurrent(d,b),true);assert.deepEqual(calculateNormal(d,b),b.result.model);assert.deepEqual(normalBook(d).history,b.history);});
test('qualified cooperative12 and genericCBF16 combine in order,additional25 once then16percent,prior15 already included',()=>{const{d,b}=setup(),assets=JSON.stringify(b.depreciation),m=calculateNormal(d,b),s=m.special,c=s.combinations;assert.equal(m.version,'IRPC-normal-adjustments/14');assert.equal(c.status,'compatible');assert.equal(val(m.taxableProfit),3130);assert.equal(val(m.colecta),1001.6);assert.equal(val(s.acceleration.additionalDeduction),25);assert.equal(val(s.taxableProfit),3105);assert.equal(val(s.taxableBase),3105);assert.equal(val(s.cooperative.rate),16);assert.equal(val(s.colecta),496.8);assert.equal(val(s.combinedColecta),496.8);assert.equal(val(c.assetClaims[0].acceleratedDeduction),75);assert.equal(val(c.assetClaims[0].priorRecoveredIncluded),15);assert.equal(val(c.assetClaims[0].additionalDeduction),25);assert.equal(JSON.stringify(b.depreciation),assets);assert.equal(s.finalBalance.payable,null);assert.doesNotMatch(s.acceleration.source,/Sem cumulação[.]/);});
test('autonomousexpense35percent is not halved by cooperative rate or added again to the taxableprofit',()=>{const{d,b}=setup(true),m=calculateNormal(d,b),s=m.special;assert.equal(val(m.taxableProfit),3230);assert.equal(val(s.taxableProfit),3205);assert.equal(val(s.taxableBase),3205);assert.equal(val(s.expenses.rows[0].l6Adjustment),100);assert.equal(val(s.expenses.rows[0].additionalProfitAdjustment),0);assert.equal(val(s.expenses.autonomousTax),35);assert.equal(val(s.colecta),512.8);assert.equal(val(s.combinedColecta),547.8);assert.equal(val(s.combinations.limits.autonomousRate),35);});
test('specificCBF22 with generic16 and declaredabsence of exception is incompatible,neither benefit automatically selected',()=>{const{d,b}=setup();Object.assign(b.special.combinations,{specificState:'known',specificReference:'QA CBF22 benefício específico infra-estruturas declarado no mesmo projecto/período',specificException:'absent'});const input=structuredClone(b.special),m=calculateNormal(d,b),s=m.special;assert.equal(s.combinations.status,'incompatible');assert.equal(s.acceleration.additionalDeduction,null);assert.equal(s.taxableProfit,null);assert.equal(s.colecta,null);assert.equal(s.combinedColecta,null);assert.equal(val(m.colecta),1001.6);assert.deepEqual(b.special,input);assert.match(s.pending.join(' '),/IncompatibilidadeCBF4\/13/);});
test('claimedexception,unknownspecificpresence,otherbenefit or missingsupport remain indeterminate without provisionalrate',()=>{for(const patch of[{specificState:'known',specificReference:'QA excepção invocada,artigo/limite não correspondidos',specificException:'claimed'},{specificState:''},{otherState:'known',otherReference:'QA outro benefício fora dos ramos calculados'},{reference:''},{specificReference:''},{otherReference:''}]){const{d,b}=setup();Object.assign(b.special.combinations,patch);const m=calculateNormal(d,b);assert.equal(m.special.combinations.status,'indeterminate');assert.equal(m.special.colecta,null);assert.equal(val(m.colecta),1001.6);}});
test('missingcooperativeperiod or mixedincome blocks its dependenttotal without choosing eligibility from location',()=>{for(const patch of[{allIncomeEligible:'no'},{withheldIncome:'known'}]){const{d,b}=setup();Object.assign(b.special.cooperative,patch);d.province_id='QA-ZEE-address';const s=calculateNormal(d,b).special;assert.equal(s.colecta,null);assert.match(s.cooperative.reason,/mistas\/retidas/);}const{d,b}=setup();b.special.cooperative.qualification.from='2026-02-01';assert.equal(calculateNormal(d,b).special.colecta,null);});
test('duplicateassetqualification cannot duplicate CBF16 deduction;remainingfiscal20 cap keepsadditionalzero',()=>{const{d,b}=setup();b.special.acceleration.rows.push({...b.special.acceleration.rows[0],id:'same-asset-again'});let s=calculateNormal(d,b).special;assert.equal(s.acceleration.additionalDeduction,null);assert.match(s.acceleration.reason,/Activo repetido/);const cap=setup();Object.assign(cap.b.depreciation.assets[0],{previousAccounting:'1000',previousFiscal:'980',priorAvailable:'20'});s=calculateNormal(cap.d,cap.b).special;assert.equal(val(s.acceleration.additionalDeduction),0);assert.equal(val(s.combinations.assetClaims[0].acceleratedDeduction),20);assert.equal(val(s.combinations.assetClaims[0].remainingFiscal),0);});
test('negativecooperative or absentCBF16 retains the other independentlyqualified treatment',()=>{const negative=setup();negative.b.special.cooperative.qualification.eligible='no';let s=calculateNormal(negative.d,negative.b).special;assert.equal(s.cooperative.status,'not_applicable');assert.equal(val(s.colecta),993.6);const absent=setup();absent.b.special.acceleration.rows=[];Object.assign(absent.b.special.acceleration.qualification,{state:'absent',eligible:''});s=calculateNormal(absent.d,absent.b).special;assert.equal(s.acceleration.status,'absent');assert.equal(val(s.acceleration.additionalDeduction),0);assert.equal(val(s.colecta),500.8);});
test('save-recover14 appends13 verbatim,leaves inputbalances/plan/otherbooks unchanged,and report explains order and no double deduction',()=>{const{d,b}=setup(true),old=structuredClone(b.result),history=structuredClone(b.history),assets=JSON.stringify(b.depreciation),plan=d.business_plan_snapshot,other=JSON.parse(d.modelo_a_snapshot).irpcWithholdings;d.modelo_a_snapshot=saveNormalBook(d,recordNormal(d,b));let back=normalBook(JSON.parse(JSON.stringify(d)));assert.equal(normalCurrent(d,back),true);assert.deepEqual(back.history,[...history,old]);assert.equal(old.model.version,'IRPC-normal-adjustments/13');assert.equal(JSON.stringify(back.depreciation),assets);assert.equal(d.business_plan_snapshot,plan);assert.deepEqual(JSON.parse(d.modelo_a_snapshot).irpcWithholdings,other);const html=normalHtml(d);assert.match(html,/Combinação permitida no recorte/);assert.match(html,/parcela anterior já incluída na dedução CBF16:15.00/);assert.match(html,/única diferença adicional:25.00/);assert.match(html,/imposto autónomo independente:35.00/);assert.match(html,/colecta combinada:547.80/);assert.match(html,/Saldo final a pagar\/recuperar:Indeterminado/);assert.match(JSON.stringify(createProjectDossierReport(d,{selectedSections:new Set(['finance'])})),/Cumulações — qualificação/);d.modelo_a_snapshot=saveNormalBook(d,recordNormal(d,back));back=normalBook(d);assert.equal(val(back.result.model.special.combinedColecta),547.8);assert.equal(back.depreciation.assets[0].priorAvailable,'50');back.special.combinations.otherState='known';assert.equal(normalCurrent(d,back),false);});
test('positive unclassified L6benefit is preserved and blocks another deduction until reconciled;zero base never offsets autonomous35',()=>{const extra=setup();extra.b.base.benefits={state:'known',amount:'100',reference:'QA dedução já declarada sem correspondência à combinação'};let m=calculateNormal(extra.d,extra.b);assert.equal(m.special.combinations.status,'indeterminate');assert.equal(m.special.colecta,null);assert.notEqual(m.taxableProfit,null);assert.equal(extra.b.base.benefits.amount,'100');assert.match(m.special.pending.join(' '),/já registado emL6/);const loss=setup(true),p=JSON.parse(loss.d.business_plan_snapshot);p.plan.products[0].unitPrice='10';loss.d.business_plan_snapshot=JSON.stringify(p);loss.b.schedules.currentLossReference='QA perda própria do período reconciliada;sem transporte oficial automático';m=calculateNormal(loss.d,loss.b);assert.ok(val(m.special.taxableProfit)<0);assert.equal(val(m.special.taxableBase),0);assert.equal(val(m.special.colecta),0);assert.equal(val(m.special.expenses.autonomousTax),35);assert.equal(val(m.special.combinedColecta),35);});
test('malformedoptionalcombination cannot overwrite acceptedbooks',()=>{for(const value of[null,{version:2},{version:1,reference:'QA',specificState:'unknown',specificReference:'QA',specificException:'',otherState:'absent',otherReference:'QA'}]){const{b}=setup();b.special.combinations=value;assert.throws(()=>validateSpecial(b.special),/não recuperável/);}});
test('prepare only new guardedAPI cases;accepted13 proofs remain separate',()=>{const cases={combined:setup(),autonomous:setup(true),incompatible:setup(),missing:setup(),exception:setup(),negative:setup(),absent:setup()};Object.assign(cases.incompatible.b.special.combinations,{specificState:'known',specificReference:'QA CBF22 no mesmo projecto/período',specificException:'absent'});cases.missing.b.special.combinations.specificReference='';Object.assign(cases.exception.b.special.combinations,{specificState:'known',specificReference:'QA excepção expressa invocada sem correspondência de artigo/limites',specificException:'claimed'});cases.negative.b.special.cooperative.qualification.eligible='no';cases.absent.b.special.acceleration.rows=[];Object.assign(cases.absent.b.special.acceleration.qualification,{state:'absent',eligible:''});fs.mkdirSync('outputs/irpc-l7-combinations',{recursive:true});for(const[name,{d,b}]of Object.entries(cases)){d.modelo_a_snapshot=saveNormalBook(d,recordNormal(d,b,'2026-10-26T16:00:00+02:00'));fs.writeFileSync('outputs/irpc-l7-combinations/'+name+'.json',JSON.stringify(d));fs.writeFileSync('outputs/irpc-l7-combinations/'+name+'.html',normalHtml(d));}});
+21
View File
@@ -0,0 +1,21 @@
<?php
$root=dirname(__DIR__);
$operation=$argv[1]??'';
if(!in_array($operation,['routes','test'],true)||basename($root)!=='portal-investidor-irpc-l7-common-qa-20261026')throw new RuntimeException('Explicit L7 QA workspace and routes/test operation required.');
foreach(['storage/framework/views','storage/framework/cache','storage/framework/sessions','bootstrap/cache','storage/logs','outputs/irpc-l7-combinations']as$dir)if(!is_dir($root.'/'.$dir))mkdir($root.'/'.$dir,0700,true);
foreach(['APP_BASE_PATH'=>$root,'APP_ENV'=>'testing','APP_KEY'=>'base64:'.base64_encode(str_repeat('Q',32)),'DB_CONNECTION'=>'pgsql','DB_HOST'=>'127.0.0.1','DB_PORT'=>'55441','DB_DATABASE'=>'simulator_fiscal_delta_20261021_test','DB_USERNAME'=>'postgres','DB_PASSWORD'=>'','DB_URL'=>'','CACHE_STORE'=>'array','SESSION_DRIVER'=>'array','QUEUE_CONNECTION'=>'sync','MAIL_MAILER'=>'array'] as $k=>$v){putenv($k.'='.$v);$_ENV[$k]=$_SERVER[$k]=$v;}
$loader=require $root.'/vendor/autoload.php';
spl_autoload_register(function($class)use($root){foreach(['App\\'=>'app/','Tests\\'=>'tests/','Database\\Factories\\'=>'database/factories/']as$prefix=>$dir){if(str_starts_with($class,$prefix)){$file=$root.'/'.$dir.str_replace('\\','/',substr($class,strlen($prefix))).'.php';if(is_file($file)){require $file;return;}}}},true,true);
$app=require $root.'/bootstrap/app.php';
(new Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables)->bootstrap($app);
(new Illuminate\Foundation\Bootstrap\LoadConfiguration)->bootstrap($app);
$c=config('database.connections.pgsql');
if($app->configurationIsCached()||$c['host']!=='127.0.0.1'||(int)$c['port']!==55441||$c['database']!=='simulator_fiscal_delta_20261021_test'||!empty($c['url']))throw new RuntimeException('QA guard');
$pdo=new PDO('pgsql:host=127.0.0.1;port=55441;dbname='.$c['database'],$c['username'],$c['password']);
if($pdo->query('select current_database()')->fetchColumn()!==$c['database'])throw new RuntimeException('QA identity');
if($operation==='routes'){
$app->make(Illuminate\Contracts\Console\Kernel::class)->bootstrap();
$status=Illuminate\Support\Facades\Artisan::call('wayfinder:generate',['--with-form'=>true]);
echo Illuminate\Support\Facades\Artisan::output();exit($status);
}
$argv=['phpunit','-c','phpunit.investmoz.xml','tests/Feature/IrpcSpecialCombinationsDraftTest.php'];$argc=count($argv);$_SERVER['argv']=$argv;$_SERVER['argc']=$argc;require $root.'/vendor/phpunit/phpunit/phpunit';