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)[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, ], ]; }