import type { IrpcRow } from './irpc-withholdings.ts'; export const irpcReliefVersion = 'IRPC-L5-reliefs/1'; export type IrpcReliefPreparation = { rule?: '' | 'cirpc68d' | 'cirpc68f' | 'ptMz12'; facts?: Partial< Record< | 'subjectToIrpc' | 'notTransparent' | 'treatyResident' | 'beneficialOwner' | 'payerResidentMz' | 'armLength' | 'royaltyDefinition', '' | 'yes' | 'no' > >; kind: 'dispensation' | 'exemption' | 'treaty'; instrument: string; article: string; country: string; residenceDocument: string; validFrom: string; validTo: string; conditions: string; evidence: string; }; export const irpcReliefRules = [ ['', 'Correspondência por identificar'], ['cirpc68d', 'Dispensa — artigo 68(d), serviços / uso de equipamento'], ['cirpc68f', 'Dispensa — artigo 68(f), gestão de imóveis próprios'], ['ptMz12', 'Portugal–Moçambique — royalties, artigo 12'], ] as const; const documentLabels: Record = { instrument: 'diploma/convenção', article: 'artigo', validFrom: 'início do período', validTo: 'fim do período', conditions: 'condições', evidence: 'documentos', country: 'Estado contratante', residenceDocument: 'prova de residência', }; const factLabels: Record = { subjectToIrpc: 'Titular sujeito a IRPC neste rendimento', notTransparent: 'Fora da transparência fiscal', treatyResident: 'Residência convencional em Portugal', beneficialOwner: 'Beneficiário efectivo', payerResidentMz: 'Devedor residente em Moçambique', armLength: 'Sem excesso por relações especiais', royaltyDefinition: 'Definição convencional de royalties', }; export function evaluateIrpcRelief( row: IrpcRow, eventDate: string, ): | { applied: false; reason: string } | { applied: true; treatment: 'dispensed' | 'treaty'; rate: number; reason: string; rule: string; } { const e = row.reliefPreparation; if (!e) return { applied: false, reason: 'Identifique a regra da dispensa/convenção e os documentos aplicáveis.', }; const preparation = prepareIrpcRelief(row, e); if ( preparation.missing.length || !preparation.validPeriod || eventDate < e.validFrom || eventDate > e.validTo ) return { applied: false, reason: 'Informação insuficiente ou período incompatível na dispensa/convenção: ' + preparation.missing.map((k) => documentLabels[k]).join(', ') + '. Confira o período e a prova aplicáveis ao primeiro evento.', }; const f = e.facts ?? {}; const article = e.article.toLowerCase().replace(/[^a-z0-9]/g, ''); if ( (e.rule === 'cirpc68d' && article !== '68d') || (e.rule === 'cirpc68f' && article !== '68f') || (e.rule === 'ptMz12' && article !== '12') ) return { applied: false, reason: 'O artigo invocado diverge da regra seleccionada; reconcilie a referência sem apagar a prova.', }; const onAccount = row.residence === 'resident' || (row.residence === 'nonresident' && row.permanentEstablishment === 'yes' && row.attributable === 'yes'); if (e.rule === 'cirpc68d' || e.rule === 'cirpc68f') { if ( e.kind !== 'dispensation' || f.subjectToIrpc !== 'yes' || row.recipientIrpc !== 'yes' || !row.ordinary || row.ordinary.nonDigital !== 'yes' || !row.ordinary.reference.trim() ) return { applied: false, reason: 'Dispensa: identifique o titular sujeito a IRPC relativamente a este rendimento e a classificação não digital, com suporte.', }; if ( e.rule === 'cirpc68d' && (!onAccount || !['equipment', 'services'].includes(row.kind)) ) return { applied: false, reason: 'Artigo 68(d) exige rendimento 67(1)b/g com natureza de imposto por conta; não dispensa retenções definitivas.', }; if ( e.rule === 'cirpc68f' && (row.kind !== 'rent' || row.ordinary.rentOwnPropertyManagement !== 'yes' || f.notTransparent !== 'yes') ) return { applied: false, reason: 'Artigo 68(f): faltam rendas de sociedade de gestão de imóveis próprios e exclusão documentada da transparência fiscal (6(1)c).', }; return { applied: true, treatment: 'dispensed', rate: 0, rule: e.rule, reason: `Dispensa de retenção ${e.rule === 'cirpc68d' ? '68(d), rendimentos 67(1)b/g' : '68(f), rendimentos prediais 67(1)c e 6(1)c'}; CIRPC Lei 34/2007, PDF19/p768(73). Sem retenção nesta operação; não é isenção do rendimento nem crédito anual.`, }; } if (e.rule === 'ptMz12') { if ( e.kind !== 'treaty' || !['PT', 'PORTUGAL'].includes(e.country.trim().toUpperCase()) || row.residence !== 'nonresident' || row.permanentEstablishment !== 'no' || !['royalty', 'equipment'].includes(row.kind) || row.ordinary?.nonDigital !== 'yes' || !row.ordinary.reference.trim() ) return { applied: false, reason: 'Convenção PT–MZ, artigo 12: este recorte exige titular residente em Portugal, sem EP em Moçambique e royalties/uso de equipamento qualificados não digitais. Outros casos conservam correspondência própria.', }; if ( [ 'treatyResident', 'beneficialOwner', 'payerResidentMz', 'armLength', 'royaltyDefinition', ].some((k) => f[k as keyof typeof f] !== 'yes') ) return { applied: false, reason: 'Informação insuficiente: documente residência convencional (art.4), beneficiário efectivo, devedor residente em Moçambique, definição de royalties e ausência de excesso por relações especiais (art.12(2–6)).', }; return { applied: true, treatment: 'treaty', rate: 10, rule: e.rule, reason: 'Limite convencional de 10% sobre royalties brutas: CDT PT–MZ artigo12(2–6), RAR36/1992; definição12(3) e IRPC actualizados pelo Protocolo/RAR36/2009 arts1/7. Cálculo da retenção; formalidades de accionamento e MRFTA não equivalem a submissão ou aceitação AT.', }; } return { applied: false, reason: 'Dispensa, isenção ou convenção sem correspondência executável neste recorte; conserve os documentos e identifique a regra específica.', }; } export const newIrpcReliefPreparation = (): IrpcReliefPreparation => ({ kind: 'dispensation', instrument: '', article: '', country: '', residenceDocument: '', validFrom: '', validTo: '', conditions: '', evidence: '', }); // A preparation records an invocation; it does not activate a tax rule. export function prepareIrpcRelief( row: IrpcRow, evidence: IrpcReliefPreparation, ) { const missing = ( [ 'instrument', 'article', 'validFrom', 'validTo', 'conditions', 'evidence', ] as const ) .filter((key) => !evidence[key].trim()) .map(String); if (evidence.kind === 'treaty') { if (!evidence.country.trim()) missing.push('country'); if (!evidence.residenceDocument.trim()) missing.push('residenceDocument'); } const validDate = (s: string) => /^\d{4}-\d{2}-\d{2}$/.test(s) && Number.isFinite(Date.parse(s)) && new Date(s).toISOString().slice(0, 10) === s; const validPeriod = validDate(evidence.validFrom) && validDate(evidence.validTo) && evidence.validFrom <= evidence.validTo; const outsidePeriod = validPeriod && row.events.some( (event) => validDate(event.date) && (event.date < evidence.validFrom || event.date > evidence.validTo), ); return { counterparty: row.counterparty, residence: row.residence, permanentEstablishment: row.permanentEstablishment, attributable: row.attributable, documentRef: row.documentRef, qualificationRef: row.qualificationRef, previousReference: row.reliefRef, evidence: structuredClone(evidence), missing, validPeriod, outsidePeriod, status: 'preparation' as const, notice: 'Condições e documentos invocados; o cálculo verifica apenas a regra seleccionada e o período aplicável.', }; } export function irpcReliefEvidenceLabel(row: IrpcRow): string { const e = row.reliefPreparation; if (!e) return row.reliefRef || 'Sem benefício invocado'; const p = prepareIrpcRelief(row, e); return `${irpcReliefRules.find(([rule]) => rule === e.rule)?.[1] ?? 'Regra por corresponder'};${e.instrument};artigo:${e.article};período:${e.validFrom}–${e.validTo};Estado:${e.country};residência fiscal:${e.residenceDocument};condições:${e.conditions};provas:${e.evidence};${Object.entries( e.facts ?? {}, ) .map( ([k, v]) => `${factLabels[k] ?? k}:${v === 'yes' ? 'Sim' : v === 'no' ? 'Não' : 'Por esclarecer'}`, ) .join( ';', )};${p.missing.length ? 'Falta: ' + p.missing.map((k) => documentLabels[k]).join(', ') : 'Elementos documentais registados'};${p.validPeriod ? 'Período válido' : 'Período por resolver'};referência anterior:${row.reliefRef}`; }