feat(irpc): prepare scoped cost limits provisions and timing in QA
tests / ci (push) Canceled after 0s
tests / ci (push) Canceled after 0s
This commit is contained in:
@@ -584,3 +584,26 @@ Actualização de **AUD-SIM-INT02 na mesma matriz**, sem novo lote ou backlog. O
|
||||
**L6 continua parcial por estes casos concretos.** Documento ou parâmetro em falta dentro de regra implementada é uma pendência do caso, não outra tarefa genérica deL6. Juros capitalizáveis, relações especiais e tratamentos especiais conservam o enquadramento próprio, sem presumir que satisfazem o recorte de juros ordinários.
|
||||
|
||||
**L7** mantém benefícios, isenções, taxas reduzidas, tributações autónomas e operações especiais. **L8** mantém retenções, pagamentos, créditos e saldo final a pagar/recuperar. A ausência desses tratamentos não reabre o percurso normal entregue nem justifica, por si, «L6 parcial». A divergência documental não autoriza preencher M22 campo302 ou309–316 com zeros presumidos. As dez dúvidas deL5 permanecem válidas e bloqueiam apenas os casos efectivamente dependentes; sem nova pesquisa ou repetição das provas aceites.
|
||||
|
||||
## 26/10/2026 — L6: custos com limites, provisões e periodização em QA
|
||||
|
||||
Actualização de **AUD-SIM-INT02 na mesma matriz**, sobre o conjunto instalado `a2de7259`. **Novo incremento em QA, sem instalação.** O percurso e as provas já aceites deL6/L3/ISPC/câmbio/L5 foram reutilizados; não foram novamente executados os testes funcionais anteriores. A fotografia5 continua actual sem activar os novos campos; a fotografia6 guarda estes mapas e conserva o resultado anterior no histórico.
|
||||
|
||||
**Reutilização e resultado demonstrado:** resultado financeiro6240MZN, acréscimos anteriores220 e mapas anteriores520 conservados; novos ajustamentos de custos/provisões/periodização+350 e obra−600 → **lucro tributável6730MZN**. Dedução expressa das perdas anteriores2300 → **matéria colectável4430MZN**; taxa normal32% → **colecta1417,60MZN**. Não é saldo final a pagar. Nenhum saldo oficial de provisões/perdas é consumido, nem é alterada declaração ou história anterior.
|
||||
|
||||
| Categoria e condição no exercício anual com termo em2026 | Parcela aceite, limite e ajustamento demonstrados | Informação insuficiente ou caso separado |
|
||||
|---|---|---|
|
||||
| Ajudas de custo/deslocação em viatura própria36(1)e: parcelas facturadas e tributadas emIRPS identificadas sem sobreposição. Representação36(1)f/36(3) ordinária documentada. | Ajudas100:facturadas20,tributadas e não facturadas20;remanescente60 →30 acrescido/70 aceite. Representação50 →40 acrescido/10 aceite. Zero conhecido nos parâmetros é distinto de campo vazio. | Parcela facturada/tributada em falta impede a aceitação dependente; não presumir ausência deIRPS. Eventual tributação autónoma continuaL7. |
|
||||
| Publicidade ordinária36(1)n,Lei20/2009: receita declarada reconciliada com vendas normais e completude anual das parcelas. | Um limite anual partilhado de1% de12000=120. Caso com duas parcelas100+100:aceites60+60,acréscimo80. A repartição proporcional é apenas aritmética de simulação, não uma ordem fiscal obrigatória. | Campanha de lançamento com horizonte superior a um ano não recebe este tratamento por aproximação: periodização36-A e articulação do limite permanecem por implementar. |
|
||||
| Encargos com viaturas36(4), combustível36i e aluguer sem condutor36h, distinguindo categoria/serviço público/locação na actividade normal. | Outros encargos100 de passageiros sem excepção:aceite50/acréscimo50. Combustível50 não sujeito a36(4),com40 comprovados no activo/locação e consumo normal:aceite40/acréscimo10. Renda100 no caso documentado de excepção36(4),quota não aceite36h de20:aceite80/acréscimo20; a quota é reutilizada do suporte e não apurada automaticamente. | Se há simultaneamente parcela excluída porh/i e aplicação dos50% de36(4), a combinação fica pendente. Exemplo:fuel100,comprovado80,passageiro sem excepção; não somar nem repartir correcções por presunção. Resolver a regra de articulação dos trechos aplicáveis ou esclarecer esse exemplo especificamente comAT. Apuramento automático da quota36h permanece no saldo de amortizações; só bloqueia esse caso. |
|
||||
| Provisão/perda por imparidade de créditos28a/29: regime ordinário identificado, créditos normais finais e provisão fiscal anterior conhecidos. | Base10000:limite anual1,5%=150,acumulado6%=600. Saldo anterior550 deixa50;provisão contabilística200 →50 aceite/150 acrescido. Duas parcelas100+100 partilham25+25; não repetem os limites nem consomem o saldo oficial. | Saldo anterior vazio não é zero; anterior superior ao tecto pede reconciliação/reversão28(2). Provisões sujeitas a supervisão financeira/seguradora28d ou reforços financeiros28(3) exigem correspondência própria, sem aplicar29 por aproximação. |
|
||||
| Existências28b:perda efectivamente observada. Processo judicial28c:em curso e obrigação subjacente fiscalmente dedutível. | Existências:parcela60,perda total40,provisão anterior10 →30 aceite/30 acrescido. Judicial:parcela90,obrigação100,anterior20 →80 aceite/10 acrescido. Perda não observada ou processo que não esteja em curso são condições negativas conhecidas:acréscimo integral, sem zero presumido como dedução. | Falta de prova do evento,obrigação,perda ou saldo anterior fica indeterminada. Objecto/saldo repetido entre documentos impede reutilizar a cobertura; agregar/reconciliar as parcelas do mesmo objecto. |
|
||||
| Reversões28(2) e provisões anteriormente tributadas:mesmo objecto e tratamento fiscal anterior comprovados. | Provisão antes deduzida:reversão50 já incluída no resultado não acrescenta novamente;omitida dos proveitos acrescenta50. Reversão contabilística50 de provisão antes tributada comprovada é excluída por50. | Não inferir o tratamento anterior do saldo contabilístico. Quantia superior à parcela fiscal demonstrada,evento que continua a subsistir ou objecto duplicado ficam pendentes. Sem alteração retrospectiva ou consumo do saldo oficial. |
|
||||
| Componentes de exercícios anteriores18(2):conhecimento/imprevisibilidade no encerramento anterior documentados. | Custo80 conhecido no fecho2025,incluído no resultado actual →acréscimo80. Se imprevisível/manifestamente desconhecido à data daquele fecho →sem acréscimo por periodização. Proveito50 conhecido no fecho anterior →excluído do actual por50;se imprevisível/desconhecido →considerado no actual. | Conhecimento no fecho em falta impede apenas a imputação da parcela. Nenhuma rectificação de declaração anterior é executada. Periodizações específicas18(6–10) não passam a estar cobertas por este recorte18(2). |
|
||||
| Obras plurianuais19 e perdas meramente estimadas36m:critério uniforme,preço,custo incorporado/custo total e acumulados fiscais anteriores documentados. | Percentagem:preço2000,custo total1000,incorrido500;proveito fiscal acumulado1000 menos anterior600=400 corrente;custo500 menos anterior300=200 corrente. Proveito contabilizado1000/custo200 →ajustamento−600. Encerramento tem caso separado;facturação parcial com acabamento correspondente/entregas fraccionadas impõem percentagem;95% com preço conhecido ou recepção provisória pública determina conclusão. Perda meramente estimada50 de obra em curso →acréscimo50. | Acumulados incoerentes ou método incompatível bloqueiam essa obra. Receita antecipada19(5) positiva e mudança de critério com autorização19(6) ficam por corresponder; o caso demonstrado declara expressamente zero de receita antecipada e método mantido, sem presumir essas condições. |
|
||||
|
||||
**Percurso demonstrado:** cálculo → gravação → recuperação pela API privada → relatório/dossier/exportação em cinco casos:conjunto calculado,informação insuficiente,categorias adicionais,limites partilhados e encerramento de obra. Só os novos casos foram testados: **22 testes frontend,1 testeAPI/83asserções**, transacção revertida na base QA127.0.0.1:55441; routes/actions gerados pelo procedimento existente,TypeScript/build e interface real passaram. Interface permite reutilizar a parcela livre da origem e o saldo financeiro de créditos quando conhecido; alterações às somas dos temas são revistas antes de aplicar e não apagam escolhas ou históricos. Fonte ausente ou saldo não acompanhado nunca cria zero. Valores racionais são conservados, apresentações decimais aproximadas são identificadas e uma perda inferior a um cêntimo não é truncada para zero.
|
||||
|
||||
**Preservação:**6ficheiros de runtime no delta,42protegidos e31originais conferidos; sem schema/API/configuração/permissões/dependências alterados, sem escrita no activo. Recibo:`docs/investmoz/simulations/irpc-l6-costs-provisions-timing-qa-20261026.json`;bundle/pacote em `outputs/irpc-l6-costs`. DivergênciaM20/M22 permanece limitada ao transporte201/quadro8 versus200/quadro10; não é uma condição destes cálculos. Não foram criadas correspondências oficiais de campos para estes novos ajustamentos por presunção.
|
||||
|
||||
**Saldo deL6 após estes casos QA:**combinação36h/i+36(4);quota36h automática e restantes amortizações;campanhas36-A e sua articulação com1%;provisões reguladas/reforços financeiros e periodizações específicas não cobertas;receita antecipada positiva/mudança autorizada do método de obra;períodos curtos/conversão fiscal anual;representação fiscal de fracções e restantes correspondênciasM20/M22. Cada pendência afecta o caso/campo dependente, não o conjunto independente demonstrado. Benefícios,isenções,taxas reduzidas e operações especiais mantêm âmbitoL7;retenções,pagamentos,créditos e saldo anual mantêm âmbitoL8. **L6 continua parcial.** As dez dúvidas deL5 não foram reabertas.
|
||||
|
||||
@@ -0,0 +1,869 @@
|
||||
{
|
||||
"status": "compatible_cost_limits_provisions_timing_QA_prepared_not_installed",
|
||||
"installed_baseline": "a2de725901c308fb2ac2bc3d72181ab50ef46b7a",
|
||||
"branch": "codex/irpc-l6-costs-provisions-timing-qa-20261026",
|
||||
"QA_workspace": "C:\\laragon\\www\\portal-investidor-irpc-l6-costs-qa-20261026",
|
||||
"version": "IRPC-normal-adjustments/6",
|
||||
"scope": "AnnualMZN periods ending2026,ordinary documented cost/provision categories and prior-year/work timing;normal profit/base/colecta only;L7/L8 remain own scopes.",
|
||||
"demonstrated_sequence": {
|
||||
"accounting_profit": "6240MZN unchanged",
|
||||
"previous_addbacks": "220MZN",
|
||||
"previous_schedule_effect": "520MZN",
|
||||
"new_cost_provision_prior_timing_adjustments": "350MZN",
|
||||
"new_work_adjustment": "-600MZN",
|
||||
"taxable_profit": "6730MZN",
|
||||
"previous_loss_deduction": "2300MZN explicit,unchanged",
|
||||
"taxable_base": "4430MZN",
|
||||
"rate": "32%",
|
||||
"colecta": "1417.60MZN",
|
||||
"final_payable": null,
|
||||
"final_recoverable": null,
|
||||
"final_balance_scope": "L8"
|
||||
},
|
||||
"cases_recovered_by_private_API": {
|
||||
"calculated": {
|
||||
"profit": {
|
||||
"numerator": "6730",
|
||||
"denominator": "1"
|
||||
},
|
||||
"base": {
|
||||
"numerator": "4430",
|
||||
"denominator": "1"
|
||||
},
|
||||
"colecta": {
|
||||
"numerator": "7088",
|
||||
"denominator": "5"
|
||||
},
|
||||
"cost_effect": {
|
||||
"numerator": "-250",
|
||||
"denominator": "1"
|
||||
},
|
||||
"pending": [],
|
||||
"payable": null,
|
||||
"recoverable": null
|
||||
},
|
||||
"missing": {
|
||||
"profit": null,
|
||||
"base": null,
|
||||
"colecta": null,
|
||||
"cost_effect": {
|
||||
"numerator": "-400",
|
||||
"denominator": "1"
|
||||
},
|
||||
"pending": [
|
||||
"QA-credit: Provisão fiscal anterior dos créditos em falta; não presumir zero."
|
||||
],
|
||||
"payable": null,
|
||||
"recoverable": null
|
||||
},
|
||||
"additional": {
|
||||
"profit": {
|
||||
"numerator": "7050",
|
||||
"denominator": "1"
|
||||
},
|
||||
"base": {
|
||||
"numerator": "4750",
|
||||
"denominator": "1"
|
||||
},
|
||||
"colecta": {
|
||||
"numerator": "1520",
|
||||
"denominator": "1"
|
||||
},
|
||||
"cost_effect": {
|
||||
"numerator": "70",
|
||||
"denominator": "1"
|
||||
},
|
||||
"pending": [],
|
||||
"payable": null,
|
||||
"recoverable": null
|
||||
},
|
||||
"limits": {
|
||||
"profit": {
|
||||
"numerator": "7210",
|
||||
"denominator": "1"
|
||||
},
|
||||
"base": {
|
||||
"numerator": "4910",
|
||||
"denominator": "1"
|
||||
},
|
||||
"colecta": {
|
||||
"numerator": "7856",
|
||||
"denominator": "5"
|
||||
},
|
||||
"cost_effect": {
|
||||
"numerator": "230",
|
||||
"denominator": "1"
|
||||
},
|
||||
"pending": [],
|
||||
"payable": null,
|
||||
"recoverable": null
|
||||
},
|
||||
"closing": {
|
||||
"profit": {
|
||||
"numerator": "6530",
|
||||
"denominator": "1"
|
||||
},
|
||||
"base": {
|
||||
"numerator": "4230",
|
||||
"denominator": "1"
|
||||
},
|
||||
"colecta": {
|
||||
"numerator": "6768",
|
||||
"denominator": "5"
|
||||
},
|
||||
"cost_effect": {
|
||||
"numerator": "-450",
|
||||
"denominator": "1"
|
||||
},
|
||||
"pending": [],
|
||||
"payable": null,
|
||||
"recoverable": null
|
||||
}
|
||||
},
|
||||
"categories_demonstrated": {
|
||||
"allowance": {
|
||||
"case": "calculated",
|
||||
"source_amount": {
|
||||
"numerator": "100",
|
||||
"denominator": "1"
|
||||
},
|
||||
"accepted": {
|
||||
"numerator": "70",
|
||||
"denominator": "1"
|
||||
},
|
||||
"adjustment": {
|
||||
"numerator": "30",
|
||||
"denominator": "1"
|
||||
},
|
||||
"limit": {
|
||||
"numerator": "30",
|
||||
"denominator": "1"
|
||||
},
|
||||
"rule_source": "CIRPC36(1)e",
|
||||
"reason": "50% só da parcela não facturada a clientes nem tributada emIRPS; parcelas mutuamente exclusivas."
|
||||
},
|
||||
"representation": {
|
||||
"case": "calculated",
|
||||
"source_amount": {
|
||||
"numerator": "50",
|
||||
"denominator": "1"
|
||||
},
|
||||
"accepted": {
|
||||
"numerator": "10",
|
||||
"denominator": "1"
|
||||
},
|
||||
"adjustment": {
|
||||
"numerator": "40",
|
||||
"denominator": "1"
|
||||
},
|
||||
"limit": null,
|
||||
"rule_source": "CIRPC36(1)f/36(3)",
|
||||
"reason": "20% aceite;80% acrescido. Eventual tributação autónoma não calculada, âmbitoL7."
|
||||
},
|
||||
"advertising": {
|
||||
"case": "calculated",
|
||||
"source_amount": {
|
||||
"numerator": "80",
|
||||
"denominator": "1"
|
||||
},
|
||||
"accepted": {
|
||||
"numerator": "80",
|
||||
"denominator": "1"
|
||||
},
|
||||
"adjustment": {
|
||||
"numerator": "0",
|
||||
"denominator": "1"
|
||||
},
|
||||
"limit": {
|
||||
"numerator": "120",
|
||||
"denominator": "1"
|
||||
},
|
||||
"rule_source": "CIRPC36(1)n,Lei20/2009",
|
||||
"reason": "Limite único anual1% das vendas normais; repartição aritmética proporcional entre parcelas para a simulação, sem ordem fiscal obrigatória."
|
||||
},
|
||||
"credit": {
|
||||
"case": "calculated",
|
||||
"source_amount": {
|
||||
"numerator": "200",
|
||||
"denominator": "1"
|
||||
},
|
||||
"accepted": {
|
||||
"numerator": "50",
|
||||
"denominator": "1"
|
||||
},
|
||||
"adjustment": {
|
||||
"numerator": "150",
|
||||
"denominator": "1"
|
||||
},
|
||||
"limit": {
|
||||
"numerator": "50",
|
||||
"denominator": "1"
|
||||
},
|
||||
"rule_source": "CIRPC28(1)a/29",
|
||||
"reason": "29:1,5% anual,6% acumulado sobre créditos da actividade normal existentes no fim; saldo fiscal anterior conhecido. Repartição proporcional do limite único, sem consumir saldo oficial."
|
||||
},
|
||||
"stock": {
|
||||
"case": "calculated",
|
||||
"source_amount": {
|
||||
"numerator": "60",
|
||||
"denominator": "1"
|
||||
},
|
||||
"accepted": {
|
||||
"numerator": "30",
|
||||
"denominator": "1"
|
||||
},
|
||||
"adjustment": {
|
||||
"numerator": "30",
|
||||
"denominator": "1"
|
||||
},
|
||||
"limit": {
|
||||
"numerator": "40",
|
||||
"denominator": "1"
|
||||
},
|
||||
"rule_source": "CIRPC28(1)b",
|
||||
"reason": "28b:limite das perdas efectivamente observadas, incluindo saldo anterior."
|
||||
},
|
||||
"litigation": {
|
||||
"case": "calculated",
|
||||
"source_amount": {
|
||||
"numerator": "90",
|
||||
"denominator": "1"
|
||||
},
|
||||
"accepted": {
|
||||
"numerator": "80",
|
||||
"denominator": "1"
|
||||
},
|
||||
"adjustment": {
|
||||
"numerator": "10",
|
||||
"denominator": "1"
|
||||
},
|
||||
"limit": {
|
||||
"numerator": "100",
|
||||
"denominator": "1"
|
||||
},
|
||||
"rule_source": "CIRPC28(1)c",
|
||||
"reason": "28c:obrigações de processo judicial em curso que seriam custos dedutíveis, com saldo anterior reconciliado."
|
||||
},
|
||||
"fuel": {
|
||||
"case": "calculated",
|
||||
"source_amount": {
|
||||
"numerator": "50",
|
||||
"denominator": "1"
|
||||
},
|
||||
"accepted": {
|
||||
"numerator": "40",
|
||||
"denominator": "1"
|
||||
},
|
||||
"adjustment": {
|
||||
"numerator": "10",
|
||||
"denominator": "1"
|
||||
},
|
||||
"limit": null,
|
||||
"rule_source": "CIRPC36(1)i/36(4)",
|
||||
"reason": "36i: parcela ligada a activo próprio/locado e consumo normal comprovados."
|
||||
},
|
||||
"prior_cost": {
|
||||
"case": "calculated",
|
||||
"source_amount": {
|
||||
"numerator": "80",
|
||||
"denominator": "1"
|
||||
},
|
||||
"accepted": {
|
||||
"numerator": "0",
|
||||
"denominator": "1"
|
||||
},
|
||||
"adjustment": {
|
||||
"numerator": "80",
|
||||
"denominator": "1"
|
||||
},
|
||||
"limit": null,
|
||||
"rule_source": "CIRPC18(2)",
|
||||
"reason": "18(2):conhecido/previsível no encerramento anterior; excluído do actual, sem alterar declarações/históricos anteriores."
|
||||
},
|
||||
"work": {
|
||||
"case": "calculated",
|
||||
"source_amount": {
|
||||
"numerator": "1000",
|
||||
"denominator": "1"
|
||||
},
|
||||
"accepted": {
|
||||
"numerator": "200",
|
||||
"denominator": "1"
|
||||
},
|
||||
"adjustment": {
|
||||
"numerator": "-600",
|
||||
"denominator": "1"
|
||||
},
|
||||
"limit": {
|
||||
"numerator": "400",
|
||||
"denominator": "1"
|
||||
},
|
||||
"rule_source": "CIRPC19(1–6)",
|
||||
"reason": "19:grau=incorrido/custo total;valores fiscais do exercício obtidos dos acumulados menos o já reconhecido. Proveito fiscal corrente:400.00 MZN;custo fiscal corrente:200.00 MZN;sem imposto/pagamento ou perda estimada presumidos."
|
||||
},
|
||||
"vehicle": {
|
||||
"case": "additional",
|
||||
"source_amount": {
|
||||
"numerator": "100",
|
||||
"denominator": "1"
|
||||
},
|
||||
"accepted": {
|
||||
"numerator": "50",
|
||||
"denominator": "1"
|
||||
},
|
||||
"adjustment": {
|
||||
"numerator": "50",
|
||||
"denominator": "1"
|
||||
},
|
||||
"limit": null,
|
||||
"rule_source": "CIRPC36(4)",
|
||||
"reason": "50% dos outros encargos de passageiros, excepto serviço público de transportes/locação na actividade normal; não inclui quotas, aluguer sem condutor ou combustível já discriminados."
|
||||
},
|
||||
"rental": {
|
||||
"case": "additional",
|
||||
"source_amount": {
|
||||
"numerator": "100",
|
||||
"denominator": "1"
|
||||
},
|
||||
"accepted": {
|
||||
"numerator": "80",
|
||||
"denominator": "1"
|
||||
},
|
||||
"adjustment": {
|
||||
"numerator": "20",
|
||||
"denominator": "1"
|
||||
},
|
||||
"limit": null,
|
||||
"rule_source": "CIRPC36(1)h/36(4)",
|
||||
"reason": "36h: reutiliza parcela de quota não aceite documentada; o portal não apura automaticamente essa quota de viatura."
|
||||
},
|
||||
"estimated_work_loss": {
|
||||
"case": "additional",
|
||||
"source_amount": {
|
||||
"numerator": "50",
|
||||
"denominator": "1"
|
||||
},
|
||||
"accepted": {
|
||||
"numerator": "0",
|
||||
"denominator": "1"
|
||||
},
|
||||
"adjustment": {
|
||||
"numerator": "50",
|
||||
"denominator": "1"
|
||||
},
|
||||
"limit": null,
|
||||
"rule_source": "CIRPC36(1)m,Lei20/2009",
|
||||
"reason": "36m:perda meramente estimada em obra em curso não dedutível; custo efectivo não confundido com estimativa."
|
||||
},
|
||||
"provision_reversal": {
|
||||
"case": "additional",
|
||||
"source_amount": {
|
||||
"numerator": "50",
|
||||
"denominator": "1"
|
||||
},
|
||||
"accepted": {
|
||||
"numerator": "50",
|
||||
"denominator": "1"
|
||||
},
|
||||
"adjustment": {
|
||||
"numerator": "50",
|
||||
"denominator": "1"
|
||||
},
|
||||
"limit": null,
|
||||
"rule_source": "CIRPC28(2)",
|
||||
"reason": "28(2):proveito da provisão antes deduzida; só acresce quando ainda não incluído no resultado."
|
||||
},
|
||||
"taxed_reversal": {
|
||||
"case": "additional",
|
||||
"source_amount": {
|
||||
"numerator": "50",
|
||||
"denominator": "1"
|
||||
},
|
||||
"accepted": {
|
||||
"numerator": "50",
|
||||
"denominator": "1"
|
||||
},
|
||||
"adjustment": {
|
||||
"numerator": "-50",
|
||||
"denominator": "1"
|
||||
},
|
||||
"limit": null,
|
||||
"rule_source": "CIRPC17/28;sem dupla tributação da parcela comprovada",
|
||||
"reason": "Parcela antes tributada comprovada; exclui a reversão contabilística sem duplicar dedução."
|
||||
},
|
||||
"prior_income": {
|
||||
"case": "additional",
|
||||
"source_amount": {
|
||||
"numerator": "50",
|
||||
"denominator": "1"
|
||||
},
|
||||
"accepted": {
|
||||
"numerator": "0",
|
||||
"denominator": "1"
|
||||
},
|
||||
"adjustment": {
|
||||
"numerator": "-50",
|
||||
"denominator": "1"
|
||||
},
|
||||
"limit": null,
|
||||
"rule_source": "CIRPC18(2)",
|
||||
"reason": "18(2):conhecido/previsível no encerramento anterior; excluído do actual, sem alterar declarações/históricos anteriores."
|
||||
}
|
||||
},
|
||||
"verification": {
|
||||
"new_frontend_tests": 22,
|
||||
"private_API_tests": 1,
|
||||
"private_API_assertions": 83,
|
||||
"database": "127.0.0.1:55441/simulator_fiscal_delta_20261021_test;DatabaseTransactions rollback",
|
||||
"routes_actions": "generated by existing guarded procedure",
|
||||
"typescript": "passed",
|
||||
"build": "passed",
|
||||
"UI": "actual component;fieldediting;missingconditions;reviewed topic reconciliation;sharedcaps;local roundtrip;download;mandatory work method",
|
||||
"dossier": "generated from5 API recovered cases",
|
||||
"accepted_functional_suites": "reused,not manually repeated"
|
||||
},
|
||||
"preservation": {
|
||||
"installed5_fixture_sha256": "109426b86ae7c14463699b1d197064617f124ecec0c55085d9e81aeddc505a5a",
|
||||
"matches_accepted5_fixture": true,
|
||||
"versions1_to5_current_keys": "unchanged without optional costs input",
|
||||
"history": "old record preserved unchanged on new calculation;no rewriting prior returns",
|
||||
"other_books": "preserved in snapshot roundtrip",
|
||||
"source_plan": "reused,no re-entry of financial result/base/colecta",
|
||||
"schema_API_config_permissions_dependencies_changed": false,
|
||||
"active_data_changed": false,
|
||||
"official_fiscal_balances_consumed": false,
|
||||
"fractional_precision_delta": "Exact numerator comparison for current loss and loss-deduction cap;negative subcent result cannot be truncated to zero"
|
||||
},
|
||||
"runtime_delta_joint_from_a2de7259": [
|
||||
{
|
||||
"path": "resources/js/components/simulation-irpc-costs.tsx",
|
||||
"sha256_LF": "c4156916835daf0f33addf89dfedd40c31b8dc3297112cd98beac33422af274d"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/components/simulation-irpc-normal.tsx",
|
||||
"sha256_LF": "8001d2e71b58fa07c77f74da2c2b51566659a5d784d9e044133192c66518cb15"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/lib/irpc-normal-base.ts",
|
||||
"sha256_LF": "fef34966db603633eb03c9830ad3c09e7cd0ffaed49720dbab60f9dc0fd8a8fb"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/lib/irpc-normal-costs.ts",
|
||||
"sha256_LF": "29aeb16bf8fe60bb322b5449fb3efd84b5108b06112b91be5fc76d8e897d8860"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/lib/irpc-normal-profit.ts",
|
||||
"sha256_LF": "26810caea4e7d177b385e36452147d84602a7b23cb5f8f23a88488d1bfaf619f"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/lib/irpc-normal.ts",
|
||||
"sha256_LF": "98adf3e1c84938d27535f65afa24edc0650bfb12a1f9b32352b0d4d71c2c2035"
|
||||
}
|
||||
],
|
||||
"protected_runtime": [
|
||||
{
|
||||
"path": "resources/js/components/simulation-irpc-schedules.tsx",
|
||||
"sha256_LF": "eebd6d9e4100920a5ebf91b800537ffe301fd6a994b86a3b080d673f4a9c9a42"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/lib/irpc-normal-assessment.ts",
|
||||
"sha256_LF": "1dc6adc0835af033ef296d12cdc1e9ef354b2f54cd36e2349b2df66e369c1ee3"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/lib/irpc-normal-forms.ts",
|
||||
"sha256_LF": "4e986addb9f2d74e7a80345f8f1e4d8e0b2f562c867fa6eee84880d9f72a3649"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/lib/irpc-normal-schedules.ts",
|
||||
"sha256_LF": "b0045fd2c072998ebb290c2a8332c26d05a1f5f746b3beb1805508147497cac8"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/components/simulation-irpc-taxable-base.tsx",
|
||||
"sha256_LF": "e4fe9f66d35d24aa68fd87de6a5a6a0e901706b270c18962fd0c7ab00f9abd93"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/components/simulation-irpc-assessment.tsx",
|
||||
"sha256_LF": "7174cc0160005a0c332ea40f02ba8a6d57985081b5ab45177ccd6e721bceea54"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/components/acquisition-beverage-restitution.tsx",
|
||||
"sha256_LF": "6f44897b8b12569314551df0022c26da57a165634087942a724293e66837558d"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/components/fiscal-calculation-gaps.tsx",
|
||||
"sha256_LF": "1dab6ceb73756d4e023096b3b2ba767aaa843ff6cc69046ea630ab449a00b0dd"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/components/fiscal-l3-preparation.tsx",
|
||||
"sha256_LF": "e84a7b1ccadaa1b5bd78d778a3287f96ccaf530855eef404786cd7aa3d321b73"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/components/simulation-acquisitions.tsx",
|
||||
"sha256_LF": "c206d4cfe575d6ba007aa2b425b5f898efa97e4162b8710e358b6ecfd9458542"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/components/simulation-operations.tsx",
|
||||
"sha256_LF": "a7e55972fa790063dcf11945055de2af46a77db9e509900af22ede7ec47bb4cf"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/lib/iva-acquisition-bc.ts",
|
||||
"sha256_LF": "b0ef55e0917bdb79f14b53ba9e95d73c08153befeb7d4ce73f81e6fb13992225"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/lib/iva-beverage-allocation.ts",
|
||||
"sha256_LF": "32968d198c7fe0c53ca89641c22d5749be6636be2c79127b867e4e3afd0dd40d"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/lib/iva-beverage-restitution.ts",
|
||||
"sha256_LF": "5c650cbd9469d5f99001f56fc4ff99ac973eb368036d4e96c6d171f1b220bd9e"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/lib/iva-calculation-gaps.ts",
|
||||
"sha256_LF": "2e7d84a0c196bd5b56fd67cc1a389d43d5041894184e675680a4de3cd10b31a2"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/lib/iva-operations.ts",
|
||||
"sha256_LF": "ec37b949f1233c5f5c14a87450c421ed29fabf1ed5b73fb32564564a2bc31f54"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/lib/l3-form-preparation.ts",
|
||||
"sha256_LF": "fc47f5fa5ec1420fbd847d0d081f00d99ebbb524164eef785b20877e0562f704"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/lib/ispc-residuals.ts",
|
||||
"sha256_LF": "40d5751c06c7c155ab4b7a17ee2d1653da2fbbf075807fbb8561cdbe19820955"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/components/simulation-ispc-residuals.tsx",
|
||||
"sha256_LF": "a9219d6f41ccbd0ce45659d4f6aa0fa1c169a685840f5cb1e18daeca377d5e18"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/lib/ispc-eligibility.ts",
|
||||
"sha256_LF": "44675c5efacdc1ae19505667611ba3b522d3043cebacc8754597c3a68ca89d25"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/components/simulation-ispc-quarter.tsx",
|
||||
"sha256_LF": "2c2555309b32d4261f14e7164f39314c9188129c2990357fc54090a2468dd2b4"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/components/simulation-ispc-withholding.tsx",
|
||||
"sha256_LF": "e0445d015b72859e48726959aa52686f79b1cf5c2ade29751efa83257068b878"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/lib/ispc-quarter.ts",
|
||||
"sha256_LF": "f66bd52e9cdfe1ddaa6b00a931465862b3d890b9be216b7a056740f027f24129"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/lib/ispc-withholding.ts",
|
||||
"sha256_LF": "6c31b240299edd3262efa28f7f3d5aee21123f94e30ef4ab90e26c35777cb6e4"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/components/simulation-ispc-eligibility.tsx",
|
||||
"sha256_LF": "f48dd4a8f87a994eda0447f81a463589905e5873b2e1044debac9d10825a880c"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/lib/iva-acquisitions.ts",
|
||||
"sha256_LF": "cce283b1d0a07fb383fcb527225f4dd5bec829010a662cbaae1d60e595221977"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/lib/iva-modelo-a.ts",
|
||||
"sha256_LF": "c641619a05613c1620385baad0e9ffbf810dd970aafb30e12b3a7f9a3bf11846"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/lib/iva-fx-selection.ts",
|
||||
"sha256_LF": "9aa07ad443bc777b80510a3b5bd6aff9ddb9814d2450f3c690e531e74d71d998"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/components/simulation-fx-selection.tsx",
|
||||
"sha256_LF": "39bb6d87d404745b0b66349bc11626545a3672ae27321ce9b4658416592059bf"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/lib/business-financial-plan.ts",
|
||||
"sha256_LF": "c20274722d1ffcd0360f041a6cacb5d868fe0621cb27a1aca6e06826612c3ebb"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/lib/iva-currency-events.ts",
|
||||
"sha256_LF": "cc9f2045d302d7987310da4006cc250fc5c99a27397918d166d649bfc32fab08"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/lib/irpc-fx.ts",
|
||||
"sha256_LF": "5c4a7a477429f8d775acf6db3e117008468f5554e5731dec81f60fbb75e948fb"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/lib/irpc-withholdings.ts",
|
||||
"sha256_LF": "8e5f3c035ce5e4d6c3fc82f933439ab580fa5519492f9c4d32cfb71b77b54803"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/components/simulation-irpc-withholdings.tsx",
|
||||
"sha256_LF": "751af4fab346659bc5ccb08b8a2169769af7521ea0035050af32b41ea7f5aa76"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/lib/irpc-interest.ts",
|
||||
"sha256_LF": "f8e6390b3b2c58c296f7e3fd14576e06ae248bc0cabce1a666b7094611f2a7dd"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/lib/irpc-mrfta.ts",
|
||||
"sha256_LF": "76dcce8954f68b081d500be61de9c5b146a452a3eb578a0b78776ba9e1f8051c"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/lib/irpc-mrfta-cases.ts",
|
||||
"sha256_LF": "4266746076ab3f70312fd042351e24dd2f136c4f3e8a277b57dd97849a9f1862"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/components/simulation-irpc-mrfta.tsx",
|
||||
"sha256_LF": "913d1bb4ac0fc59763cf5d27bc637f6a713b2050c4463748db554af4be451538"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/lib/financial-fiscal-bridge.ts",
|
||||
"sha256_LF": "5a30c501653b16b5fedcb12631ce81fcce4fe473aa542e94374986b3c50f2d33"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/components/simulation-form.tsx",
|
||||
"sha256_LF": "85b024a7f45ceb52efd6ff1f21d0eab1fd8fcf1ab59e4b5302c08685235c472a"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/lib/project-dossier.ts",
|
||||
"sha256_LF": "a0909bbfcdf7e8542b010faa104fcb918e86ae38ddf096082d4d5be98d31b2b9"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/lib/project-dossier-report.ts",
|
||||
"sha256_LF": "5207a2ffeb208cd5ace6713038cee85208020ee618194dca7114e94fc3200b6c"
|
||||
}
|
||||
],
|
||||
"originals_preserved": [
|
||||
{
|
||||
"path": "docs/investmoz/simulations/source-archive/AT-Comunicado-9-2026-ISPC.pdf",
|
||||
"sha256": "106f8a9ce9255c03549c70bf07fe999d32e6e55302d009b509862aa74ccb4973"
|
||||
},
|
||||
{
|
||||
"path": "docs/investmoz/simulations/source-archive/AT-Modelo-30-ISPC.pdf",
|
||||
"sha256": "6802edd06b6ff638a0b851a3ec7ecf69af8f07840faa47446becad67d35d445c"
|
||||
},
|
||||
{
|
||||
"path": "docs/investmoz/simulations/source-archive/AT-Modelo-B-Geral.pdf",
|
||||
"sha256": "bd977c7c59d4d98befd92e2c566b1bf168cb1343bb519f51b41a3807f431fc57"
|
||||
},
|
||||
{
|
||||
"path": "docs/investmoz/simulations/source-archive/AT-MRFTA-19-39.pdf",
|
||||
"sha256": "8ea62c53066b482f87078a7a7b1ecf15ca98a12a122e8d7abca63acfc993186b"
|
||||
},
|
||||
{
|
||||
"path": "docs/investmoz/simulations/source-archive/BM-Mercado-Monetario-MAIBOR-20261025.html",
|
||||
"sha256": "00e8a7e0d3a1db4ca3beb56d5361b2084a0f1684f3d40fc1c203702a2190e063"
|
||||
},
|
||||
{
|
||||
"path": "docs/investmoz/simulations/source-archive/BR-16-I-Serie-2008-04-16.pdf",
|
||||
"sha256": "fc373ff5637edf75ec6a9595dec8763a8503d4ee786e3b7d8f1c742eae9d80ee"
|
||||
},
|
||||
{
|
||||
"path": "docs/investmoz/simulations/source-archive/BR-248-I-Suplemento-1-2025.pdf",
|
||||
"sha256": "ccf2a8deeca820f2dd076b4b2141b1efe3f6a806568c3a587ca59a8e92f4c251"
|
||||
},
|
||||
{
|
||||
"path": "docs/investmoz/simulations/source-archive/BR-89-I-Serie-2017-06-08.pdf",
|
||||
"sha256": "11f0cab15eb6e51010952203ffa24fdc533a24fd642e19525fe4f46b17a1f5e5"
|
||||
},
|
||||
{
|
||||
"path": "docs/investmoz/simulations/source-archive/BR-95-I-Suplemento-2025-05-21.pdf",
|
||||
"sha256": "e0e682e2c027c3cd9c1d46a73a3f48bc5f6bdbf05667fd97314d7a3f6a456358"
|
||||
},
|
||||
{
|
||||
"path": "docs/investmoz/simulations/source-archive/BR252-I-Serie-2022-Lei20-PESOE.pdf",
|
||||
"sha256": "1159fd3ce1ba5581671796d083534da48bd3c5578298819e896f1c2a8026318d"
|
||||
},
|
||||
{
|
||||
"path": "docs/investmoz/simulations/source-archive/Decreto-22-2021-BR70.pdf",
|
||||
"sha256": "a8fac38dcfe08f9c7a8e46254cff10e1ec82fc4f7a8d88ad6e4fe7d91ffc21a5"
|
||||
},
|
||||
{
|
||||
"path": "docs/investmoz/simulations/source-archive/Decreto-27-2026-BR123.pdf",
|
||||
"sha256": "26249c3fa58f149c81de36e99d1aac2819a4771379d9e8833159660dabf539bb"
|
||||
},
|
||||
{
|
||||
"path": "docs/investmoz/simulations/source-archive/Decreto-29-1998-BR22-5-suplemento.pdf",
|
||||
"sha256": "6242cbe675faadf085f43f95ec8c7e8868aafe67c3b2ab2bb010992288f954f8"
|
||||
},
|
||||
{
|
||||
"path": "docs/investmoz/simulations/source-archive/Decreto-3-2011-BR-11-I-mineiros.pdf",
|
||||
"sha256": "5bca1133666ba9c69870b89823420175b7b5b8d0eccb667ecdf5c29716f3b6be"
|
||||
},
|
||||
{
|
||||
"path": "docs/investmoz/simulations/source-archive/Decreto-37-2023-BR124.pdf",
|
||||
"sha256": "7e680b191481faa0e2e9057fa5053587eb2a29d1d8c819e0eb9f02814255a04c"
|
||||
},
|
||||
{
|
||||
"path": "docs/investmoz/simulations/source-archive/Decreto-9-2008-BR16.pdf",
|
||||
"sha256": "96fccdecbfdc11c9eafb90fbc6c7bc259f88bbd4c7507d1ac3af1fa03f960697"
|
||||
},
|
||||
{
|
||||
"path": "docs/investmoz/simulations/source-archive/Lei-13-2016-CIVA-BR-156-10-suplemento.pdf",
|
||||
"sha256": "b90dc7c14ecea2e378c4a62e8e5a47d54a5644c457baeb8b8053b92fd2a5710b"
|
||||
},
|
||||
{
|
||||
"path": "docs/investmoz/simulations/source-archive/Lei-14-1992-BR42.pdf",
|
||||
"sha256": "089fff7423e99a70a230a73ca379e4ea3a91d124703f91016e57ff82794b9e56"
|
||||
},
|
||||
{
|
||||
"path": "docs/investmoz/simulations/source-archive/Lei-17-2022-BR251-exemplar-MCNet5.pdf",
|
||||
"sha256": "14eb34f0af91a8f5cb1770c7b52d6ff839c6818302d1ed96d79afbfb9fe999d4"
|
||||
},
|
||||
{
|
||||
"path": "docs/investmoz/simulations/source-archive/Lei-17-2022-IPP-BR250-Parlamento.pdf",
|
||||
"sha256": "978cd22403ffd336a631704ecd7a5bbd814a427c815b9acdaca5515591dd9e2a"
|
||||
},
|
||||
{
|
||||
"path": "docs/investmoz/simulations/source-archive/Lei-19-2013-BR76.pdf",
|
||||
"sha256": "bba96d645cc935ffc3d1a79b9f0f73ac1c693d6459c2ad41f56cd2841accae76"
|
||||
},
|
||||
{
|
||||
"path": "docs/investmoz/simulations/source-archive/Lei-19-2017-IRPS-BR-OCAM.pdf",
|
||||
"sha256": "c3a0186cb9ad090d7a9533006295dc9bb6a62ad0d6150173cbd49dba4ee4b52d"
|
||||
},
|
||||
{
|
||||
"path": "docs/investmoz/simulations/source-archive/Lei-2-2011-BR-1-4-suplemento-MCNet.pdf",
|
||||
"sha256": "a7543a68cc3287a46a83c39a04821b40f649dd06c4e9b5dc51ff0b47fba08899"
|
||||
},
|
||||
{
|
||||
"path": "docs/investmoz/simulations/source-archive/Lei-20-2009-BR36-AT-exemplar19.pdf",
|
||||
"sha256": "fc1c8144db42960cbf73e717e095896f511ece0f907f58f78819b27d6917a7ee"
|
||||
},
|
||||
{
|
||||
"path": "docs/investmoz/simulations/source-archive/Lei-22-2022-IVA-republicada-BR-250-2-suplemento.pdf",
|
||||
"sha256": "ea1b0dc6f5cf6e595f6d9a19e7042f398999e0087caef1785f6e8fac571b5f53"
|
||||
},
|
||||
{
|
||||
"path": "docs/investmoz/simulations/source-archive/Lei-34-2007-BR52.pdf",
|
||||
"sha256": "cdbef875ad7ccc1517b12faf5de0e893a70ed2ed81e33913cd6dc4a674474e6e"
|
||||
},
|
||||
{
|
||||
"path": "docs/investmoz/simulations/source-archive/Lei-4-1994-BR37-3-suplemento.pdf",
|
||||
"sha256": "210df9db8980d0a5690cd187a6eeec7e37a900d743e39062c16f4e0d12215c7e"
|
||||
},
|
||||
{
|
||||
"path": "docs/investmoz/simulations/source-archive/Lei-4-2012-BR3.pdf",
|
||||
"sha256": "b8845cef41dcbd9ba50c43dbfa743a56030dd3d348242a107e1ae30279eec121"
|
||||
},
|
||||
{
|
||||
"path": "docs/investmoz/simulations/source-archive/Lei-7-1991-Partidos-IFES.pdf",
|
||||
"sha256": "6b783a0bb3330688ef10660a58060d9724aa2da6a873d234fe902f0eaaeda42d"
|
||||
},
|
||||
{
|
||||
"path": "docs/investmoz/simulations/source-archive/PT-MZ-CDT-1992.pdf",
|
||||
"sha256": "d504e1024b28aee5c6acf4592acb96ec9c915a8414f67a61ab6984d6acf9df9d"
|
||||
},
|
||||
{
|
||||
"path": "docs/investmoz/simulations/source-archive/PT-MZ-Protocolo-2009.pdf",
|
||||
"sha256": "f353773dcf41f09f4daa6cf5ba5a5f7994f3e02615867c2dcffc61c3d901bf9b"
|
||||
}
|
||||
],
|
||||
"source_evidence": [
|
||||
{
|
||||
"path": "docs/investmoz/simulations/source-archive/Lei-34-2007-BR52.pdf",
|
||||
"new_clauses": "18(2)/19 PDF6–7;28/29 PDF8–9;36e/f/h/i/4 PDF10–11",
|
||||
"use": "ordinaryconditions/rates/caps/timing,visual excerpts retained inQA"
|
||||
},
|
||||
{
|
||||
"path": "docs/investmoz/simulations/source-archive/Lei-20-2009-BR36-AT-exemplar19.pdf",
|
||||
"new_clauses": "PDF2 retains18(1–6),adds7–10;PDF3 updates28 wording;PDF4 adds36m/n and36-A",
|
||||
"use": "provisions/estimatedworkloss/ordinary advertising1percent;campaign36-A explicitlypending"
|
||||
},
|
||||
{
|
||||
"path": "docs/investmoz/simulations/source-archive/Lei-4-2012-BR3.pdf",
|
||||
"new_clauses": "PDF2 retains36a–n;adds36o",
|
||||
"use": "scope cross-check of supplied amended source"
|
||||
},
|
||||
{
|
||||
"path": "docs/investmoz/simulations/source-archive/Lei-19-2013-BR76.pdf",
|
||||
"new_clauses": "PDF2 retains36a–o;adds p/q",
|
||||
"use": "scope cross-check;no new related-party interest rule introduced"
|
||||
},
|
||||
{
|
||||
"path": "docs/investmoz/simulations/source-archive/BR-248-I-Suplemento-1-2025.pdf",
|
||||
"use": "Existing supplied scope ofLei12/2025,source acceptance and rateproof reused;no unrelated revalidation or L5 research"
|
||||
}
|
||||
],
|
||||
"case_specific_remaining": [
|
||||
{
|
||||
"case": "Overlapping36h/i exclusion and36(4)50percent",
|
||||
"example": "Fuel100,supported80,ordinary passenger vehicle",
|
||||
"condition": "Correspond explicit combination rule for the cited clauses or obtain a targeted AT clarification;no additive/proportional tax rule presumed"
|
||||
},
|
||||
{
|
||||
"case": "Rental36h quota",
|
||||
"condition": "Supply documented nonaccepted quota for supported matching;automatic vehicle-quota determination remains in asset scope"
|
||||
},
|
||||
{
|
||||
"case": "Multi-year launch campaign36-A and1percent limit",
|
||||
"condition": "Implement3exercise timing and its interaction with36n;do not treat as ordinary advertising"
|
||||
},
|
||||
{
|
||||
"case": "Regulated provisions28d,financial unwind28(3),other sector provisions",
|
||||
"condition": "Map applicable supervision/sector rule and documented balances;do not reuse ordinary29 caps"
|
||||
},
|
||||
{
|
||||
"case": "Positive deferred receipts19(5),authorised method change19(6)",
|
||||
"condition": "Map remainingcost/deferredreceipt rollforward and applicable authorisation;only knownzero deferral/unchanged method demonstrated"
|
||||
},
|
||||
{
|
||||
"case": "Specific timing18(6–10),other asset/method/shortperiod/annualFX/fiscalrepresentation/form mappings",
|
||||
"condition": "Remaining concrete L6 cases preserved in existing matrix,not closed by this ordinary increment"
|
||||
}
|
||||
],
|
||||
"m20_m22_divergence": "Unchanged and limited to result transfer201/Q8 versus200/Q10;no dependency gate in cost calculation;no new official field correspondence presumed",
|
||||
"L5_ten_questions": "unchanged,not researched;only actual dependent cases blocked",
|
||||
"L6_complete": false,
|
||||
"L7_L8_own_scopes": true,
|
||||
"installation_performed": false,
|
||||
"proof_sha256": {
|
||||
"additional.json": "b76790cb34736281fd181cbf0c871135cb42e75e1acc34ab3c42ed7e696ad5b1",
|
||||
"api.txt": "dff9be0d1338e833adf5c7f10dc4c4e5d5cefde97f284db927608f3c3c334be2",
|
||||
"build-exit.txt": "13bf7b3039c63bf5a50491fa3cfd8eb4e699d1ba1436315aef9cbe5711530354",
|
||||
"build.txt": "21ce0d3af4edc5c74d5cbeebd817b4716b233b0eb54ad03b1f22ebe80edd844a",
|
||||
"calculated.json": "23119339a0337c755f2a045078903858b7da6b95196a91de51170ca115d468a7",
|
||||
"closing.json": "9f87eec9710aeec4819a7b10b64986ad9e4afbb458cd95a49a8f6f035c235ad8",
|
||||
"Decreto-9-2008-BR16.txt": "c680dfb73eeb638cf85d06fb0fe19550487de2994ce76f60077ef4baa88e3238",
|
||||
"dossier-additional.json": "19ad04483c38306982cd182e08445952ec1e5ad5ce821cc345df5ccf75ac4a7a",
|
||||
"dossier-calculated.json": "99c4bff798b7338138216a405aa5de228e88429af3c12290bf1b7071bd40e01a",
|
||||
"dossier-closing.json": "d054eb3da44363d3861037c7bb59074572f630078f6d3aa3d2e4de89983928b0",
|
||||
"dossier-limits.json": "52aaea2415159cacdbc44d56a98918a8438e3bacde06199e0545a265b7d6982d",
|
||||
"dossier-missing.json": "50eff5af4b909ef08f4619ac768d5e8f8d4de01d003c81ef3748bd74a9bccfe2",
|
||||
"dossier.mjs": "0824a3b05fa4bf758bbb165c0fce74fe9cd6ab817ceda5437ef7c73b28794a46",
|
||||
"download.html": "ebf40a955530b10753da0cfd7769c0ad879b079bdade64f4e06ec901912456df",
|
||||
"frontend.txt": "c6aa5222a8b5c867e50a2aca4ab5edd6ab9812f9fb2f6bce8482092951e022bd",
|
||||
"index.html": "09941968500d2910121bdcb0029e326fc6d44bb025b01766654289a28caa62fa",
|
||||
"Lei-19-2013-BR76-page2.png": "952cb5d68fa162043964c282b1ac32faf8bcbb92f2bb83fef2084ae691afae12",
|
||||
"Lei-19-2013-BR76.txt": "7060584c3e59f0b2f12f34aa0b40faadf4aa5790a0459f3319852bcd63262cbb",
|
||||
"Lei-20-2009-BR36-AT-exemplar19-page2.png": "e39eadef731b6c3ae8cb4820238c4db3184111557d982a20dadee139ea2952a2",
|
||||
"Lei-20-2009-BR36-AT-exemplar19-page3.png": "a4a39a81849f32e4c596ef7c0726d2054e74bf45531dd8e920d3927cafdb70bd",
|
||||
"Lei-20-2009-BR36-AT-exemplar19-page4.png": "cafa0e22b0f29296bd28772609fa87708811704c25ae7bf676dba53783e25168",
|
||||
"Lei-20-2009-BR36-AT-exemplar19.txt": "2e04eb1c5be52619e21eb1c376f71603d998672f4c220e0bfc1648df80d5e6fe",
|
||||
"Lei-34-2007-BR52-page10.png": "74937b18d2863c1199e88380e25d39c7ada80c37621196e5402bffa61f6b49be",
|
||||
"Lei-34-2007-BR52-page6.png": "de2c1cc9c8bf5b0cefa9671cc8ad776e21a8492b791648be492d1540d78f748f",
|
||||
"Lei-34-2007-BR52-page8.png": "f69bbe8484e315a5bf5818277c1bb3d67e4e86724544b34565731d5c3f4408df",
|
||||
"Lei-34-2007-BR52-page9.png": "e449f13d39c9f23e408c185e00bafd621706d9cccd037d399e602eece33b6ba5",
|
||||
"Lei-4-2012-BR3-page2.png": "99c5a820ad1e1201af25f60b51c86b3c7bced25acc12343366eadb3133185128",
|
||||
"Lei-4-2012-BR3.txt": "9478ac3ef61ae9df286d951edbfce4c24049f65e860f053ed9eb2c3fbd78d051",
|
||||
"limits.json": "c73905029246c1bbdf91b24869582b6057d62d01dd853e76d2e603786d8810ea",
|
||||
"missing.json": "159c4d3f21d87ff3ea43ad5cfa8a42faea05105feab0a4b5529d37e72d9aea73",
|
||||
"originals-inventory.json": "f91baf686619187dac23bce2c3bde71cc7ddf201fef10a24479548c44abc7d94",
|
||||
"preparation.html": "e9951a85471c44fd9ff5211e59a8eb740e5b8823a4bb6b63c12237feb3d997fd",
|
||||
"qa.tsx": "58e66698e44d68d169117a70d0d15aa07ce77b88bed1234d922d0844a9aac9b0",
|
||||
"recovered-additional.html": "3f562a91fcc2ba6f47c1661f13a39a6c7d939c4730bd4ba9178b60fb05d5b6a6",
|
||||
"recovered-additional.json": "a7adf543887479edf259d2501fe061a14c8aa03e206848726fae0f567b594419",
|
||||
"recovered-calculated.html": "e9951a85471c44fd9ff5211e59a8eb740e5b8823a4bb6b63c12237feb3d997fd",
|
||||
"recovered-calculated.json": "e000b426aefbfad520fe2a03707ca756a87fe04ab39536d509008e03c6288c2b",
|
||||
"recovered-closing.html": "d17bd5944b2f37d752883b22955bcd3c57695f72b464580087403dfa448d785e",
|
||||
"recovered-closing.json": "5f1b2cbe830154e35e60512882aa023fdc443125cf37253e10b85af7f47dddb2",
|
||||
"recovered-limits.html": "4b24bebe5b4d93bc6b06233d795197bb28ca4116eb29832981bafb33804d1a2d",
|
||||
"recovered-limits.json": "28611229e027a0d51593ecf71bae32b6f35f7f2767c26680fa2dceb0e7d9ad11",
|
||||
"recovered-missing.html": "6fa22cb9757a5812d23a1615a43337f20ba1812097d8b4d79c1a3db1a356d8dd",
|
||||
"recovered-missing.json": "b5aa2c3045d79193518621b4764540775470db97bdbc84ac19804c70baa05324",
|
||||
"types.txt": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"ui-calculated.png": "3c1070fbb0b9ca66d1b128935ab23e737a11ee2918fc566259a5d70bbae4aa26",
|
||||
"ui-missing.png": "d0c1c138e9b2404ae8a17c0732d8ab1b7bfbcf89ff431aa4d80e3dfaf7f8d5db",
|
||||
"ui-proof.json": "108af6bd1557c6a920842102b1a9b54711fc3ab96e32296e1c2c3f77abda177a",
|
||||
"ui.cjs": "2de9dd0e333edca1a3fd23a6e409cd41271f619cfb2186bbfdf6444855dd35d0",
|
||||
"ui.txt": "8799b836baa3cf8efefce8a5b0b6951c7963e50fb3e08b0be3d2de3434ff4795",
|
||||
"vite.config.ts": "2ad3c19a485d8175b33550b017f2a67934f9fbebda5f6f16e69b9fb89809c398"
|
||||
}
|
||||
}
|
||||
@@ -6101,3 +6101,26 @@ Actualização de **AUD-SIM-INT02 na mesma matriz**, sem novo lote ou backlog. O
|
||||
**L6 continua parcial por estes casos concretos.** Documento ou parâmetro em falta dentro de regra implementada é uma pendência do caso, não outra tarefa genérica deL6. Juros capitalizáveis, relações especiais e tratamentos especiais conservam o enquadramento próprio, sem presumir que satisfazem o recorte de juros ordinários.
|
||||
|
||||
**L7** mantém benefícios, isenções, taxas reduzidas, tributações autónomas e operações especiais. **L8** mantém retenções, pagamentos, créditos e saldo final a pagar/recuperar. A ausência desses tratamentos não reabre o percurso normal entregue nem justifica, por si, «L6 parcial». A divergência documental não autoriza preencher M22 campo302 ou309–316 com zeros presumidos. As dez dúvidas deL5 permanecem válidas e bloqueiam apenas os casos efectivamente dependentes; sem nova pesquisa ou repetição das provas aceites.
|
||||
|
||||
## 26/10/2026 — L6: custos com limites, provisões e periodização em QA
|
||||
|
||||
Actualização de **AUD-SIM-INT02 na mesma matriz**, sobre o conjunto instalado `a2de7259`. **Novo incremento em QA, sem instalação.** O percurso e as provas já aceites deL6/L3/ISPC/câmbio/L5 foram reutilizados; não foram novamente executados os testes funcionais anteriores. A fotografia5 continua actual sem activar os novos campos; a fotografia6 guarda estes mapas e conserva o resultado anterior no histórico.
|
||||
|
||||
**Reutilização e resultado demonstrado:** resultado financeiro6240MZN, acréscimos anteriores220 e mapas anteriores520 conservados; novos ajustamentos de custos/provisões/periodização+350 e obra−600 → **lucro tributável6730MZN**. Dedução expressa das perdas anteriores2300 → **matéria colectável4430MZN**; taxa normal32% → **colecta1417,60MZN**. Não é saldo final a pagar. Nenhum saldo oficial de provisões/perdas é consumido, nem é alterada declaração ou história anterior.
|
||||
|
||||
| Categoria e condição no exercício anual com termo em2026 | Parcela aceite, limite e ajustamento demonstrados | Informação insuficiente ou caso separado |
|
||||
|---|---|---|
|
||||
| Ajudas de custo/deslocação em viatura própria36(1)e: parcelas facturadas e tributadas emIRPS identificadas sem sobreposição. Representação36(1)f/36(3) ordinária documentada. | Ajudas100:facturadas20,tributadas e não facturadas20;remanescente60 →30 acrescido/70 aceite. Representação50 →40 acrescido/10 aceite. Zero conhecido nos parâmetros é distinto de campo vazio. | Parcela facturada/tributada em falta impede a aceitação dependente; não presumir ausência deIRPS. Eventual tributação autónoma continuaL7. |
|
||||
| Publicidade ordinária36(1)n,Lei20/2009: receita declarada reconciliada com vendas normais e completude anual das parcelas. | Um limite anual partilhado de1% de12000=120. Caso com duas parcelas100+100:aceites60+60,acréscimo80. A repartição proporcional é apenas aritmética de simulação, não uma ordem fiscal obrigatória. | Campanha de lançamento com horizonte superior a um ano não recebe este tratamento por aproximação: periodização36-A e articulação do limite permanecem por implementar. |
|
||||
| Encargos com viaturas36(4), combustível36i e aluguer sem condutor36h, distinguindo categoria/serviço público/locação na actividade normal. | Outros encargos100 de passageiros sem excepção:aceite50/acréscimo50. Combustível50 não sujeito a36(4),com40 comprovados no activo/locação e consumo normal:aceite40/acréscimo10. Renda100 no caso documentado de excepção36(4),quota não aceite36h de20:aceite80/acréscimo20; a quota é reutilizada do suporte e não apurada automaticamente. | Se há simultaneamente parcela excluída porh/i e aplicação dos50% de36(4), a combinação fica pendente. Exemplo:fuel100,comprovado80,passageiro sem excepção; não somar nem repartir correcções por presunção. Resolver a regra de articulação dos trechos aplicáveis ou esclarecer esse exemplo especificamente comAT. Apuramento automático da quota36h permanece no saldo de amortizações; só bloqueia esse caso. |
|
||||
| Provisão/perda por imparidade de créditos28a/29: regime ordinário identificado, créditos normais finais e provisão fiscal anterior conhecidos. | Base10000:limite anual1,5%=150,acumulado6%=600. Saldo anterior550 deixa50;provisão contabilística200 →50 aceite/150 acrescido. Duas parcelas100+100 partilham25+25; não repetem os limites nem consomem o saldo oficial. | Saldo anterior vazio não é zero; anterior superior ao tecto pede reconciliação/reversão28(2). Provisões sujeitas a supervisão financeira/seguradora28d ou reforços financeiros28(3) exigem correspondência própria, sem aplicar29 por aproximação. |
|
||||
| Existências28b:perda efectivamente observada. Processo judicial28c:em curso e obrigação subjacente fiscalmente dedutível. | Existências:parcela60,perda total40,provisão anterior10 →30 aceite/30 acrescido. Judicial:parcela90,obrigação100,anterior20 →80 aceite/10 acrescido. Perda não observada ou processo que não esteja em curso são condições negativas conhecidas:acréscimo integral, sem zero presumido como dedução. | Falta de prova do evento,obrigação,perda ou saldo anterior fica indeterminada. Objecto/saldo repetido entre documentos impede reutilizar a cobertura; agregar/reconciliar as parcelas do mesmo objecto. |
|
||||
| Reversões28(2) e provisões anteriormente tributadas:mesmo objecto e tratamento fiscal anterior comprovados. | Provisão antes deduzida:reversão50 já incluída no resultado não acrescenta novamente;omitida dos proveitos acrescenta50. Reversão contabilística50 de provisão antes tributada comprovada é excluída por50. | Não inferir o tratamento anterior do saldo contabilístico. Quantia superior à parcela fiscal demonstrada,evento que continua a subsistir ou objecto duplicado ficam pendentes. Sem alteração retrospectiva ou consumo do saldo oficial. |
|
||||
| Componentes de exercícios anteriores18(2):conhecimento/imprevisibilidade no encerramento anterior documentados. | Custo80 conhecido no fecho2025,incluído no resultado actual →acréscimo80. Se imprevisível/manifestamente desconhecido à data daquele fecho →sem acréscimo por periodização. Proveito50 conhecido no fecho anterior →excluído do actual por50;se imprevisível/desconhecido →considerado no actual. | Conhecimento no fecho em falta impede apenas a imputação da parcela. Nenhuma rectificação de declaração anterior é executada. Periodizações específicas18(6–10) não passam a estar cobertas por este recorte18(2). |
|
||||
| Obras plurianuais19 e perdas meramente estimadas36m:critério uniforme,preço,custo incorporado/custo total e acumulados fiscais anteriores documentados. | Percentagem:preço2000,custo total1000,incorrido500;proveito fiscal acumulado1000 menos anterior600=400 corrente;custo500 menos anterior300=200 corrente. Proveito contabilizado1000/custo200 →ajustamento−600. Encerramento tem caso separado;facturação parcial com acabamento correspondente/entregas fraccionadas impõem percentagem;95% com preço conhecido ou recepção provisória pública determina conclusão. Perda meramente estimada50 de obra em curso →acréscimo50. | Acumulados incoerentes ou método incompatível bloqueiam essa obra. Receita antecipada19(5) positiva e mudança de critério com autorização19(6) ficam por corresponder; o caso demonstrado declara expressamente zero de receita antecipada e método mantido, sem presumir essas condições. |
|
||||
|
||||
**Percurso demonstrado:** cálculo → gravação → recuperação pela API privada → relatório/dossier/exportação em cinco casos:conjunto calculado,informação insuficiente,categorias adicionais,limites partilhados e encerramento de obra. Só os novos casos foram testados: **22 testes frontend,1 testeAPI/83asserções**, transacção revertida na base QA127.0.0.1:55441; routes/actions gerados pelo procedimento existente,TypeScript/build e interface real passaram. Interface permite reutilizar a parcela livre da origem e o saldo financeiro de créditos quando conhecido; alterações às somas dos temas são revistas antes de aplicar e não apagam escolhas ou históricos. Fonte ausente ou saldo não acompanhado nunca cria zero. Valores racionais são conservados, apresentações decimais aproximadas são identificadas e uma perda inferior a um cêntimo não é truncada para zero.
|
||||
|
||||
**Preservação:**6ficheiros de runtime no delta,42protegidos e31originais conferidos; sem schema/API/configuração/permissões/dependências alterados, sem escrita no activo. Recibo:`docs/investmoz/simulations/irpc-l6-costs-provisions-timing-qa-20261026.json`;bundle/pacote em `outputs/irpc-l6-costs`. DivergênciaM20/M22 permanece limitada ao transporte201/quadro8 versus200/quadro10; não é uma condição destes cálculos. Não foram criadas correspondências oficiais de campos para estes novos ajustamentos por presunção.
|
||||
|
||||
**Saldo deL6 após estes casos QA:**combinação36h/i+36(4);quota36h automática e restantes amortizações;campanhas36-A e sua articulação com1%;provisões reguladas/reforços financeiros e periodizações específicas não cobertas;receita antecipada positiva/mudança autorizada do método de obra;períodos curtos/conversão fiscal anual;representação fiscal de fracções e restantes correspondênciasM20/M22. Cada pendência afecta o caso/campo dependente, não o conjunto independente demonstrado. Benefícios,isenções,taxas reduzidas e operações especiais mantêm âmbitoL7;retenções,pagamentos,créditos e saldo anual mantêm âmbitoL8. **L6 continua parcial.** As dez dúvidas deL5 não foram reabertas.
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,43 @@
|
||||
import {useState} from 'react';
|
||||
import {costRules,newNormalCosts,newCostRow,reusableCreditBalance,type CostRow,type NormalCostsInput} from '@/lib/irpc-normal-costs';
|
||||
import {normalFinancialSource,type NormalBook} from '@/lib/irpc-normal';
|
||||
import {fiscalMoney} from '@/lib/irpc-normal-schedules';
|
||||
import {addFractions,subtractFractions,fractionPresentation} from '@/lib/fiscal-rational';
|
||||
import {type ProfitTopic} from '@/lib/irpc-normal-profit';
|
||||
type Field=[string,string,'money'|'text'|'choice'|'method'|'source'];
|
||||
const fields:Record<string,Field[]>={
|
||||
allowance:[['billed','Parcela facturada a clientes','money'],['taxedNotBilled','Parcela tributada emIRPS e não facturada a clientes','money']],
|
||||
advertising:[['campaign','Campanha de lançamento com horizonte superior a um ano','choice']],
|
||||
vehicle:[['passenger','Viatura ligeira de passageiros','choice'],['publicOrRental','Afecta a serviço público de transportes ou aluguer na actividade normal','choice']],
|
||||
fuel:[['supportedNormal','Parcela com activo/locação e consumo normal comprovados','money'],['passenger','Viatura ligeira de passageiros','choice'],['publicOrRental','Afecta a serviço público de transportes ou aluguer na actividade normal','choice']],
|
||||
rental:[['nonAcceptedQuota','Parcela documentada da quota não aceite (36h)','money'],['quotaReference','Documento e regra da quota não aceite','text'],['passenger','Aplicação36(4) — ligeira de passageiros','choice'],['publicOrRental','Afecta a serviço público de transportes ou aluguer na actividade normal','choice']],
|
||||
stock:[['balanceKey','Identificação única do inventário/objecto','text'],['lossObserved','Perda de valor efectivamente observada','choice'],['observedLoss','Perda total observada documentada','money'],['previous','Provisão fiscal anterior que subsiste','money']],
|
||||
litigation:[['balanceKey','Identificação única do processo/objecto','text'],['caseOngoing','Processo judicial em curso','choice'],['chargeDeductible','Obrigação daria lugar a custo fiscal dedutível','choice'],['obligation','Obrigação coberta documentada','money'],['previous','Provisão fiscal anterior que subsiste','money']],
|
||||
provision_reversal:[['balanceKey','Identificação única da provisão/objecto','text'],['previous','Parcela antes fiscalmente deduzida','money'],['eventEnded','Evento não subsiste ou utilização para fim diferente','choice'],['inResult','Reversão já incluída em proveitos do resultado actual','choice']],
|
||||
taxed_reversal:[['balanceKey','Identificação única da provisão/objecto','text'],['previous','Parcela antes tributada e não deduzida','money'],['eventEnded','Evento não subsiste ou utilização para fim diferente','choice'],['inResult','Reversão já incluída em proveitos do resultado actual','choice']],
|
||||
prior_cost:[['originalYear','Exercício de origem','text'],['unknownAtClose','Imprevisível ou manifestamente desconhecido no encerramento anterior','choice'],['closingEvidence','Suporte do conhecimento no encerramento anterior','text']],
|
||||
prior_income:[['originalYear','Exercício de origem','text'],['unknownAtClose','Imprevisível ou manifestamente desconhecido no encerramento anterior','choice'],['closingEvidence','Suporte do conhecimento no encerramento anterior','text']],
|
||||
work:[['projectKey','Identificação única da obra','text'],['method','Critério fiscal da obra','method'],['price','Preço contratual estabelecido','money'],['totalCost','Custo total estimado para concluir','money'],['incurred','Custos efectivamente incorporados acumulados','money'],['priorRevenue','Proveitos fiscais reconhecidos em exercícios anteriores','money'],['priorCost','Custos fiscais reconhecidos em exercícios anteriores','money'],['costSource','Linha de custos da obra no resultado actual','source'],['bookCost','Parcela de custos da obra já contabilizada no exercício','money'],['partialBilling','Facturação parcial com grau de acabamento correspondente','choice'],['ownFractionalDelivery','Obra por conta própria vendida e entregue fraccionadamente','choice'],['consistentMethod','Critério uniforme para obras idênticas e mantido até ao final','choice'],['publicReception','Recepção provisória de obra pública','choice'],['deferredReceipt','Receita antecipada19(5) proposta — zero conhecido se não aplicada','money']],
|
||||
};
|
||||
export function SimulationIrpcCosts({draft,book,save}:{draft:Record<string,unknown>;book:NormalBook;save:(b:NormalBook)=>void}){
|
||||
const[review,setReview]=useState(false),[error,setError]=useState(''),i=book.costs,source=normalFinancialSource(draft);
|
||||
if(!i)return<button type="button" onClick={()=>save({...book,costs:newNormalCosts()})}>Iniciar custos com limites, provisões e periodização</button>;
|
||||
const patch=(p:Partial<NormalCostsInput>)=>{setReview(false);save({...book,costs:{...i,...p}});};
|
||||
const row=(id:string,p:Partial<CostRow>)=>patch({rows:i.rows.map(r=>r.id===id?{...r,...p}:r)});
|
||||
const fact=(r:CostRow,k:string,v:string)=>row(r.id,{facts:{...r.facts,[k]:v}});
|
||||
const summaries=()=>{const groups=new Map<ProfitTopic,string[]>();for(const r of book.schedules?.rows??[]){if(!r.rule)continue;const k=({tax_own:'expenseCorrections',tax_other:'expenseCorrections',undocumented:'expenseCorrections',capital_in:'positivePatrimonial',patrimonial_gain:'positivePatrimonial',capital_out:'negativePatrimonial',patrimonial_cost:'negativePatrimonial',future_cost:'timing',future_income:'timing',current_cost:'timing',current_income:'timing'}as Record<string,ProfitTopic>)[r.rule];groups.set(k,[...(groups.get(k)??[]),r.amount]);}for(const r of i.rows)if(r.rule){const k=costRules[r.rule][1];groups.set(k,[...(groups.get(k)??[]),r.amount]);}return[...groups].map(([key,values])=>({key,amount:fractionPresentation(addFractions(values.map(fiscalMoney))).decimal}));};
|
||||
const reuse=(r:CostRow)=>{try{const code=r.sourceCode,l=source?.accountingLines.find(l=>l.code===code);if(!l)throw Error('Seleccione uma origem.');const claims=[...book.rows,...(book.schedules?.rows??[]),...i.rows.filter(q=>q.id!==r.id),...i.rows.filter(q=>q.rule==='work').map(q=>({sourceCode:q.facts.costSource,amount:q.facts.bookCost}))].filter(q=>q.sourceCode===code);const available=subtractFractions(fiscalMoney(l.total),addFractions(claims.map(q=>fiscalMoney(q.amount))));if(BigInt(available.numerator)<=0n)throw Error('Não há parcela livre conhecida nesta origem.');row(r.id,{amount:fractionPresentation(available).decimal});setError('');}catch(e){setError(String(e));}};
|
||||
return<fieldset aria-label="Custos com limites provisões periodização" className="space-y-3 rounded border p-3"><legend>Custos com limites, provisões e periodização — exercício2026</legend>
|
||||
<p>Reutiliza o resultado e as linhas já declaradas. Identifique só a parcela de cada categoria e os parâmetros em falta. Os limites anuais de publicidade e créditos são partilhados por todas as parcelas; os saldos de provisões não são consumidos. A divergência M20/M22 não bloqueia estes cálculos. Benefícios/casos especiaisL7 e saldo anualL8 mantêm os seus âmbitos.</p>{error&&<p role="alert">{error}</p>}
|
||||
<button type="button" onClick={()=>patch({rows:[...i.rows,newCostRow(crypto.randomUUID())]})}>Acrescentar custo, provisão ou periodização</button>
|
||||
{i.rows.some(r=>r.rule==='advertising')&&<fieldset><legend>Publicidade — limite único anual</legend><p>Volume de vendas da origem: {source?.accountingLines.find(l=>l.code==='revenue')?.total??'Em falta'} MZN. O limite não se repete por factura.</p><label>Correspondência às vendas normais do exercício<select aria-label="Vendas normais para publicidade" value={i.salesOrdinary} onChange={e=>patch({salesOrdinary:e.target.value as ''|'yes'})}><option value="">Informação em falta</option><option value="yes">Toda a receita da origem corresponde a vendas/serviços normais</option></select></label><label className="block">Suporte do volume de vendas e completude da publicidade<input value={i.advertisingReference} onChange={e=>patch({advertisingReference:e.target.value})}/></label><p>Campanhas de lançamento com efeito superior a um ano exigem36-A e articulação própria; permanecem discriminadas, sem dedução ordinária presumida.</p></fieldset>}
|
||||
{i.rows.some(r=>r.rule==='credit')&&<fieldset><legend>Créditos — limites anual e acumulado</legend><label>Regime da provisão de créditos<select aria-label="Regime da provisão de créditos" value={i.creditRegime} onChange={e=>patch({creditRegime:e.target.value as NormalCostsInput['creditRegime']})}><option value="">Por identificar</option><option value="ordinary">Ordinário — fora de supervisão financeira/seguradora</option><option value="regulated">Provisão sujeita a supervisão — correspondência própria</option></select></label><label className="block">Créditos da actividade normal existentes no fim do exercício<input value={i.creditBase} onChange={e=>patch({creditBase:e.target.value})}/></label>{reusableCreditBalance(source)&&<button type="button" onClick={()=>patch({creditBase:fractionPresentation(reusableCreditBalance(source)!).decimal})}>Reutilizar saldo financeiro de créditos: {fractionPresentation(reusableCreditBalance(source)!).decimal} MZN — conferir âmbito</button>}<label className="block">Provisão fiscal anterior que subsiste no termo do exercício<input value={i.creditPrevious} onChange={e=>patch({creditPrevious:e.target.value})}/></label><label className="block">Suporte do saldo de créditos e provisão anterior<input value={i.creditReference} onChange={e=>patch({creditReference:e.target.value})}/></label><p>1,5% anual e6% acumulado; saldo anterior em falta não significa zero. Não aplica regras bancárias/seguradoras nem percentagens de antiguidade presumidas.</p></fieldset>}
|
||||
{i.rows.map((r,n)=>{const income=['provision_reversal','taxed_reversal','prior_income','work'].includes(r.rule),omitted=r.rule==='provision_reversal'&&r.facts.inResult==='no';return<fieldset key={r.id} className="rounded border p-3"><legend>Parcela {n+1} — {r.document||'Por identificar'}</legend><label>Categoria<select aria-label={'Categoria custo '+(n+1)} value={r.rule} onChange={e=>row(r.id,{rule:e.target.value as CostRow['rule']})}><option value="">Por identificar</option>{Object.entries(costRules).map(([k,v])=><option key={k} value={k}>{v[0]} — {v[2]}</option>)}</select></label>
|
||||
{(['document','line','amount','reference']as const).map(k=><label key={k} className="block">{({document:'Documento ou mapa discriminado',line:'Linha distinta',amount:r.rule==='work'?'Proveitos da obra já contabilizados no exercício — zero se conhecido':'Parcela contabilística MZN',reference:'Suporte da classificação, condições e período'})[k]}<input aria-label={'Custo '+(n+1)+' '+k} value={r[k]} onChange={e=>row(r.id,{[k]:e.target.value})}/></label>)}
|
||||
{!omitted&&<><label>Origem contabilística<select aria-label={'Origem custo '+(n+1)} value={r.sourceCode} onChange={e=>row(r.id,{sourceCode:e.target.value})}><option value="">Por corresponder</option>{source?.accountingLines.filter(l=>l.accountingEffect===(income?'income':'expense')).map(l=><option key={l.code} value={l.code}>{l.origin}: {l.total} MZN</option>)}</select></label><button type="button" onClick={()=>reuse(r)}>Reutilizar parcela livre da origem {n+1}</button></>}
|
||||
{(fields[r.rule]??[]).filter(([k])=>!(k==='publicOrRental'&&r.facts.passenger!=='yes')&&!(k==='publicReception'&&r.facts.method!=='closing')&&!(r.rule==='stock'&&r.facts.lossObserved==='no'&&['observedLoss','previous'].includes(k))&&!(r.rule==='litigation'&&r.facts.caseOngoing==='no'&&['obligation','previous','chargeDeductible'].includes(k))).map(([k,label,type])=><label key={k} className="block">{label}{type==='choice'?<select aria-label={'Custo '+(n+1)+' '+k} value={r.facts[k]??''} onChange={e=>fact(r,k,e.target.value)}><option value="">Informação em falta</option><option value="yes">Sim — documentado</option><option value="no">Não — documentado</option></select>:type==='method'?<select aria-label={'Custo '+(n+1)+' '+k} value={r.facts[k]??''} onChange={e=>fact(r,k,e.target.value)}><option value="">Por identificar</option><option value="percentage">Percentagem de acabamento</option><option value="closing">Encerramento da obra</option></select>:type==='source'?<select aria-label={'Custo '+(n+1)+' '+k} value={r.facts[k]??''} onChange={e=>fact(r,k,e.target.value)}><option value="">Por corresponder/zero fora de linha</option>{source?.accountingLines.filter(l=>l.accountingEffect==='expense').map(l=><option key={l.code} value={l.code}>{l.origin}: {l.total} MZN</option>)}</select>:<input aria-label={'Custo '+(n+1)+' '+k} value={r.facts[k]??''} onChange={e=>fact(r,k,e.target.value)}/>}</label>)}
|
||||
<label>Condições ordinárias e inclusão na origem demonstradas<select aria-label={'Condições custo '+(n+1)} value={r.conditions} onChange={e=>row(r.id,{conditions:e.target.value as ''|'yes'})}><option value="">Informação em falta</option><option value="yes">Sim, nos documentos identificados</option></select></label><button type="button" onClick={()=>patch({rows:i.rows.filter(q=>q.id!==r.id)})}>Retirar parcela {n+1} das entradas actuais</button></fieldset>;})}
|
||||
{book.profit&&<><button type="button" onClick={()=>{try{summaries();setReview(true);setError('');}catch(e){setError(String(e));}}}>Conferir correspondência dos temas com os mapas</button>{review&&<div><p>Reveja as somas brutas antes de aplicar; não alteram os factos, o resultado financeiro ou os históricos.</p>{summaries().map(t=><p key={t.key}>{({expenseCorrections:'Correcções aos custos',incomeCorrections:'Correcções aos proveitos',positivePatrimonial:'Variações positivas',negativePatrimonial:'Variações negativas',timing:'Periodização',special:'Casos especiais'})[t.key]}: actual {book.profit!.topics[t.key].amount||book.profit!.topics[t.key].state||'Em falta'} → mapas {t.amount} MZN</p>)}<button type="button" onClick={()=>{const topics=structuredClone(book.profit!.topics);for(const t of summaries())topics[t.key]={state:'known',amount:t.amount,reference:'Correspondência aritmética aos documentos/linhas dos mapas de ajustamentos e custos do exercício.'};save({...book,profit:{...book.profit!,topics}});setReview(false);}}>Aplicar correspondências conferidas dos mapas</button></div>}</>}
|
||||
<p>Ao calcular e guardar, os novos resultados entram numa versão própria; o resultado anterior permanece no histórico. Informação em falta impede apenas os totais que dependem dessa parcela.</p>
|
||||
</fieldset>;
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import {SimulationIrpcCosts} from '@/components/simulation-irpc-costs';
|
||||
import {SimulationIrpcSchedules} from '@/components/simulation-irpc-schedules';
|
||||
import {SimulationIrpcAssessment} from '@/components/simulation-irpc-assessment';
|
||||
import {useState} from 'react';
|
||||
@@ -30,6 +31,7 @@ export function SimulationIrpcNormal({draft,onChange}:{draft:Record<string,unkno
|
||||
{profitTopics.map(([k,label])=>{const t=b.profit!.topics[k];const change=(p:Partial<ProfitEvidence>)=>profit({topics:{...b.profit!.topics,[k]:{...t,...p}}});return<fieldset key={k} className="rounded border p-2"><legend>{label}</legend><label className="block">Estado — {label}<select aria-label={'Estado — '+label} value={t.state} onChange={e=>change({state:e.target.value as ProfitEvidence['state']})}><option value="">Informação em falta</option><option value="known">Valor conhecido</option><option value="absent">Ausência declarada com suporte</option></select></label>{(t.state==='known'||t.amount!=='')&&<label className="block">Montante conhecido — {label}<input value={t.amount} onChange={e=>change({amount:e.target.value})}/></label>}<label className="block">Suporte — {label}<input value={t.reference} onChange={e=>change({reference:e.target.value})}/></label></fieldset>;})}
|
||||
</fieldset>}
|
||||
<SimulationIrpcSchedules book={b} save={save}/>
|
||||
<SimulationIrpcCosts draft={draft} book={b} save={save}/>
|
||||
<SimulationIrpcTaxableBase draft={draft} book={b} save={save}/>
|
||||
<SimulationIrpcAssessment book={b} save={save}/>
|
||||
<button type="button" onClick={()=>{try{save(recordNormal(draft,b));}catch(e){setError(String(e));}}}>Calcular e guardar ajustamentos L6</button>
|
||||
|
||||
@@ -21,7 +21,7 @@ export function calculateNormalBase(input:NormalBaseInput,profit:FiscalFraction|
|
||||
if(!/^\d{9}$/.test(i.subjectNuit)||!i.identityReference.trim())pending.push('Identifique NUIT e suporte do sujeito passivo do lucro e das perdas.');
|
||||
if(i.method!=='direct'||!i.methodReference.trim())pending.push('Método declarativo/directo e suporte por identificar;apuramento indirecto fora deste recorte (15(2),41(2)).');
|
||||
if(!profit)pending.push('Lucro tributável actual indeterminado;nenhuma dedução transforma informação em falta em zero.');
|
||||
else if(units(profit)<0n&&!currentLossReference?.trim())pending.push('Resultado fiscal negativo conhecido;tratamento da perda do próprio exercício fora deste recorte,sem matéria colectável zero ou reporte automático.');
|
||||
else if(BigInt(profit.numerator)<0n&&!currentLossReference?.trim())pending.push('Resultado fiscal negativo conhecido;tratamento da perda do próprio exercício fora deste recorte,sem matéria colectável zero ou reporte automático.');
|
||||
if(!i.lossesState||!i.lossesReference.trim())pending.push('Situação e completude das perdas anteriores em falta;sem ausência presumida.');
|
||||
if(i.lossesState==='absent'&&i.losses.length)pending.push('Ausência declarada de perdas contradiz os registos existentes;reconcilie sem apagar histórico.');
|
||||
if(i.lossesState==='known'&&!i.losses.length)pending.push('Perdas declaradas conhecidas sem discriminação por exercício.');
|
||||
@@ -46,12 +46,12 @@ export function calculateNormalBase(input:NormalBaseInput,profit:FiscalFraction|
|
||||
losses.push(r);
|
||||
}
|
||||
const requestedLosses=addFractions(losses.filter(l=>l.status==='eligible').map(l=>l.requestedAmount!));
|
||||
if(profit&&units(requestedLosses)>(units(profit)>0n?units(profit):0n))pending.push('Utilizações propostas excedem o lucro tributável disponível;reparta explicitamente sem criar base negativa ou escolher uma ordem de perdas.');
|
||||
if(profit&&BigInt(requestedLosses.numerator)*BigInt(profit.denominator)>(BigInt(profit.numerator)>0n?BigInt(profit.numerator)*BigInt(requestedLosses.denominator):0n))pending.push('Utilizações propostas excedem o lucro tributável disponível;reparta explicitamente sem criar base negativa ou escolher uma ordem de perdas.');
|
||||
let benefitDeduction:FiscalFraction|null=null;const benefit=i.benefits;
|
||||
if(!benefit.state||!benefit.reference.trim())pending.push('Benefícios/deduções à matéria colectável por identificar (15(4));créditos à colecta e retenções não são esta dedução.');
|
||||
else if(benefit.state==='absent'){if(benefit.amount.trim())pending.push('Ausência de benefícios contradiz montante preenchido.');else benefitDeduction=zero();}
|
||||
else{try{const value=amount(benefit.amount);if(units(value)!==0n)pending.push('Benefício de montante conhecido:regra,condições,limite e ordem próprios por implementar;sem subtracção livre do valor.');else benefitDeduction=value;}catch{pending.push('Montante conhecido das deduções por preencher ou inválido.');}}
|
||||
const ready=pending.length===0&&profit!==null,deductedLosses=ready?requestedLosses:null,taxableBase=ready?subtractFractions(subtractFractions(units(profit!)<0n?zero():profit!,requestedLosses),benefitDeduction!):null;
|
||||
const ready=pending.length===0&&profit!==null,deductedLosses=ready?requestedLosses:null,taxableBase=ready?subtractFractions(subtractFractions(BigInt(profit!.numerator)<0n?zero():profit!,requestedLosses),benefitDeduction!):null;
|
||||
for(const l of losses){if(l.available){l.deducted=ready?(l.status==='eligible'?l.requestedAmount!:zero()):null;l.remaining=l.deducted?subtractFractions(l.available,l.deducted):null;}}
|
||||
return{input:i,taxableProfit:profit,requestedLosses,deductedLosses,benefitDeduction,taxableBase,losses,pending,source:'CIRPC15(1)a/15(2–4),41(1–7),Lei34/2007 PDF5/p768(59),PDF12/p768(66). Recorte2026,residente geral,método declarativo,perdas próprias em exercícios anuais ordinários;cinco exercícios posteriores,sem transferência41-A,regimes especiais ou benefícios positivos implementados. Pedido de utilização expresso e limitado ao saldo/lucro;semFIFO obrigatório ou limite percentual inventado. Matéria colectável simulada,não colecta/imposto a pagar,semM20/M22/submissão ou consumo persistente do saldo oficial.'};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
import {addFractions,fraction,subtractFractions,fractionPresentation,type FiscalFraction} from './fiscal-rational.ts';
|
||||
import {fiscalMoney} from './irpc-normal-schedules.ts';
|
||||
export const normalCostsVersion='IRPC-normal-adjustments/6';
|
||||
export const costRules={
|
||||
allowance:['Ajudas de custo/deslocação em viatura própria','expenseCorrections','36(1)e'],
|
||||
representation:['Despesas de representação','expenseCorrections','36(1)f/36(3)'],
|
||||
advertising:['Publicidade ordinária — limite anual partilhado','expenseCorrections','36(1)n,Lei20/2009'],
|
||||
vehicle:['Outros encargos com viatura ligeira de passageiros','expenseCorrections','36(4)'],
|
||||
fuel:['Combustível — prova do activo/locação e consumo normal','expenseCorrections','36(1)i/36(4)'],
|
||||
rental:['Aluguer sem condutor — parcela de quota não aceite documentada','expenseCorrections','36(1)h/36(4)'],
|
||||
credit:['Provisão/perda por imparidade de créditos ordinários','expenseCorrections','28(1)a/29'],
|
||||
stock:['Provisão/perda por imparidade de existências','expenseCorrections','28(1)b'],
|
||||
litigation:['Provisão para processo judicial em curso','expenseCorrections','28(1)c'],
|
||||
provision_reversal:['Reversão de provisão antes deduzida','incomeCorrections','28(2)'],
|
||||
taxed_reversal:['Reversão contabilística de provisão antes tributada','incomeCorrections','17/28;sem dupla tributação da parcela comprovada'],
|
||||
prior_cost:['Custo de exercício anterior incluído no resultado actual','timing','18(2)'],
|
||||
prior_income:['Proveito de exercício anterior incluído no resultado actual','timing','18(2)'],
|
||||
estimated_work_loss:['Perda estimada em obra plurianual em curso','expenseCorrections','36(1)m,Lei20/2009'],
|
||||
work:['Resultado de obra plurianual — percentagem/encerramento','timing','19(1–6)'],
|
||||
}as const;
|
||||
export type CostRule=keyof typeof costRules;
|
||||
export type CostRow={id:string;rule:''|CostRule;document:string;line:string;amount:string;sourceCode:string;reference:string;conditions:''|'yes';facts:Record<string,string>};
|
||||
export type NormalCostsInput={version:1;rows:CostRow[];advertisingReference:string;salesOrdinary:''|'yes';creditBase:string;creditPrevious:string;creditReference:string;creditRegime:''|'ordinary'|'regulated'};
|
||||
export const newNormalCosts=():NormalCostsInput=>({version:1,rows:[],advertisingReference:'',salesOrdinary:'',creditBase:'',creditPrevious:'',creditReference:'',creditRegime:''});
|
||||
export const newCostRow=(id:string):CostRow=>({id,rule:'',document:'',line:'',amount:'',sourceCode:'',reference:'',conditions:'',facts:{}});
|
||||
export function validateNormalCosts(i:NormalCostsInput){
|
||||
if(!i||i.version!==1||!Array.isArray(i.rows)||['advertisingReference','creditBase','creditPrevious','creditReference'].some(k=>typeof i[k as 'creditBase']!=='string')||!['','yes'].includes(i.salesOrdinary)||!['','ordinary','regulated'].includes(i.creditRegime))throw Error('Mapa de custos/provisões/periodização não recuperável.');
|
||||
if(new Set(i.rows.map(r=>r.id)).size!==i.rows.length)throw Error('Identificador repetido no mapa de custos.');
|
||||
for(const r of i.rows)if(!r||['id','rule','document','line','amount','sourceCode','reference','conditions'].some(k=>typeof r[k as 'id']!=='string')||r.rule!==''&&!Object.hasOwn(costRules,r.rule)||!['','yes'].includes(r.conditions)||!r.facts||typeof r.facts!=='object'||Array.isArray(r.facts)||Object.values(r.facts).some(v=>typeof v!=='string'))throw Error('Linha de custos/provisões/periodização não recuperável.');
|
||||
}
|
||||
type Source={currency:string;accountingLines:Array<{code:string;total:string;accountingEffect:string}>;cashSchedule?:Array<{salesReceivablesBalance:string|null;openingReceivablesBalance:string}>};
|
||||
export type CostClaim={document:string;line:string;sourceCode:string;amount:string};
|
||||
const z=()=>fraction(0n),cmp=(a:FiscalFraction,b:FiscalFraction)=>BigInt(a.numerator)*BigInt(b.denominator)-BigInt(b.numerator)*BigInt(a.denominator);
|
||||
const mul=(a:FiscalFraction,n:bigint,d=1n)=>fraction(BigInt(a.numerator)*n,BigInt(a.denominator)*d);
|
||||
const min=(...v:FiscalFraction[])=>v.reduce((a,b)=>cmp(a,b)<0n?a:b),max0=(v:FiscalFraction)=>cmp(v,z())<0n?z():v;
|
||||
const costMoney=(v:FiscalFraction|null)=>{if(!v)return'Indeterminado';const p=fractionPresentation(v);return(p.approximate?'≈':'')+p.decimal+' MZN'+(p.approximate?' (exacto '+v.numerator+'/'+v.denominator+')':'');};
|
||||
const identity=(r:{document:string;line:string})=>r.document.trim()+'\u0000'+r.line.trim();
|
||||
export function reusableCreditBalance(source:Source|null){const l=source?.cashSchedule?.at(-1);if(!l||l.salesReceivablesBalance===null)return null;try{return addFractions([fiscalMoney(l.salesReceivablesBalance),fiscalMoney(l.openingReceivablesBalance)]);}catch{return null;}}
|
||||
export function calculateNormalCosts(input:NormalCostsInput,source:Source|null,existing:CostClaim[],period:{from:string;to:string}){
|
||||
validateNormalCosts(input);const rows=input.rows,claims=rows.flatMap(r=>[{...r},...(r.rule==='work'?[{id:r.id,document:r.document,line:r.line+'/custos',sourceCode:r.facts.costSource??'',amount:r.facts.bookCost??''}]:[])]),pending:string[]=[];
|
||||
const sourceAmount=(code:string,effect:string)=>{const l=source?.accountingLines.find(l=>l.code===code);if(!l||l.accountingEffect!==effect)throw Error('Seleccione uma linha contabilística compatível.');return fiscalMoney(l.total);};
|
||||
const pool=(rule:CostRule)=>addFractions(rows.filter(r=>r.rule===rule).map(r=>fiscalMoney(r.amount)));
|
||||
const pools:Record<string,{label:string;base:FiscalFraction;previous:FiscalFraction|null;annualLimit:FiscalFraction;accumulatedLimit:FiscalFraction|null;reference:string}>={};
|
||||
const results=rows.map(r=>{const issues:string[]=[],rule=r.rule?costRules[r.rule]:null,topic=rule?.[1]??'',f=r.facts;let amount:FiscalFraction|null=null,accepted:FiscalFraction|null=null,adjustment:FiscalFraction|null=null,limit:FiscalFraction|null=null;const details:string[]=[];
|
||||
const a=(key:string)=>{if(!f[key]?.trim())throw Error('Falta parâmetro: '+(factLabels[key]??key));return fiscalMoney(f[key]);};
|
||||
const yesno=(key:string)=>{if(!['yes','no'].includes(f[key]))throw Error('Falta condição documentada: '+(factLabels[key]??key));return f[key]==='yes';};
|
||||
const ref=(key:string)=>{if(!f[key]?.trim())throw Error('Falta suporte: '+(factLabels[key]??key));};
|
||||
const balance=()=>{ref('balanceKey');if(rows.some(q=>q!==r&&q.facts.balanceKey===f.balanceKey))throw Error('Saldo/objecto fiscal repetido entre linhas; agregue as parcelas no mesmo mapa para não reutilizar o limite.');return a('previous');};
|
||||
const checkOrigin=(code:string,value:FiscalFraction,effect:string)=>{if(cmp(value,z())===0n&&!code)return;if(['depreciation','interest_expense'].includes(code))throw Error('Parcela de amortização/juros exige reconciliação própria com o mapa existente; sem dupla qualificação neste incremento.');const total=sourceAmount(code,effect);const allocated=addFractions([...existing,...claims].filter(c=>c.sourceCode===code).map(c=>fiscalMoney(c.amount)));if(cmp(allocated,total)>0n)throw Error('Parcelas dos mapas excedem a linha de origem; reconcilie sem duplicar ajustamentos.');};
|
||||
if(!rule||!r.document.trim()||!r.line.trim()||!r.reference.trim()||r.conditions!=='yes')issues.push('Regra, documento/linha, condições e suporte em falta.');
|
||||
if([...existing,...claims.filter(c=>c!==r&&c.id!==r.id)].some(c=>identity(c)===identity(r)))issues.push('Documento/linha repetidos entre mapas.');
|
||||
if(claims.filter(c=>identity(c)===identity(r)).length>1)issues.push('Documento/linha repetidos no mapa.');
|
||||
if(source?.currency!=='MZN'||!period.to.startsWith('2026-'))issues.push('Recorte anual emMZN com termo em2026 por reconciliar.');
|
||||
try{amount=fiscalMoney(r.amount);if(cmp(amount,z())<=0n&&r.rule!=='work')throw Error('Identifique parcela positiva conhecida.');
|
||||
const isIncome=['provision_reversal','taxed_reversal','prior_income','work'].includes(r.rule);const omitted=r.rule==='provision_reversal'&&f.inResult==='no';if(!omitted)checkOrigin(r.sourceCode,amount,isIncome?'income':'expense');else if(r.sourceCode)throw Error('Reversão não incluída no resultado não deve repetir uma linha de proveitos.');
|
||||
switch(r.rule){
|
||||
case 'allowance':{const billed=a('billed'),taxed=a('taxedNotBilled');if(cmp(addFractions([billed,taxed]),amount)>0n)throw Error('Parcelas facturadas e tributadas emIRPS não podem sobrepor-se ou exceder o encargo.');const limited=subtractFractions(subtractFractions(amount,billed),taxed);adjustment=mul(limited,1n,2n);accepted=subtractFractions(amount,adjustment);limit=mul(limited,1n,2n);details.push('50% só da parcela não facturada a clientes nem tributada emIRPS; parcelas mutuamente exclusivas.');break;}
|
||||
case 'representation':accepted=mul(amount,1n,5n);adjustment=subtractFractions(amount,accepted);details.push('20% aceite;80% acrescido. Eventual tributação autónoma não calculada, âmbitoL7.');break;
|
||||
case 'advertising':{if(f.campaign!=='no')throw Error('Campanha de lançamento com horizonte superior a um ano: periodização36-A e articulação do limite não correspondidas; não tratar como publicidade ordinária.');if(input.salesOrdinary!=='yes'||!input.advertisingReference.trim())throw Error('Confirme completude da publicidade e correspondência do volume de vendas normal do exercício à receita declarada.');const total=pool('advertising');limit=mul(sourceAmount('revenue','income'),1n,100n);pools.advertising={label:'Publicidade — limite anual partilhado',base:sourceAmount('revenue','income'),previous:null,annualLimit:limit,accumulatedLimit:null,reference:input.advertisingReference};const eligible=min(total,limit);accepted=fraction(BigInt(amount.numerator)*BigInt(eligible.numerator)*BigInt(total.denominator),BigInt(amount.denominator)*BigInt(eligible.denominator)*BigInt(total.numerator));adjustment=subtractFractions(amount,accepted);details.push('Limite único anual1% das vendas normais; repartição aritmética proporcional entre parcelas para a simulação, sem ordem fiscal obrigatória.');break;}
|
||||
case 'vehicle':{if(!yesno('passenger')){accepted=amount;details.push('Viatura fora da categoria ligeira de passageiros;36(4)não aplicado, restantes condições ordinárias documentadas.');}else{accepted=yesno('publicOrRental')?amount:mul(amount,1n,2n);details.push('50% dos outros encargos de passageiros, excepto serviço público de transportes/locação na actividade normal; não inclui quotas, aluguer sem condutor ou combustível já discriminados.');}adjustment=subtractFractions(amount,accepted);break;}
|
||||
case 'fuel':case 'rental':{let eligible:FiscalFraction;if(r.rule==='fuel'){eligible=a('supportedNormal');if(cmp(eligible,amount)>0n)throw Error('Consumo normal comprovado excede o encargo.');details.push('36i: parcela ligada a activo próprio/locado e consumo normal comprovados.');}else{const excluded=a('nonAcceptedQuota');ref('quotaReference');if(cmp(excluded,amount)>0n)throw Error('Parcela de quota não aceite excede a renda.');eligible=subtractFractions(amount,excluded);details.push('36h: reutiliza parcela de quota não aceite documentada; o portal não apura automaticamente essa quota de viatura.');}if(yesno('passenger')&&!yesno('publicOrRental')){if(cmp(eligible,amount)!==0n)throw Error('Articulação da parcela excluída por36h/i com os50% de36(4) ainda não correspondida; este caso combinado fica pendente, sem somar nem repartir correcções por presunção.');eligible=mul(eligible,1n,2n);details.push('36(4):50% do encargo; sem parcela excluída concorrente porh/i.');}accepted=eligible;adjustment=subtractFractions(amount,accepted);break;}
|
||||
case 'credit':{if(input.creditRegime!=='ordinary')throw Error('Regime das provisões por identificar ou sujeito a supervisão financeira/seguradora28d; sem limites29 aplicados por aproximação.');if(!input.creditReference.trim())throw Error('Saldo de créditos ordinários e provisão fiscal anterior por documentar.');if(!input.creditBase.trim())throw Error('Créditos ordinários existentes no fim do exercício em falta.');if(!input.creditPrevious.trim())throw Error('Provisão fiscal anterior dos créditos em falta; não presumir zero.');const base=fiscalMoney(input.creditBase),previous=fiscalMoney(input.creditPrevious),total=pool('credit'),annual=mul(base,15n,1000n),accumulated=mul(base,6n,100n);pools.credit={label:'Créditos — limites anual e acumulado',base,previous,annualLimit:annual,accumulatedLimit:accumulated,reference:input.creditReference};if(cmp(previous,accumulated)>0n)throw Error('Provisão fiscal anterior excede6% dos créditos finais; reconcilie subsistência/reversão28(2), sem saldo presumido.');limit=min(annual,subtractFractions(accumulated,previous));const eligible=min(total,limit);accepted=fraction(BigInt(amount.numerator)*BigInt(eligible.numerator)*BigInt(total.denominator),BigInt(amount.denominator)*BigInt(eligible.denominator)*BigInt(total.numerator));adjustment=subtractFractions(amount,accepted);details.push('29:1,5% anual,6% acumulado sobre créditos da actividade normal existentes no fim; saldo fiscal anterior conhecido. Repartição proporcional do limite único, sem consumir saldo oficial.');break;}
|
||||
case 'stock':case 'litigation':{let previous:FiscalFraction;if(r.rule==='litigation'){if(!yesno('caseOngoing')){accepted=z();adjustment=amount;details.push('Não há processo judicial em curso; provisão não elegível por28c.');break;}if(!yesno('chargeDeductible')){accepted=z();adjustment=amount;details.push('Obrigação não daria lugar a custo dedutível; provisão não aceite por28c.');break;}}else if(!yesno('lossObserved')){accepted=z();adjustment=amount;details.push('Perda de valor não efectivamente observada; estimativa não aceite por28b.');break;}previous=balance();limit=a(r.rule==='stock'?'observedLoss':'obligation');if(cmp(previous,limit)>0n)throw Error('Saldo fiscal anterior excede a cobertura actual; reconcilie a reversão28(2).');accepted=min(amount,subtractFractions(limit,previous));adjustment=subtractFractions(amount,accepted);details.push(r.rule==='stock'?'28b:limite das perdas efectivamente observadas, incluindo saldo anterior.':'28c:obrigações de processo judicial em curso que seriam custos dedutíveis, com saldo anterior reconciliado.');break;}
|
||||
case 'provision_reversal':case 'taxed_reversal':{const previous=balance();if(cmp(amount,previous)>0n)throw Error('Reversão excede a parcela fiscal anterior demonstrada.');if(!yesno('eventEnded'))throw Error('Evento não cessou/não houve utilização diferente; fundamento da reversão por corresponder.');const inResult=yesno('inResult');if(r.rule==='taxed_reversal'&&!inResult)throw Error('Só excluir proveito de reversão já incluído na contabilidade e antes não deduzido.');accepted=amount;adjustment=r.rule==='taxed_reversal'?mul(amount,-1n):inResult?z():amount;details.push(r.rule==='taxed_reversal'?'Parcela antes tributada comprovada; exclui a reversão contabilística sem duplicar dedução.':'28(2):proveito da provisão antes deduzida; só acresce quando ainda não incluído no resultado.');break;}
|
||||
case 'prior_cost':case 'prior_income':{if(!/^20\d{2}$/.test(f.originalYear)||Number(f.originalYear)>=2026)throw Error('Exercício de origem deve ser anterior ao exercício actual.');ref('closingEvidence');const unknown=yesno('unknownAtClose');accepted=unknown?amount:z();adjustment=unknown?z():r.rule==='prior_cost'?amount:mul(amount,-1n);details.push(unknown?'18(2):imprevisível ou manifestamente desconhecido no encerramento anterior; imputável ao actual.':'18(2):conhecido/previsível no encerramento anterior; excluído do actual, sem alterar declarações/históricos anteriores.');break;}
|
||||
case 'estimated_work_loss':accepted=z();adjustment=amount;details.push('36m:perda meramente estimada em obra em curso não dedutível; custo efectivo não confundido com estimativa.');break;
|
||||
case 'work':{const price=a('price'),totalCost=a('totalCost'),incurred=a('incurred'),priorRevenue=a('priorRevenue'),priorCost=a('priorCost'),bookCost=a('bookCost');ref('projectKey');if(rows.some(q=>q!==r&&q.facts.projectKey===f.projectKey))throw Error('Obra repetida; agregue os valores acumulados sem duplicar resultados.');checkOrigin(f.costSource??'',bookCost,'expense');if(cmp(totalCost,z())<=0n||cmp(incurred,totalCost)>0n||cmp(priorCost,incurred)>0n||cmp(priorRevenue,price)>0n)throw Error('Preço, custo total, incorrido e valores fiscais anteriores incoerentes.');if(!yesno('consistentMethod'))throw Error('Mudança de critério exige correspondência à autorização19(6).');const mandatory=yesno('partialBilling')||yesno('ownFractionalDelivery');let revenue:FiscalFraction,cost:FiscalFraction;
|
||||
if(f.method==='percentage'){revenue=fraction(BigInt(price.numerator)*BigInt(incurred.numerator)*BigInt(totalCost.denominator),BigInt(price.denominator)*BigInt(incurred.denominator)*BigInt(totalCost.numerator));cost=incurred;}
|
||||
else if(f.method==='closing'){if(mandatory)throw Error('Facturação parcial com acabamento correspondente/entrega fraccionada: critério da percentagem obrigatório19(2).');const concluded=cmp(mul(incurred,100n),mul(totalCost,95n))>=0n||yesno('publicReception');revenue=concluded?price:z();cost=concluded?incurred:z();}
|
||||
else throw Error('Seleccione percentagem de acabamento ou encerramento.');if(cmp(priorRevenue,revenue)>0n||cmp(priorCost,cost)>0n)throw Error('Acumulados fiscais anteriores excedem o resultado reconhecível pelo critério; reconcilie sem correcção retrospectiva presumida.');const deferred=a('deferredReceipt');if(cmp(deferred,z())!==0n)throw Error('Receita antecipada19(5) de montante não zero exige correspondência própria.');const currentRevenue=subtractFractions(revenue,priorRevenue),currentCost=subtractFractions(cost,priorCost);accepted=currentCost;adjustment=addFractions([subtractFractions(currentRevenue,amount),subtractFractions(bookCost,currentCost)]);limit=currentRevenue;details.push('19:grau=incorrido/custo total;valores fiscais do exercício obtidos dos acumulados menos o já reconhecido. Proveito fiscal corrente:'+costMoney(currentRevenue)+';custo fiscal corrente:'+costMoney(currentCost)+';sem imposto/pagamento ou perda estimada presumidos.');break;}
|
||||
default:throw Error('Categoria por identificar.');
|
||||
}
|
||||
}catch(e){issues.push(e instanceof Error?e.message:String(e));accepted=null;adjustment=null;}
|
||||
if(issues.length){accepted=null;adjustment=null;pending.push((r.document||r.id)+': '+issues.join(' '));}return{...structuredClone(r),topic,amountValue:amount,accepted,adjustment,limit,status:issues.length?'indeterminate' as const:'calculated' as const,reason:issues.join(' ')||details.join(' '),ruleSource:rule?'CIRPC'+rule[2]:''};
|
||||
});
|
||||
return{input:structuredClone(input),rows:results,pools,pending,effects:results.map(r=>({id:r.id,topic:r.topic,amount:r.amountValue,effect:r.adjustment,reason:r.reason})),net:addFractions(results.flatMap(r=>r.adjustment?[r.adjustment]:[])),source:'CIRPC18(2)/19/28/29/36;Lei34/2007,Lei20/2009,alterações4/2012 e19/2013 e diplomas posteriores inventariados. Recorte2026/MZN,condições documentadas,sem efeitos retroactivos ou consumo de saldos oficiais;L7/L8 separados.'};
|
||||
}
|
||||
export type NormalCostsModel=ReturnType<typeof calculateNormalCosts>;
|
||||
const factLabels:Record<string,string>={billed:'Facturado a clientes',taxedNotBilled:'Tributado emIRPS e não facturado',campaign:'Campanha plurianual',passenger:'Ligeira de passageiros',publicOrRental:'Serviço público/locação na actividade normal',supportedNormal:'Consumo normal comprovado',nonAcceptedQuota:'Quota não aceite documentada',quotaReference:'Suporte da quota',balanceKey:'Identificação do saldo/objecto',previous:'Parcela fiscal anterior',lossObserved:'Perda efectivamente observada',observedLoss:'Perda total observada',caseOngoing:'Processo judicial em curso',chargeDeductible:'Obrigação fiscalmente dedutível',obligation:'Obrigação coberta',eventEnded:'Evento cessou/utilização diferente',inResult:'Incluído no resultado',originalYear:'Exercício de origem',unknownAtClose:'Imprevisível/desconhecido no encerramento',closingEvidence:'Suporte do encerramento',projectKey:'Obra',method:'Critério',price:'Preço',totalCost:'Custo total estimado',incurred:'Custos incorporados acumulados',priorRevenue:'Proveitos fiscais anteriores',priorCost:'Custos fiscais anteriores',costSource:'Origem dos custos',bookCost:'Custo contabilizado corrente',partialBilling:'Facturação parcial com acabamento correspondente',ownFractionalDelivery:'Venda/entrega fraccionada por conta própria',consistentMethod:'Critério uniforme e mantido',publicReception:'Recepção provisória pública',deferredReceipt:'Receita antecipada proposta'};
|
||||
const originLabels:Record<string,string>={revenue:'Receitas do plano',cost_of_sales:'Custo das vendas',operating_expenses:'Despesas operacionais',personnel:'Pessoal',depreciation:'Depreciação contabilística',interest_expense:'Juros financeiros'};
|
||||
export function normalCostReport(m:NormalCostsModel):Array<[string,string,string,string]>{const money=costMoney;return[['IRPC normal — L6','Custos com limites, provisões e periodização',`Efeito líquido das parcelas resolvidas:${money(m.net)};${m.pending.join(' | ')}`,m.source],...Object.values(m.pools).map(p=>['IRPC normal — L6',p.label,`Base documentada:${money(p.base)};limite anual:${money(p.annualLimit)};saldo fiscal anterior:${p.previous?money(p.previous):'Não aplicável'};limite acumulado:${p.accumulatedLimit?money(p.accumulatedLimit):'Não aplicável'};limite partilhado entre todas as parcelas, sem consumo do saldo oficial.`,p.reference]as[string,string,string,string]),...m.rows.map(r=>['IRPC normal — L6',(r.rule?costRules[r.rule][0]:'Categoria por identificar')+' — '+r.document+'/'+r.line,`Estado:${r.status==='calculated'?'Calculado no recorte':'Informação insuficiente'};montante identificado:${money(r.amountValue)};parcela aceite/custo fiscal ou reversão:${money(r.accepted)};limite/base ou proveito fiscal:${r.limit?money(r.limit):r.status==='calculated'?'Sem limite monetário próprio; ver regra':'Em falta/não aplicável à parcela'};ajustamento com sinal:${money(r.adjustment)};${r.reason}`,`${r.ruleSource};${r.reference};origem:${originLabels[r.sourceCode]??'Fora do resultado, documentado'};parâmetros:${Object.entries(r.facts).filter(([k])=>factLabels[k]).map(([k,v])=>factLabels[k]+': '+(v==='yes'?'Sim':v==='no'?'Não':v==='percentage'?'Percentagem de acabamento':v==='closing'?'Encerramento':originLabels[v]??(v||'Em falta'))).join('; ')};sem alteração do histórico ou saldo oficial.`]as[string,string,string,string])];}
|
||||
@@ -1,3 +1,4 @@
|
||||
import type {NormalCostsModel} from './irpc-normal-costs.ts';
|
||||
import type {SchedulesModel} from './irpc-normal-schedules.ts';
|
||||
import {addFractions,decimalFraction,fraction,type FiscalFraction} from './fiscal-rational.ts';
|
||||
|
||||
@@ -24,7 +25,7 @@ const zero=()=>fraction(0n,1n);
|
||||
function money(s:string){if(!/^-?\d+(?:\.\d{1,2})?$/.test(s))throw Error('Montante inválido');const v=decimalFraction(s.startsWith('-')?s.slice(1):s);return s.startsWith('-')?fraction(-BigInt(v.numerator),BigInt(v.denominator)):v;}
|
||||
const units=(f:FiscalFraction)=>BigInt(f.numerator)*100n/BigInt(f.denominator);
|
||||
const difference=(a:FiscalFraction,b:FiscalFraction)=>fraction(BigInt(a.numerator)*BigInt(b.denominator)-BigInt(b.numerator)*BigInt(a.denominator),BigInt(a.denominator)*BigInt(b.denominator));
|
||||
export function closeNormalProfit(input:NormalProfitInput,source:Source|null,rows:Array<{sourceCode:string;adjustment:FiscalFraction|null;status:string}>,accounting:FiscalFraction|null,additions:FiscalFraction,contextReady:boolean,schedules?:SchedulesModel):NormalProfitModel{
|
||||
export function closeNormalProfit(input:NormalProfitInput,source:Source|null,rows:Array<{sourceCode:string;adjustment:FiscalFraction|null;status:string}>,accounting:FiscalFraction|null,additions:FiscalFraction,contextReady:boolean,schedules?:SchedulesModel,costs?:NormalCostsModel):NormalProfitModel{
|
||||
validateNormalProfit(input);const pending:string[]=[],lines:NormalProfitModel['lines']=[],topics:NormalProfitModel['topics']=[];
|
||||
if(!contextReady)pending.push('Âmbito,exercício,moeda ou correspondência anual ainda insuficientes.');
|
||||
if(!input.completenessReference.trim())pending.push('Documente que o plano e os ajustamentos abrangem o sujeito passivo/exercício completo;uma oportunidade isolada não equivale automaticamente ao conjunto da empresa.');
|
||||
@@ -49,12 +50,13 @@ export function closeNormalProfit(input:NormalProfitInput,source:Source|null,row
|
||||
if(!t.state||!t.reference.trim())reason='Informação em falta;sem zero presumido.';
|
||||
else if(t.state==='absent'){if(t.amount.trim())reason='Ausência declarada com montante preenchido;reconcilie os dois estados.';else{value=zero();reason='Ausência expressamente declarada com suporte;parcela zero apenas por esta declaração.';}}
|
||||
else{try{value=money(t.amount);if(units(value)!==0n)reason='Montante conhecido,mas tratamento específico ainda por corresponder;conservado sem aplicação presumida.';else reason='Valor conhecido zero com suporte;distinto de ausência declarada.';}catch{reason='Montante conhecido por preencher ou inválido.';}}
|
||||
const mapped=schedules?.effects.filter(e=>e.topic===key)??[];
|
||||
const covered=schedules&&mapped.length>0&&mapped.every(e=>e.effect!==null)&&value!==null&&t.state==='known'&&units(addFractions(mapped.flatMap(e=>e.amount?[e.amount]:[])))===units(value);
|
||||
const mapped=[...(schedules?.effects??[]),...(costs?.effects??[])].filter(e=>e.topic===key);
|
||||
const covered=(schedules||costs)&&mapped.length>0&&mapped.every(e=>e.effect!==null)&&value!==null&&t.state==='known'&&units(addFractions(mapped.flatMap(e=>e.amount?[e.amount]:[])))===units(value);
|
||||
if(covered)reason='Montante reconciliado com as regras do mapa complementar; efeito fiscal discriminado por linha.';
|
||||
if(value===null||units(value)!==0n&&!covered||t.state==='absent'&&!!t.amount.trim()||mapped.length>0&&!covered)pending.push(label+': '+(mapped.length&&!covered?'Mapa complementar não reconciliado com este estado/montante. ': '')+reason);
|
||||
topics.push({...structuredClone(t),key,label,value,reason});
|
||||
}
|
||||
if(schedules)pending.push(...schedules.pending);
|
||||
return{input:structuredClone(input),lines,topics,pending,taxableProfit:pending.length===0&&accounting?addFractions([accounting,additions,...(schedules?[schedules.net]:[])]):null,source:schedules?'CIRPC17/18/20/21/22/24/36; Decreto72/2013 art3–7. Mapa complementar de efeitos com sinal reconciliado por tema, juros financeiros ordinários e quotas anuais constantes documentadas. Simulação emMZN/2026; benefícios e casos especiais separados emL7, saldo anual emL8.':'CIRPC17(1)/18/20/22/36(1)c;recorte anual2026,residente geral,MZN. Resultado antes de imposto + acréscimos36(1)c;demais correcções/variações sem efeito apenas quando ausência ou zero conhecidos expressamente suportados. Receita/custos ordinários classificados pelo utilizador;depreciação fiscal e juros financeiros remanescentes fora deste percurso. Lucro tributável simulado,não matéria colectável,colecta ou declaraçãoM20/22.'};
|
||||
if(costs)pending.push(...costs.pending);
|
||||
return{input:structuredClone(input),lines,topics,pending,taxableProfit:pending.length===0&&accounting?addFractions([accounting,additions,...(schedules?[schedules.net]:[]),...(costs?[costs.net]:[])]):null,source:schedules?'CIRPC17/18/20/21/22/24/36; Decreto72/2013 art3–7. Mapa complementar de efeitos com sinal reconciliado por tema, juros financeiros ordinários e quotas anuais constantes documentadas. Simulação emMZN/2026; benefícios e casos especiais separados emL7, saldo anual emL8.':'CIRPC17(1)/18/20/22/36(1)c;recorte anual2026,residente geral,MZN. Resultado antes de imposto + acréscimos36(1)c;demais correcções/variações sem efeito apenas quando ausência ou zero conhecidos expressamente suportados. Receita/custos ordinários classificados pelo utilizador;depreciação fiscal e juros financeiros remanescentes fora deste percurso. Lucro tributável simulado,não matéria colectável,colecta ou declaraçãoM20/22.'};
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import {normalCostsVersion,validateNormalCosts,calculateNormalCosts,normalCostReport,type NormalCostsInput,type NormalCostsModel} from './irpc-normal-costs.ts';
|
||||
import {normalFormFields} from './irpc-normal-forms.ts';
|
||||
import {normalSchedulesVersion,normalSchedulePeriod,validateSchedules,calculateSchedules,type NormalSchedules,type SchedulesModel} from './irpc-normal-schedules.ts';
|
||||
import {normalAssessmentVersion,validateNormalAssessment,calculateNormalAssessment,type NormalAssessmentInput,type NormalAssessmentModel} from './irpc-normal-assessment.ts';
|
||||
@@ -11,8 +12,8 @@ import {addFractions,decimalFraction,fraction,fractionPresentation,type FiscalFr
|
||||
export const irpcNormalVersion='IRPC-normal-adjustments/1';
|
||||
export const irpcNormalSource='CIRPC Lei34/2007 art17(1),PDF6/p768(60):resultado e correcções;art36(1)c,PDF10/p768(64):multas e encargos por infracções sem origem contratual,incluindo juros compensatórios. Lei4/2012 PDF2 altera17(6)/acrescenta36(1)o;Lei19/2013 PDF2 mantém36(1)a–o e acrescenta p/q. Lei12/2025 altera3/5/20/62/67/75,adita61-A e revoga39/47/75(2)/76;sem alteração deste recorte36(1)c. Não é apuramento integral nem Modelo20/22.';
|
||||
export type NormalAdjustment={id:string;sourceCode:string;document:string;line:string;amount:string;rule:''|'noncontractual_fine'|'compensatory_interest'|'other';included:''|'yes'|'no';reference:string};
|
||||
export type NormalBook={version:1;year:string;mappingReference:string;scope:''|'resident_general'|'ep_general'|'other';scopeReference:string;rows:NormalAdjustment[];profit?:NormalProfitInput;base?:NormalBaseInput;assessment?:NormalAssessmentInput;schedules?:NormalSchedules;result?:NormalRecord;history?:NormalRecord[]};
|
||||
type NormalModel={fiscalPeriod?:ReturnType<typeof normalSchedulePeriod>;schedules?:SchedulesModel;currentLoss?:FiscalFraction|null;assessment?:NormalAssessmentModel;colecta?:FiscalFraction|null;base?:NormalBaseModel;profit?:NormalProfitModel;version:string;year:string;currency:string;origin:'projected';accountingProfit:FiscalFraction|null;additions:FiscalFraction;partialAdjustedResult:FiscalFraction|null;taxableProfit:FiscalFraction|null;taxableBase:FiscalFraction|null;tax:null;rows:Array<NormalAdjustment&{status:'calculated'|'indeterminate'|'outside_scope';adjustment:FiscalFraction|null;reason:string}>;pending:string[];source:string};
|
||||
export type NormalBook={version:1;year:string;mappingReference:string;scope:''|'resident_general'|'ep_general'|'other';scopeReference:string;rows:NormalAdjustment[];profit?:NormalProfitInput;base?:NormalBaseInput;assessment?:NormalAssessmentInput;schedules?:NormalSchedules;costs?:NormalCostsInput;result?:NormalRecord;history?:NormalRecord[]};
|
||||
type NormalModel={costs?:NormalCostsModel;fiscalPeriod?:ReturnType<typeof normalSchedulePeriod>;schedules?:SchedulesModel;currentLoss?:FiscalFraction|null;assessment?:NormalAssessmentModel;colecta?:FiscalFraction|null;base?:NormalBaseModel;profit?:NormalProfitModel;version:string;year:string;currency:string;origin:'projected';accountingProfit:FiscalFraction|null;additions:FiscalFraction;partialAdjustedResult:FiscalFraction|null;taxableProfit:FiscalFraction|null;taxableBase:FiscalFraction|null;tax:null;rows:Array<NormalAdjustment&{status:'calculated'|'indeterminate'|'outside_scope';adjustment:FiscalFraction|null;reason:string}>;pending:string[];source:string};
|
||||
type NormalRecord={key:string;createdAt:string;model:NormalModel};
|
||||
export const newNormalBook=():NormalBook=>({version:1,year:'',mappingReference:'',scope:'',scopeReference:'',rows:[]});
|
||||
export const newNormalAdjustment=(id:string):NormalAdjustment=>({id,sourceCode:'',document:'',line:'',amount:'',rule:'',included:'',reference:''});
|
||||
@@ -23,6 +24,7 @@ export function normalFinancialSource(d:Record<string,unknown>){
|
||||
}
|
||||
function store(d:Record<string,unknown>){const s=JSON.parse(String(d.modelo_a_snapshot||'{"version":1,"books":{}}'));if(s.version!==1||!s.books||typeof s.books!=='object'||Array.isArray(s.books))throw Error('Livros fiscais não recuperáveis;originais preservados.');return s;}
|
||||
export function validateNormal(b:NormalBook){
|
||||
if(b?.costs!==undefined)validateNormalCosts(b.costs);
|
||||
if(b?.schedules!==undefined)validateSchedules(b.schedules);
|
||||
if(b?.assessment!==undefined)validateNormalAssessment(b.assessment);
|
||||
if(b?.base!==undefined)validateNormalBase(b.base);
|
||||
@@ -30,13 +32,13 @@ 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||![irpcNormalVersion,normalProfitVersion,normalBaseVersion,normalAssessmentVersion,normalSchedulesVersion].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||![irpcNormalVersion,normalProfitVersion,normalBaseVersion,normalAssessmentVersion,normalSchedulesVersion,normalCostsVersion].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;
|
||||
}
|
||||
export function saveNormalBook(d:Record<string,unknown>,b:NormalBook){validateNormal(b);const s=store(d);if(s.irpcNormal!==undefined&&(!s.irpcNormal||typeof s.irpcNormal!=='object'||Array.isArray(s.irpcNormal)))throw Error('Mapa IRPC normal não recuperável.');return JSON.stringify({...s,irpcNormal:{...s.irpcNormal,[acquisitionScope(d)]:b}});}
|
||||
function key(d:Record<string,unknown>,b:NormalBook){return JSON.stringify([b.schedules?normalSchedulesVersion:b.assessment?normalAssessmentVersion:b.base?normalBaseVersion:b.profit?normalProfitVersion:irpcNormalVersion,acquisitionScope(d),d.business_plan_snapshot??'',b.year,b.mappingReference,b.scope,b.scopeReference,b.rows,...(b.profit?[b.profit]:[]),...(b.base?[b.base]:[]),...(b.assessment?[b.assessment]:[]),...(b.schedules?[b.schedules]:[])]);}
|
||||
function key(d:Record<string,unknown>,b:NormalBook){return JSON.stringify([b.costs?normalCostsVersion:b.schedules?normalSchedulesVersion:b.assessment?normalAssessmentVersion:b.base?normalBaseVersion:b.profit?normalProfitVersion:irpcNormalVersion,acquisitionScope(d),d.business_plan_snapshot??'',b.year,b.mappingReference,b.scope,b.scopeReference,b.rows,...(b.profit?[b.profit]:[]),...(b.base?[b.base]:[]),...(b.assessment?[b.assessment]:[]),...(b.schedules?[b.schedules]:[]),...(b.costs?[b.costs]:[])]);}
|
||||
export function normalCurrent(d:Record<string,unknown>,b:NormalBook){return !!b.result&&b.result.key===key(d,b);}
|
||||
const zero=()=>fraction(0n,1n);
|
||||
const cents=(s:string)=>{if(!/^-?\d+(?:\.\d{1,2})?$/.test(s))throw Error('Montante MZN inválido;máximo dois decimais.');const v=decimalFraction(s.startsWith('-')?s.slice(1):s);return s.startsWith('-')?fraction(-BigInt(v.numerator),BigInt(v.denominator)):v;};
|
||||
@@ -71,18 +73,19 @@ export function calculateNormal(d:Record<string,unknown>,b:NormalBook):NormalMod
|
||||
if(rows.length===0&&!b.profit)pending.push('Sem ajustamentos documentados;ausência de registos não prova inexistência de correcções.');
|
||||
if(!b.profit)pending.push('L6 parcial:resultado ajustado apenas destes acréscimos;restantes custos/variações patrimoniais,amortizações,perdas,deduções e colecta por tratar. Lucro tributável,matéria colectável e imposto indeterminados.');
|
||||
const schedules=b.schedules?calculateSchedules(b.schedules,source,rows):undefined;
|
||||
const profit=b.profit?closeNormalProfit(b.profit,source,rows,accountingProfit,additions,contextReady,schedules):undefined;
|
||||
const costs=b.costs?calculateNormalCosts(b.costs,source,[...b.rows,...(b.schedules?.rows??[])],fiscalPeriod??{from:'2026-01-01',to:b.year+'-12-31'}):undefined;
|
||||
const profit=b.profit?closeNormalProfit(b.profit,source,rows,accountingProfit,additions,contextReady,schedules,costs):undefined;
|
||||
if(profit)pending.push(...profit.pending,...(!b.base?['Matéria colectável,perdas reportadas,deduções e imposto não apurados neste percurso.']:[]));
|
||||
const base=b.base?calculateNormalBase(b.base,profit?.taxableProfit??null,b.year,b.schedules?.currentLossReference):undefined;
|
||||
if(base)pending.push(...base.pending,...(!b.assessment?['Colecta/imposto a pagar,créditos anuais eM20/M22 não determinados;matéria colectável não é imposto.']:[]));
|
||||
const assessment=b.assessment?calculateNormalAssessment(b.assessment,base?.taxableBase??null,{year:b.year,scope:b.scope,extended:!!b.schedules,currency:source?.currency??'',...(fiscalPeriod?{period:{from:fiscalPeriod.from,to:fiscalPeriod.to}}:{})}):undefined;
|
||||
if(assessment)pending.push(...assessment.pending,assessment.finalBalance.reason);
|
||||
const currentLoss=b.schedules&&profit?.taxableProfit&&BigInt(profit.taxableProfit.numerator)<0n?fraction(-BigInt(profit.taxableProfit.numerator),BigInt(profit.taxableProfit.denominator)):null;
|
||||
return{...(schedules?{schedules,currentLoss,fiscalPeriod}:{}),...(assessment?{assessment,colecta:assessment.colecta}:{}),...(base?{base}:{}),...(profit?{profit}:{}),version:b.schedules?normalSchedulesVersion:b.assessment?normalAssessmentVersion:b.base?normalBaseVersion:b.profit?normalProfitVersion:irpcNormalVersion,year:b.year,currency:source?.currency??'',origin:'projected',accountingProfit,additions,partialAdjustedResult:contextReady&&rows.length>0&&rows.every(r=>r.status==='calculated')&&accountingProfit?addFractions([accountingProfit,additions]):null,taxableProfit:profit?.taxableProfit??null,taxableBase:base?.taxableBase??null,tax:null,rows,pending,source:irpcNormalSource+(b.schedules?' Mapas complementares: CIRPC18/21/22/24/36; Decreto72/2013 art3–7. EP: CIRPC15(1)c, apenas resultado imputável ao estabelecimento no regime geral, anual2026/MZN.':'')};
|
||||
return{...(costs?{costs}:{}),...(schedules?{schedules,currentLoss,fiscalPeriod}:{}),...(assessment?{assessment,colecta:assessment.colecta}:{}),...(base?{base}:{}),...(profit?{profit}:{}),version:b.costs?normalCostsVersion:b.schedules?normalSchedulesVersion:b.assessment?normalAssessmentVersion:b.base?normalBaseVersion:b.profit?normalProfitVersion:irpcNormalVersion,year:b.year,currency:source?.currency??'',origin:'projected',accountingProfit,additions,partialAdjustedResult:contextReady&&rows.length>0&&rows.every(r=>r.status==='calculated')&&accountingProfit?addFractions([accountingProfit,additions]):null,taxableProfit:profit?.taxableProfit??null,taxableBase:base?.taxableBase??null,tax:null,rows,pending,source:irpcNormalSource+(b.schedules?' Mapas complementares: CIRPC18/21/22/24/36; Decreto72/2013 art3–7. EP: CIRPC15(1)c, apenas resultado imputável ao estabelecimento no regime geral, anual2026/MZN.':'')};
|
||||
}
|
||||
export function recordNormal(d:Record<string,unknown>,b:NormalBook,now=new Date().toISOString()):NormalBook{return{...b,history:b.result?[...(b.history??[]),structuredClone(b.result)]:b.history??[],result:{key:key(d,b),createdAt:now,model:calculateNormal(d,b)}};}
|
||||
const money=(v:FiscalFraction|null)=>v?fractionPresentation(v).decimal+' MZN':'Indeterminado';
|
||||
function normalBaseReport(m:NormalBaseModel):Array<[string,string,string,string]>{return[['IRPC normal — L6','Matéria colectável — deduções/perdas',`Lucro tributável:${money(m.taxableProfit)};perdas propostas elegíveis:${money(m.requestedLosses)};perdas deduzidas nesta simulação:${money(m.deductedLosses)};benefícios deduzidos:${money(m.benefitDeduction)};matéria colectável simulada:${money(m.taxableBase)};imposto a pagar:Indeterminado. ${m.pending.join(' | ')}`,`NUIT:${m.input.subjectNuit};${m.input.identityReference};método:${m.input.method==='direct'?'Declarativo/directo':m.input.method==='indirect'?'Indirecto':'Por identificar'};${m.input.methodReference};perdas:${m.input.lossesState==='absent'?'Ausência declarada':m.input.lossesState==='known'?'Conhecidas':'Informação em falta'};${m.input.lossesReference};objecto:${m.input.unchangedObject==='yes'?'Sem alteração declarada':m.input.unchangedObject==='no'?'Alteração declarada':'Por esclarecer'};${m.input.objectReference};${m.source}`],['IRPC normal — L6','Benefícios à matéria colectável',`Estado:${m.input.benefits.state==='known'?'Valor conhecido':m.input.benefits.state==='absent'?'Ausência declarada':'Informação em falta'};montante original:${m.input.benefits.amount||'Não preenchido'};dedução:${money(m.benefitDeduction)};não inclui créditos à colecta ou retenções sofridas.`,m.input.benefits.reference],...m.losses.map(l=>['IRPC normal — L6','Perda fiscal de '+l.year,`Estado:${l.status==='eligible'?'Elegível neste recorte':l.status==='excluded'?'Excluída neste exercício':'Indeterminada'};apurada:${l.assessed};utilizada antes:${l.previouslyUsed};saldo:${money(l.available)};utilização proposta:${l.requested};dedução nesta simulação:${money(l.deducted)};saldo aritmético posterior:${money(l.remaining)};último exercício ordinário:${l.lastOrdinaryYear??'Por determinar'};${l.reason}`,`NUIT:${l.subjectNuit};${l.reference};sem consumo do saldo oficial nem reporte automático.`]as[string,string,string,string])];}
|
||||
function normalAssessmentReport(m:NormalAssessmentModel):Array<[string,string,string,string]>{return[['IRPC normal — L6','Colecta — regime normal',`Matéria colectável:${money(m.taxableBase)};taxa:${m.rate?m.rate.numerator+'%':'Por identificar'};colecta simulada:${money(m.colecta)};${m.classification};${m.centExact===false?'Valor racional exacto com fracção de cêntimo;sem arredondamento fiscal aplicado.':''}${m.pending.join(' | ')}`,`Exercício:${m.period.year};período:${m.period.from}–${m.period.to};${m.input.reference};${m.rule};${m.source}`],['IRPC normal — L6','Saldo final — âmbito L8','A pagar:Indeterminado;a recuperar:Indeterminado. '+m.finalBalance.reason,'Sequência:lucro tributável → matéria colectável → colecta → saldo final;retenções,pagamentos e créditos anuais reservados aL8.']];}
|
||||
export function normalReportRows(d:Record<string,unknown>):Array<[string,string,string,string]>{try{const b=normalBook(d),r=b.result;if(!r)return[];const m=r.model;return[['IRPC normal — L6','Ajustamentos ao resultado financeiro',`Exercício:${m.year};resultado contabilístico antes de imposto:${money(m.accountingProfit)};acréscimos demonstrados:${money(m.additions)};resultado parcial ajustado:${money(m.partialAdjustedResult)};${m.profit?'lucro tributável simulado:'+money(m.taxableProfit)+(m.base?';matéria colectável simulada:'+money(m.taxableBase)+(m.assessment?';colecta simulada:'+money(m.colecta??null)+';saldo final a pagar ou recuperar:Indeterminado (L8).':';imposto a pagar:Indeterminado.'):';matéria colectável/imposto:Indeterminados.'):'lucro tributável/matéria colectável/imposto:Indeterminados.'} Projecção com pressupostos;${m.pending.join(' | ')}`,`${normalCurrent(d,b)?'Entradas correspondentes':'Histórico;origem ou entradas alteradas'};${r.createdAt};${m.source}`],...(m.base?normalBaseReport(m.base):[]),...(m.assessment?normalAssessmentReport(m.assessment):[]),...(m.schedules?[['IRPC normal — L6','Mapas complementares',`Efeito líquido adicional:${money(m.schedules.net)};perda fiscal do próprio exercício:${money(m.currentLoss??null)};sem reporte ou consumo automático. ${m.schedules.pending.join(' | ')}`,m.schedules.source]as[string,string,string,string],...m.schedules.effects.map(e=>{const i=m.schedules!.input.rows.find(r=>r.id===e.id)!;return['IRPC normal — L6',i.document+'/'+i.line,`Montante:${money(e.amount)};efeito:${money(e.effect)};${e.reason}`,`${i.reference};exercício de imputação:${i.fiscalYear||'Não aplicável'}`]as[string,string,string,string];}),...m.schedules.assets.map(a=>['IRPC normal — L6',a.description,`Depreciação contabilística:${money(a.accountingValue)};quota fiscal:${money(a.fiscal)};acréscimo:${money(a.adjustment)};${a.reason}`,`${a.tableReference};${a.reference}`]as[string,string,string,string]),['IRPC normal — L6','Juros financeiros ordinários',`Remanescente aceite no recorte:${money(m.schedules.interest)};não corresponde a juros de mora/compensatórios L5.`,m.schedules.input.interest.reference]as[string,string,string,string]]:[]),...(m.schedules?[...normalFormFields(m).fields.map(f=>['IRPC normal — L6','M22 campo '+f.code+' — '+f.label,`${f.display};exacto:${f.value?f.value.numerator+'/'+f.value.denominator:'Indeterminado'};transcrição:${f.transcriptionReady?'Preparação parcial com valor exacto ao cêntimo':'Pendente ou sem aplicação'};${f.reason}`,normalFormFields(m).source]as[string,string,string,string]),...normalFormFields(m).m20.fields.map(f=>['IRPC normal — L6','M20 A.1 — conta '+f.account+' — '+f.label,`${f.display};representação em meticais inteiros:${f.transcriptionReady?'Exacta':'Pendente'};${f.reason}`,`${f.reference};${normalFormFields(m).m20.source}`]as[string,string,string,string]),['IRPC normal — L6','M20 — correspondência',normalFormFields(m).m20.reason,normalFormFields(m).m20.source]as[string,string,string,string]]:[]),...m.rows.map(r=>['IRPC normal — L6',r.document+'/'+r.line,`${({calculated:'Calculado neste recorte',indeterminate:'Indeterminado',outside_scope:'Fora deste recorte'})[r.status]};montante:${r.amount};acréscimo:${money(r.adjustment)};${r.reason}`,`Origem:${({revenue:'Receitas',cost_of_sales:'Custo das vendas',operating_expenses:'Despesas operacionais',personnel:'Pessoal',depreciation:'Depreciação contabilística',interest_expense:'Juros reconhecidos'}as Record<string,string>)[r.sourceCode]??'Por corresponder'};incluído:${r.included==='yes'?'Sim':r.included==='no'?'Não':'Por demonstrar'};suporte:${r.reference}`]as[string,string,string,string]),...(m.profit?[...m.profit.lines.map(l=>['IRPC normal — L6','Conferência: '+l.label,`Estado:${l.state==='known'?'Valor conhecido':l.state==='absent'?'Ausência declarada':'Informação em falta'};origem:${l.amount};acréscimos:${money(l.addedBack)};remanescente:${money(l.remainder)};${l.reason}`,l.reference]as[string,string,string,string]),...m.profit.topics.map(t=>['IRPC normal — L6',t.label,`Estado:${t.state==='known'?'Valor conhecido':t.state==='absent'?'Ausência declarada':'Informação em falta'};montante:${money(t.value)};${t.reason}`,t.reference]as[string,string,string,string]),['IRPC normal — L6','Âmbito da conferência',m.profit.input.completenessReference,m.profit.source]as[string,string,string,string]]:[])];}catch(e){return[['IRPC normal — L6','Não recuperável',String(e),'Originais conservados.']];}}
|
||||
export function normalReportRows(d:Record<string,unknown>):Array<[string,string,string,string]>{try{const b=normalBook(d),r=b.result;if(!r)return[];const m=r.model;return[['IRPC normal — L6','Ajustamentos ao resultado financeiro',`Exercício:${m.year};resultado contabilístico antes de imposto:${money(m.accountingProfit)};acréscimos demonstrados:${money(m.additions)};resultado parcial ajustado:${money(m.partialAdjustedResult)};${m.profit?'lucro tributável simulado:'+money(m.taxableProfit)+(m.base?';matéria colectável simulada:'+money(m.taxableBase)+(m.assessment?';colecta simulada:'+money(m.colecta??null)+';saldo final a pagar ou recuperar:Indeterminado (L8).':';imposto a pagar:Indeterminado.'):';matéria colectável/imposto:Indeterminados.'):'lucro tributável/matéria colectável/imposto:Indeterminados.'} Projecção com pressupostos;${m.pending.join(' | ')}`,`${normalCurrent(d,b)?'Entradas correspondentes':'Histórico;origem ou entradas alteradas'};${r.createdAt};${m.source}`],...(m.costs?normalCostReport(m.costs):[]),...(m.base?normalBaseReport(m.base):[]),...(m.assessment?normalAssessmentReport(m.assessment):[]),...(m.schedules?[['IRPC normal — L6','Mapas complementares',`Efeito líquido adicional:${money(m.schedules.net)};perda fiscal do próprio exercício:${money(m.currentLoss??null)};sem reporte ou consumo automático. ${m.schedules.pending.join(' | ')}`,m.schedules.source]as[string,string,string,string],...m.schedules.effects.map(e=>{const i=m.schedules!.input.rows.find(r=>r.id===e.id)!;return['IRPC normal — L6',i.document+'/'+i.line,`Montante:${money(e.amount)};efeito:${money(e.effect)};${e.reason}`,`${i.reference};exercício de imputação:${i.fiscalYear||'Não aplicável'}`]as[string,string,string,string];}),...m.schedules.assets.map(a=>['IRPC normal — L6',a.description,`Depreciação contabilística:${money(a.accountingValue)};quota fiscal:${money(a.fiscal)};acréscimo:${money(a.adjustment)};${a.reason}`,`${a.tableReference};${a.reference}`]as[string,string,string,string]),['IRPC normal — L6','Juros financeiros ordinários',`Remanescente aceite no recorte:${money(m.schedules.interest)};não corresponde a juros de mora/compensatórios L5.`,m.schedules.input.interest.reference]as[string,string,string,string]]:[]),...(m.schedules?[...normalFormFields(m).fields.map(f=>['IRPC normal — L6','M22 campo '+f.code+' — '+f.label,`${f.display};exacto:${f.value?f.value.numerator+'/'+f.value.denominator:'Indeterminado'};transcrição:${f.transcriptionReady?'Preparação parcial com valor exacto ao cêntimo':'Pendente ou sem aplicação'};${f.reason}`,normalFormFields(m).source]as[string,string,string,string]),...normalFormFields(m).m20.fields.map(f=>['IRPC normal — L6','M20 A.1 — conta '+f.account+' — '+f.label,`${f.display};representação em meticais inteiros:${f.transcriptionReady?'Exacta':'Pendente'};${f.reason}`,`${f.reference};${normalFormFields(m).m20.source}`]as[string,string,string,string]),['IRPC normal — L6','M20 — correspondência',normalFormFields(m).m20.reason,normalFormFields(m).m20.source]as[string,string,string,string]]:[]),...m.rows.map(r=>['IRPC normal — L6',r.document+'/'+r.line,`${({calculated:'Calculado neste recorte',indeterminate:'Indeterminado',outside_scope:'Fora deste recorte'})[r.status]};montante:${r.amount};acréscimo:${money(r.adjustment)};${r.reason}`,`Origem:${({revenue:'Receitas',cost_of_sales:'Custo das vendas',operating_expenses:'Despesas operacionais',personnel:'Pessoal',depreciation:'Depreciação contabilística',interest_expense:'Juros reconhecidos'}as Record<string,string>)[r.sourceCode]??'Por corresponder'};incluído:${r.included==='yes'?'Sim':r.included==='no'?'Não':'Por demonstrar'};suporte:${r.reference}`]as[string,string,string,string]),...(m.profit?[...m.profit.lines.map(l=>['IRPC normal — L6','Conferência: '+l.label,`Estado:${l.state==='known'?'Valor conhecido':l.state==='absent'?'Ausência declarada':'Informação em falta'};origem:${l.amount};acréscimos:${money(l.addedBack)};remanescente:${money(l.remainder)};${l.reason}`,l.reference]as[string,string,string,string]),...m.profit.topics.map(t=>['IRPC normal — L6',t.label,`Estado:${t.state==='known'?'Valor conhecido':t.state==='absent'?'Ausência declarada':'Informação em falta'};montante:${money(t.value)};${t.reason}`,t.reference]as[string,string,string,string]),['IRPC normal — L6','Âmbito da conferência',m.profit.input.completenessReference,m.profit.source]as[string,string,string,string]]:[])];}catch(e){return[['IRPC normal — L6','Não recuperável',String(e),'Originais conservados.']];}}
|
||||
export function normalHtml(d:Record<string,unknown>){const b=normalBook(d);if(!normalCurrent(d,b))throw Error('Calcule e guarde as entradas actuais antes de exportar.');const esc=(s:string)=>s.replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]!));return'<!doctype html><html lang="pt"><meta charset="utf-8"><title>IRPC normal — ajustamentos</title><h1>L6 — mapa parcial de ajustamentos</h1><p>Projecção pelos pressupostos do plano. Não é demonstração contabilística,liquidação ou Modelo20/22.</p>'+normalReportRows(d).map(r=>'<section><h2>'+esc(r[1])+'</h2><p>'+esc(r[2])+'</p><small>'+esc(r[3])+'</small></section>').join('')+'</html>';}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
<?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 IrpcNormalCostsDraftTest extends TestCase
|
||||
{
|
||||
use DatabaseTransactions;
|
||||
public function test_cost_limits_provisions_and_timing_recover_their_facts_results_and_original_histories(): 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()]));
|
||||
$expected=['calculated'=>['6730','4430','7088'],'additional'=>['7050','4750','1520'],'limits'=>['7210','4910','7856'],'closing'=>['6530','4230','6768']];$id=null;$etag=null;
|
||||
foreach(['calculated','missing','additional','limits','closing']as$case){
|
||||
$d=json_decode(file_get_contents(base_path('outputs/irpc-l6-costs/'.$case.'.json')),true,flags:JSON_THROW_ON_ERROR);$p=['person_type'=>$d['person_type']];unset($d['person_type']);
|
||||
if($id===null)$id=$this->postJson('/api/v1/simulation-drafts',['personal_snapshot'=>$p,'economic_snapshot'=>$d],['Idempotency-Key'=>(string)Str::uuid()])->assertCreated()->json('data.id');
|
||||
else $this->putJson('/api/v1/simulation-drafts/'.$id,['personal_snapshot'=>$p,'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'];
|
||||
$this->assertSame('IRPC-normal-adjustments/6',$m['version']);$this->assertArrayHasKey('irpcWithholdings',$s);$this->assertArrayHasKey('books',$s);
|
||||
$this->assertContains('IRPC-normal-adjustments/5',array_column(array_column($b['history'],'model'),'version'));$this->assertSame($b['costs'],$m['costs']['input']);
|
||||
$this->assertNull($m['tax']);$this->assertNull($m['assessment']['finalBalance']['payable']);$this->assertNull($m['assessment']['finalBalance']['recoverable']);
|
||||
if($case==='missing'){$this->assertNull($m['taxableProfit']);$this->assertNull($m['taxableBase']);$this->assertNull($m['colecta']);$this->assertSame('520',$m['schedules']['net']['numerator']);$this->assertSame('40',$m['costs']['rows'][1]['adjustment']['numerator']);}
|
||||
else{[$profit,$base,$tax]=$expected[$case];$this->assertSame($profit,$m['taxableProfit']['numerator']);$this->assertSame($base,$m['taxableBase']['numerator']);$this->assertSame(['numerator'=>$tax,'denominator'=>$case==='additional'?'1':'5'],$m['colecta']);$this->assertSame([],$m['costs']['pending']);}
|
||||
file_put_contents(base_path('outputs/irpc-l6-costs/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,40 @@
|
||||
import{calculateNormalBase}from'../../resources/js/lib/irpc-normal-base.ts';import{fraction}from'../../resources/js/lib/fiscal-rational.ts';
|
||||
import test from 'node:test';import assert from 'node:assert/strict';import fs from 'node:fs';
|
||||
import{normalBook,normalCurrent,calculateNormal,recordNormal,saveNormalBook,normalHtml,normalReportRows,normalFinancialSource}from'../../resources/js/lib/irpc-normal.ts';
|
||||
import{newNormalCosts,newCostRow,calculateNormalCosts,reusableCreditBalance,costRules,normalCostReport}from'../../resources/js/lib/irpc-normal-costs.ts';
|
||||
import{createProjectDossierReport}from'../../resources/js/lib/project-dossier-report.ts';
|
||||
const seed=()=>JSON.parse(fs.readFileSync(new URL('./fixtures/irpc-schedules-preserved.json',import.meta.url),'utf8'));const val=f=>f?Number(f.numerator)/Number(f.denominator):null;
|
||||
function row(id,rule,amount,facts={},sourceCode='operating_expenses'){return{...newCostRow(id),rule,amount,sourceCode,document:'QA-'+id,line:'1',reference:'QA documento e factos qualificados para o período anual2026; valores sintéticos',conditions:'yes',facts};}
|
||||
function reconcile(b){const totals={};for(const r of b.schedules.rows){const k={tax_own:'expenseCorrections',tax_other:'expenseCorrections',undocumented:'expenseCorrections',capital_in:'positivePatrimonial',patrimonial_gain:'positivePatrimonial',capital_out:'negativePatrimonial',patrimonial_cost:'negativePatrimonial',future_cost:'timing',future_income:'timing',current_cost:'timing',current_income:'timing'}[r.rule];totals[k]=(totals[k]??0)+Number(r.amount);}for(const r of b.costs.rows){const k=costRules[r.rule][1];totals[k]=(totals[k]??0)+Number(r.amount);}for(const[k,a]of Object.entries(totals))b.profit.topics[k]={state:'known',amount:String(a),reference:'QA reconciliação aritmética por documento/linha dos mapas'};}
|
||||
function setup(rows){const d=seed(),b=normalBook(d);b.costs=newNormalCosts();b.costs.rows=rows;b.costs.advertisingReference='QA publicidade completa e vendas normais12000 integralmente reconciliadas';b.costs.salesOrdinary='yes';b.costs.creditRegime='ordinary';b.costs.creditBase='10000';b.costs.creditPrevious='550';b.costs.creditReference='QA créditos ordinários finais10000 e provisão fiscal anterior550 documentados, sem regime financeiro';reconcile(b);return{d,b};}
|
||||
function joint(){return setup([
|
||||
row('allowance','allowance','100',{billed:'20',taxedNotBilled:'20'}),row('representation','representation','50'),row('advertising','advertising','80',{campaign:'no'}),row('credit','credit','200'),
|
||||
row('stock','stock','60',{balanceKey:'inventory-A',lossObserved:'yes',observedLoss:'40',previous:'10'}),row('litigation','litigation','90',{balanceKey:'court-A',caseOngoing:'yes',chargeDeductible:'yes',obligation:'100',previous:'20'}),row('fuel','fuel','50',{supportedNormal:'40',passenger:'no'}),row('prior','prior_cost','80',{originalYear:'2025',unknownAtClose:'no',closingEvidence:'QA factura conhecida no fecho2025'}),
|
||||
row('work','work','1000',{projectKey:'project-A',method:'percentage',price:'2000',totalCost:'1000',incurred:'500',priorRevenue:'600',priorCost:'300',bookCost:'200',costSource:'cost_of_sales',partialBilling:'yes',ownFractionalDelivery:'no',consistentMethod:'yes',deferredReceipt:'0'},'revenue'),
|
||||
]);}
|
||||
function model(r){const{d,b}=setup([r]);return calculateNormal(d,b);}
|
||||
const item=m=>m.costs.rows[0];
|
||||
test('compatible costs/provisions/prior timing/work produce exact profit6730 base4430 colecta1417.60 from existing6240 result',()=>{const{d,b}=joint(),m=calculateNormal(d,b);assert.equal(val(m.accountingProfit),6240);assert.equal(val(m.schedules.net),520);assert.equal(val(m.costs.net),-250);assert.equal(val(m.taxableProfit),6730);assert.equal(val(m.taxableBase),4430);assert.equal(val(m.colecta),1417.6);assert.equal(m.version,'IRPC-normal-adjustments/6');assert.equal(m.tax,null);assert.equal(m.assessment.finalBalance.payable,null);assert.deepEqual(m.costs.rows.map(r=>val(r.adjustment)),[30,40,0,150,30,10,10,80,-600]);assert.equal(m.costs.pending.length,0);});
|
||||
test('allowance exceptions are disjoint amounts; accepted70 plus addback30; known zero differs from missing',()=>{let m=model(row('e','allowance','100',{billed:'20',taxedNotBilled:'20'}));assert.equal(val(item(m).accepted),70);assert.equal(val(item(m).adjustment),30);for(const facts of[{billed:'0',taxedNotBilled:'0'},{billed:'100',taxedNotBilled:'0'}])assert.notEqual(model(row('e','allowance','100',facts)).taxableProfit,null);for(const facts of[{billed:'',taxedNotBilled:'0'},{billed:'60',taxedNotBilled:'50'}])assert.equal(model(row('e','allowance','100',facts)).taxableProfit,null);});
|
||||
test('representation20percent accepted; fractional cents remain exact without fiscal rounding or autonomous tax',()=>{const m=model(row('f','representation','0.01'));assert.deepEqual(item(m).accepted,{numerator:'1',denominator:'500'});assert.deepEqual(item(m).adjustment,{numerator:'1',denominator:'125'});assert.equal(m.tax,null);assert.equal(m.assessment.finalBalance.scope,'L8');});
|
||||
test('advertising shares one annual1percent cap across documents and has no per-invoice duplicate allowance',()=>{const{d,b}=setup([row('n1','advertising','100',{campaign:'no'}),row('n2','advertising','100',{campaign:'no'})]);let m=calculateNormal(d,b);assert.deepEqual(m.costs.rows.map(r=>val(r.accepted)),[60,60]);assert.equal(val(m.costs.net),80);assert.deepEqual(m.costs.rows.map(r=>val(r.limit)),[120,120]);b.costs.rows.reverse();assert.equal(val(calculateNormal(d,b).costs.net),80);b.costs.salesOrdinary='';m=calculateNormal(d,b);assert.equal(m.taxableProfit,null);assert.equal(m.schedules.net.numerator,'520');const c=setup([row('ad-a','advertising','100',{campaign:'no'}),row('ad-b','advertising','250',{campaign:'no'})]);const f=calculateNormal(c.d,c.b);assert.match(JSON.stringify(normalCostReport(f.costs)),/exacto 240\/7/);});
|
||||
test('campaign36A never silently receives ordinary advertising treatment',()=>{const m=model(row('campaign','advertising','100',{campaign:'yes'}));assert.equal(m.taxableProfit,null);assert.equal(item(m).accepted,null);assert.match(item(m).reason,/36-A/);});
|
||||
test('vehicle costs distinguish ordinary passenger50percent, documented public/rental exception and nonpassenger',()=>{for(const[facts,accepted]of[[{passenger:'yes',publicOrRental:'no'},50],[{passenger:'yes',publicOrRental:'yes'},100],[{passenger:'no'},100]])assert.equal(val(item(model(row('v','vehicle','100',facts))).accepted),accepted);assert.equal(model(row('v','vehicle','100',{passenger:'yes'})).taxableProfit,null);});
|
||||
test('fuel and rent cover independent rules; overlapping36h-i and36-4 stays pending without an invented combination',()=>{let m=model(row('fuel','fuel','100',{supportedNormal:'80',passenger:'yes',publicOrRental:'no'}));assert.equal(m.taxableProfit,null);assert.match(item(m).reason,/Articulação/);m=model(row('fuel','fuel','100',{supportedNormal:'100',passenger:'yes',publicOrRental:'no'}));assert.equal(val(item(m).accepted),50);m=model(row('rent','rental','100',{nonAcceptedQuota:'20',quotaReference:'QA quota36h documentada; não calculada automaticamente',passenger:'yes',publicOrRental:'yes'}));assert.equal(val(item(m).accepted),80);assert.equal(val(item(m).adjustment),20);assert.match(item(m).reason,/não apura automaticamente/);assert.equal(model(row('fuel','fuel','100',{supportedNormal:'101',passenger:'no'})).taxableProfit,null);assert.equal(model(row('rent','rental','100',{nonAcceptedQuota:'',quotaReference:'QA',passenger:'yes',publicOrRental:'no'})).taxableProfit,null);});
|
||||
test('credit provision caps both annual1.5percent and cumulative6percent; multiple rows share headroom and do not consume source balance',()=>{const{d,b}=setup([row('c1','credit','100'),row('c2','credit','100')]);let m=calculateNormal(d,b);assert.deepEqual(m.costs.rows.map(r=>val(r.accepted)),[25,25]);assert.equal(val(m.costs.net),150);assert.equal(b.costs.creditPrevious,'550');b.costs.creditPrevious='0';m=calculateNormal(d,b);assert.equal(val(m.costs.net),50);b.costs.creditPrevious='600';assert.equal(val(calculateNormal(d,b).costs.net),200);b.costs.creditPrevious='601';assert.equal(calculateNormal(d,b).taxableProfit,null);b.costs.creditRegime='regulated';assert.equal(calculateNormal(d,b).taxableProfit,null);b.costs.creditRegime='ordinary';b.costs.creditPrevious='';assert.equal(calculateNormal(d,b).taxableProfit,null);});
|
||||
test('inventory allowance respects observed loss less existing fiscal provision; absent observation is known rejection, not unknown zero',()=>{let m=model(row('s','stock','60',{balanceKey:'stock',lossObserved:'yes',observedLoss:'40',previous:'10'}));assert.equal(val(item(m).accepted),30);assert.equal(val(item(m).adjustment),30);m=model(row('s','stock','60',{lossObserved:'no'}));assert.equal(val(item(m).accepted),0);assert.equal(val(item(m).adjustment),60);m=model(row('s','stock','60',{balanceKey:'stock',lossObserved:'yes',observedLoss:'40',previous:''}));assert.equal(m.taxableProfit,null);});
|
||||
test('litigation needs current court process and deductible underlying charge; known failure adds back full amount',()=>{for(const facts of[{caseOngoing:'no'},{caseOngoing:'yes',chargeDeductible:'no'}]){const m=model(row('l','litigation','90',facts));assert.equal(val(item(m).accepted),0);assert.equal(val(item(m).adjustment),90);}const m=model(row('l','litigation','90',{caseOngoing:'yes',chargeDeductible:'yes',balanceKey:'court',obligation:'100',previous:'20'}));assert.equal(val(item(m).accepted),80);assert.equal(model(row('l','litigation','90',{caseOngoing:'yes',chargeDeductible:''})).taxableProfit,null);});
|
||||
test('reversal before deducted is taxed once; taxed-provision reversal is excluded only with prior fiscal evidence',()=>{let m=model(row('r','provision_reversal','50',{balanceKey:'old',previous:'70',eventEnded:'yes',inResult:'yes'},'revenue'));assert.equal(val(item(m).adjustment),0);m=model(row('r','provision_reversal','50',{balanceKey:'old',previous:'70',eventEnded:'yes',inResult:'no'},''));assert.equal(val(item(m).adjustment),50);m=model(row('r','taxed_reversal','50',{balanceKey:'old',previous:'70',eventEnded:'yes',inResult:'yes'},'revenue'));assert.equal(val(item(m).adjustment),-50);assert.equal(model(row('r','taxed_reversal','80',{balanceKey:'old',previous:'70',eventEnded:'yes',inResult:'yes'},'revenue')).taxableProfit,null);});
|
||||
test('same fiscal object cannot reuse stock/litigation/reversal balance with another document',()=>{const{d,b}=setup([row('s1','stock','50',{balanceKey:'same',lossObserved:'yes',observedLoss:'100',previous:'0'}),row('s2','stock','50',{balanceKey:'same',lossObserved:'yes',observedLoss:'100',previous:'0'})]);const m=calculateNormal(d,b);assert.equal(m.taxableProfit,null);assert.ok(m.costs.rows.every(r=>r.adjustment===null));});
|
||||
test('prior-period cost and income distinguish knowledge at previous closing; never edit earlier returns or recorded histories',()=>{for(const[rule,src,unknown,expected]of[['prior_cost','operating_expenses','yes',0],['prior_cost','operating_expenses','no',100],['prior_income','revenue','yes',0],['prior_income','revenue','no',-100]]){const m=model(row('p',rule,'100',{originalYear:'2025',unknownAtClose:unknown,closingEvidence:'QA fecho anterior'},src));assert.equal(val(item(m).adjustment),expected);}assert.equal(model(row('p','prior_cost','100',{originalYear:'2025',unknownAtClose:'',closingEvidence:'QA'})).taxableProfit,null);});
|
||||
test('work percentage uses accumulated performance less prior recognized revenue and cost, no double recognition',()=>{const{d,b}=joint(),m=calculateNormal(d,b),w=m.costs.rows.at(-1);assert.equal(val(w.limit),400);assert.equal(val(w.accepted),200);assert.equal(val(w.adjustment),-600);assert.match(w.reason,/grau=incorrido/);assert.equal(b.costs.rows.at(-1).facts.priorRevenue,'600');});
|
||||
test('closing work respects mandatory percentage,95percent threshold and missing facts; estimated loss is separate full addback',()=>{const{d,b}=joint(),w=b.costs.rows.at(-1);w.facts.method='closing';assert.equal(calculateNormal(d,b).taxableProfit,null);w.facts.partialBilling='no';w.facts.priorRevenue='0';w.facts.priorCost='0';w.facts.publicReception='no';let m=calculateNormal(d,b);assert.equal(val(m.costs.rows.at(-1).limit),0);w.facts.incurred='950';m=calculateNormal(d,b);assert.equal(val(m.costs.rows.at(-1).limit),2000);w.facts.deferredReceipt='1';assert.equal(calculateNormal(d,b).taxableProfit,null);assert.equal(val(item(model(row('estimate','estimated_work_loss','100'))).adjustment),100);});
|
||||
test('cross-map duplicate identity and cumulative source over-allocation block only dependent totals',()=>{const{d,b}=joint();b.costs.rows[0].document=b.rows[0].document+' ';b.costs.rows[0].line=b.rows[0].line;let m=calculateNormal(d,b);assert.equal(m.taxableProfit,null);assert.match(m.costs.rows[0].reason,/repetidos/);assert.equal(m.costs.rows[0].adjustment,null);assert.equal(val(m.costs.rows[1].adjustment),40);const c=joint();c.b.costs.rows[0].amount='1000';reconcile(c.b);m=calculateNormal(c.d,c.b);assert.equal(m.taxableProfit,null);assert.match(m.costs.pending.join(' '),/excedem a linha/);});
|
||||
test('missing one credit parameter retains known independent corrections and makes downstream totals unknown; upstream records remain',()=>{const{d,b}=joint();b.costs.creditPrevious='';const m=calculateNormal(d,b);assert.equal(m.taxableProfit,null);assert.equal(m.taxableBase,null);assert.equal(m.colecta,null);assert.equal(val(m.accountingProfit),6240);assert.equal(val(m.costs.rows[1].adjustment),40);assert.equal(val(m.schedules.net),520);assert.equal(m.schedules.pending.length,0);});
|
||||
test('credit reuse requires known financial closing balances; absent schedule never creates zero',()=>{assert.equal(reusableCreditBalance(null),null);assert.equal(reusableCreditBalance({cashSchedule:[{salesReceivablesBalance:null,openingReceivablesBalance:'0'}]}),null);assert.equal(val(reusableCreditBalance({cashSchedule:[{salesReceivablesBalance:'100',openingReceivablesBalance:'20'}]})),120);});
|
||||
test('installed5 stays current without recalculation; version6 API-ready snapshots preserve books/history and export exact rules/amounts',()=>{const old=seed(),before=normalBook(old);assert.equal(normalCurrent(old,before),true);assert.equal(before.result.model.version,'IRPC-normal-adjustments/5');const{d,b}=joint(),store=JSON.parse(d.modelo_a_snapshot);d.modelo_a_snapshot=saveNormalBook(d,recordNormal(d,b,'2026-10-26T00:30:00+02:00'));const back=normalBook(JSON.parse(JSON.stringify(d)));assert.equal(normalCurrent(d,back),true);assert.deepEqual(back.history.at(-1),before.result);const saved=JSON.parse(d.modelo_a_snapshot);assert.deepEqual(saved.books,store.books);assert.deepEqual(saved.irpcWithholdings,store.irpcWithholdings);assert.match(normalHtml(d),/1417.60/);assert.match(normalHtml(d),/parcela aceite/);assert.match(JSON.stringify(createProjectDossierReport(d,{selectedSections:new Set(['finance'])})),/36\(1\)n/);assert.match(JSON.stringify(normalReportRows(d)),/M20 — correspondência/);fs.mkdirSync('outputs/irpc-l6-costs',{recursive:true});fs.writeFileSync('outputs/irpc-l6-costs/calculated.json',JSON.stringify(d));fs.writeFileSync('outputs/irpc-l6-costs/preparation.html',normalHtml(d));const missing=normalBook(d);missing.costs.creditPrevious='';d.modelo_a_snapshot=saveNormalBook(d,recordNormal(d,missing));fs.writeFileSync('outputs/irpc-l6-costs/missing.json',JSON.stringify(d));const changed=normalBook(d);changed.costs.rows[0].facts.billed='30';d.modelo_a_snapshot=saveNormalBook(d,changed);assert.equal(normalCurrent(d,normalBook(d)),false);assert.throws(()=>normalHtml(d));});
|
||||
test('malformed versions, unknown rules and non-string facts are refused with saved originals intact',()=>{const{d,b}=joint();for(const change of[x=>x.version=2,x=>x.rows[0].rule='__proto__',x=>x.rows[0].facts.billed=20]){const c=structuredClone(b);change(c.costs);assert.throws(()=>saveNormalBook(d,c));}assert.equal(normalCurrent(d,normalBook(d)),true);});
|
||||
|
||||
test('additional categories,shared limits and closing method generate separate recoverable compatible cases',()=>{const cases={additional:setup([row('vehicle','vehicle','100',{passenger:'yes',publicOrRental:'no'}),row('rental','rental','100',{nonAcceptedQuota:'20',quotaReference:'QA quota36h documentada',passenger:'yes',publicOrRental:'yes'}),row('estimate','estimated_work_loss','50'),row('reverse','provision_reversal','50',{balanceKey:'provision-old-deducted',previous:'50',eventEnded:'yes',inResult:'no'},''),row('taxed-reverse','taxed_reversal','50',{balanceKey:'provision-old-taxed',previous:'50',eventEnded:'yes',inResult:'yes'},'revenue'),row('prior-income','prior_income','50',{originalYear:'2025',unknownAtClose:'no',closingEvidence:'QA receita conhecida no encerramento anterior'},'revenue')]),limits:setup([row('ad1','advertising','100',{campaign:'no'}),row('ad2','advertising','100',{campaign:'no'}),row('cr1','credit','100'),row('cr2','credit','100')]),closing:joint()};const w=cases.closing.b.costs.rows.at(-1);Object.assign(w.facts,{method:'closing',partialBilling:'no',priorRevenue:'0',priorCost:'0',publicReception:'no'});const expected={additional:[7050,4750,1520],limits:[7210,4910,1571.2],closing:[6530,4230,1353.6]};for(const[name,{d,b}]of Object.entries(cases)){const m=calculateNormal(d,b);assert.deepEqual([val(m.taxableProfit),val(m.taxableBase),val(m.colecta)],expected[name]);d.modelo_a_snapshot=saveNormalBook(d,recordNormal(d,b));fs.writeFileSync('outputs/irpc-l6-costs/'+name+'.json',JSON.stringify(d));}});
|
||||
|
||||
test('percentage-generated loss below one cent remains a negative fiscal result, never a silently truncated zero',()=>{const{b}=joint();b.base.lossesState='absent';b.base.losses=[];assert.equal(calculateNormalBase(b.base,fraction(-1n,1000n),'2026').taxableBase,null);assert.equal(val(calculateNormalBase(b.base,fraction(-1n,1000n),'2026','QA perda corrente exacta').taxableBase),0);assert.equal(val(calculateNormalBase(b.base,fraction(1n,1000n),'2026').taxableBase),0.001);});
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
$root=dirname(__DIR__);
|
||||
$operation=$argv[1]??'';
|
||||
if(!in_array($operation,['routes','test'],true)||basename($root)!=='portal-investidor-irpc-l6-costs-qa-20261026')throw new RuntimeException('Explicit reliefs QA workspace and routes/test operation required.');
|
||||
foreach(['storage/framework/views','storage/framework/cache','storage/framework/sessions','bootstrap/cache','storage/logs','outputs/irpc-l6-costs']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/IrpcNormalCostsDraftTest.php'];$argc=count($argv);$_SERVER['argv']=$argv;$_SERVER['argc']=$argc;require $root.'/vendor/phpunit/phpunit/phpunit';
|
||||
Reference in New Issue
Block a user