import { subtractFractions, decimalFraction, type FiscalFraction, } from './fiscal-rational.ts'; import { validateProofParcel, requireProofParcel, type FiscalProofParcel, } from './fiscal-proof-parcel.ts'; export const pecVersion = 'IRPC-PEC/1'; export const pecSource = 'CIRPC, art. 66(1–2), Lei 34/2007, exemplar BR52, p. 768-(72): dedução após créditos anteriores, até à colecta, sem reembolso do PEC. Recorte demonstrado: pagamento efectivo do próprio exercício 2026, residente geral, ano civil, MZN. Não calcula a obrigação PEC, transporte, caducidade ou consumo de saldos oficiais.'; export type PecPayment = FiscalProofParcel & { id: string; document: string; issuer: string; date: string; subjectNuit: string; year: string; currency: string; proofAmount: string; amount: string; effective: '' | 'yes' | 'no'; reference: string; previouslyUsed: string; priorUseReference: string; }; export type PecPayments = { version: 1; state: '' | 'known' | 'absent'; reference: string; rows: PecPayment[]; }; export const newPecPayment = (id: string): PecPayment => ({ id, document: '', issuer: '', date: '', subjectNuit: '', year: '', currency: 'MZN', proofAmount: '', amount: '', effective: '', reference: '', previouslyUsed: '', priorUseReference: '', }); export function validatePec(p: PecPayments) { const keys = Object.keys(newPecPayment('')); if ( !p || p.version !== 1 || !['', 'known', 'absent'].includes(p.state) || typeof p.reference !== 'string' || !Array.isArray(p.rows) || new Set(p.rows.map((r) => r?.id)).size !== p.rows.length || p.rows.some( (r) => !r || keys.some( (k) => typeof (r as unknown as Record)[k] !== 'string', ) || !['', 'yes', 'no'].includes(r.effective), ) ) { throw Error( 'Comprovativos PEC não recuperáveis; originais conservados.', ); } for (const r of p.rows) validateProofParcel(r); } export const pecCompare = (a: FiscalFraction, b: FiscalFraction) => BigInt(a.numerator) * BigInt(b.denominator) - BigInt(b.numerator) * BigInt(a.denominator); const money = (s: string) => { if (!/^\d{1,18}(\.\d{1,2})?$/.test(s)) throw Error( 'Montante PEC em falta/inválido; declare zero explicitamente quando conhecido.', ); return decimalFraction(s); }; export function pecPaymentValue( r: PecPayment, subjectNuit: string, year: string, ) { requireProofParcel(r); if ( !r.id.trim() || !r.document.trim() || !r.issuer.trim() || !r.reference.trim() || !r.priorUseReference.trim() || r.effective !== 'yes' || r.currency !== 'MZN' || !/^20\d{2}-\d{2}-\d{2}$/.test(r.date) || !Number.isFinite(Date.parse(r.date)) || new Date(r.date).toISOString().slice(0, 10) !== r.date || !r.date.startsWith(year + '-') || r.subjectNuit !== subjectNuit || r.year !== year || year !== '2026' ) { throw Error( 'PEC: exige prova efectiva, natureza PEC, titular, exercício 2026, data válida e MZN; saldos anteriores e previsões ficam separados.', ); } const proofAmount = money(r.proofAmount), value = money(r.amount), previouslyUsed = money(r.previouslyUsed); if ( pecCompare(previouslyUsed, proofAmount) > 0n || pecCompare(value, subtractFractions(proofAmount, previouslyUsed)) > 0n ) throw Error( 'PEC: parcela e utilizações anteriores excedem o comprovativo disponível.', ); return { proofAmount, value, previouslyUsed }; } export function pecDeduction( available: FiscalFraction, colecta: FiscalFraction | null, ready: boolean, selected?: FiscalFraction | null, ) { const deducted = ready && colecta ? (selected ?? (pecCompare(available, colecta) > 0n ? colecta : available)) : null; return { version: pecVersion, available, deducted, remaining: deducted ? subtractFractions(available, deducted) : null, afterPec: deducted && colecta ? subtractFractions(colecta, deducted) : null, source: pecSource, officialBalancesConsumed: false, carryCreated: false, }; }