Files
2026-11-11 10:37:33 +02:00

148 lines
5.2 KiB
TypeScript

import {
decimalFraction,
addFractions,
subtractFractions,
fractionPresentation,
type FiscalFraction,
} from './fiscal-rational.ts';
export const pecPlanVersion = 'IRPC-PEC-plan/1';
export const pecPlanSource =
'RIRPC29(1):mesesJunho/Agosto/Outubro e opção de pagamento único. Montantes repartidos e mês único são hipótese explícita do utilizador,sem regra de igualdade,arredondamento,guia ou prazo legal presumidos. Conferência de prazo da opção única permanece separada.';
export type PecPlan = {
version: 1;
mode: '' | 'three' | 'single';
reference: string;
singleMonth: string;
june: string;
august: string;
october: string;
};
export const newPecPlan = (): PecPlan => ({
version: 1,
mode: '',
reference: '',
singleMonth: '',
june: '',
august: '',
october: '',
});
export function validatePecPlan(p: PecPlan) {
if (
!p ||
p.version !== 1 ||
Object.keys(newPecPlan())
.filter((k) => k !== 'version')
.some(
(k) =>
typeof (p as unknown as Record<string, unknown>)[k] !==
'string',
) ||
!['', 'three', 'single'].includes(p.mode)
)
throw Error('Projecção PEC não recuperável;original preservado.');
}
export function calculatePecPlan(
p: PecPlan,
total: FiscalFraction | null,
notApplicable: boolean,
year: string,
) {
validatePecPlan(p);
const pending: string[] = [];
let rows: Array<{ month: string; amount: FiscalFraction }> = [];
if (!total)
pending.push(
'Projecção PEC:total estimado indeterminada;reconciliar a base antes de preparar os montantes.',
);
else if (notApplicable || BigInt(total.numerator) === 0n)
pending.push(
'Projecção PEC:sem estimativa positiva,não se distribuem saídas de caixa.',
);
if (year !== '2026')
pending.push('Projecção PEC:exercício fora do recorte2026.');
if (!p.reference.trim())
pending.push(
'Projecção PEC:identifique o fundamento da opção e dos montantes preparados.',
);
if (!['three', 'single'].includes(p.mode))
pending.push(
'Projecção PEC:escolha três prestações ou pagamento único.',
);
if (!pending.length && total) {
if (p.mode === 'three') {
const values = [p.june, p.august, p.october];
if (!values.every((v) => /^\d{1,18}(\.\d{1,2})?$/.test(v)))
pending.push(
'Projecção PEC:preencha os três montantes em MZN,com até dois decimais;sem preenchimento automático.',
);
else {
const amounts = values.map(decimalFraction);
if (amounts.some((v) => BigInt(v.numerator) <= 0n))
pending.push(
'Projecção PEC:três prestações exigem três montantes positivos.',
);
else if (
BigInt(
subtractFractions(addFractions(amounts), total)
.numerator,
) !== 0n
)
pending.push(
'Projecção PEC:a soma das três prestações difere do total preparado;sem arredondamento ou compensação silenciosos.',
);
else
rows = amounts.map((amount, i) => ({
month: ['2026-06', '2026-08', '2026-10'][i],
amount,
}));
}
} else if (!/^2026-(0[1-9]|1[0-2])$/.test(p.singleMonth))
pending.push(
'Projecção PEC:indique o mês previsto do pagamento único;isto não valida o prazo legal.',
);
else rows = [{ month: p.singleMonth, amount: total }];
}
return {
version: pecPlanVersion,
source: pecPlanSource,
mode: p.mode,
pending,
rows,
total,
allocationReconciled: pending.length === 0,
legalDeadlineValidated: false,
paymentExecuted: false,
annualDeductionCreated: false,
};
}
export function pecPlanReport(
p: PecPlan,
total: FiscalFraction | null,
notApplicable: boolean,
year: string,
): Array<[string, string, string, string]> {
const m = calculatePecPlan(p, total, notApplicable, year);
return [
[
'IRPC — L8',
'PEC — projecção de tesouraria',
m.pending.length
? m.pending.join(' ')
: (m.mode === 'single'
? 'Saída de caixa concentrada (hipótese): '
: 'Distribuição de caixa (hipótese): ') +
m.rows
.map(
(r) =>
r.month +
': ' +
fractionPresentation(r.amount).decimal +
' MZN',
)
.join('; ') +
'. Soma reconciliada;prazo legal não validado. Não é pagamento ou dedução.',
p.reference + '; ' + m.source,
],
];
}