148 lines
5.2 KiB
TypeScript
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,
|
|
],
|
|
];
|
|
}
|