Files
Investidor-V2/resources/js/lib/irpc-pec-plan.ts
T

193 lines
7.0 KiB
TypeScript

import {
advanceCalendar,
type AdvanceFiscalPeriod,
} from './irpc-advance-calendar.ts';
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;
periodFrom?: string;
periodTo?: string;
mode: '' | 'three' | 'single';
reference: string;
singleMonth: string;
june: string;
august: string;
october: string;
};
export const newPecPlan = (period?: AdvanceFiscalPeriod): PecPlan => ({
...(period?.from && period?.to
? { periodFrom: period.from, periodTo: period.to }
: {}),
version: 1,
mode: '',
reference: '',
singleMonth: '',
june: '',
august: '',
october: '',
});
export function validatePecPlan(p: PecPlan) {
if (
(p?.periodFrom !== undefined || p?.periodTo !== undefined) &&
(typeof p.periodFrom !== 'string' || typeof p.periodTo !== 'string')
)
throw Error(
'Contexto da hipótese PEC não recuperável;original preservado.',
);
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,
period?: AdvanceFiscalPeriod,
) {
validatePecPlan(p);
const pending: string[] = [];
const calendar = advanceCalendar(period, year);
pending.push(...calendar.pending.map((p) => 'Projecção PEC: ' + p));
const needsPeriodConfirmation =
(calendar.mode === 'annual_non_civil' || p.periodFrom !== undefined) &&
(p.periodFrom !== calendar.from || p.periodTo !== calendar.to);
if (needsPeriodConfirmation)
pending.push(
'Projecção PEC: reconfira a distribuição no período actual;montantes e mês anterior conservados sem deslocação automática.',
);
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: calendar.pecMonths[i],
amount,
}));
}
} else if (
!/^20\d{2}-(0[1-9]|1[0-2])$/.test(p.singleMonth) ||
p.singleMonth < calendar.from.slice(0, 7) ||
p.singleMonth > calendar.to.slice(0, 7)
)
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:
calendar.mode === 'annual_non_civil'
? pecPlanSource.replace(
'mesesJunho/Agosto/Outubro',
'6.º/8.º/10.º mês do período anual não civil',
) +
' ' +
calendar.source
: pecPlanSource,
months: calendar.pecMonths,
needsPeriodConfirmation,
contextFrom: calendar.from,
contextTo: calendar.to,
calendarPending: calendar.mode === 'unavailable',
nonCivil: calendar.mode === 'annual_non_civil',
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,
period?: AdvanceFiscalPeriod,
): Array<[string, string, string, string]> {
const m = calculatePecPlan(p, total, notApplicable, year, period);
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,
],
];
}