import { fraction, type FiscalFraction } from './fiscal-rational.ts'; export const normalAssessmentVersion = 'IRPC-normal-adjustments/4'; export type NormalAssessmentInput = { version: 1; treatment: '' | 'general_61_1' | 'other'; reference: string; }; export const newNormalAssessmentInput = (): NormalAssessmentInput => ({ version: 1, treatment: '', reference: '', }); export function validateNormalAssessment(i: NormalAssessmentInput) { if ( !i || i.version !== 1 || !['', 'general_61_1', 'other'].includes(i.treatment) || typeof i.reference !== 'string' ) throw Error( 'Enquadramento da colecta não recuperável;original conservado.', ); } export type NormalAssessmentModel = { input: NormalAssessmentInput; period: { year: string; from: string; to: string }; classification: string; rate: FiscalFraction | null; taxableBase: FiscalFraction | null; colecta: FiscalFraction | null; centExact: boolean | null; pending: string[]; rule: string; source: string; finalBalance: { scope: 'L8'; status: 'not_calculated'; payable: null; recoverable: null; reason: string; }; }; export function calculateNormalAssessment( input: NormalAssessmentInput, base: FiscalFraction | null, context: { year: string; scope: string; currency: string; extended?: boolean; short?: boolean; period?: { from: string; to: string }; }, ): NormalAssessmentModel { validateNormalAssessment(input); const pending: string[] = [], period = { year: context.year, from: /^20\d{2}$/.test(context.year) ? context.year + '-01-01' : '', to: /^20\d{2}$/.test(context.year) ? context.year + '-12-31' : '', }; if (context.year !== '2026') pending.push( 'Taxa geral correspondida neste incremento apenas ao exercício2026;sem taxa transportada para outro período.', ); if ( !( context.scope === 'resident_general' || (context.extended && context.scope === 'ep_general') ) || context.currency !== 'MZN' ) pending.push( 'Colecta delimitada ao sujeito residente no regime geral emMZN;outros âmbitos por corresponder.', ); if (input.treatment !== 'general_61_1') pending.push( input.treatment === 'other' ? 'Benefício,taxa reduzida,tributação autónoma ou outro caso especial:tratamento próprio;sem taxa geral aplicada por aproximação.' : 'Enquadramento da taxa geral por identificar;sem escolha presumida.', ); if (!input.reference.trim()) pending.push( 'Identifique suporte ou pressuposto da aplicação da taxa geral ao caso;uma actividade semelhante não prova o regime.', ); if (base === null) pending.push( 'Matéria colectável actual indeterminada;lucro tributável não é substituto da base.', ); else if ( !/^-?\d{1,80}$/.test(base.numerator) || !/^\d{1,80}$/.test(base.denominator) || BigInt(base.denominator) === 0n || BigInt(base.numerator) < 0n ) pending.push( 'Matéria colectável deve ser um valor conhecido não negativo e reconciliado.', ); if (context.period) { period.from = context.period.from; period.to = context.period.to; const start = new Date(period.from + 'T00:00:00Z'), end = new Date(period.to + 'T00:00:00Z'); if ( !Number.isFinite(+start) || !Number.isFinite(+end) || start.toISOString().slice(0, 10) !== period.from || end.toISOString().slice(0, 10) !== period.to || !period.from.endsWith('-01') || !period.to.startsWith('2026-') ) pending.push('Período fiscal anual não reconciliado.'); else if (context.short) { if ( period.from > period.to || !period.from.startsWith('2026-') || (period.from === context.year + '-01-01' && period.to === context.year + '-12-31') ) pending.push('Período curto por reconciliar.'); } else { start.setUTCFullYear(start.getUTCFullYear() + 1); start.setUTCDate(start.getUTCDate() - 1); if (start.toISOString().slice(0, 10) !== period.to) pending.push( 'Colecta anual: período de12 meses por reconciliar.', ); } } const rate = input.treatment === 'general_61_1' && context.year === '2026' ? fraction(32n) : null; const colecta = pending.length === 0 && base !== null && rate ? fraction( BigInt(base.numerator) * 32n, BigInt(base.denominator) * 100n, ) : null; return { input: structuredClone(input), period, classification: input.treatment === 'general_61_1' ? 'Regime normal — taxa geral do artigo61(1)' : input.treatment === 'other' ? 'Caso especial fora deste recorte' : 'Enquadramento por identificar', rate, taxableBase: base, colecta, centExact: colecta ? (BigInt(colecta.numerator) * 100n) % BigInt(colecta.denominator) === 0n : null, pending, rule: 'CIRPC61(1)-general-2026/1', source: context.short ? 'CIRPC7(4)/61(1):período curto documentado em2026,matéria colectável efectiva ×32%;sem anualizar lucro/base ou proporcionalizar a taxa;saldo finalL8 indeterminado.' : context.extended ? 'CIRPC61(1), taxa geral32% mantida pelos diplomas inventariados até Lei12/2025; período anual documentado com termo em2026. CIRPC15(1)a/c,7(1–3): sujeito residente ou resultado imputável aEP no regime geral; ano civil, autorização ou comunicação expressamente identificadas. Sem arredondamento fiscal ou saldoL8 presumidos.' : 'CIRPC Lei34/2007 art61(1),PDF17/p768(71):taxa geral32%. Lei4/2012 altera61(2);Lei20/2022 PDF1/p2731 mantém61(1),altera61(2) com termo31/12/2025;Lei12/2025 PDF18/20 não altera61(1),adita61-A para mais-valias,fora deste recorte. Exercício anual2026/MZN/residente geral,matéria colectável actual×32/100 em aritmética racional exacta. Sem arredondamento fiscal ou transcriçãoM20/M22 presumidos.', finalBalance: { scope: 'L8', status: 'not_calculated', payable: null, recoverable: null, reason: 'Saldo final a pagar ou recuperar por determinar emL8:retenções,pagamentos e créditos do fecho anual não apurados nem abatidos à colecta neste incremento. Colecta não equivale ao saldo final.', }, }; }