feat(irpc): qualify CBF16 monthly quotas for resident start periods in QA
This commit is contained in:
@@ -959,3 +959,24 @@ Conferidos3ficheiros do delta,60ficheiros de execução protegidos e31originais.
|
||||
O exemplo mantém quota normal50,quotaCBF16 75 limitada ao contabilizado60:dedução adicional**60−50=10MZN**,não75 nem uma segunda amortização. Lucro/base apósL7 6300 e**colecta simulada2016MZN**;saldo final a pagar/recuperar por determinar emL8. Os tratamentos de despesas autónomas,cooperativas abrangidas e amortizações anuais anteriores permanecem disponíveis,com fotografias/históricos conservados. O estadoQAsem instalação é histórico para este incremento.
|
||||
|
||||
**SaldoL7:**CBF16 anual com duodécimos entregue;continuam fora deste recorte períodos curtos/não civis,saídas,anos/quotas anteriores,métodos/categorias não correspondidos,cumulações e restantes famílias/transportesM20M22 já discriminados na mesma matriz. Retoma por regra/período/contabilização e compatibilidade do caso;documentos em falta só limitam o dependente. O próximo conjunto a executar emQA é**CBF16 em período curto por início de actividade de residente em2026**,reutilizando tipo/período e mesesL6,sem anualizar base ou taxa. Cessação,transição e outras extensões conservam âmbito próprio. L6operacional delimitado;L7parcial;localização não concede benefício;L8conserva liquidação final. Recibo:`docs/investmoz/simulations/irpc-l7-months-delivery-20261026.json`.
|
||||
|
||||
## 26/10/2026 — CBF16 no início de actividade demonstrado em QA
|
||||
|
||||
**QA preparado, sem instalação.** O portal mantém `0bd5d495`, com duodécimos anuais e os tratamentos anteriores preservados. Este incremento cobre apenas CBF16 num período curto por início de actividade de residente em2026,com meses completos até31/12. Reutiliza período,tipo,suporte e activosL6,sem anualizar resultados. Não altera o motor cambial/de amortizaçõesL6 nem volta a pesquisar fontes/provas aceites.
|
||||
|
||||
A preparação é explícita dentro dos duodécimosCBF16. Pode reutilizar o suporte já declarado emL6 por acção do utilizador,sem modificar datas,activos ou qualificação. Exige condiçõesCBF3/9,reconhecimento10 quando aplicável ou excepção identificada,afectação do activo e suporte do período. Activo novo já correspondido,entrada emuso no período,método constante pela taxa normal máxima,sem quotas anteriores,saída ou cumulação. A localização não activa benefícios. Sem a nova opção,as fotografias10/11 mantêm chaves e resultados;com ela,a fotografia12 conserva o resultado11 e todo o histórico anterior.
|
||||
|
||||
| Caso novo | Efeito demonstrado |
|
||||
| --- | --- |
|
||||
| Início1Julho;máquina com entrada15Julho,base1000,taxa10%,contabilizado60 | 6mesesL6;quota normal50,acelerada75,limitada60:dedução adicional10MZN. Lucro/baseL6 3130→L7 3120;colecta simulada1001,60→998,40MZN. Saldo finalL8indeterminado. |
|
||||
| InícioOutubro / Dezembro | 3 /1meses efectivos;sem anualização. Dedução adicional5 /5/3MZN,com fracção exacta e aproximação de apresentação separadas. |
|
||||
| Suporte em falta | SóCBF16 fica indeterminado;referênciaL6 conhecida e entradas conservadas. |
|
||||
| Condição negativa / ausência declarada | Estados distintos e justificados;efeito zero identificado,sem taxa acelerada aplicada à parcela negativa. |
|
||||
|
||||
Provas novas dirigidas:9testes;API privada1teste/97asserções emQA isolada127.0.0.1:55441/base `simulator_fiscal_delta_20261021_test`,com guarda e transacção/rollback. Cinco casos passaram cálculo→gravação→recuperação→HTML e dossier financeiro. Comparação exacta do resultado11 aceite e conservação de todo o histórico,plano,activos e outros livros;recalcular não acumula deduções. Interface real demonstrou preparar/cancelar,reutilizar suporteL6,pendência,limites,fracções,guardar/recuperar e exportar,sem erros. Wayfinder`--with-form`,TypeScript e buildQA passaram. Conferidos584ficheiros protegidos e31originais sem alteração;nenhuma migração/importação/escrita privada no activo. SGF TEMPO separado.
|
||||
|
||||
**Saldo concretoL7 — código:**(1)outras origens/parcelas de despesas autónomas;(2)cooperativas mistas/retidas e demais entidades9–14;(3)CBF16 cessação/transição/períodos não civis,saídas,quotas/anos anteriores,quota inferior,métodos/categorias e cumulações;(4)mais-valias61-A/operações54–56/41-A;(5)CBF17–19 e direito potencial15;(6)regimes sectoriais/territoriais;(7)transporteL7M20/M22. A extensão de início de actividade sai deste saldo apenas no recorteQA acima;cessação é um próximo candidato compatível,reutilizando o períodoL6,com regra/quotas/saída próprias a demonstrar antes de se declarar suportada.
|
||||
|
||||
**Documentos por caso — exemplos/retoma:**despesa36g sem qualificação61(4)→associar natureza/parcela/período;projecto sem reconhecimento10→instrumento ou norma de excepção exigida;cumulação cooperativa+CBF16→disposição aplicável,ordem e limites;efeitoL7 sem campoM20/M22→exemplar/edição/campo e representação das fracções. Não são novos aprovadores ou revalidações genéricas. A dúvida limita o caso/campo dependente;as demais provas permanecem válidas.
|
||||
|
||||
Entrega agrupada preparada para os cinco casos compatíveis,sem instalação deste incremento. Na futura entrega:medir novamente espaço/inodes/carga de compilação e backup,conferir o main então actual e executar backup/restauro fresco,mantendo a versão activa até a nova ficar pronta. O controlo de5,50GiB antes/5,33GiB após backup desta entrega0bd5d495 é histórico,não garantia futura. **L6 operacional delimitado;L7 parcial;colecta distinta do saldo final deL8.** Recibo:`docs/investmoz/simulations/irpc-l7-start-period-qa-20261026.json`;provas:`outputs/irpc-l7-start-period/`. Reprodução dirigida:`node --test tests/frontend/irpc-special-start-period.test.mjs`;`php tools/irpc-special-start-period-qa.php routes`;`php tools/irpc-special-start-period-qa.php test`;`tsc --noEmit`;build com `INVESTMOZ_QA_BUILD=1`.
|
||||
|
||||
@@ -0,0 +1,309 @@
|
||||
{
|
||||
"status": "QA_ready_not_installed",
|
||||
"date": "2026-10-26",
|
||||
"branch": "codex/irpc-l7-start-period-qa-20261026",
|
||||
"installed_baseline": "0bd5d495505d512088887efd3dc5c61f645b1647",
|
||||
"documentation_baseline": "eccd95f9e79c871b592839aceca5f968440d067d",
|
||||
"scope": "CBF16:resident start of activity,short complete-month period ending2026-12-31,new supported asset entering inperiod,constant maximum table rate,no priorquotas/exit/cumulation",
|
||||
"version": "IRPC-normal-adjustments/12",
|
||||
"explicit_optional_input": "acceleration.monthly.startPeriod version1/reference;without it versions10/11 retain keys/models/history",
|
||||
"runtime_delta": {
|
||||
"resources/js/lib/irpc-special.ts": "052bd703cdfd7fbb10ea8089146d5e52ea247d612ce557b1e2c9c20730600261",
|
||||
"resources/js/lib/irpc-normal.ts": "290d7d66621668d2b684850729c57c0435cce144acf0704f9fd9e1d4a6d491f3",
|
||||
"resources/js/components/simulation-irpc-special.tsx": "6f6b203062df36fedbdfa341bba8b8aa7796d2f3169f1bd84395ada8f71fd749"
|
||||
},
|
||||
"reused_L6_period_fx_and_depreciation_engines_unchanged": true,
|
||||
"cases_recovered": {
|
||||
"start": {
|
||||
"period": {
|
||||
"from": "2026-07-01",
|
||||
"to": "2026-12-31",
|
||||
"pending": []
|
||||
},
|
||||
"L6_profit": {
|
||||
"numerator": "3130",
|
||||
"denominator": "1"
|
||||
},
|
||||
"L6_base": {
|
||||
"numerator": "3130",
|
||||
"denominator": "1"
|
||||
},
|
||||
"L6_colecta": {
|
||||
"numerator": "5008",
|
||||
"denominator": "5"
|
||||
},
|
||||
"status": "calculated",
|
||||
"additional_deduction": {
|
||||
"numerator": "10",
|
||||
"denominator": "1"
|
||||
},
|
||||
"L7_profit": {
|
||||
"numerator": "3120",
|
||||
"denominator": "1"
|
||||
},
|
||||
"L7_base": {
|
||||
"numerator": "3120",
|
||||
"denominator": "1"
|
||||
},
|
||||
"L7_colecta": {
|
||||
"numerator": "4992",
|
||||
"denominator": "5"
|
||||
},
|
||||
"L8_final": {
|
||||
"scope": "L8",
|
||||
"payable": null,
|
||||
"recoverable": null
|
||||
}
|
||||
},
|
||||
"fraction": {
|
||||
"period": {
|
||||
"from": "2026-12-01",
|
||||
"to": "2026-12-31",
|
||||
"pending": []
|
||||
},
|
||||
"L6_profit": {
|
||||
"numerator": "1565",
|
||||
"denominator": "3"
|
||||
},
|
||||
"L6_base": {
|
||||
"numerator": "1565",
|
||||
"denominator": "3"
|
||||
},
|
||||
"L6_colecta": {
|
||||
"numerator": "2504",
|
||||
"denominator": "15"
|
||||
},
|
||||
"status": "calculated",
|
||||
"additional_deduction": {
|
||||
"numerator": "5",
|
||||
"denominator": "3"
|
||||
},
|
||||
"L7_profit": {
|
||||
"numerator": "520",
|
||||
"denominator": "1"
|
||||
},
|
||||
"L7_base": {
|
||||
"numerator": "520",
|
||||
"denominator": "1"
|
||||
},
|
||||
"L7_colecta": {
|
||||
"numerator": "832",
|
||||
"denominator": "5"
|
||||
},
|
||||
"L8_final": {
|
||||
"scope": "L8",
|
||||
"payable": null,
|
||||
"recoverable": null
|
||||
}
|
||||
},
|
||||
"missing": {
|
||||
"period": {
|
||||
"from": "2026-07-01",
|
||||
"to": "2026-12-31",
|
||||
"pending": []
|
||||
},
|
||||
"L6_profit": {
|
||||
"numerator": "3130",
|
||||
"denominator": "1"
|
||||
},
|
||||
"L6_base": {
|
||||
"numerator": "3130",
|
||||
"denominator": "1"
|
||||
},
|
||||
"L6_colecta": {
|
||||
"numerator": "5008",
|
||||
"denominator": "5"
|
||||
},
|
||||
"status": "indeterminate",
|
||||
"additional_deduction": null,
|
||||
"L7_profit": null,
|
||||
"L7_base": null,
|
||||
"L7_colecta": null,
|
||||
"L8_final": {
|
||||
"scope": "L8",
|
||||
"payable": null,
|
||||
"recoverable": null
|
||||
}
|
||||
},
|
||||
"negative": {
|
||||
"period": {
|
||||
"from": "2026-07-01",
|
||||
"to": "2026-12-31",
|
||||
"pending": []
|
||||
},
|
||||
"L6_profit": {
|
||||
"numerator": "3130",
|
||||
"denominator": "1"
|
||||
},
|
||||
"L6_base": {
|
||||
"numerator": "3130",
|
||||
"denominator": "1"
|
||||
},
|
||||
"L6_colecta": {
|
||||
"numerator": "5008",
|
||||
"denominator": "5"
|
||||
},
|
||||
"status": "calculated",
|
||||
"additional_deduction": {
|
||||
"numerator": "0",
|
||||
"denominator": "1"
|
||||
},
|
||||
"L7_profit": {
|
||||
"numerator": "3130",
|
||||
"denominator": "1"
|
||||
},
|
||||
"L7_base": {
|
||||
"numerator": "3130",
|
||||
"denominator": "1"
|
||||
},
|
||||
"L7_colecta": {
|
||||
"numerator": "5008",
|
||||
"denominator": "5"
|
||||
},
|
||||
"L8_final": {
|
||||
"scope": "L8",
|
||||
"payable": null,
|
||||
"recoverable": null
|
||||
}
|
||||
},
|
||||
"absent": {
|
||||
"period": {
|
||||
"from": "2026-07-01",
|
||||
"to": "2026-12-31",
|
||||
"pending": []
|
||||
},
|
||||
"L6_profit": {
|
||||
"numerator": "3130",
|
||||
"denominator": "1"
|
||||
},
|
||||
"L6_base": {
|
||||
"numerator": "3130",
|
||||
"denominator": "1"
|
||||
},
|
||||
"L6_colecta": {
|
||||
"numerator": "5008",
|
||||
"denominator": "5"
|
||||
},
|
||||
"status": "absent",
|
||||
"additional_deduction": {
|
||||
"numerator": "0",
|
||||
"denominator": "1"
|
||||
},
|
||||
"L7_profit": {
|
||||
"numerator": "3130",
|
||||
"denominator": "1"
|
||||
},
|
||||
"L7_base": {
|
||||
"numerator": "3130",
|
||||
"denominator": "1"
|
||||
},
|
||||
"L7_colecta": {
|
||||
"numerator": "5008",
|
||||
"denominator": "5"
|
||||
},
|
||||
"L8_final": {
|
||||
"scope": "L8",
|
||||
"payable": null,
|
||||
"recoverable": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"checks": {
|
||||
"new_frontend_tests": 9,
|
||||
"API_tests": 1,
|
||||
"API_assertions": 97,
|
||||
"API_database": "simulator_fiscal_delta_20261021_test",
|
||||
"API_host": "127.0.0.1",
|
||||
"API_port": 55441,
|
||||
"API_transaction_rollback": true,
|
||||
"wayfinder_with_form": true,
|
||||
"typescript": true,
|
||||
"isolated_build": true,
|
||||
"HTML_financial_dossier_from_5_API_recoveries": true,
|
||||
"actual_component": {
|
||||
"actualComponent": true,
|
||||
"explicitStartOptionAndCancel": true,
|
||||
"reuseL6PeriodEvidenceExplicit": true,
|
||||
"assetsAndPeriodUnchanged": true,
|
||||
"missingPreservesNormal1001_60": true,
|
||||
"sixActualMonths50Normal75Accelerated60Cap10Additional": true,
|
||||
"simulatedColecta998_40": true,
|
||||
"exactFraction5over3": true,
|
||||
"saveRecoveryDownload": true,
|
||||
"negativeAbsenceDistinct": true,
|
||||
"L8FinalUnknown": true,
|
||||
"errors": []
|
||||
}
|
||||
},
|
||||
"preservation": {
|
||||
"protected_code_schema_config_routes_contract_files": 584,
|
||||
"original_files": {
|
||||
"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"
|
||||
},
|
||||
"prior_result_and_entire_history_preserved": true,
|
||||
"plan_assets_and_other_tax_books_unchanged": true,
|
||||
"active_migrations": 0,
|
||||
"active_private_writes": 0,
|
||||
"SGF_TEMPO_touched": false
|
||||
},
|
||||
"sources_reused": [
|
||||
"CBF3/9/10/16 accepted APIEX compilation pp66-68/72",
|
||||
"CIRPC7(4) accepted shortperiod implementation",
|
||||
"Decreto72/2013 art3(3)/9(3),accepted source inventory8143"
|
||||
],
|
||||
"remaining_code": [
|
||||
"other autonomous expense origins/parcels",
|
||||
"mixed/withheld cooperative income and other CIRPC9-14 entities",
|
||||
"CBF16 cessation/transition/noncivil/previousquotas/exit/lateryears/lowerquota/othercategories/methods/cumulation",
|
||||
"capitalgains61-A and specialoperations54-56/41-A",
|
||||
"CBF17-19 and potential15 entitlement;annualuseL8",
|
||||
"sector/territorial/specificregimes",
|
||||
"official L7 M20/M22 transport"
|
||||
],
|
||||
"document_conditions": [
|
||||
"classification/parcels/period evidence",
|
||||
"statute/recognition required by disposition or exception",
|
||||
"specific normativeperiod and cumulation order/limits",
|
||||
"form edition/field/fraction representation"
|
||||
],
|
||||
"remote_capacity_before_future_installation": {
|
||||
"fresh_space_inodes_build_backup_measurement": true,
|
||||
"main_compatibility_recheck": true,
|
||||
"fresh_backup_restore": true,
|
||||
"keep_active_until_ready": true
|
||||
},
|
||||
"L6_operational_delimited": true,
|
||||
"L7_partial": true,
|
||||
"L8_final_balance_separate": true,
|
||||
"evidence_directory": "outputs/irpc-l7-start-period"
|
||||
}
|
||||
@@ -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 | 0bd5d495 instalado sobre e07f89da;CBF16 com duodécimos anuais disponível | L7parcial;saldo restante preservado. PróximoQA:CBF16 em período curto por início de actividade de residente2026,com mesesL6 | Activos/qualificação/períodoL6 e prova aceites reutilizados;sem duplicação | Regra/período/documentos por caso;capacidade/backup/restauro frescos em cada entrega;L8 conserva saldo final | Estimativa inicial48–80h;saldo não reestimado |
|
||||
| L7 — IRPC: tratamentos específicos | 0bd5d495 instalado;CBF16 no início de actividade emQA fotografia12,sem instalação | L7parcial;7grupos de código e4dependências por caso preservados;início2026 retirado só no recorteQA demonstrado | Activos/meses/período/suporteL6 reutilizados;sem anualização,duplicaçāo ou benefício por localização | Cessação e demais extensões exigem regra/quotas/saída próprias;futura entrega com capacidade/main/backup/restauro frescos;L8 conserva saldo final | Estimativa inicial48–80h;saldo 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 |
|
||||
@@ -6476,3 +6476,24 @@ Conferidos3ficheiros do delta,60ficheiros de execução protegidos e31originais.
|
||||
O exemplo mantém quota normal50,quotaCBF16 75 limitada ao contabilizado60:dedução adicional**60−50=10MZN**,não75 nem uma segunda amortização. Lucro/base apósL7 6300 e**colecta simulada2016MZN**;saldo final a pagar/recuperar por determinar emL8. Os tratamentos de despesas autónomas,cooperativas abrangidas e amortizações anuais anteriores permanecem disponíveis,com fotografias/históricos conservados. O estadoQAsem instalação é histórico para este incremento.
|
||||
|
||||
**SaldoL7:**CBF16 anual com duodécimos entregue;continuam fora deste recorte períodos curtos/não civis,saídas,anos/quotas anteriores,métodos/categorias não correspondidos,cumulações e restantes famílias/transportesM20M22 já discriminados na mesma matriz. Retoma por regra/período/contabilização e compatibilidade do caso;documentos em falta só limitam o dependente. O próximo conjunto a executar emQA é**CBF16 em período curto por início de actividade de residente em2026**,reutilizando tipo/período e mesesL6,sem anualizar base ou taxa. Cessação,transição e outras extensões conservam âmbito próprio. L6operacional delimitado;L7parcial;localização não concede benefício;L8conserva liquidação final. Recibo:`docs/investmoz/simulations/irpc-l7-months-delivery-20261026.json`.
|
||||
|
||||
## 26/10/2026 — CBF16 no início de actividade demonstrado em QA
|
||||
|
||||
**QA preparado, sem instalação.** O portal mantém `0bd5d495`, com duodécimos anuais e os tratamentos anteriores preservados. Este incremento cobre apenas CBF16 num período curto por início de actividade de residente em2026,com meses completos até31/12. Reutiliza período,tipo,suporte e activosL6,sem anualizar resultados. Não altera o motor cambial/de amortizaçõesL6 nem volta a pesquisar fontes/provas aceites.
|
||||
|
||||
A preparação é explícita dentro dos duodécimosCBF16. Pode reutilizar o suporte já declarado emL6 por acção do utilizador,sem modificar datas,activos ou qualificação. Exige condiçõesCBF3/9,reconhecimento10 quando aplicável ou excepção identificada,afectação do activo e suporte do período. Activo novo já correspondido,entrada emuso no período,método constante pela taxa normal máxima,sem quotas anteriores,saída ou cumulação. A localização não activa benefícios. Sem a nova opção,as fotografias10/11 mantêm chaves e resultados;com ela,a fotografia12 conserva o resultado11 e todo o histórico anterior.
|
||||
|
||||
| Caso novo | Efeito demonstrado |
|
||||
| --- | --- |
|
||||
| Início1Julho;máquina com entrada15Julho,base1000,taxa10%,contabilizado60 | 6mesesL6;quota normal50,acelerada75,limitada60:dedução adicional10MZN. Lucro/baseL6 3130→L7 3120;colecta simulada1001,60→998,40MZN. Saldo finalL8indeterminado. |
|
||||
| InícioOutubro / Dezembro | 3 /1meses efectivos;sem anualização. Dedução adicional5 /5/3MZN,com fracção exacta e aproximação de apresentação separadas. |
|
||||
| Suporte em falta | SóCBF16 fica indeterminado;referênciaL6 conhecida e entradas conservadas. |
|
||||
| Condição negativa / ausência declarada | Estados distintos e justificados;efeito zero identificado,sem taxa acelerada aplicada à parcela negativa. |
|
||||
|
||||
Provas novas dirigidas:9testes;API privada1teste/97asserções emQA isolada127.0.0.1:55441/base `simulator_fiscal_delta_20261021_test`,com guarda e transacção/rollback. Cinco casos passaram cálculo→gravação→recuperação→HTML e dossier financeiro. Comparação exacta do resultado11 aceite e conservação de todo o histórico,plano,activos e outros livros;recalcular não acumula deduções. Interface real demonstrou preparar/cancelar,reutilizar suporteL6,pendência,limites,fracções,guardar/recuperar e exportar,sem erros. Wayfinder`--with-form`,TypeScript e buildQA passaram. Conferidos584ficheiros protegidos e31originais sem alteração;nenhuma migração/importação/escrita privada no activo. SGF TEMPO separado.
|
||||
|
||||
**Saldo concretoL7 — código:**(1)outras origens/parcelas de despesas autónomas;(2)cooperativas mistas/retidas e demais entidades9–14;(3)CBF16 cessação/transição/períodos não civis,saídas,quotas/anos anteriores,quota inferior,métodos/categorias e cumulações;(4)mais-valias61-A/operações54–56/41-A;(5)CBF17–19 e direito potencial15;(6)regimes sectoriais/territoriais;(7)transporteL7M20/M22. A extensão de início de actividade sai deste saldo apenas no recorteQA acima;cessação é um próximo candidato compatível,reutilizando o períodoL6,com regra/quotas/saída próprias a demonstrar antes de se declarar suportada.
|
||||
|
||||
**Documentos por caso — exemplos/retoma:**despesa36g sem qualificação61(4)→associar natureza/parcela/período;projecto sem reconhecimento10→instrumento ou norma de excepção exigida;cumulação cooperativa+CBF16→disposição aplicável,ordem e limites;efeitoL7 sem campoM20/M22→exemplar/edição/campo e representação das fracções. Não são novos aprovadores ou revalidações genéricas. A dúvida limita o caso/campo dependente;as demais provas permanecem válidas.
|
||||
|
||||
Entrega agrupada preparada para os cinco casos compatíveis,sem instalação deste incremento. Na futura entrega:medir novamente espaço/inodes/carga de compilação e backup,conferir o main então actual e executar backup/restauro fresco,mantendo a versão activa até a nova ficar pronta. O controlo de5,50GiB antes/5,33GiB após backup desta entrega0bd5d495 é histórico,não garantia futura. **L6 operacional delimitado;L7 parcial;colecta distinta do saldo final deL8.** Recibo:`docs/investmoz/simulations/irpc-l7-start-period-qa-20261026.json`;provas:`outputs/irpc-l7-start-period/`. Reprodução dirigida:`node --test tests/frontend/irpc-special-start-period.test.mjs`;`php tools/irpc-special-start-period-qa.php routes`;`php tools/irpc-special-start-period-qa.php test`;`tsc --noEmit`;build com `INVESTMOZ_QA_BUILD=1`.
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,5 +1,5 @@
|
||||
import type {NormalBook} from '@/lib/irpc-normal';
|
||||
import {newSpecialInput,newSpecialExpense,newAcceleratedAsset,specialMonthlySource,specialSources,type SpecialInput,type Qualification} from '@/lib/irpc-special';
|
||||
import {newSpecialInput,newSpecialExpense,newAcceleratedAsset,specialStartPeriodSource,specialMonthlySource,specialSources,type SpecialInput,type Qualification} from '@/lib/irpc-special';
|
||||
|
||||
type Props={draft:Record<string,unknown>;book:NormalBook;save:(b:NormalBook)=>void};
|
||||
const states=[['','Informação em falta'],['known','Tratamento conhecido — qualificar condições'],['absent','Ausência declarada com suporte'],['not_applicable','Tratamento não aplicável — justificar']]as const;
|
||||
@@ -18,8 +18,8 @@ export function SimulationIrpcSpecial({draft,book:b,save}:Props){
|
||||
<button type="button" onClick={()=>change({expenses:{...i.expenses,rows:[...i.expenses.rows,...(b.schedules?.rows??[]).filter(r=>r.rule==='undocumented'&&!i.expenses.rows.some(x=>x.scheduleId===r.id)).map(r=>({...newSpecialExpense('expense-'+r.id),scheduleId:r.id}))]}})}>Reutilizar despesas36g de L6 para qualificar</button>
|
||||
{i.expenses.rows.map((r,index)=>{const origin=b.schedules?.rows.find(x=>x.id===r.scheduleId),patch=(p:Partial<typeof r>)=>change({expenses:{...i.expenses,rows:i.expenses.rows.map(x=>x.id===r.id?{...x,...p}:x)}});return<fieldset key={r.id} className="rounded border p-2"><legend>{origin?origin.document+'/'+origin.line:'Parcela por corresponder'} — {origin?.amount??'Indeterminado'} MZN</legend><p>O acréscimo36g já pertence aL6;esta linha só apura o eventual imposto autónomo.</p><label className="block">Natureza legal da despesa<select aria-label={'Natureza da despesa '+(index+1)} value={r.nature} onChange={e=>patch({nature:e.target.value as typeof r.nature})}><option value="">Informação em falta</option><option value="undocumented">Encargo não devidamente documentado</option><option value="confidential">Despesa confidencial</option><option value="illicit">Despesa ilícita</option><option value="other">Outra natureza — correspondência própria</option></select></label>{yesno('Incidência61(4) — parcela '+(index+1),r.eligible,eligible=>patch({eligible}))}<label className="block">Suporte da classificação e incidência<input aria-label={'Suporte da despesa '+(index+1)} value={r.reference} onChange={e=>patch({reference:e.target.value})}/></label><button type="button" onClick={()=>change({expenses:{...i.expenses,rows:i.expenses.rows.filter(x=>x.id!==r.id)}})}>Retirar qualificação da parcela {index+1}</button></fieldset>;})}</fieldset>
|
||||
<fieldset className="space-y-2 rounded border p-3"><legend>Cooperativas abrangidas — artigo12</legend><p>{specialSources.cooperative} Este incremento exige base integralmente elegível;parcelas mistas continuam explícitas.</p><QualificationFields label="Cooperativa" value={i.cooperative.qualification} onChange={qualification=>change({cooperative:{...i.cooperative,qualification}})}/>{(i.cooperative.qualification.state==='known'||i.cooperative.kind!==''||i.cooperative.allIncomeEligible!==''||i.cooperative.withheldIncome!=='')&&<><label className="block">Tipo de cooperativa documentado<select value={i.cooperative.kind} onChange={e=>change({cooperative:{...i.cooperative,kind:e.target.value as typeof i.cooperative.kind}})}><option value="">Por identificar</option><option value="agrarian">Agrária</option><option value="craft">Artesanato</option><option value="cultural">Cultural</option><option value="other">Outra — não correspondida</option></select></label>{yesno('Base ordinária integralmente elegível',i.cooperative.allIncomeEligible,allIncomeEligible=>change({cooperative:{...i.cooperative,allIncomeEligible}}))}<label className="block">Rendimentos sujeitos a retenção<select value={i.cooperative.withheldIncome} onChange={e=>change({cooperative:{...i.cooperative,withheldIncome:e.target.value as typeof i.cooperative.withheldIncome}})}><option value="">Informação em falta</option><option value="absent">Ausência documentada</option><option value="known">Existem — segregação própria em falta</option></select></label></>}</fieldset>
|
||||
<fieldset className="space-y-2 rounded border p-3"><legend>Amortizações aceleradas — CBF16</legend><p>{specialSources.acceleration} Apenas activos já suportados,primeiro ano civil2026 e método constante;outras combinações permanecem por corresponder.</p><QualificationFields label="Amortização acelerada" value={i.acceleration.qualification} onChange={qualification=>change({acceleration:{...i.acceleration,qualification}})}/>{i.acceleration.qualification.state==='known'&&<>{yesno('EnquadramentoCBF3 e pressupostos9 documentados',i.acceleration.cbfRequirements,cbfRequirements=>change({acceleration:{...i.acceleration,cbfRequirements}}))}<label className="block">ReconhecimentoCBF10<select value={i.acceleration.recognition} onChange={e=>change({acceleration:{...i.acceleration,recognition:e.target.value as typeof i.acceleration.recognition}})}><option value="">Informação em falta</option><option value="documented">Instrumento/reconhecimento documentado</option><option value="not_required">Excepção prevista — identificar norma e suporte</option></select></label><label className="block">Instrumento ou norma da excepção<input value={i.acceleration.recognitionReference} onChange={e=>change({acceleration:{...i.acceleration,recognitionReference:e.target.value}})}/></label>{yesno('Ausência documentada de outros benefícios a cumular',i.acceleration.noOtherBenefits,noOtherBenefits=>change({acceleration:{...i.acceleration,noOtherBenefits}}))}</>}
|
||||
{!i.acceleration.monthly?<button type="button" onClick={()=>change({acceleration:{...i.acceleration,monthly:{version:1,reference:''}}})}>Preparar CBF16 com duodécimos</button>:<fieldset aria-label="CBF16 por duodécimos" className="space-y-2 rounded border p-2"><legend>CBF16 — reutilizar duodécimos L6</legend><p>{specialMonthlySource} Conserva datas,opção mensal e valores do activo existente;preencha no mapaL6 apenas o que falta ou precisa de correcção.</p><label className="block">Suporte da reutilização dos duodécimos L6<input aria-label="Suporte dos duodécimos CBF16" value={i.acceleration.monthly.reference} onChange={e=>change({acceleration:{...i.acceleration,monthly:{...i.acceleration.monthly!,reference:e.target.value}}})}/></label><p>Não altera automaticamente o activo nem transforma ausência de documentos em elegibilidade. A versão anterior permanece no histórico depois de calcular e guardar.</p><button type="button" onClick={()=>{const acceleration={...i.acceleration};delete acceleration.monthly;change({acceleration});}}>Manter preparação anual CBF16</button></fieldset>}
|
||||
<fieldset className="space-y-2 rounded border p-3"><legend>Amortizações aceleradas — CBF16</legend><p>{i.acceleration.monthly?.startPeriod?specialStartPeriodSource:specialSources.acceleration} Apenas activos já suportados e método constante;prepare explicitamente o recorte anual ou de início de actividade.</p><QualificationFields label="Amortização acelerada" value={i.acceleration.qualification} onChange={qualification=>change({acceleration:{...i.acceleration,qualification}})}/>{i.acceleration.qualification.state==='known'&&<>{yesno('EnquadramentoCBF3 e pressupostos9 documentados',i.acceleration.cbfRequirements,cbfRequirements=>change({acceleration:{...i.acceleration,cbfRequirements}}))}<label className="block">ReconhecimentoCBF10<select value={i.acceleration.recognition} onChange={e=>change({acceleration:{...i.acceleration,recognition:e.target.value as typeof i.acceleration.recognition}})}><option value="">Informação em falta</option><option value="documented">Instrumento/reconhecimento documentado</option><option value="not_required">Excepção prevista — identificar norma e suporte</option></select></label><label className="block">Instrumento ou norma da excepção<input value={i.acceleration.recognitionReference} onChange={e=>change({acceleration:{...i.acceleration,recognitionReference:e.target.value}})}/></label>{yesno('Ausência documentada de outros benefícios a cumular',i.acceleration.noOtherBenefits,noOtherBenefits=>change({acceleration:{...i.acceleration,noOtherBenefits}}))}</>}
|
||||
{!i.acceleration.monthly?<button type="button" onClick={()=>change({acceleration:{...i.acceleration,monthly:{version:1,reference:''}}})}>Preparar CBF16 com duodécimos</button>:<fieldset aria-label="CBF16 por duodécimos" className="space-y-2 rounded border p-2"><legend>CBF16 — reutilizar duodécimos L6</legend><p>{i.acceleration.monthly.startPeriod?specialStartPeriodSource:specialMonthlySource} Conserva datas,opção mensal e valores do activo existente;preencha no mapaL6 apenas o que falta ou precisa de correcção.</p><label className="block">Suporte da reutilização dos duodécimos L6<input aria-label="Suporte dos duodécimos CBF16" value={i.acceleration.monthly.reference} onChange={e=>change({acceleration:{...i.acceleration,monthly:{...i.acceleration.monthly!,reference:e.target.value}}})}/></label>{!i.acceleration.monthly.startPeriod?<button type="button" onClick={()=>change({acceleration:{...i.acceleration,monthly:{...i.acceleration.monthly!,startPeriod:{version:1,reference:''}}}})}>Preparar CBF16 para início de actividade</button>:<fieldset aria-label="CBF16 no início de actividade" className="rounded border p-2"><legend>CBF16 — período curto de início de actividade</legend><p>Período L6:{b.periodsFx?.from||'Por identificar'} a {b.periodsFx?.to||'Por identificar'};tipo:{b.periodsFx?.kind||'Por identificar'}. Reutiliza o período declarado;esta opção não altera datas,activos ou enquadramento.</p><label className="block">Suporte do início de actividade<input aria-label="Suporte do início de actividade CBF16" value={i.acceleration.monthly.startPeriod.reference} onChange={e=>change({acceleration:{...i.acceleration,monthly:{...i.acceleration.monthly!,startPeriod:{version:1,reference:e.target.value}}}})}/></label><button type="button" disabled={b.periodsFx?.kind!=='start'||!b.periodsFx.reference.trim()} onClick={()=>change({acceleration:{...i.acceleration,monthly:{...i.acceleration.monthly!,startPeriod:{version:1,reference:b.periodsFx!.reference}}}})}>Reutilizar suporte do início de actividade L6</button><button type="button" onClick={()=>{const monthly={...i.acceleration.monthly!};delete monthly.startPeriod;change({acceleration:{...i.acceleration,monthly}});}}>Manter duodécimos no exercício anual</button></fieldset>}<p>Não altera automaticamente o activo nem transforma ausência de documentos em elegibilidade. A versão anterior permanece no histórico depois de calcular e guardar.</p><button type="button" onClick={()=>{const acceleration={...i.acceleration};delete acceleration.monthly;change({acceleration});}}>Manter preparação anual CBF16</button></fieldset>}
|
||||
<button type="button" onClick={()=>change({acceleration:{...i.acceleration,rows:[...i.acceleration.rows,newAcceleratedAsset(crypto.randomUUID())]}})}>Qualificar activo existente paraCBF16</button>{i.acceleration.rows.map((r,index)=>{const patch=(p:Partial<typeof r>)=>change({acceleration:{...i.acceleration,rows:i.acceleration.rows.map(x=>x.id===r.id?{...x,...p}:x)}});return<fieldset key={r.id} className="rounded border p-2"><legend>Activo qualificado {index+1}</legend><label className="block">Activo do mapaL6<select value={r.assetId} onChange={e=>patch({assetId:e.target.value})}><option value="">Seleccione activo existente</option>{b.depreciation?.assets.map(a=><option key={a.id} value={a.id}>{a.description} — {a.assetKey}</option>)}</select></label><label className="block">Afectação documentada<select value={r.kind} onChange={e=>patch({kind:e.target.value as typeof r.kind})}><option value="">Por identificar</option><option value="new_project_building">Imóvel novo utilizado no projecto</option><option value="rehabilitated_project_building">Imóvel reabilitado — regra ainda por corresponder</option><option value="industrial_equipment">Máquina/equipamento industrial</option><option value="agroindustrial_equipment">Máquina/equipamento agro-industrial</option><option value="other">Outro — correspondência própria</option></select></label>{yesno('Condições do activoCBF16 — '+(index+1),r.eligible,eligible=>patch({eligible}))}<label className="block">Prova da afectação e condições do activo<input value={r.reference} onChange={e=>patch({reference:e.target.value})}/></label><button type="button" onClick={()=>change({acceleration:{...i.acceleration,rows:i.acceleration.rows.filter(x=>x.id!==r.id)}})}>Retirar tratamento do activo {index+1}</button></fieldset>;})}</fieldset>
|
||||
<p>Use «Calcular e guardar» abaixo para gravar uma nova versão e apresentar os efeitos. Os históricos anteriores são conservados. M20/M22 mantém apenas correspondências demonstradas;camposL7 não são preenchidos por aproximação.</p></section>;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {irpcSpecialVersion,irpcSpecialMonthlyVersion,specialCalculationVersion,validateSpecial,calculateSpecial,specialReportRows,specialDependency,type SpecialInput,type SpecialModel} from './irpc-special.ts';
|
||||
import {irpcSpecialVersion,irpcSpecialMonthlyVersion,irpcSpecialStartPeriodVersion,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';
|
||||
import {normalDepreciationVersion,validateNormalDepreciation,calculateNormalDepreciation,normalDepreciationReport,type NormalDepreciationInput,type NormalDepreciationModel} from './irpc-normal-depreciation.ts';
|
||||
@@ -41,7 +41,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||![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||![irpcSpecialStartPeriodVersion,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;
|
||||
|
||||
@@ -6,15 +6,17 @@ import {addFractions,subtractFractions,decimalFraction,fraction,fractionPresenta
|
||||
|
||||
export const irpcSpecialVersion='IRPC-normal-adjustments/10';
|
||||
export const irpcSpecialMonthlyVersion='IRPC-normal-adjustments/11';
|
||||
export const irpcSpecialStartPeriodVersion='IRPC-normal-adjustments/12';
|
||||
export const specialStartPeriodSource='CBF3/9/10/16;CIRPC7(4);Decreto72/2013 art3(3)/9(3):período curto por início de actividade de residente em2026. Taxa normal máxima ×1,5 ×meses efectivos/12 reutilizados de L6,sem anualização;activo novo emuso no período,método constante,sem saída/quotas anteriores/cumulação. Limites contabilizado elegível e saldo fiscal;apenas diferença adicional.';
|
||||
export const specialMonthlySource='CBF16 com Decreto72/2013 art9:taxa normal máxima ×1,5 ×duodécimos/12,meses reutilizados do mapaL6. Exercício anual civil2026,entrada emuso no ano,método constante,sem saída/quotas anteriores/cumulação. Dedução limitada ao contabilizado elegível e saldo fiscal;apenas diferença adicional,não nova amortização contabilística.';
|
||||
export const specialSources={expenses:'CIRPC,Lei34/2007,art61(4),PDF17/p768(71):35% sobre encargos não devidamente documentados e despesas confidenciais/ilícitas,sem prejuízo do36(1)g. Exercício2026;sem regra inferida da falta de anexo.',cooperative:'CIRPC,Lei34/2007,art12/14,PDF5/p768(59),e61(1):cooperativas agrárias,de artesanato e culturais;redução de50% da taxa geral32%=16%;rendimentos sujeitos a retenção excluídos deste benefício. Recorte2026,residente,parcela ordinária integralmente elegível.',acceleration:'CBF,Lei4/2009,art3/4/9/10/16;compilação oficial APIEX pp66–68/72:incremento50% das taxas normais nos imóveis novos do projecto e,nas mesmas condições,imóveis reabilitados/máquinas e equipamentos industriais ou agro-industriais. Decreto72/2013 art3(3):limite contabilizado e saldo fiscal. Recorte constante pela taxa normal máxima da tabela,primeiro exercício anual2026,sem cumulação presumida.'}as const;
|
||||
export type Qualification={state:''|'known'|'absent'|'not_applicable';eligible:''|'yes'|'no';from:string;to:string;reference:string;periodReference:string};
|
||||
export const newQualification=():Qualification=>({state:'',eligible:'',from:'',to:'',reference:'',periodReference:''});
|
||||
export type SpecialExpense={id:string;scheduleId:string;nature:''|'undocumented'|'confidential'|'illicit'|'other';eligible:''|'yes'|'no';reference:string};
|
||||
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 SpecialMonthlyInput={version:1;reference:string};
|
||||
export type SpecialMonthlyInput={version:1;reference:string;startPeriod?:{version:1;reference:string}};
|
||||
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?irpcSpecialMonthlyVersion:irpcSpecialVersion;
|
||||
export const specialCalculationVersion=(i:SpecialInput)=>i.acceleration.monthly?.startPeriod?irpcSpecialStartPeriodVersion:i.acceleration.monthly?irpcSpecialMonthlyVersion:irpcSpecialVersion;
|
||||
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:''});
|
||||
@@ -22,6 +24,7 @@ const choices=(v:unknown,allowed:string[])=>typeof v==='string'&&allowed.include
|
||||
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.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.');
|
||||
for(const q of[i.expenses.qualification,i.cooperative.qualification,i.acceleration.qualification])if(!q||!choices(q.state,['','known','absent','not_applicable'])||!choices(q.eligible,['','yes','no'])||['from','to','reference','periodReference'].some(k=>typeof q[k as 'from']!=='string'))throw Error('Condição/período L7 não recuperável.');
|
||||
for(const [rows,keys]of [[i.expenses.rows,Object.keys(newSpecialExpense(''))],[i.acceleration.rows,Object.keys(newAcceleratedAsset(''))]]as const){if(!Array.isArray(rows)||new Set(rows.map(r=>r.id)).size!==rows.length||rows.some(r=>!r||keys.some(k=>typeof(r as unknown as Record<string,unknown>)[k]!=='string')||!choices(r.eligible,['','yes','no'])))throw Error('Parcela L7 não recuperável ou identificador repetido.');}
|
||||
if(i.expenses.rows.some(r=>!choices(r.nature,['','undocumented','confidential','illicit','other']))||i.acceleration.rows.some(r=>!choices(r.kind,['','new_project_building','rehabilitated_project_building','industrial_equipment','agroindustrial_equipment','other'])))throw Error('Natureza L7 desconhecida.');
|
||||
@@ -77,6 +80,7 @@ export function calculateSpecial(draft:Record<string,unknown>,b:NormalBook,m:Nor
|
||||
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(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.');
|
||||
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.');
|
||||
@@ -95,7 +99,9 @@ export function calculateSpecial(draft:Record<string,unknown>,b:NormalBook,m:Nor
|
||||
if(base?.catalog&&cmp(decimalFraction(a.rate),decimalFraction(base.catalog.rate))!==0n)issues.push('RecorteCBF16 usa a taxa normal máxima da tabela;opção por quota inferior exige correspondência própria,sem a acelerar por aproximação.');
|
||||
if(i.acceleration.monthly){
|
||||
const p=specialPeriod(b,m),priorZero=[a.previousAccounting,a.previousFiscal,a.priorAvailable].every(v=>cmp(decimalFraction(v),z())===0n);
|
||||
if(a.method!=='constant'||a.timing!=='months'||!iso(a.start)||a.start<'2026-01-01'||a.start>'2026-12-31'||a.exitState!=='absent'||!priorZero||p.from!=='2026-01-01'||p.to!=='2026-12-31'||base?.months===null||base?.months===undefined)issues.push('CBF16 por duodécimos:exercício anual civil2026,método constante,entrada no ano,opção mensalL6 e ausência documentada de saída/quotas anteriores;outros períodos/casos permanecem separados.');
|
||||
if(i.acceleration.monthly.startPeriod){
|
||||
if(b.scope!=='resident_general'||b.periodsFx?.kind!=='start'||!m.periodsFx?.short||p.pending.length||!iso(p.from)||p.from<'2026-02-01'||p.from>'2026-12-01'||p.from.slice(-2)!=='01'||p.to!=='2026-12-31'||a.method!=='constant'||a.timing!=='months'||!iso(a.start)||a.start<p.from||a.start>p.to||a.exitState!=='absent'||!priorZero||base?.months===null||base?.months===undefined)issues.push('CBF16 início de actividade:residente,período curtoL6 de meses completos em2026 até31/12,activo novo emuso dentro do período,método constante,opção mensal e ausência documentada de saída/quotas anteriores;cessação/transição e demais situações permanecem separadas.');
|
||||
}else if(a.method!=='constant'||a.timing!=='months'||!iso(a.start)||a.start<'2026-01-01'||a.start>'2026-12-31'||a.exitState!=='absent'||!priorZero||p.from!=='2026-01-01'||p.to!=='2026-12-31'||base?.months===null||base?.months===undefined)issues.push('CBF16 por duodécimos:exercício anual civil2026,método constante,entrada no ano,opção mensalL6 e ausência documentada de saída/quotas anteriores;outros períodos/casos permanecem separados.');
|
||||
}else if(a.method!=='constant'||a.timing!=='annual'||a.start!=='2026-01-01'||a.exitState!=='absent'||a.previousAccounting!=='0'||a.previousFiscal!=='0'||a.priorAvailable!=='0'||m.fiscalPeriod?.from!=='2026-01-01'||m.fiscalPeriod?.to!=='2026-12-31')issues.push('CBF16 inicial:primeiro exercício anual civil2026,método constante,sem quotas anteriores ou saída;duodécimos e recuperação anterior continuam separados.');
|
||||
if(!issues.length)status='calculated';
|
||||
}
|
||||
@@ -105,7 +111,9 @@ export function calculateSpecial(draft:Record<string,unknown>,b:NormalBook,m:Nor
|
||||
if(accDecision.status==='calculated'){
|
||||
for(const r of accelerations)if(r.status==='indeterminate')fail(accDecision,(r.description||r.id)+': '+r.reason);
|
||||
if(accDecision.status==='calculated'&&b.depreciation&&m.depreciation?.adjustment){
|
||||
acceleratedDepreciation=calculateNormalDepreciation(b.depreciation,source,{from:'2026-01-01',to:'2026-12-31'},[...b.rows,...(b.schedules?.rows??[]),...(b.costs?.rows??[])],false,new Set(accelerations.filter(r=>r.status==='calculated').map(r=>r.assetId)));
|
||||
const startPeriod=!!i.acceleration.monthly?.startPeriod;
|
||||
acceleratedDepreciation=calculateNormalDepreciation(b.depreciation,source,startPeriod?specialPeriod(b,m):{from:'2026-01-01',to:'2026-12-31'},[...b.rows,...(b.schedules?.rows??[]),...(b.costs?.rows??[])],startPeriod,new Set(accelerations.filter(r=>r.status==='calculated').map(r=>r.assetId)));
|
||||
if(startPeriod)acceleratedDepreciation={...acceleratedDepreciation,source:specialStartPeriodSource};
|
||||
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.');
|
||||
@@ -117,11 +125,12 @@ 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 monthlyModel=i.acceleration.monthly?{input:structuredClone(i.acceleration.monthly),period:specialPeriod(b,m),source:specialMonthlySource,assets:accelerations.map(r=>{
|
||||
const monthlySource=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{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?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}};
|
||||
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?.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}};
|
||||
}
|
||||
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+')';};
|
||||
@@ -139,5 +148,5 @@ export function specialReportRows(m:SpecialModel):Array<[string,string,string,st
|
||||
];}
|
||||
|
||||
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.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.reason,m.source]as[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.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.reason,m.source]as[string,string,string,string])];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
<?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 IrpcSpecialStartPeriodDraftTest extends TestCase
|
||||
{
|
||||
use DatabaseTransactions;
|
||||
|
||||
public function test_start_period_cbf16_recovers_new_results_missing_cases_and_accepted_history(): 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(['start','fraction','missing','negative','absent']as$case){
|
||||
$d=json_decode(file_get_contents(base_path('outputs/irpc-l7-start-period/'.$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'];$a=$m['special']['acceleration'];
|
||||
$this->assertSame('IRPC-normal-adjustments/12',$m['version']);$this->assertSame($b['special']['acceleration']['monthly'],$a['monthly']['input']);
|
||||
$this->assertContains('IRPC-normal-adjustments/11',array_column(array_column($b['history'],'model'),'version'));$this->assertContains('IRPC-normal-adjustments/10',array_column(array_column($b['history'],'model'),'version'));
|
||||
$this->assertSame($b['depreciation'],$a['normal']['input']);$this->assertSame('L8',$m['special']['finalBalance']['scope']);$this->assertNull($m['special']['finalBalance']['payable']);
|
||||
$this->assertNull($m['special']['finalBalance']['recoverable']);$this->assertNull($m['tax']);$this->assertArrayHasKey('irpcWithholdings',$s);
|
||||
if($case==='start'){
|
||||
$v=$a['monthly']['assets'][0];$this->assertSame(6,$v['months']);$this->assertSame('50',$v['normalQuota']['numerator']);$this->assertSame('75',$v['acceleratedQuota']['numerator']);
|
||||
$this->assertSame('60',$v['acceleratedDeduction']['numerator']);$this->assertSame('10',$a['additionalDeduction']['numerator']);
|
||||
$this->assertSame('3130',$m['taxableProfit']['numerator']);$this->assertSame('3120',$m['special']['taxableProfit']['numerator']);$this->assertSame(['numerator'=>'4992','denominator'=>'5'],$m['special']['colecta']);$this->assertTrue($m['periodsFx']['short']);$this->assertSame('2026-07-01',$a['monthly']['period']['from']);$this->assertStringContainsString('início de actividade',$a['source']);
|
||||
$this->assertSame($b['depreciation'],$a['accelerated']['input']);
|
||||
}elseif($case==='fraction'){
|
||||
$v=$a['monthly']['assets'][0];$this->assertSame(1,$v['months']);$this->assertSame(['numerator'=>'25','denominator'=>'3'],$v['normalQuota']);$this->assertSame(['numerator'=>'5','denominator'=>'3'],$a['additionalDeduction']);
|
||||
}elseif($case==='missing'){
|
||||
$this->assertSame('indeterminate',$a['status']);$this->assertNull($a['additionalDeduction']);$this->assertSame('3130',$m['taxableProfit']['numerator']);$this->assertNull($m['special']['colecta']);
|
||||
}elseif($case==='negative'){
|
||||
$this->assertSame('not_applicable',$a['rows'][0]['status']);$this->assertFalse($a['monthly']['assets'][0]['applied']);$this->assertSame('0',$a['additionalDeduction']['numerator']);
|
||||
}else{
|
||||
$this->assertSame('absent',$a['status']);$this->assertSame([],$a['monthly']['assets']);$this->assertSame('0',$a['additionalDeduction']['numerator']);
|
||||
}
|
||||
file_put_contents(base_path('outputs/irpc-l7-start-period/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,37 @@
|
||||
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 {fraction} from '../../resources/js/lib/fiscal-rational.ts';
|
||||
import {createProjectDossierReport} from '../../resources/js/lib/project-dossier-report.ts';
|
||||
const read=name=>JSON.parse(fs.readFileSync(new URL('./fixtures/'+name,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-monthly-preserved.json');d.business_plan_snapshot=f.plan;d.modelo_a_snapshot=saveNormalBook(d,f.book);return{d,b:normalBook(d)};}
|
||||
function setup(month=7){
|
||||
const{d,b}=accepted(),p=JSON.parse(d.business_plan_snapshot);p.plan.assetDepreciation='120';d.business_plan_snapshot=JSON.stringify(p);
|
||||
const old=normalBook(read('irpc-l6-values-preserved.json'));b.periodsFx=structuredClone(old.periodsFx);b.periodsFx.from='2026-'+String(month).padStart(2,'0')+'-01';
|
||||
b.periodsFx.reference='QA início documentado em'+b.periodsFx.from+';meses completos até31/12';
|
||||
delete b.forms;const months=13-month;b.schedules.interest.amount=String(months*20);
|
||||
Object.assign(b.depreciation.assets[0],{accounting:String(months*10),start:b.periodsFx.from.slice(0,8)+'15',timing:'months'});
|
||||
for(const part of[b.special.expenses,b.special.cooperative,b.special.acceleration])Object.assign(part.qualification,{from:b.periodsFx.from,periodReference:b.periodsFx.reference});
|
||||
b.special.acceleration.monthly.startPeriod={version:1,reference:'QA início de actividade residente2026,CBF16 e períodoL6 reconciliados'};return{d,b};
|
||||
}
|
||||
|
||||
test('accepted11 result and currentkey remain exact without startPeriod;10 histories still readable',()=>{const{d,b}=accepted();assert.equal(normalCurrent(d,b),true);assert.deepEqual(calculateNormal(d,b),b.result.model);const old=read('irpc-l7-preserved.json').acceleration;assert.equal(normalCurrent(old,normalBook(old)),true);});
|
||||
|
||||
test('resident July start reuses6 actualmonths,50 normalquota,75 accelerated capped60;only10 additional',()=>{const{d,b}=setup(),asset=JSON.stringify(b.depreciation),plan=d.business_plan_snapshot,m=calculateNormal(d,b),s=m.special,a=s.acceleration.monthly.assets[0];assert.equal(m.version,'IRPC-normal-adjustments/12');assert.equal(m.periodsFx.short,true);assert.equal(a.months,6);assert.equal(val(a.normalQuota),50);assert.equal(val(a.acceleratedQuota),75);assert.equal(val(a.acceleratedDeduction),60);assert.equal(val(s.acceleration.additionalDeduction),10);assert.equal(val(m.taxableProfit),3130);assert.equal(val(m.taxableBase),3130);assert.equal(val(m.colecta),1001.6);assert.equal(val(s.taxableProfit),3120);assert.equal(val(s.taxableBase),3120);assert.equal(val(s.colecta),998.4);assert.equal(s.finalBalance.payable,null);assert.equal(s.acceleration.accelerated.assets.length,b.depreciation.assets.length);assert.equal(JSON.stringify(b.depreciation),asset);assert.equal(d.business_plan_snapshot,plan);assert.match(s.acceleration.source,/início de actividade/);assert.doesNotMatch(s.acceleration.source,/primeiro exercício anual/);});
|
||||
|
||||
test('October and December use3 and1 months without annualisation and retain exact fiscalfraction',()=>{for(const[month,months,diff]of[[10,3,fraction(5n)],[12,1,fraction(5n,3n)]]){const{d,b}=setup(month),m=calculateNormal(d,b),a=m.special.acceleration.monthly.assets[0];assert.equal(m.periodsFx.months,months);assert.equal(a.months,months);assert.deepEqual(a.additionalDeduction,diff);assert.deepEqual(a.normalQuota,fraction(BigInt(months)*25n,3n));d.modelo_a_snapshot=saveNormalBook(d,recordNormal(d,b));assert.match(normalHtml(d),new RegExp('duodécimosL6:'+months));if(month===12)assert.match(normalHtml(d),/5\/3/);}});
|
||||
|
||||
test('missing start evidence blocks only L7;absent and negative remain distinct withnormal totals',()=>{const missing=setup();missing.b.special.acceleration.monthly.startPeriod.reference='';let m=calculateNormal(missing.d,missing.b);assert.equal(m.special.acceleration.status,'indeterminate');assert.equal(m.special.acceleration.additionalDeduction,null);assert.equal(val(m.colecta),1001.6);assert.match(m.special.acceleration.reason,/Falta suporte do período curto/);for(const state of['negative','absent']){const{d,b}=setup();if(state==='negative')b.special.acceleration.rows[0].eligible='no';else{b.special.acceleration.rows=[];Object.assign(b.special.acceleration.qualification,{state:'absent',eligible:''});}m=calculateNormal(d,b);assert.equal(val(m.special.acceleration.additionalDeduction),0);assert.equal(m.special.acceleration.status,state==='absent'?'absent':'calculated');assert.equal(val(m.special.colecta),1001.6);if(state==='negative')assert.equal(m.special.acceleration.rows[0].status,'not_applicable');}});
|
||||
|
||||
test('cessation,otherperiodkind,nonresident,previousyearentry,priorquotas and exits remain outside newrecorte',()=>{for(const mutate of[b=>b.periodsFx.kind='same_year',b=>{b.periodsFx.kind='cessation';b.periodsFx.from='2026-01-01';b.periodsFx.to='2026-06-30';},b=>b.scope='ep_general',b=>b.depreciation.assets[0].start='2026-06-01',b=>b.depreciation.assets[0].previousAccounting='1',b=>{b.depreciation.assets[0].exitState='known';b.depreciation.assets[0].exitDate='2026-12-01';}]){const{d,b}=setup();mutate(b);const m=calculateNormal(d,b);assert.equal(m.special.acceleration.additionalDeduction,null);assert.equal(m.special.acceleration.status,'indeterminate');assert.notEqual(m.taxableProfit,null);}});
|
||||
|
||||
test('short fiscalperiod remains unsupported in11 unless explicitly prepared;cancel returns same11 decision',()=>{const{d,b}=setup(),period=JSON.stringify(b.periodsFx),asset=JSON.stringify(b.depreciation);delete b.special.acceleration.monthly.startPeriod;const m=calculateNormal(d,b);assert.equal(m.version,'IRPC-normal-adjustments/11');assert.equal(m.special.acceleration.additionalDeduction,null);assert.match(m.special.acceleration.reason,/exercício anual civil2026/);assert.equal(JSON.stringify(b.periodsFx),period);assert.equal(JSON.stringify(b.depreciation),asset);});
|
||||
|
||||
test('save-recover12 keeps11result verbatim,originalassets,otherbooks and fractional report;recalc is idempotent',()=>{const{d,b}=setup(),old=structuredClone(b.result),history=structuredClone(b.history),asset=JSON.stringify(b.depreciation),plan=d.business_plan_snapshot,others=JSON.parse(d.modelo_a_snapshot);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.deepEqual(back.history.at(-1),old);assert.equal(back.history.at(-1).model.version,'IRPC-normal-adjustments/11');assert.equal(JSON.stringify(back.depreciation),asset);assert.equal(d.business_plan_snapshot,plan);assert.deepEqual(JSON.parse(d.modelo_a_snapshot).irpcWithholdings,others.irpcWithholdings);const html=normalHtml(d);assert.match(html,/2026-07-01–2026-12-31/);assert.match(html,/dedução adicional conciliada:10.00/);assert.match(html,/colecta sobre a base:998.40/);assert.match(html,/Saldo final a pagar\/recuperar:Indeterminado/);assert.match(JSON.stringify(createProjectDossierReport(d,{selectedSections:new Set(['finance'])})),/início de actividade/);d.modelo_a_snapshot=saveNormalBook(d,recordNormal(d,back));back=normalBook(d);assert.equal(val(back.result.model.special.acceleration.additionalDeduction),10);assert.equal(val(back.result.model.special.colecta),998.4);back.periodsFx.from='2026-10-01';assert.equal(normalCurrent(d,back),false);});
|
||||
|
||||
test('invalid startPeriod child cannot replace originalbooks',()=>{for(const child of[null,{version:2,reference:'QA'},{version:1,reference:3}]){const{b}=setup();b.special.acceleration.monthly.startPeriod=child;assert.throws(()=>validateSpecial(b.special),/não recuperável/);}});
|
||||
|
||||
test('prepare only new guardedAPI cases and reports',()=>{const cases={start:setup(),fraction:setup(12),missing:setup(),negative:setup(),absent:setup()};cases.missing.b.special.acceleration.monthly.startPeriod.reference='';cases.negative.b.special.acceleration.rows[0].eligible='no';cases.absent.b.special.acceleration.rows=[];Object.assign(cases.absent.b.special.acceleration.qualification,{state:'absent',eligible:''});fs.mkdirSync('outputs/irpc-l7-start-period',{recursive:true});for(const[name,{d,b}]of Object.entries(cases)){d.modelo_a_snapshot=saveNormalBook(d,recordNormal(d,b,'2026-10-26T17:00:00+02:00'));fs.writeFileSync('outputs/irpc-l7-start-period/'+name+'.json',JSON.stringify(d));fs.writeFileSync('outputs/irpc-l7-start-period/'+name+'.html',normalHtml(d));}});
|
||||
@@ -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-start-period']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/IrpcSpecialStartPeriodDraftTest.php'];$argc=count($argv);$_SERVER['argv']=$argv;$_SERVER['argc']=$argc;require $root.'/vendor/phpunit/phpunit/phpunit';
|
||||
Reference in New Issue
Block a user