import { inputText } from './input-text.ts'; import { acquisitionScope } from './iva-acquisitions.ts'; import { ispcEligibilityBook, ispcEligibilityCurrent, } from './ispc-eligibility.ts'; import { ispcWithholdingBook } from './ispc-withholding.ts'; import { ispcQuarterBook, ispcQuarterCurrent } from './ispc-quarter.ts'; import { addFractions, decimalFraction, fraction, fractionPresentation, subtractFractions, type FiscalFraction, } from './fiscal-rational.ts'; export type ResidualKind = 'occasional' | 'acquisitions' | 'formalities'; export type ResidualResult = { key: string; scope: string; calculatedAt: string; status: | 'calculated' | 'settled_by_withholding' | 'partial' | 'indeterminate' | 'not_applicable'; reason: string; missing: string[]; input: Record; amounts: Record; details: Record; source: string; }; export type ResidualBook = { version: 1; kind: ResidualKind; input: Record; result?: ResidualResult; history?: ResidualResult[]; }; export const residualSources = { occasional: 'ISPC Lei9/2025 arts3(3–4),4,5,8,9,10-A,12(3);BR248 PDF10–11. IRPS republicação Lei19/2017,BR202/7.ºsuplemento,art8(3h,4),9;PDF4/p2602(128). Lei11/2025 não altera estes artigos. Operação imprevisível/não reiterada, pessoa singular sem actividade habitual e famílias restritas; imposto próprio distinto de retenção liberatória documentada.', acquisitions: 'ISPC12(3–4):limite global anual2.500.000MZN para factura por conta/retencão5%. Parcela acima do limite não adquire taxa por ultrapassagem. IRPS57(1,2k,4),republicação19/2017 PDF13/p2602(137),e65(1)naLei11/2025 PDF17:acto isolado qualificado e sem opçãoISPC aplicável,20%sobre rendimento ilíquido declarado; não confundir com20%do excesso de vendasISPC8(1)d.', formalities: 'AT Comunicado9/2026,06/10/2026,p1:ModeloB–Geral enquanto se ajusta sistema;pagamentos trimestrais emJaneiro/Abril/Julho/Outubro. AT ModeloBGeral2p:quadros1/2/3/5/6/8/10. Modelo30 disponível contém75.000/3%antigos,não usado para calcular2026. Sem submissãoAT automática.', }; const blank = (kind: ResidualKind): Record => kind === 'occasional' ? { personType: '', currency: '', origin: 'projected', operationDate: '', quarter: '', noHabitual: '', territory: '', sporadic: '', activity: '', reference: '', amount: '', allQuarterOperations: '', priorYearTurnover: '', priorYearRef: '', organizedRequired: '', option: 'preparation', optionRef: '', optionDate: '', settlement: '', annual2026: '', annualOrigin: '', annualRef: '', priorInYear: '', buyerIspc: '', unregistered: '', buyerAnnualPurchases: '', withheldActual: '', withheldRef: '', } : kind === 'acquisitions' ? { route: 'unknown', noIspcOnExcess: '', isolatedAct: '', incomeInMoz: '', grossExcess: '', grossRef: '', supplierOptionRef: '', reference: '', } : { sourceKind: 'quarter', name: '', nuit: '', fiscalArea: '', address: '', declaration: 'initial', formMonth: '', formYear: '2026', formPeriodRef: '', interestSituation: '', interestAmount: '', interestRef: '', externalStatus: 'preparation', externalRef: '', externalDate: '', }; function store(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 maps( d: Record, ): Record> { const v = store(d).ispcResiduals; if (v === undefined) return {}; if (!v || typeof v !== 'object' || Array.isArray(v)) throw Error('Mapa residualISPC inválido; originais preservados.'); return v; } function validate(b: ResidualBook, kind: ResidualKind) { const enums: Record = kind === 'occasional' ? { personType: ['', 'singular', 'collective'], origin: ['projected', 'effective'], noHabitual: ['', 'yes', 'no'], territory: ['', 'yes', 'no'], sporadic: ['', 'yes', 'no'], activity: [ '', 'forestry', 'fishery', 'livestock', 'agriculture', 'poultry', 'beekeeping', 'other', ], allQuarterOperations: ['', 'yes', 'no'], organizedRequired: ['', 'yes', 'no'], option: ['preparation', 'opted', 'not_opted'], settlement: ['', 'self', 'withheld'], annualOrigin: ['', 'forecast', 'closed_year'], buyerIspc: ['', 'yes', 'no'], unregistered: ['', 'yes', 'no'], } : kind === 'acquisitions' ? { route: ['unknown', 'ispc_supplier', 'irps_isolated'], noIspcOnExcess: ['', 'yes', 'no'], isolatedAct: ['', 'yes', 'no'], incomeInMoz: ['', 'yes', 'no'], } : { sourceKind: ['quarter', 'occasional', 'acquisitions'], declaration: ['initial', 'substitution'], interestSituation: ['', 'none', 'amount'], externalStatus: [ 'preparation', 'submitted', 'received', 'paid', ], }; if ( b?.input && Object.entries(enums).some(([k, v]) => !v.includes(b.input[k])) ) throw Error('Opções residuais não recuperáveis;entradas preservadas.'); if ( !b || b.version !== 1 || b.kind !== kind || !b.input || Object.keys(blank(kind)).some((k) => typeof b.input[k] !== 'string') || (b.history !== undefined && !Array.isArray(b.history)) || (b.result !== undefined && (!b.result.input || !b.result.amounts || !Array.isArray(b.result.missing))) ) throw Error( 'Livro residualISPC não recuperável; sem substituir por vazio.', ); } export function ispcResidualBook( d: Record, kind: ResidualKind, ): ResidualBook { const m = maps(d)[acquisitionScope(d)]; if (m !== undefined && (!m || typeof m !== 'object' || Array.isArray(m))) throw Error('Contexto residual inválido.'); let b = m?.[kind]; if (!b) { const input = blank(kind); if (kind === 'occasional') { const i = ispcEligibilityBook(d).input; Object.assign(input, { personType: i.personType, currency: i.currency, origin: i.origin, operationDate: i.assessmentDate, territory: i.territory, option: i.option, optionRef: i.optionReference, optionDate: i.optionDate, }); } if (kind === 'formalities') { if ( ispcEligibilityBook(d).input.mode === 'occasional' && m?.occasional?.result ) input.sourceKind = 'occasional'; else if (!ispcQuarterBook(d).result && m?.acquisitions?.result) input.sourceKind = 'acquisitions'; Object.assign(input, { name: inputText(d.legal_name ?? d.organization_name ?? ''), nuit: inputText(d.nuit ?? ''), address: inputText(d.address ?? ''), }); } b = { version: 1, kind, input }; } validate(b, kind); return b; } export function saveIspcResidualBook( d: Record, b: ResidualBook, ): string { validate(b, b.kind); const scope = acquisitionScope(d); return JSON.stringify({ ...store(d), ispcResiduals: { ...maps(d), [scope]: { ...maps(d)[scope], [b.kind]: b }, }, }); } const cmp = (a: FiscalFraction, b: FiscalFraction) => BigInt(a.numerator) * BigInt(b.denominator) - BigInt(b.numerator) * BigInt(a.denominator); const rate = (a: FiscalFraction, n: number) => fraction(BigInt(a.numerator) * BigInt(n), BigInt(a.denominator) * 100n); const min = (a: FiscalFraction, b: FiscalFraction) => (cmp(a, b) < 0n ? a : b); const positive = (a: FiscalFraction) => cmp(a, fraction(0n)) > 0n ? a : fraction(0n); const validDate = (s: string) => /^2026-\d{2}-\d{2}$/.test(s) && !Number.isNaN(Date.parse(s)) && new Date(s).toISOString().slice(0, 10) === s; const amount = (s: string, label: string, r: ResidualResult) => { try { return decimalFraction(s); } catch { r.missing.push(label); return null; } }; function dependency( d: Record, kind: ResidualKind, i: Record, ): unknown { if (kind === 'acquisitions') { const e = ispcEligibilityBook(d); return [ispcWithholdingBook(d).input, e.input, e.result?.key]; } if (kind === 'formalities') { if (i.sourceKind === 'quarter') { const b = ispcQuarterBook(d); return [b.input, b.result?.key, ispcQuarterCurrent(d, b)]; } const b = ispcResidualBook( d, i.sourceKind === 'occasional' ? 'occasional' : 'acquisitions', ); return [ b.input, b.result?.key, b.result?.calculatedAt, ispcResidualCurrent(d, b), ]; } return null; } const key = (d: Record, b: ResidualBook) => JSON.stringify([ 'ispc-residual/1', acquisitionScope(d), b.kind, b.input, dependency(d, b.kind, b.input), ]); export const ispcResidualCurrent = ( d: Record, b: ResidualBook, ) => !!b.result && b.result.key === key(d, b); function occasional(i: Record, r: ResidualResult) { if ( i.personType === 'collective' || i.noHabitual === 'no' || i.territory === 'no' || i.sporadic === 'no' || i.activity === 'other' ) { r.status = 'not_applicable'; r.reason = 'Os factos excluem o recorte ocasional3(3–4); conserve o percurso habitual ou o imposto próprio aplicável, sem presumir isenção.'; return; } if (i.territory !== 'yes') r.missing.push( 'Operação em território nacional,sem inferir da moeda ou nacionalidade', ); if (!i.personType) r.missing.push('Pessoa singular por identificar'); if ( i.currency !== 'MZN' || !validDate(i.operationDate) || !['1', '2', '3', '4'].includes(i.quarter) || (validDate(i.operationDate) && Math.ceil(Number(i.operationDate.slice(5, 7)) / 3) !== Number(i.quarter)) ) r.missing.push('Data/trimestre de2026 coerentes e moedaMZN'); if ( i.noHabitual !== 'yes' || i.sporadic !== 'yes' || ![ 'forestry', 'fishery', 'livestock', 'agriculture', 'poultry', 'beekeeping', ].includes(i.activity) || !i.reference.trim() ) r.missing.push( 'Pessoa sem actividade habitual,acto imprevisível/não reiterado,família3(4)e suporte da operação', ); const base = amount( i.amount, 'Volume das operações ocasionais deste trimestre, incluindo zero expresso', r, ); r.amounts.base = base; r.details.IRPS = 'Acto isolado8(3h)referente a8(2a),não previsível/reiterado8(4);família3(4)ISPC. Não se infere pelo nome do sector.'; if (i.settlement === 'withheld') { const purchases = amount( i.buyerAnnualPurchases, 'Aquisições globais anuais do adquirente, incluindo estas operações', r, ), actual = amount( i.withheldActual, 'Montante efectivamente retido', r, ); if ( i.buyerIspc !== 'yes' || i.unregistered !== 'yes' || !i.withheldRef.trim() ) r.missing.push( 'AdquirenteISPC,fornecedor não inscrito e documento de retenção liberatória12(3)', ); if ( purchases && base && (cmp(purchases, base) < 0n || cmp(purchases, fraction(2500000n)) > 0n) ) r.missing.push( 'Reconciliar aquisições globais do adquirente dentro do limite2.500.000;fora dele usar tratamento próprio', ); if (!r.missing.length && base && actual) { r.amounts.tax = rate(base, 5); r.amounts.withheld = actual; if (cmp(actual, r.amounts.tax) !== 0n) { r.missing.push( 'Retenção documentada diverge de5%da base;reconciliar diferença sem presumir quitação', ); return; } r.status = 'settled_by_withholding'; r.details.rate = '5'; r.reason = 'Retenção liberatória documentada para estas operações; não somar nova liquidação própria3/4/5%nem usar a retenção como crédito duplicado. Pagamento próprio adicional não é gerado.'; } return; } if (i.settlement !== 'self') { r.missing.push( 'Via de liquidação própria ou retenção liberatória documentada', ); return; } if (i.organizedRequired === 'yes') { r.status = 'not_applicable'; r.reason = 'Obrigação de contabilidade organizada declarada:fora da liquidação própriaISPC4(1b);entradas e documentos conservados,sem presumir isenção noutro imposto.'; return; } if (i.allQuarterOperations !== 'yes') r.missing.push( 'Volume de todas as operações ocasionais do sujeito passivo no trimestre', ); const access = amount( i.priorYearTurnover, 'Volume de referência2025, sem inferir zero por ausência de actividade habitual', r, ); if (!i.priorYearRef.trim() || i.organizedRequired !== 'no') r.missing.push( 'Suporte da referência anual e inexistência de obrigação de contabilidade organizada4(1)', ); if (access && cmp(access, fraction(4000000n)) > 0n) { r.status = 'not_applicable'; r.reason = 'Volume de acesso4(1)superior4.000.000MZN declarado:fora da liquidação própriaISPC;sem atribuir taxa de outro regime por presunção.'; return; } if (i.option === 'not_opted') { r.status = 'not_applicable'; r.reason = 'Sem opçãoISPC para esta operação; não aplicar exclusãoIRPS5nem taxaISPC automaticamente.'; return; } if ( !['preparation', 'opted'].includes(i.option) || (i.option === 'opted' && (!i.optionRef.trim() || !validDate(i.optionDate) || i.optionDate > i.operationDate)) || (i.origin === 'effective' && i.option !== 'opted') ) r.missing.push( 'Opção documentada aplicável à operação efectiva;preparação apenas permite cenário', ); const annual = amount( i.annual2026, 'Volume anual2026 para escalão, distinto do acesso2025', r, ), prior = amount(i.priorInYear, 'Volume anterior no ano2026', r); if ( !['forecast', 'closed_year'].includes(i.annualOrigin) || !i.annualRef.trim() ) r.missing.push( 'Referência anual suportada e previsão identificada como cenário', ); if ( base && prior && annual && (cmp(annual, addFractions([base, prior])) < 0n || (i.annualOrigin === 'closed_year' && i.quarter === '4' && cmp(annual, addFractions([base, prior])) !== 0n)) ) r.missing.push( 'Reconciliar volume anual com acumulado anterior e trimestre', ); if (r.missing.length || !base || !prior || !annual) return; const n = cmp(annual, fraction(1000000n)) <= 0n ? 3 : cmp(annual, fraction(2500000n)) <= 0n ? 4 : 5, ordinary = min( base, positive(subtractFractions(fraction(4000000n), prior)), ), excess = subtractFractions(base, ordinary), tax = addFractions([rate(ordinary, n), rate(excess, 20)]); r.amounts = { ...r.amounts, ordinary, excess, tax, payment: cmp(tax, fraction(500n)) < 0n ? fraction(0n) : tax, }; r.details.projected = i.origin === 'projected' || i.option === 'preparation' || i.annualOrigin === 'forecast' ? 'yes' : 'no'; r.details.rate = String(n); r.details.declaration = 'Mantida mesmo quando prestação inferior500MZN'; r.status = 'calculated'; r.reason = `${r.details.projected === 'yes' ? 'Cenário declarado;sem adesão automática.' : 'Opção e factos efectivos declarados.'} Operações ocasionais próprias3(3–4),base trimestral9,taxa${n}%e20%somente no excesso de vendas acima4.000.000. Não há retenção presumida nem dedução de custos/IVA.`; } function acquisitions( d: Record, i: Record, r: ResidualResult, ) { const w = ispcWithholdingBook(d).input, e = ispcEligibilityBook(d); if ( !ispcEligibilityCurrent(d, e) || e.result?.status !== 'compatible' || e.result.optionStatus === 'not_opted' || e.result.optionStatus === 'indeterminate' || (e.input.origin === 'effective' && e.result.optionStatus !== 'recorded') ) { r.missing.push('Enquadramento/opção actual do adquirenteISPC'); return; } if ( w.applicable !== 'yes' || w.naturalOccasional !== 'yes' || w.unregistered !== 'yes' || w.related !== 'yes' || ![ 'forestry', 'fishery', 'livestock', 'agriculture', 'poultry', 'beekeeping', ].includes(w.activity) || !w.reference.trim() ) { r.missing.push( 'Condições específicas12(3–4)e suporte no livro das aquisições', ); return; } const base = amount(w.base, 'Base das aquisições', r), prior = amount( w.priorAnnualPurchases, 'Aquisições globais anteriores do ano', r, ); if (!base || !prior) return; const covered = min( base, positive(subtractFractions(fraction(2500000n), prior)), ), excess = subtractFractions(base, covered); r.amounts = { base, prior, covered, excess, coveredTax: rate(covered, 5), excessTax: null, total: null, }; r.details.quarter = w.quarter; r.details.acquisitionRef = w.reference; if (cmp(excess, fraction(0n)) <= 0n) { r.status = 'not_applicable'; r.reason = 'Sem parcela acima do limite; o cálculo5%já disponível permanece no seu livro, sem segunda liquidação.'; return; } r.status = 'partial'; r.reason = 'Parcela até ao limite separada a5%; parcela excedente quantificada, sem presumir5%ou20%pelo simples excesso. Montante total continua indeterminado até identificar tratamento próprio.'; if (i.route === 'irps_isolated') { const net = amount( i.grossExcess, 'Rendimento ilíquido da parcela excedente, distinto de rendimento líquido e do preço líquido de IVA', r, ); if ( i.noIspcOnExcess !== 'yes' || i.isolatedAct !== 'yes' || i.incomeInMoz !== 'yes' || !i.grossRef.trim() || !i.reference.trim() ) r.missing.push( 'Acto isoladoIRPS8(3h,4),rendimento emMoçambique,sem opçãoISPC5aplicável à parcela,e referências da classificação/base', ); if (net && cmp(net, excess) !== 0n) r.missing.push( 'Base ilíquida57(4)não coincide com a parcela bruta identificada;reconciliar sem deduzir custos/IVA automaticamente', ); if (!r.missing.length && net) { r.amounts.excessTax = rate(net, 20); r.amounts.total = addFractions([ r.amounts.coveredTax!, r.amounts.excessTax, ]); r.status = 'calculated'; r.reason = 'Correspondência positiva independente:IRPS57(2k)acto isolado,20%sobre rendimento ilíquido57(4)da parcela identificada,sem opçãoISPCaplicável. Não se usa taxa20%do excesso de vendasISPC. Retenções5%e20%separadas,não são crédito do imposto próprio do adquirente.'; } } else if (i.route === 'ispc_supplier') { if (!i.supplierOptionRef.trim()) r.missing.push( 'Referência da opção/registoISPCdo fornecedor aplicável a estas operações', ); r.reason = 'Fornecedor com opçãoISPC declarada:art5impede aplicarIRPSporpresunção. A parcela fora12(3)exige factura/registo do fornecedor e liquidação própria correspondente; o comprador não atribui5%ou20%automaticamente. Sem total de retenção presumido.'; } else r.missing.push( 'Enquadramento específico da parcela excedente:opçãoISPCdo fornecedor ou acto isoladoIRPS,base e suporte', ); } function formalities( d: Record, i: Record, r: ResidualResult, ) { let src: ResidualResult | undefined; if (i.sourceKind === 'quarter') { const b = ispcQuarterBook(d), v = b.result; if (!v || !ispcQuarterCurrent(d, b) || v.status !== 'calculated') { r.missing.push('Apuramento trimestral actual e calculado'); return; } r.amounts = { base: v.base, tax: v.tax, payment: v.payment, ordinary: v.ordinary, excess: v.excess, }; r.details = { quarter: v.input.quarter, rate: String(v.rate), sourceDate: v.calculatedAt, }; } else { const b = ispcResidualBook( d, i.sourceKind === 'occasional' ? 'occasional' : 'acquisitions', ); src = b.result; if (!src || !ispcResidualCurrent(d, b)) { r.missing.push('Resultado actual do percurso seleccionado'); return; } if (src.status === 'settled_by_withholding') { r.status = 'not_applicable'; r.reason = 'Operação própria já com retenção liberatória documentada:conservar comprovativo,sem emitir liquidação própria duplicada.'; return; } if (src.status !== 'calculated') { r.missing.push( 'Cálculo completo do percurso seleccionado;as partes determinadas permanecem consultáveis', ); return; } r.amounts = { ...src.amounts, tax: src.amounts.total ?? src.amounts.tax, payment: src.amounts.total ?? src.amounts.payment, }; r.details = { quarter: src.input.quarter || src.details.quarter, rate: src.details.rate || 'retenções separadas', sourceDate: src.calculatedAt, }; } const first = r.amounts.covered ?? r.amounts.ordinary ?? r.amounts.base, second = r.amounts.excess ?? fraction(0n), firstRate = i.sourceKind === 'acquisitions' ? 5 : Number(r.details.rate); if (first && [3, 4, 5, 12, 15].includes(firstRate)) { const firstTax = r.amounts.coveredTax ?? rate(first, firstRate), secondTax = r.amounts.excessTax ?? rate(second, 20); r.amounts.q8base01 = first; r.amounts.q8tax05 = firstTax; r.amounts.q8base02 = second; r.amounts.q8tax06 = secondTax; r.details.q8rate01 = String(firstRate); r.details.q8rate02 = '20'; if ( r.amounts.tax && cmp(addFractions([firstTax, secondTax]), r.amounts.tax) !== 0n ) r.missing.push( 'Parcelas do quadro8divergem do cálculo guardado;conferir apenas essa correspondência', ); } if (i.sourceKind === 'acquisitions' && !(i.retentionFormRef ?? '').trim()) r.missing.push( 'Modelo/código de entrega da retençãoISPC12(3)e da retençãoIRPS57(2k),distintos do imposto próprio;comunicado9não resolve isoladamente este modelo', ); if ( r.amounts.tax && r.amounts.payment && cmp(r.amounts.tax, fraction(0n)) > 0n && cmp(r.amounts.tax, fraction(500n)) < 0n && cmp(r.amounts.payment, fraction(0n)) === 0n ) r.missing.push( 'Representação da dispensa de pagamento10-A noB–Geral:declarar imposto apurado sem presumir zero no campo de montante apurado', ); r.details.model = 'Modelo B–Geral;comunicadoAT9/2026'; r.details.paymentMonth = ['Abril2026', 'Julho2026', 'Outubro2026', 'Janeiro2027'][ Number(r.details.quarter) - 1 ] || 'Trimestre por identificar'; r.details.formPeriod = i.formMonth ? `${i.formMonth}/${i.formYear}` : 'Indeterminado:quadro2pede mês/ano;comunicado identifica trimestre/mês de pagamento,sem resolver a codificação do período'; if ( !/^[0-9]{9}$/.test(i.nuit) || !i.name.trim() || !i.fiscalArea.trim() || !i.address.trim() ) r.missing.push( 'NUIT9dígitos,nome,área fiscal/UGC e domicílio para formalização;preparação do cálculo não exige esses campos', ); if ( !/^(0[1-9]|1[0-2])$/.test(i.formMonth) || !['2026', '2027'].includes(i.formYear) || !i.formPeriodRef.trim() ) r.missing.push( 'Mês/ano a inscrever no quadro2e instrução/correspondência utilizada; não preencher automaticamente com mês de pagamento', ); if ( !['none', 'amount'].includes(i.interestSituation) || !i.interestRef.trim() ) r.missing.push( 'Situação dos juros compensatórios e suporte;desconhecido não é zero', ); if (i.interestSituation === 'amount') r.amounts.interest = amount( i.interestAmount, 'Juros documentados,não calculados por falta de taxa/período regulamentar', r, ); else if (i.interestSituation === 'none') r.amounts.interest = fraction(0n); r.amounts.finalPayment = r.amounts.payment && r.amounts.interest ? addFractions([r.amounts.payment, r.amounts.interest]) : null; if ( i.externalStatus !== 'preparation' && (!i.externalRef.trim() || !i.externalDate.trim()) ) r.missing.push( 'Referência e data da submissão/recepção/pagamento declarado;sem confirmação automáticaAT', ); r.status = r.missing.length ? 'partial' : 'calculated'; r.reason = 'Preparação dos campos identificados do B–Geral,com cálculo e prestação separados. O Modelo30antigo não recebe taxas2026. Submissão,recepção e pagamento são actos externos;este registo conserva referências e histórico,sem os efectuar ou certificar.'; } export function priorOccasionalSettlement( b: ResidualBook, ): ResidualResult | undefined { if (b.kind !== 'occasional' || b.input.settlement !== 'self') return; return [b.result, ...(b.history ?? [])].find( (r) => r?.status === 'settled_by_withholding' && r.input.reference.trim() === b.input.reference.trim() && r.input.operationDate === b.input.operationDate && r.input.quarter === b.input.quarter && r.input.amount === b.input.amount, ); } export function recordIspcResidual( d: Record, b: ResidualBook, now = new Date().toISOString(), ): ResidualBook { validate(b, b.kind); const r: ResidualResult = { key: key(d, b), scope: acquisitionScope(d), calculatedAt: now, status: 'indeterminate', reason: 'Dados específicos em falta;sem presumir zero.', missing: [], input: structuredClone(b.input), amounts: {}, details: {}, source: residualSources[b.kind], }; if (b.kind === 'occasional') { occasional(b.input, r); const prior = priorOccasionalSettlement(b); if (prior && r.status === 'calculated') { r.details.priorWithholdingRef = prior.input.withheldRef; r.details.priorWithholdingDate = prior.calculatedAt; if ( b.input.withholdingCorrected === 'yes' && (b.input.withholdingCorrectionRef ?? '').trim() ) { r.details.withholdingCorrectionRef = b.input.withholdingCorrectionRef; r.reason += ' Correcção da retenção anterior declarada segundo referência externa,sem certificar esse acto;histórico preservado.'; } else { r.status = 'partial'; r.amounts.hypotheticalOwnTax = r.amounts.tax; r.amounts.tax = null; r.amounts.payment = null; r.amounts.withheld = prior.amounts.withheld ?? prior.amounts.tax; r.missing.push( 'Retenção liberatória anterior para a mesma operação;reconciliar eventual correcção documentada antes de nova prestação própria', ); r.reason = 'A via foi alterada,mas a retenção liberatória documentada permanece no histórico. Não é gerada segunda prestação;os dados da operação e as duas versões ficam preservados.'; } } } else if (b.kind === 'acquisitions') acquisitions(d, b.input, r); else formalities(d, b.input, r); return { ...b, ...(b.result ? { history: [...(b.history ?? []), structuredClone(b.result)] } : {}), result: r, }; } const labels: Record = { base: 'Base', prior: 'Acumulado global anterior', ordinary: 'Parcela ordinária', excess: 'Parcela excedente', tax: 'Imposto', payment: 'Prestação própria', withheld: 'Retenção documentada', hypotheticalOwnTax: 'Cálculo próprio apenas hipotético,sem prestação gerada', covered: 'Parcela abrangida pelo limite', coveredTax: 'Retenção5%da parcela abrangida', excessTax: 'RetençãoIRPS20%da parcela qualificada', total: 'Total de retenções', interest: 'Juros documentados', finalPayment: 'Preparação do montante a entregar', }; export function ispcResidualReportRows( d: Record, ): Array<[string, string, string, string]> { const rows: Array<[string, string, string, string]> = []; for (const kind of ['occasional', 'acquisitions', 'formalities'] as const) { try { const b = ispcResidualBook(d, kind), r = b.result; if (!r) continue; const title = { occasional: 'Operações ocasionais próprias', acquisitions: 'Aquisições acima do limite', formalities: 'Formalidades2026—B–Geral', }[kind]; rows.push([ 'ISPC', title, `${({ calculated: 'Calculado', settled_by_withholding: 'Retenção liberatória documentada', partial: 'Parcial', indeterminate: 'Indeterminado', not_applicable: 'Não aplicável ao recorte' } as Record)[r.status]};${r.reason};falta:${r.missing.join('; ') || 'nenhum dado deste recorte'}`, `${ispcResidualCurrent(d, b) ? 'Entradas correspondentes' : 'Histórico preservado;rever entradas'};${r.calculatedAt};${r.source}`, ]); for (const [k, v] of Object.entries(r.amounts).filter( ([k]) => !k.startsWith('q8'), )) rows.push([ 'ISPC', `${title}—${kind === 'formalities' && r.input.sourceKind === 'acquisitions' && k === 'payment' ? 'Retenções a entregar pelo adquirente' : labels[k] || k}`, v ? `${fractionPresentation(v).decimal}MZN (${v.numerator}/${v.denominator})` : 'Indeterminado', `${r.input.reference || r.details.acquisitionRef || r.input.externalRef || 'Pressupostos/documentos declarados'};${r.scope}`, ]); } catch (e) { rows.push([ 'ISPC', 'Residual não recuperável', String(e), 'Originais preservados.', ]); } } return rows; } export function ispcFormalPreparationHtml(d: Record): string { const b = ispcResidualBook(d, 'formalities'), r = b.result; if (!r || !ispcResidualCurrent(d, b) || !Object.keys(r.amounts).length) throw Error( 'Guarde primeiro preparação actual com cálculo identificado.', ); const esc = (v: unknown) => inputText(v ?? '').replace( /[&<>"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''', })[c]!, ); const v = (n: string) => r.amounts[n] ? fractionPresentation(r.amounts[n]!).decimal : 'Indeterminado'; const fields = [ [ 'Quadro1—tipo', b.input.declaration === 'initial' ? 'Inicial' : 'Substituição', ], ['Quadro2—mês/ano', r.details.formPeriod], [ 'Contexto—trimestre e mês de pagamento', `2026/T${r.details.quarter};${r.details.paymentMonth} (não copiado automaticamente para quadro2)`, ], ['Quadro3—NUIT/ÁreaFiscal', `${b.input.nuit}/${b.input.fiscalArea}`], ['Quadro5—nome', b.input.name], ['Quadro6—domicílio', b.input.address], [ 'Quadro8—linha1:base01/taxa/montante05', `${b.input.sourceKind === 'acquisitions' ? 'ISPC—retenção12(3)' : 'ISPC—parcela ordinária'};base01:${v('q8base01')};taxa:${r.details.q8rate01}%;montante05:${v('q8tax05')}`, ], [ 'Quadro8—linha2:base02/taxa/montante06', r.amounts.q8base02 && cmp(r.amounts.q8base02, fraction(0n)) > 0n ? `${b.input.sourceKind === 'acquisitions' ? 'IRPS—acto isolado57(2k)' : 'ISPC—excesso de vendas8(1)d'};base02:${v('q8base02')};taxa20%;montante06:${v('q8tax06')}` : 'Não aplicável ao cálculo seleccionado;sem segunda parcela', ], ['Quadro8—subtotal1campo09', v('tax')], [ 'Quadro10—montante/juros/importância', `Imposto calculado:${v('tax')};prestação legal:${v('payment')};juros:${v('interest')};totalpreparado:${v('finalPayment')}`, ], [ 'Modelo/código das retenções', b.input.retentionFormRef || 'Por corresponder se aplicável', ], [ 'Estado externo declarado', `${({ preparation: 'Em preparação', submitted: 'Submetido segundo referência declarada', received: 'Recebido segundo referência declarada', paid: 'Pago segundo referência declarada' } as Record)[b.input.externalStatus]};${b.input.externalRef};${b.input.externalDate}`, ], ['Falta', r.missing.join('; ') || 'Nenhum dado deste recorte'], ]; return `PreparaçãoISPC—B–Geral

Preparação de campos ISPC—Modelo B–Geral

Correspondência de apoio; não é formulário oficial emitido, declaração submetida ou comprovativo de pagamento. Original e instrução AT preservados,sem os alterar.

${fields.map(([a, c]) => ``).join('')}
${esc(a)}${esc(c)}

${esc(r.source)}

Quadros8e10têm numeração própria;campos homónimos não são somados entre quadros. Não preencher área de serviços nem assinatura automaticamente.

`; }