39 lines
3.7 KiB
TypeScript
39 lines
3.7 KiB
TypeScript
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<string, unknown>)[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};
|
||
}
|