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 = { 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; book: NormalBook; save: (b: NormalBook) => void; }) { const [review, setReview] = useState(false), [error, setError] = useState(''), i = book.costs, source = normalFinancialSource(draft); if (!i) return ( ); const patch = (p: Partial) => { setReview(false); save({ ...book, costs: { ...i, ...p } }); }; const row = (id: string, p: Partial) => 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(); 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 )[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 (
Custos com limites, provisões e periodização — exercício2026

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.

{error &&

{error}

} {i.rows.some((r) => r.rule === 'advertising') && (
Publicidade — limite único anual

Volume de vendas da origem:{' '} {source?.accountingLines.find( (l) => l.code === 'revenue', )?.total ?? 'Em falta'}{' '} MZN. O limite não se repete por factura.

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.

)} {i.rows.some((r) => r.rule === 'credit') && (
Créditos — limites anual e acumulado {reusableCreditBalance(source) && ( )}

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.

)} {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 (
Parcela {n + 1} — {r.document || 'Por identificar'} {( ['document', 'line', 'amount', 'reference'] as const ).map((k) => ( ))} {!omitted && ( <> )} {(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]) => ( ))}
); })} {book.profit && ( <> {review && (

Reveja as somas brutas antes de aplicar; não alteram os factos, o resultado financeiro ou os históricos.

{summaries().map((t) => (

{ { 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

))}
)} )}

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.

); }