import { inputText } from './input-text.ts'; import { acquisitionScope } from './iva-acquisitions.ts'; import { ispcEligibilityBook, ispcEligibilityCurrent, type IspcEligibilityResult, } from './ispc-eligibility.ts'; import { addFractions, decimalFraction, fraction, fractionPresentation, subtractFractions, type FiscalFraction, } from './fiscal-rational.ts'; export const ispcQuarterSource = 'Lei 9/2025, republicação ISPC, arts 8, 9, 10, 10-A e 11; BR248/2025, PDF11/p2180(11). Taxa do escalão anual aplicada à base trimestral (8(1)a e 9); 20% apenas na parcela em excesso de 4.000.000; actividades cumuladas: taxa mais alta (8(2)). Sem fraccionar os limites anuais por quatro nem copiar o volume do ano anterior. Preparação do apuramento; sem submissão ou pagamento AT.'; export type IspcQuarterInput = { year: string; quarter: string; sales: [string, string, string, string]; salesRefs: [string, string, string, string]; annualBandVolume: string; annualBandOrigin: '' | 'forecast' | 'closed_year'; annualBandRef: string; coverage: '' | 'all_taxpayer_activity'; }; export type IspcQuarterResult = { key: string; calculatedAt: string; scope: string; input: IspcQuarterInput; eligibility: IspcEligibilityResult | null; status: 'calculated' | 'indeterminate' | 'not_applicable'; reason: string; missing: string[]; base: FiscalFraction | null; prior: FiscalFraction | null; ordinary: FiscalFraction | null; excess: FiscalFraction | null; rate: number | null; tax: FiscalFraction | null; payment: FiscalFraction | null; declaration: boolean; projected: boolean; source: string; }; export type IspcQuarterBook = { version: 1; input: IspcQuarterInput; result?: IspcQuarterResult; history?: IspcQuarterResult[]; }; export const newIspcQuarterInput = (): IspcQuarterInput => ({ year: '2026', quarter: '1', sales: ['', '', '', ''], salesRefs: ['', '', '', ''], annualBandVolume: '', annualBandOrigin: '', annualBandRef: '', coverage: '', }); function snapshot(d: Record) { const s = JSON.parse( inputText(d.modelo_a_snapshot || '{"version":1,"books":{}}'), ); if ( s.version !== 1 || !s.books || typeof s.books !== 'object' || Array.isArray(s.books) ) throw Error('Livros não recuperáveis; originais preservados.'); return s; } function validate(b: IspcQuarterBook) { if ( !b || b.version !== 1 || !b.input || [ 'year', 'quarter', 'annualBandVolume', 'annualBandOrigin', 'annualBandRef', 'coverage', ].some( (k) => typeof b.input[k as keyof IspcQuarterInput] !== 'string', ) || !['', 'forecast', 'closed_year'].includes(b.input.annualBandOrigin) || !['', 'all_taxpayer_activity'].includes(b.input.coverage) || !Array.isArray(b.input.sales) || b.input.sales.length !== 4 || b.input.sales.some((v) => typeof v !== 'string') || !Array.isArray(b.input.salesRefs) || b.input.salesRefs.length !== 4 || b.input.salesRefs.some((v) => typeof v !== 'string') || (b.history !== undefined && !Array.isArray(b.history)) || (b.result !== undefined && (!b.result.input || !Array.isArray(b.result.missing))) ) throw Error( 'Apuramento ISPC não recuperável; sem substituir por zero ou apagar histórico.', ); } function books(d: Record): Record { const v = snapshot(d).ispcQuarters; if (v === undefined) return {}; if (!v || typeof v !== 'object' || Array.isArray(v)) throw Error('Mapa trimestral ISPC inválido; originais preservados.'); return v; } export function ispcQuarterBook(d: Record): IspcQuarterBook { const b = books(d)[acquisitionScope(d)] ?? { version: 1, input: newIspcQuarterInput(), }; validate(b); return b; } export function saveIspcQuarterBook( d: Record, b: IspcQuarterBook, ): string { validate(b); return JSON.stringify({ ...snapshot(d), ispcQuarters: { ...books(d), [acquisitionScope(d)]: b }, }); } const cmp = (a: FiscalFraction, b: FiscalFraction) => BigInt(a.numerator) * BigInt(b.denominator) - BigInt(b.numerator) * BigInt(a.denominator); const minimum = (a: FiscalFraction, b: FiscalFraction) => cmp(a, b) <= 0n ? a : b; const positive = (a: FiscalFraction) => cmp(a, fraction(0n)) > 0n ? a : fraction(0n); const atRate = (v: FiscalFraction, r: number) => fraction(BigInt(v.numerator) * BigInt(r), BigInt(v.denominator) * 100n); function eligibility(d: Record) { const b = ispcEligibilityBook(d); return ispcEligibilityCurrent(d, b) ? (b.result ?? null) : null; } const key = (d: Record, b: IspcQuarterBook) => JSON.stringify([ 'ispc-quarter/1', acquisitionScope(d), b.input, eligibility(d), ]); export const ispcQuarterCurrent = ( d: Record, b: IspcQuarterBook, ) => !!b.result && b.result.key === key(d, b); export function recordIspcQuarter( d: Record, b: IspcQuarterBook, now = new Date().toISOString(), ): IspcQuarterBook { validate(b); const i = b.input, e = eligibility(d), r: IspcQuarterResult = { key: key(d, b), calculatedAt: now, scope: acquisitionScope(d), input: structuredClone(i), eligibility: e ? structuredClone(e) : null, status: 'indeterminate', reason: '', missing: [], base: null, prior: null, ordinary: null, excess: null, rate: null, tax: null, payment: null, declaration: false, projected: e?.input.origin === 'projected' || i.annualBandOrigin === 'forecast', source: ispcQuarterSource, }; const finish = () => ({ ...b, ...(b.result ? { history: [...(b.history ?? []), structuredClone(b.result)] } : {}), result: r, }); if (!e) { r.missing.push( 'Avaliação de elegibilidade actual correspondente às entradas', ); r.reason = 'Reveja e guarde o enquadramento; a avaliação anterior continua no histórico.'; return finish(); } if (e.status === 'incompatible' || e.optionStatus === 'not_opted') { r.status = 'not_applicable'; r.reason = 'Os factos guardados não permitem aplicar ISPC ou indicam que não optou. Outros regimes mantêm os seus percursos.'; return finish(); } if (e.status !== 'compatible') { r.missing.push(...e.missing); r.reason = 'Enquadramento indeterminado; não produzir imposto zero.'; return finish(); } // A preparation scenario may estimate tax, but never asserts an effective election. if ( e.optionStatus === 'indeterminate' || (e.input.origin === 'effective' && e.optionStatus !== 'recorded') ) { r.missing.push( 'Opção documentada para apuramento efectivo; a preparação permite somente simulação', ); } if (e.optionStatus === 'preparation') r.projected = true; if (i.year !== '2026' || !['1', '2', '3', '4'].includes(i.quarter)) r.missing.push('Ano 2026 e trimestre entre 1 e 4'); if (i.coverage !== 'all_taxpayer_activity') r.missing.push( 'Confirmar volume de todas as actividades do sujeito passivo, não apenas desta oportunidade', ); const q = Number(i.quarter), amounts: FiscalFraction[] = [], allFour = i.annualBandOrigin === 'closed_year' && !e.input.activities.some((a) => ['liberal15', 'services12'].includes(a), ); if ( e.input.origin === 'effective' && e.input.optionDate && ['1', '2', '3', '4'].includes(i.quarter) && e.input.optionDate > `2026-${['03-31', '06-30', '09-30', '12-31'][q - 1]}` ) r.missing.push( 'A opção registada é posterior ao trimestre; esclarecer operações abrangidas sem retroagir automaticamente', ); if (['1', '2', '3', '4'].includes(i.quarter)) for (let n = 0; n < (allFour ? 4 : q); n++) { try { amounts[n] = decimalFraction(i.sales[n]); } catch { r.missing.push( `Volume do trimestre ${n + 1}, incluindo zero expresso`, ); } if (!i.salesRefs[n].trim()) r.missing.push(`Suporte ou pressuposto do trimestre ${n + 1}`); } const liberal = e.input.activities.includes('liberal15'), service = e.input.activities.includes('services12'); let band: FiscalFraction | null = null; if (!liberal && !service) { try { band = decimalFraction(i.annualBandVolume); } catch { r.missing.push( 'Volume anual de 2026 para identificar o escalão; distinto do volume de acesso 2025', ); } if (!i.annualBandOrigin || !i.annualBandRef.trim()) r.missing.push( 'Origem (previsão ou ano encerrado) e suporte do escalão anual', ); } if (r.missing.length) { r.reason = 'Faltam dados específicos para este apuramento; os restantes percursos mantêm-se disponíveis.'; return finish(); } r.base = amounts[q - 1]; r.prior = addFractions(amounts.slice(0, q - 1)); const cumulative = addFractions(amounts.slice(0, q)), annualTotal = addFractions(amounts); if ( band && (cmp(band, cumulative) < 0n || (i.annualBandOrigin === 'closed_year' && cmp(band, annualTotal) !== 0n)) ) { r.reason = 'Volume anual do escalão diverge dos trimestres declarados; reveja a divergência antes de calcular.'; r.missing.push( 'Reconciliar referência anual 2026 com o acumulado trimestral', ); return finish(); } r.rate = liberal ? 15 : service ? 12 : band && cmp(band, fraction(1000000n)) <= 0n ? 3 : band && cmp(band, fraction(2500000n)) <= 0n ? 4 : 5; r.ordinary = minimum( r.base, positive(subtractFractions(fraction(4000000n), r.prior)), ); r.excess = subtractFractions(r.base, r.ordinary); r.tax = addFractions([atRate(r.ordinary, r.rate), atRate(r.excess, 20)]); r.payment = cmp(r.tax, fraction(500n)) < 0n ? fraction(0n) : r.tax; r.declaration = true; r.status = 'calculated'; r.reason = `${r.projected ? 'Simulação com pressupostos explícitos; sem adesão automática.' : 'Apuramento segundo factos e opção documentados.'} Taxa ${r.rate}% sobre a parcela ordinária trimestral e 20% apenas sobre o excesso anual. ${cmp(r.tax, fraction(500n)) < 0n ? 'Prestação inferior a 500 MZN: sem pagamento; obrigação declarativa preservada.' : 'Prestação igual ou superior a 500 MZN: montante a preparar para pagamento.'} Sem dedução de custos, retenções sofridas presumidas, compensação ou acumulação de prestações dispensadas.`; return finish(); } const amount = (v: FiscalFraction | null) => v ? `${fractionPresentation(v).decimal} MZN (${v.numerator}/${v.denominator})` : 'Indeterminado'; export function ispcQuarterReportRows( d: Record, ): Array<[string, string, string, string]> { try { const b = ispcQuarterBook(d), r = b.result; if (!r) return []; return [ [ 'ISPC', `Apuramento ${r.input.year}/T${r.input.quarter}`, `${r.status === 'calculated' ? 'Calculado' : r.status === 'not_applicable' ? 'Não aplicável' : 'Indeterminado'}; ${r.reason}; falta: ${r.missing.join('; ') || 'nenhum dado deste recorte'}`, `${r.scope}; ${ispcQuarterCurrent(d, b) ? 'Entradas correspondentes' : 'Histórico preservado; rever entradas'}; ${r.calculatedAt}; ${r.source}`, ], [ 'ISPC', 'Base e taxa trimestral', `Base: ${amount(r.base)}; anterior no ano: ${amount(r.prior)}; ordinária: ${amount(r.ordinary)} a ${r.rate ?? 'indeterminada'}%; excesso: ${amount(r.excess)} a 20%`, `Volume anual de escalão: ${r.input.annualBandVolume || 'não necessário para taxa de serviços'}; ${r.input.annualBandOrigin}; ${r.input.annualBandRef}; suportes: ${r.input.salesRefs.join('; ')}`, ], [ 'ISPC', 'Imposto, pagamento e declaração', `Imposto: ${amount(r.tax)}; prestação a pagar: ${amount(r.payment)}; obrigação declarativa: ${r.declaration ? 'mantida' : 'por determinar neste percurso'}; ${r.projected ? 'simulação' : 'factos efectivos'}`, `Opção: ${r.eligibility?.optionStatus ?? 'indeterminada'}; referência: ${r.eligibility?.input.optionReference ?? ''}; histórico: ${b.history?.length ?? 0} versão(ões) anterior(es). Sem submissão AT.`, ], ]; } catch (error) { return [ [ 'ISPC', 'Apuramento não recuperável', String(error), 'Originais preservados.', ], ]; } }