224 lines
42 KiB
TypeScript
224 lines
42 KiB
TypeScript
import {carryPlan,carryIdentity,carrySource,newCreditCarry,validateCreditCarry,type CreditCarry} from './irpc-credit-carry.ts';
|
||
import {economicTaxProof,economicGroupPending,economicDistributionIdentity,economicPosition,economicDeduction,economicSource,newEconomicCredits,validateEconomic,type EconomicCredits} from './irpc-economic-credit.ts';
|
||
import {validateProofParcel,requireProofParcel,parcelProjection,overlappingProofParcels,type FiscalProofParcel} from './fiscal-proof-parcel.ts';
|
||
import {validateInternational,internationalProofValue,internationalGroupPending,internationalPosition,internationalDeduction,internationalCreditSource,newInternationalCredits,type InternationalCredits,type InternationalTaxProof} from './irpc-international-credit.ts';
|
||
import {validatePec,pecPaymentValue,pecDeduction,pecSource,type PecPayments} from './irpc-pec.ts';
|
||
import {normalBook,normalCurrent} from './irpc-normal.ts';
|
||
import {irpcBook,irpcCurrent} from './irpc-withholdings.ts';
|
||
import {acquisitionScope} from './iva-acquisitions.ts';
|
||
import {closureFingerprint} from './iva-closure.ts';
|
||
import {addFractions,subtractFractions,decimalFraction,fraction,fractionPresentation,type FiscalFraction} from './fiscal-rational.ts';
|
||
|
||
export const annualSettlementVersion='IRPC-annual-settlement/1';
|
||
export const annualSettlementSource='CIRPC, arts. 63 e 67 (Lei 34/2007 e natureza da retenção na redacção aplicável da Lei 12/2025); Regulamento, Decreto 9/2008, arts. 21(2–5) e 27(1–2/6). Recorte: 2026, residente no regime geral, ano civil e MZN. Colecta corrente válida, retenções efectivamente sofridas por conta e PPC comprovados. PEC/créditos positivos e tributação autónoma ficam fora deste fecho inicial. Art. 27(6): montante inferior a 100 MZN sem cobrança/reembolso; saldo aritmético exacto conservado. Saldo simulado por colecta projectada; não é declaração submetida, pagamento ou reembolso recebido.';
|
||
type State=''|'known'|'absent';
|
||
type Evidence={state:State;amount:string;reference:string};
|
||
type Group={state:State;reference:string};
|
||
export type AnnualWithholding=FiscalProofParcel&{id:string;rowId:string;paymentId:string;sourceKey:string;subjectNuit:string;year:string;amount:string;incomeIncluded:''|'yes'|'no';reference:string};
|
||
export type AnnualPpc=FiscalProofParcel&{id:string;document:string;issuer:string;date:string;subjectNuit:string;year:string;currency:string;proofAmount:string;amount:string;effective:''|'yes'|'no';reference:string};
|
||
type AnnualRecord={version:typeof annualSettlementVersion;key:string;createdAt:string;model:AnnualModel};
|
||
export type AnnualBook={version:1;subjectNuit:string;year:string;reference:string;assessment:''|'normal'|'special';assessmentKey:string;assessmentReference:string;withholdings:Group&{rows:AnnualWithholding[]};ppc:Group&{rows:AnnualPpc[]};pec:Evidence;pecPayments?:PecPayments;credits:Evidence;internationalCredits?:InternationalCredits;creditCarry?:CreditCarry;economicCredits?:EconomicCredits;result?:AnnualRecord;history:AnnualRecord[]};
|
||
export const annualSource=(b:AnnualBook)=>{
|
||
const excluded=b.internationalCredits?(b.pecPayments?'Créditos económicos/benefícios positivos':'PEC/créditos económicos/benefícios positivos'):(b.pecPayments?'Créditos próprios positivos':'PEC/créditos positivos');
|
||
return annualSettlementSource.replace('PEC/créditos positivos',b.economicCredits?(b.pecPayments?'Benefícios positivos':'PEC/benefícios positivos'):excluded)+(b.pecPayments?' '+pecSource:'')+(b.internationalCredits?' '+internationalCreditSource:'')+(b.creditCarry?' '+carrySource:'')+(b.economicCredits?' '+economicSource:'');
|
||
};
|
||
export function prepareEconomicBook(b:AnnualBook):AnnualBook{
|
||
if(b.economicCredits)return b;
|
||
const next=prepareInternationalBook(b),e=newEconomicCredits(),old=next.internationalCredits!.economic;
|
||
if(old.state==='absent'){e.state='absent';e.reference=old.reference;}
|
||
return{...next,economicCredits:e};
|
||
}
|
||
export function prepareCreditCarry(b:AnnualBook):AnnualBook{return b.creditCarry?b:{...b,creditCarry:newCreditCarry()};}
|
||
export function prepareInternationalBook(b:AnnualBook):AnnualBook{
|
||
if(b.internationalCredits)return b;
|
||
const c=newInternationalCredits(),old=b.credits;
|
||
if(old.state==='absent'||old.state==='known'&&old.amount==='0'){
|
||
c.economic=structuredClone(old);c.benefits=structuredClone(old);
|
||
if(old.state==='absent'){c.state='absent';c.reference=old.reference;}
|
||
}
|
||
return{...b,internationalCredits:c};
|
||
}
|
||
|
||
const evidence=():Evidence=>({state:'',amount:'',reference:''});
|
||
export const newAnnualBook=():AnnualBook=>({version:1,subjectNuit:'',year:'',reference:'',assessment:'',assessmentKey:'',assessmentReference:'',withholdings:{state:'',reference:'',rows:[]},ppc:{state:'',reference:'',rows:[]},pec:evidence(),credits:evidence(),history:[]});
|
||
export const newAnnualPpc=(id:string):AnnualPpc=>({id,document:'',issuer:'',date:'',subjectNuit:'',year:'',currency:'MZN',proofAmount:'',amount:'',effective:'',reference:''});
|
||
export const newInternationalProof=(id:string):InternationalTaxProof=>({...newAnnualPpc(id),incomeTax:'',taxReference:'',exerciseReference:''});
|
||
const zero=()=>fraction(0n),cmp=(a:FiscalFraction,b:FiscalFraction)=>BigInt(a.numerator)*BigInt(b.denominator)-BigInt(b.numerator)*BigInt(a.denominator);
|
||
const iso=(s:string)=>/^20\d{2}-\d{2}-\d{2}$/.test(s)&&Number.isFinite(Date.parse(s))&&new Date(s).toISOString().slice(0,10)===s;
|
||
const moneyInput=(s:string)=>{if(!/^\d{1,18}(\.\d{1,2})?$/.test(s))throw Error('MontanteMZN em falta/inválido;máximo dois decimais,sem zero presumido.');return decimalFraction(s);};
|
||
const canonical=(s:string)=>s.normalize('NFKC').trim().toLocaleLowerCase('pt').replace(/\s+/g,' ');
|
||
const proofIdentity=(issuer:string,document:string,_date:string)=>JSON.stringify([canonical(issuer),canonical(document)]);
|
||
function store(d:Record<string,unknown>){const s=JSON.parse(String(d.modelo_a_snapshot||'{"version":1,"books":{}}'));if(s.version!==1||!s.books||typeof s.books!=='object'||Array.isArray(s.books))throw Error('Livros fiscais não recuperáveis;originais conservados.');return s;}
|
||
function books(d:Record<string,unknown>):Record<string,AnnualBook>{const m=store(d).irpcAnnual;if(m===undefined)return{};if(!m||typeof m!=='object'||Array.isArray(m))throw Error('MapaL8 não recuperável;originais conservados.');return m;}
|
||
export function validateAnnual(b:AnnualBook){
|
||
if(b?.economicCredits!==undefined)validateEconomic(b.economicCredits);
|
||
if(b?.creditCarry!==undefined)validateCreditCarry(b.creditCarry);
|
||
if(b?.pecPayments!==undefined)validatePec(b.pecPayments);
|
||
if(b?.internationalCredits!==undefined)validateInternational(b.internationalCredits);
|
||
if(!b||b.version!==1||['subjectNuit','year','reference','assessmentKey','assessmentReference'].some(k=>typeof b[k as 'year']!=='string')||!['','normal','special'].includes(b.assessment)||!Array.isArray(b.history))throw Error('PreparaçãoL8 não recuperável.');
|
||
for(const g of[b.withholdings,b.ppc,b.pec,b.credits])if(!g||!['','known','absent'].includes(g.state)||typeof g.reference!=='string')throw Error('EstadoL8 não recuperável.');
|
||
for(const g of[b.pec,b.credits])if(typeof g.amount!=='string')throw Error('CréditoL8 não recuperável.');
|
||
const w=['id','rowId','paymentId','sourceKey','subjectNuit','year','amount','incomeIncluded','reference'],p=Object.keys(newAnnualPpc(''));
|
||
for(const[rows,keys]of[[b.withholdings.rows,w],[b.ppc.rows,p]]as const)if(!Array.isArray(rows)||new Set(rows.map(r=>r?.id)).size!==rows.length||rows.some(r=>!r||keys.some(k=>typeof(r as unknown as Record<string,unknown>)[k]!=='string')))throw Error('ComprovativosL8 não recuperáveis ou identificador repetido.');
|
||
for(const r of[...b.withholdings.rows,...b.ppc.rows])validateProofParcel(r);
|
||
if(b.withholdings.rows.some(r=>!['','yes','no'].includes(r.incomeIncluded))||b.ppc.rows.some(r=>!['','yes','no'].includes(r.effective)))throw Error('QualificaçãoL8 não recuperável.');
|
||
for(const r of[b.result,...b.history])if(r&&(r.version!==annualSettlementVersion||typeof r.key!=='string'||typeof r.createdAt!=='string'||!r.model||!Array.isArray(r.model.pending)))throw Error('HistóricoL8 não recuperável.');
|
||
}
|
||
export function annualBook(d:Record<string,unknown>){const b=books(d)[acquisitionScope(d)];if(b)validateAnnual(b);return b;}
|
||
export function saveAnnualBook(d:Record<string,unknown>,b:AnnualBook){validateAnnual(b);return JSON.stringify({...store(d),irpcAnnual:{...books(d),[acquisitionScope(d)]:b}});}
|
||
export function annualAssessment(d:Record<string,unknown>){
|
||
const b=normalBook(d),r=b.result,m=r?.model;
|
||
return{book:b,record:r,current:normalCurrent(d,b),key:r?closureFingerprint(r):'',subjectNuit:b.base?.subjectNuit??'',year:b.year,period:m?.periodsFx?{from:m.periodsFx.input.from,to:m.periodsFx.input.to}:m?.fiscalPeriod??m?.assessment?.period??null,kind:b.special?'special' as const:'normal' as const,colecta:b.special?m?.special?.colecta??null:m?.colecta??null,autonomous:b.special?m?.special?.expenses.autonomousTax??null:zero()};
|
||
}
|
||
export function reuseAnnualAssessment(d:Record<string,unknown>,b:AnnualBook):AnnualBook{const a=annualAssessment(d);return{...b,subjectNuit:b.subjectNuit||a.subjectNuit,year:b.year||a.year,assessment:a.kind,assessmentKey:a.key,assessmentReference:b.assessmentReference||a.book.assessment?.reference||''};}
|
||
export function annualWithholdingOptions(d:Record<string,unknown>){
|
||
const b=irpcBook(d),current=irpcCurrent(d,b);return b.rows.flatMap(r=>r.allocations.map(a=>{
|
||
const p=b.payments.find(p=>p.id===a.paymentId),v=b.result?.rows.find(v=>v.id===r.id),issues:string[]=[];
|
||
if(!current||!b.result)issues.push('Recalcule e guardeL5 com as entradas actuais.');
|
||
if(r.direction!=='received'||r.origin!=='effective'||v?.direction!=='received'||v.status!=='calculated'||v.nature!=='on_account'||r.settlement!=='complete'||v.settled===null)issues.push('Apenas retenção recebida/efectiva por conta,calculada e reconciliada emL5;remessa por terceiros,definitiva ou projecção não é crédito próprio.');
|
||
if(!p||p.kind!=='withholding_proof'||p.currency!=='MZN'||r.currency!=='MZN'||!iso(p.date)||!p.reference.trim())issues.push('Comprovativo de retenção sofrida emMZN em falta ou fora do recorte.');
|
||
return{rowId:r.id,paymentId:a.paymentId,document:p?.reference??'',issuer:r.counterparty,date:p?.date??'',eventDate:v?.eventDate??'',year:v?.eventDate.slice(0,4)??'',amount:a.amount,proofAmount:p?.amount??'',identity:proofIdentity(r.counterparty,p?.reference??'',p?.date??''),key:closureFingerprint([r,v,p,a]),ready:!issues.length,reason:issues.join(' ')};
|
||
}));
|
||
}
|
||
export function reuseAnnualWithholding(d:Record<string,unknown>,b:AnnualBook,rowId:string,paymentId:string,id:string):AnnualBook{
|
||
const o=annualWithholdingOptions(d).find(o=>o.rowId===rowId&&o.paymentId===paymentId);if(!o)throw Error('ComprovativoL5 não encontrado;originais conservados.');
|
||
if(b.withholdings.rows.some(r=>r.rowId===rowId&&r.paymentId===paymentId))throw Error('Retenção/prova já seleccionada;altere a parcela existente sem duplicar.');
|
||
return{...b,withholdings:{...b.withholdings,rows:[...b.withholdings.rows,{id,rowId,paymentId,sourceKey:o.key,subjectNuit:b.subjectNuit,year:o.year,amount:o.amount,incomeIncluded:'',reference:''}]}};
|
||
}
|
||
function dependencies(d:Record<string,unknown>){let normal:unknown,L5:unknown;try{const a=annualAssessment(d);normal=[a.record,a.current];}catch(e){normal=String(e);}try{const b=irpcBook(d);L5=[b.rows,b.payments,b.result,irpcCurrent(d,b)];}catch(e){L5=String(e);}return[normal,L5,Object.entries(books(d)).filter(([k])=>k!==acquisitionScope(d)).map(([k,b])=>[k,b.subjectNuit,b.year,b.withholdings,b.ppc,...(b.pecPayments?[b.pecPayments]:[]),...(b.internationalCredits?[b.internationalCredits]:[]),...(b.creditCarry?[b.creditCarry]:[]),...(b.economicCredits?[b.economicCredits]:[])])];}
|
||
function key(d:Record<string,unknown>,b:AnnualBook){const{result:_,history:__,...input}=b;return closureFingerprint([annualSettlementVersion,acquisitionScope(d),input,dependencies(d)]);}
|
||
export function annualCurrent(d:Record<string,unknown>,b:AnnualBook){return !!b.result&&b.result.key===key(d,b);}
|
||
export function calculateAnnual(d:Record<string,unknown>,b:AnnualBook){
|
||
validateAnnual(b);const{result:_result,history:_history,...input}=b;const pending:string[]=[],warn:string[]=[];let assessment:ReturnType<typeof annualAssessment>|null=null,colecta:FiscalFraction|null=null;let options:ReturnType<typeof annualWithholdingOptions>=[];
|
||
try{assessment=annualAssessment(d);const a=assessment,m=a.record?.model;
|
||
if(!a.record||!a.current||a.key!==b.assessmentKey||b.assessment!==a.kind)pending.push('Colecta de origem ausente,alterada ou não corrente;reutilize a execuçãoL6/L7 aplicável,não some nem contorne efeitosL7.');
|
||
if(!/^\d{9}$/.test(b.subjectNuit)||b.subjectNuit!==a.subjectNuit||b.year!=='2026'||b.year!==a.year||a.book.scope!=='resident_general'||m?.currency!=='MZN'||a.period?.from!=='2026-01-01'||a.period?.to!=='2026-12-31')pending.push('Entidade/NUIT,exercício ou âmbito fora do recorte residente/ano civil2026/MZN;períodos especiais permanecem separados.');
|
||
if(!b.reference.trim()||!b.assessmentReference.trim())pending.push('Falta suporte do fecho do sujeito/exercício e da colecta seleccionada.');
|
||
if(!a.colecta||cmp(a.colecta,zero())<0n)pending.push('Colecta aplicável indeterminada;uma parcelaL6 não substitui totalL7 pendente.');else colecta=structuredClone(a.colecta);
|
||
if(a.kind==='special'&&(!m?.special||!a.autonomous||cmp(a.autonomous,zero())!==0n))pending.push('Tributação autónoma positiva/indeterminada exige liquidação própria;este primeiro saldo não a ignora nem a compensa.');
|
||
}catch(e){pending.push('Origem da colecta não recuperável: '+String(e));}
|
||
try{options=annualWithholdingOptions(d);}catch(e){if(b.withholdings.state==='known'||b.withholdings.rows.length)pending.push('OrigemL5 não recuperável: '+String(e));}
|
||
const group=(g:Group,rows:unknown[],label:string)=>{if(!g.state||!g.reference.trim())pending.push(label+':identifique valor conhecido,ausência declarada ou informação em falta com suporte.');if(g.state==='absent'&&rows.length)pending.push(label+':ausência declarada contradiz parcelas presentes;conserve e reconcilie.');if(g.state==='known'&&!rows.length)pending.push(label+':não há comprovativos seleccionados;ausência exige declaração própria.');};
|
||
group(b.withholdings,b.withholdings.rows,'Retenções sofridas por conta');group(b.ppc,b.ppc.rows,'PPC efectuados');
|
||
try{if(b.withholdings.state==='absent'&&options.some(o=>o.ready&&o.year===b.year&&cmp(moneyInput(o.amount),zero())>0n))pending.push('Ausência de retenções sofridas contradiz provaL5 efectiva por conta no exercício;reconcilie sem a ignorar.');}catch(e){pending.push('Reconciliação da ausência de retenções: '+String(e));}
|
||
for(const[label,e]of[...(b.pecPayments?[]:[['PEC',b.pec]as const]),...(b.internationalCredits?[]:[['Créditos com regras próprias',b.credits]as const])]){try{if(!e.state||!e.reference.trim())throw Error('presença/ausência e suporte em falta');if(e.state==='absent'){if(e.amount.trim())throw Error('ausência contradiz montante preenchido');}else if(cmp(moneyInput(e.amount),zero())!==0n)throw Error('montante positivo fora deste recorte:corresponder ordem,limite e transporte próprios;sem abatimento ou reembolso automático');}catch(err){pending.push(label+': '+String(err));}}
|
||
const claims:Array<FiscalProofParcel&{id:string;kind:'withholding'|'ppc'|'pec'|'foreign_credit'|'economic_credit';country?:string;positionId?:string;previouslyUsed?:FiscalFraction;identity:string;proofAmount:FiscalFraction|null;value:FiscalFraction|null;sourceKey:string;allocationAmount:FiscalFraction|null;document:string;issuer:string;date:string;subjectNuit:string;year:string;reason:string}>=[];
|
||
for(const r of b.withholdings.rows){const o=options.find(o=>o.rowId===r.rowId&&o.paymentId===r.paymentId),issues:string[]=[];let value:FiscalFraction|null=null,proofAmount:FiscalFraction|null=null;
|
||
try{requireProofParcel(r);if(!o||!o.ready||r.sourceKey!==o.key)throw Error(o?.reason||'provaL5 ausente ou alterada;reutilize sem duplicar');if(r.subjectNuit!==b.subjectNuit||r.year!==b.year||o.year!==b.year||!o.date.startsWith(b.year+'-'))throw Error('entidade/exercício da retenção não correspondem ao fecho');if(r.incomeIncluded!=='yes'||!r.reference.trim())throw Error('confirme o próprio beneficiário/NUIT e inclusão do rendimento na base do exercício com suporte');value=moneyInput(r.amount);proofAmount=moneyInput(o.proofAmount);if(cmp(value,moneyInput(o.amount))>0n)throw Error('parcela ultrapassa a retenção efectivamente comprovada e afectada emL5');if(b.withholdings.rows.filter(x=>x.rowId===r.rowId&&x.paymentId===r.paymentId).length!==1)throw Error('retenção/prova repetida;uma afectação por linhaL5');if(assessment?.record?.model.special?.cooperative.status==='calculated'&&cmp(value,zero())>0n)throw Error('cooperativa integralmente elegível declara ausência de rendimentos retidos;segregue o casoL7 antes do crédito anual');}catch(e){issues.push(String(e));value=null;}
|
||
claims.push({...parcelProjection(r),id:r.id,kind:'withholding',identity:o?.identity??'',proofAmount,value,sourceKey:r.sourceKey,allocationAmount:o&&value?moneyInput(o.amount):null,document:o?.document??r.paymentId,issuer:o?.issuer??'',date:o?.date??'',subjectNuit:r.subjectNuit,year:r.year,reason:issues.join(' ')});
|
||
}
|
||
for(const r of b.ppc.rows){const issues:string[]=[];let value:FiscalFraction|null=null,proofAmount:FiscalFraction|null=null;
|
||
try{requireProofParcel(r);if(!r.document.trim()||!r.issuer.trim()||!r.reference.trim()||r.effective!=='yes'||r.currency!=='MZN'||!iso(r.date)||!r.date.startsWith(b.year+'-')||r.subjectNuit!==b.subjectNuit||r.year!==b.year)throw Error('PPC exige comprovativo efectivo,MZN,data,entidade e exercício correspondentes;projecção/tesouraria não é pagamento');value=moneyInput(r.amount);proofAmount=moneyInput(r.proofAmount);if(cmp(value,proofAmount)>0n)throw Error('parcelaPPC ultrapassa o montante comprovado');}catch(e){issues.push(String(e));value=null;}
|
||
claims.push({...parcelProjection(r),id:r.id,kind:'ppc',identity:proofIdentity(r.issuer,r.document,r.date),proofAmount,value,sourceKey:'',allocationAmount:null,document:r.document,issuer:r.issuer,date:r.date,subjectNuit:r.subjectNuit,year:r.year,reason:issues.join(' ')});
|
||
}
|
||
if(b.pecPayments){group(b.pecPayments,b.pecPayments.rows,'PEC pagos no próprio exercício');for(const r of b.pecPayments.rows){let value:FiscalFraction|null=null,proofAmount:FiscalFraction|null=null,previouslyUsed:FiscalFraction|undefined;let reason='';try{({value,proofAmount,previouslyUsed}=pecPaymentValue(r,b.subjectNuit,b.year));}catch(e){reason=String(e);}claims.push({...parcelProjection(r),id:r.id,kind:'pec',identity:proofIdentity(r.issuer,r.document,r.date),value,proofAmount,previouslyUsed,sourceKey:'',allocationAmount:null,document:r.document,issuer:r.issuer,date:r.date,subjectNuit:r.subjectNuit,year:r.year,reason});}}
|
||
if(b.internationalCredits){
|
||
pending.push(...internationalGroupPending(b.internationalCredits,!!b.economicCredits));
|
||
const countries=b.internationalCredits.positions.map(p=>p.country);
|
||
if(new Set(countries).size!==countries.length)pending.push('Um país/exercício tem várias posições;consolide rendimentos e comprovativos sob uma única fracção nacional,sem repetir o limite.');
|
||
for(const p of b.internationalCredits.positions)for(const r of p.payments){let value:FiscalFraction|null=null,proofAmount:FiscalFraction|null=null,reason='';try{({value,proofAmount}=internationalProofValue(r,b.subjectNuit,b.year));}catch(e){reason=String(e);}claims.push({...parcelProjection(r),id:r.id,kind:'foreign_credit',country:p.country,positionId:p.id,identity:proofIdentity(r.issuer,r.document,r.date),proofAmount,value,sourceKey:'',allocationAmount:null,document:r.document,issuer:r.issuer,date:r.date,subjectNuit:r.subjectNuit,year:r.year,reason});}
|
||
}
|
||
if(b.economicCredits){
|
||
pending.push(...economicGroupPending(b.economicCredits));
|
||
if(!b.internationalCredits)pending.push('Distinguir o detalhe económico do inventário internacional e dos benefícios; use a preparação existente sem apagar o resumo anterior.');
|
||
for(const r of b.economicCredits.rows){let value:FiscalFraction|null=null,proofAmount:FiscalFraction|null=null,previouslyUsed:FiscalFraction|undefined,reason='';try{({value,proofAmount,previouslyUsed}=economicTaxProof(r));}catch(e){reason=String(e);}claims.push({id:r.id,kind:'economic_credit',positionId:r.id,identity:proofIdentity(r.taxIssuer,r.taxDocument,r.distributionDate),parcel:r.taxParcel,parcelReference:r.parcelReference,proofAmount,value,previouslyUsed,sourceKey:'',allocationAmount:null,document:r.taxDocument,issuer:r.taxIssuer,date:r.distributionDate,subjectNuit:r.subjectNuit,year:r.year,reason});}
|
||
}
|
||
const elsewhere:Array<FiscalProofParcel&{identity:string;subjectNuit:string;year:string;value:FiscalFraction;proofAmount:FiscalFraction;sourceKey:string;kind:'ppc'|'pec'|'withholding'|'foreign_credit'|'economic_credit';country?:string;previouslyUsed?:FiscalFraction}>=[];
|
||
for(const[scope,other]of Object.entries(books(d))){if(scope===acquisitionScope(d))continue;try{validateAnnual(other);for(const r of other.economicCredits?.rows??[]){
|
||
if(b.economicCredits?.rows.some(x=>economicDistributionIdentity(x)===economicDistributionIdentity(r)))pending.push('Parcela de lucros económicos já preparada noutro mapa;consolide sem duplicar o crédito, mesmo com documentos de imposto diferentes.');
|
||
const v=economicTaxProof(r);elsewhere.push({identity:proofIdentity(r.taxIssuer,r.taxDocument,r.distributionDate),parcel:r.taxParcel,parcelReference:r.parcelReference,subjectNuit:r.subjectNuit,year:r.year,...v,sourceKey:'',kind:'economic_credit'});
|
||
}for(const p of other.internationalCredits?.positions??[]){if(other.subjectNuit===b.subjectNuit&&other.year===b.year&&b.internationalCredits?.positions.some(x=>x.country===p.country))pending.push('País '+p.country+' já preparado noutro mapa da entidade/exercício;consolide a fracção nacional e usos sem duplicar quotas.');for(const r of p.payments){const v=internationalProofValue(r,other.subjectNuit,other.year);elsewhere.push({...parcelProjection(r),identity:proofIdentity(r.issuer,r.document,r.date),subjectNuit:r.subjectNuit,year:r.year,...v,sourceKey:'',kind:'foreign_credit',country:p.country});}}for(const r of other.pecPayments?.rows??[]){const p=pecPaymentValue(r,other.subjectNuit,other.year);elsewhere.push({...parcelProjection(r),identity:proofIdentity(r.issuer,r.document,r.date),subjectNuit:r.subjectNuit,year:r.year,...p,sourceKey:'',kind:'pec'});}for(const r of other.ppc.rows)elsewhere.push({...parcelProjection(r),identity:proofIdentity(r.issuer,r.document,r.date),subjectNuit:r.subjectNuit,year:r.year,value:moneyInput(r.amount),proofAmount:moneyInput(r.proofAmount),sourceKey:'',kind:'ppc'});for(const r of other.withholdings.rows){const source=(store(d).irpcWithholdings??{})[scope];const row=source?.rows?.find((x:{id:string})=>x.id===r.rowId),p=source?.payments?.find((x:{id:string})=>x.id===r.paymentId);if(row&&p)elsewhere.push({...parcelProjection(r),identity:proofIdentity(row.counterparty,p.reference,p.date),subjectNuit:r.subjectNuit,year:r.year,value:moneyInput(r.amount),proofAmount:moneyInput(p.amount),sourceKey:r.sourceKey,kind:'withholding'});}}catch(e){if(other?.subjectNuit===b.subjectNuit)pending.push('Outro mapa da mesma entidade não recuperável para conferir duplicações: '+scope+';'+String(e));}}
|
||
for(const c of claims){if(!c.value||!c.proofAmount)continue;const same=claims.filter(x=>overlappingProofParcels(x,c)&&x.value),other=elsewhere.filter(x=>overlappingProofParcels(x,c));let issue='';
|
||
if(same.filter(x=>x.kind!=='withholding').length>1&&!same.every(x=>x.kind==='economic_credit')||new Set(same.map(x=>x.kind)).size>1)issue='Mesmo comprovativo repetido ou usado com naturezas diferentes (retenção/PPC/PEC/crédito internacional);reconcilie sem dedução dupla.';
|
||
if(other.some(x=>x.subjectNuit!==b.subjectNuit||x.year!==b.year))issue='Mesmo comprovativo já associado a outra entidade/exercício;reconcilie a titularidade e a afectação.';
|
||
if([...same,...other].some(x=>x.proofAmount&&cmp(x.proofAmount,c.proofAmount!)!==0n)||cmp(addFractions([...same.flatMap(x=>x.value?[x.value]:[]),...other.map(x=>x.value)]),c.proofAmount)>0n)issue='Comprovativo utilizado além do montante disponível ou com montantes divergentes neste/outro mapa.';
|
||
if((c.kind==='pec'||c.kind==='economic_credit')&&c.previouslyUsed){if(other.some(x=>x.kind!==c.kind)||[...same,...other].some(x=>!x.previouslyUsed||cmp(x.previouslyUsed,c.previouslyUsed!)!==0n)||cmp(addFractions([c.previouslyUsed,...same.flatMap(x=>x.value?[x.value]:[]),...other.map(x=>x.value)]),c.proofAmount)>0n)issue='PEC/IRPC económico com utilização/atribuição anterior divergente, natureza incompatível ou parcelas acima do saldo disponível.';}
|
||
if(other.some(x=>x.kind!==c.kind))issue='Comprovativo já afectado a outra natureza (retenção/PPC/PEC/crédito internacional);sem dupla dedução.';
|
||
if(c.kind==='foreign_credit'&&other.some(x=>x.country!==c.country))issue='Comprovativo estrangeiro associado a outro país/natureza;reconcilie sem duplicação.';
|
||
if(c.kind==='withholding'&&c.sourceKey&&c.allocationAmount&&cmp(addFractions([...claims.filter(x=>x.kind==='withholding'&&x.sourceKey===c.sourceKey).flatMap(x=>x.value?[x.value]:[]),...elsewhere.filter(x=>x.kind==='withholding'&&x.sourceKey===c.sourceKey).map(x=>x.value)]),c.allocationAmount)>0n)issue='Mesma afectação L5 reutilizada além da parcela disponível, ainda que o comprovativo global seja maior.';
|
||
if(issue)c.reason=[c.reason,issue].filter(Boolean).join(' ');
|
||
}
|
||
for(const c of claims)if(c.reason)pending.push((c.document||c.id)+': '+c.reason);
|
||
const knownWithholdings=addFractions(claims.filter(c=>c.kind==='withholding'&&!c.reason&&c.value).map(c=>c.value!)),knownPpc=addFractions(claims.filter(c=>c.kind==='ppc'&&!c.reason&&c.value).map(c=>c.value!));
|
||
const positions=b.internationalCredits?.positions.map(p=>{const own=claims.filter(c=>c.kind==='foreign_credit'&&c.positionId===p.id);const m=internationalPosition(p,addFractions(own.filter(c=>!c.reason&&c.value).map(c=>c.value!)),own.filter(c=>c.reason).map(c=>c.reason),b.subjectNuit,b.year,b.assessmentKey);pending.push(...m.pending.map(e=>'País '+p.country+': '+e));return m;})??[];
|
||
const economicRows=b.economicCredits?.rows.map(r=>{
|
||
const issues=claims.filter(c=>c.kind==='economic_credit'&&c.positionId===r.id&&c.reason).map(c=>c.reason);
|
||
const m=economicPosition(r,b.subjectNuit,b.year,b.assessmentKey,colecta,issues);
|
||
pending.push(...m.pending.map(e=>'Lucros '+r.distributionDocument+': '+e));return m;
|
||
})??[];
|
||
if(b.economicCredits){
|
||
if(b.economicCredits.rows.length&&assessment?.kind!=='normal')pending.push('Crédito económico delimitado à colecta normal corrente;efeitos L7 exigem correspondência própria.');
|
||
}
|
||
const economicPreview=b.economicCredits?economicDeduction(economicRows,colecta,true):null;
|
||
const remainingForCarry=economicPreview?.afterCredit??colecta;
|
||
const pecAvailable=addFractions(claims.filter(c=>c.kind==='pec'&&!c.reason&&c.value).map(c=>c.value!));
|
||
const carryDuplicates:string[]=[];
|
||
if(b.creditCarry)for(const[scope,other]of Object.entries(books(d))){
|
||
if(scope===acquisitionScope(d)||other.subjectNuit!==b.subjectNuit)continue;
|
||
try{validateAnnual(other);if(other.creditCarry?.lots.some(r=>b.creditCarry!.lots.some(x=>carryIdentity(x)===carryIdentity(r))))carryDuplicates.push('Saldo anterior já preparado noutro mapa da entidade;consolide a origem e usos sem duplicar: '+scope);}
|
||
catch(e){carryDuplicates.push('Outro mapa da entidade não recuperável para conferir saldos anteriores: '+scope+';'+String(e));}
|
||
}
|
||
const carry=b.creditCarry?carryPlan(b.creditCarry,b.subjectNuit,b.year,b.assessmentKey,addFractions(positions.flatMap(p=>p.available&&!p.pending.length?[p.available]:[])),pecAvailable,remainingForCarry,carryDuplicates):null;
|
||
if(carry)pending.push(...carry.pending);
|
||
const ready=pending.length===0&&colecta!==null,economic=b.economicCredits?economicDeduction(economicRows,colecta,ready):null,afterEconomic=economic?.afterCredit??colecta;
|
||
const international=b.internationalCredits?internationalDeduction(positions,afterEconomic,ready,carry?.international??undefined):null;
|
||
const creditCollecta=ready&&carry?subtractFractions(international?.afterCredit??afterEconomic!,carry.priorInternational):international?.afterCredit??afterEconomic;
|
||
const pec=b.pecPayments?pecDeduction(pecAvailable,creditCollecta,ready,carry?.pec??undefined):null;
|
||
const afterPec=ready&&carry?subtractFractions(pec?.afterPec??creditCollecta!,carry.priorPec):pec?.afterPec??creditCollecta;
|
||
const arithmeticBalance=ready?subtractFractions(subtractFractions(afterPec!,knownWithholdings),knownPpc):null;
|
||
if(international&&international.insufficientCollecta&&cmp(international.insufficientCollecta,zero())>0n)warn.push('Crédito elegível sem colecta suficiente:sem reembolso,transporte ou escolha automática da afectação por país.');
|
||
let payable:FiscalFraction|null=null,recoverable:FiscalFraction|null=null,belowMinimum:FiscalFraction|null=null;let status:'payable'|'recoverable'|'balanced'|'below_minimum'|'indeterminate'='indeterminate';
|
||
if(arithmeticBalance){const n=BigInt(arithmeticBalance.numerator),abs=fraction(n<0n?-n:n,BigInt(arithmeticBalance.denominator));payable=zero();recoverable=zero();belowMinimum=zero();if(!n)status='balanced';else if(cmp(abs,fraction(100n))<0n){status='below_minimum';belowMinimum=abs;warn.push('Art27(6):saldo inferior a100MZN,sem cobrança/reembolso neste recorte;aritmética conservada,não é transporte de crédito.');}else if(n>0n){status='payable';payable=abs;}else{status='recoverable';recoverable=abs;}}
|
||
return{version:annualSettlementVersion,input:structuredClone(input),subjectNuit:b.subjectNuit,year:b.year,assessment:assessment?.record?{key:b.assessmentKey,kind:b.assessment,version:assessment.record.model.version,createdAt:assessment.record.createdAt,colecta,period:assessment.period,origin:assessment.record.model.origin}:null,claims,...(economic?{economicCredit:economic}:{}),internationalCredit:international,pecDeduction:pec,...(carry?{creditCarry:{...carry,afterInternational:ready?creditCollecta:null,afterPec:ready?afterPec:null}}:{}),knownWithholdings,knownPpc,arithmeticBalance,payable,recoverable,belowMinimum,status,pending:[...new Set(pending)],warnings:warn,source:annualSource(b),officialBalancesConsumed:false,submitted:false,refundReceived:false,limitations:[...(b.economicCredits?['Crédito económico ordinário:IRPC correspondente aos lucros,acréscimo53 já incluído e quota proporcional documentados. Sem cálculo automático da inclusão ou da exclusão40;liquidação64(3),associações64(4),benefícios e regras especiais separados. Sem reembolso ou transporte económico presumidos.']:[]),...(b.creditCarry?['Saldos anteriores documentados: escolha explícita das parcelas correntes e anteriores, sem prioridade presumida entre exercícios, reembolso ou criação/consumo de saldos oficiais.']:[]),...(b.internationalCredits?['Crédito internacional do próprio exercício:fracção nacional,convenção e montantes MZN são documentados;sem cálculo presumido,transporte ou consumo oficial. '+(b.economicCredits?'Benefícios positivos ficam separados;crédito económico no detalhe próprio.':'Créditos económicos/benefícios positivos ficam separados.')]:[]),b.economicCredits?'L8 parcial:benefícios próprios,derivação automática de quotas,inclusão/correcção económica na base,liquidação/associações,obrigações,autónomos,períodos especiais,moedas eM20/M22 separados. Saldos anteriores só quando detalhados e documentados.':b.creditCarry?'L8 parcial:créditos económicos/benefícios,obrigações,tributação autónoma,períodos especiais,outras moedas e correspondência M20/M22 separados. Prazos dos saldos anteriores demonstrados apenas para origens anuais ordinárias documentadas;sem criação automática de transporte.':b.internationalCredits?'L8 parcial:créditos económicos/benefícios positivos,transporte/caducidade,obrigações,tributação autónoma,períodos especiais e outras moedas separados. PEC sem reembolso ou transporte automático.':(b.pecPayments?'L8 parcial:transporte/caducidade de PEC, cálculo da obrigação, créditos próprios positivos, tributação autónoma, períodos especiais e outras moedas separados. Excedente PEC não é reembolso nem saldo transportado oficialmente.':'L8 parcial:PEC e créditos positivos,tributação autónoma,períodos especiais e pagamentos noutra moeda exigem regras próprias.'),'PPC aqui é pagamento comprovado;não cálculo automático da obrigação ou calendário.','Retenção sofrida não é retenção entregue pela empresa por conta de terceiros.','Valores racionais exactos;camposM20/M22 e representação não presumidos.','Saldo simulado por colecta projectada;sem pagamento,submissão ou reembolso automático.']};
|
||
}
|
||
export type AnnualModel=ReturnType<typeof calculateAnnual>;
|
||
export function recordAnnual(d:Record<string,unknown>,b:AnnualBook,now=new Date().toISOString()):AnnualBook{return{...b,history:b.result?[...b.history,structuredClone(b.result)]:b.history,result:{version:annualSettlementVersion,key:key(d,b),createdAt:now,model:calculateAnnual(d,b)}};}
|
||
const show=(v:FiscalFraction|null)=>v?(fractionPresentation(v).approximate?'≈':'')+fractionPresentation(v).decimal+' MZN (exacto '+v.numerator+'/'+v.denominator+')':'Indeterminado';
|
||
const stateLabel=(s:State)=>s==='known'?'Valor conhecido':s==='absent'?'Ausência declarada':'Informação em falta';
|
||
export function annualReportRows(d:Record<string,unknown>):Array<[string,string,string,string]>{
|
||
try{
|
||
const b=annualBook(d),r=b?.result;if(!b||!r)return[];const m=r.model;
|
||
const status={payable:'A pagar',recoverable:'A recuperar',balanced:'Saldo zero',below_minimum:'Inferior ao mínimo de cobrança/reembolso',indeterminate:'Indeterminado'}[m.status];
|
||
return[
|
||
['IRPC — L8','Fecho anual — âmbito e origem',
|
||
'NUIT: '+m.subjectNuit+'; exercício: '+m.year+'; colecta: '+show(m.assessment?.colecta??null)+'; origem: '+(m.assessment?.kind==='special'?'L7 qualificado':'L6 normal')+'. '+(annualCurrent(d,b)?'Entradas correspondentes.':'Histórico; origem/entradas alteradas: recalcule o saldo corrente.')+' Saldo simulado, não liquidação submetida.',
|
||
m.input.reference+'; '+m.input.assessmentReference+'; '+m.source],
|
||
...m.claims.map(c=>[
|
||
'IRPC — L8',(c.kind==='withholding'?'Retenção sofrida':c.kind==='pec'?'PEC':c.kind==='economic_credit'?'IRPC correspondente aos lucros':c.kind==='foreign_credit'?'Imposto estrangeiro':'PPC')+' — '+(c.document||c.id),
|
||
'Entidade: '+c.subjectNuit+'; exercício: '+c.year+'; emitente: '+c.issuer+'; data: '+c.date+'; comprovado: '+show(c.proofAmount)+'; parcela: '+(c.reason?'Indeterminado':show(c.value))+(c.parcel?'; parcela documental: '+c.parcel+'; '+(c.parcelReference??''):'')+'. '+(c.reason||'Reconciliada sem utilização duplicada.'),
|
||
c.kind==='economic_credit'?(m.input.economicCredits?.reference??'')+'; '+(m.input.economicCredits?.rows.find(r=>r.id===c.id)?.attributionReference??''):c.kind==='withholding'?m.input.withholdings.reference+'; '+(m.input.withholdings.rows.find(r=>r.id===c.id)?.reference??''):c.kind==='foreign_credit'?(m.input.internationalCredits?.reference??'')+'; '+(m.input.internationalCredits?.positions.flatMap(p=>p.payments).find(r=>r.id===c.id)?.reference??'')+'; '+(m.input.internationalCredits?.positions.flatMap(p=>p.payments).find(r=>r.id===c.id)?.exerciseReference??''):c.kind==='pec'?(m.input.pecPayments?.reference??'')+'; '+(m.input.pecPayments?.rows.find(r=>r.id===c.id)?.reference??'')+'; utilizações anteriores: '+(m.input.pecPayments?.rows.find(r=>r.id===c.id)?.previouslyUsed??'')+' MZN; '+(m.input.pecPayments?.rows.find(r=>r.id===c.id)?.priorUseReference??''):m.input.ppc.reference+'; '+(m.input.ppc.rows.find(r=>r.id===c.id)?.reference??'')
|
||
]as[string,string,string,string]),
|
||
...(m.economicCredit?[
|
||
...m.economicCredit.rows.map(r=>['IRPC — L8','Crédito económico — '+r.input.distributionDocument,
|
||
'Lucros incluídos: '+r.input.income+' MZN;IRPC correspondente: '+r.input.taxAmount+' MZN;crédito nominal60%: '+show(r.nominal)+';acréscimo53 incluído: '+r.input.grossUpIncluded+' MZN;quota proporcional: '+show(r.nationalLimit)+';elegível: '+show(r.eligible)+';uso efectivo anterior: '+show(r.previousUse)+';disponível: '+show(r.available)+';estado: '+r.status+'. '+r.pending.join(' | '),
|
||
r.input.qualificationReference+';40(1): '+r.input.article40Reference+';inclusão: '+r.input.incomeReference+';53: '+r.input.grossUpReference+';quota: '+r.input.nationalLimitReference+';uso: '+r.input.previousUse.reference]as[string,string,string,string]),
|
||
['IRPC — L8','Crédito económico — antes do internacional','Disponível: '+show(m.economicCredit.available)+';dedução: '+show(m.economicCredit.deducted)+';colecta após crédito económico: '+show(m.economicCredit.afterCredit)+'. Sem reembolso ou criação/consumo de saldo oficial.',m.economicCredit.source+'; '+(m.input.economicCredits?.reference??'')]as[string,string,string,string]
|
||
]:[]),
|
||
...(m.internationalCredit?[
|
||
...m.internationalCredit.positions.map(p=>['IRPC — L8','Crédito internacional — '+p.country,'Imposto pago seleccionado: '+show(p.paid)+'; fracção nacional: '+show(p.nationalLimit)+'; limite CDT: '+(p.input.treaty==='no'?'Não aplicável documentado':show(p.treatyLimit))+'; elegível: '+show(p.eligible)+'; utilização anterior: '+show(p.previousUse)+'; disponível: '+show(p.available)+'; imposto acima dos limites: '+show(p.aboveLimit)+'. '+p.pending.join(' | '),p.input.incomeReference+'; '+p.input.nationalLimitReference+'; '+p.input.treatyReference+'; '+p.input.previousUse.reference]as[string,string,string,string]),
|
||
['IRPC — L8','Crédito internacional — antes do PEC','Disponível conhecido: '+show(m.internationalCredit.available)+'; dedução: '+show(m.internationalCredit.deducted)+'; colecta após crédito: '+show(m.internationalCredit.afterCredit)+'; elegível não deduzido por insuficiência de colecta: '+show(m.internationalCredit.insufficientCollecta)+(m.creditCarry?'; crédito corrente não escolhido: '+show(m.internationalCredit.notSelected??null):'')+'. Sem reembolso,transporte ou afectação oficial por país. Estado: '+stateLabel(m.input.internationalCredits?.state??'')+'. Créditos económicos/benefícios: '+stateLabel(m.input.internationalCredits?.economic.state??'')+' / '+stateLabel(m.input.internationalCredits?.benefits.state??'')+'.',m.internationalCredit.source+'; '+(m.input.internationalCredits?.reference??'')+'; '+(m.input.internationalCredits?.economic.reference??'')+'; '+(m.input.internationalCredits?.benefits.reference??'')]as[string,string,string,string]
|
||
]:[]),
|
||
...(m.creditCarry?[
|
||
...m.creditCarry.lots.map(l=>['IRPC — L8','Saldo anterior — '+(l.kind==='pec'?'PEC':'Crédito internacional '+l.country)+' — '+l.year,
|
||
'Remanescente da origem: '+show(l.opening)+'; usos efectivos: '+show(l.used)+'; disponível: '+show(l.available)+'; parcela escolhida: '+show(l.selected)+'; saldo aritmético posterior: '+show(l.remaining)+'; último exercício ordinário: '+(l.lastYear??'Indeterminado')+'; prazo terminado: '+(l.expired?'Sim':'Não')+'. '+l.pending.join(' | '),
|
||
l.input.originDocument+'; '+l.input.originReference+'; '+l.input.ruleReference+'; '+l.input.usesReference+'; '+l.input.uses.map(u=>u.year+': '+u.amount+' MZN; '+u.document+'; '+u.reference).join(' | ')]as[string,string,string,string]),
|
||
['IRPC — L8','Afectação escolhida entre exercícios',
|
||
'Corrente internacional: '+show(m.creditCarry.international)+'; anterior internacional: '+show(m.creditCarry.priorInternational)+'; PEC corrente: '+show(m.creditCarry.pec)+'; PEC anterior: '+show(m.creditCarry.priorPec)+'; colecta após créditos internacionais: '+show(m.creditCarry.afterInternational)+'; colecta após todos os PEC: '+show(m.creditCarry.afterPec)+'. Parcelas só deduzidas se o saldo estiver determinado. Sem criação ou consumo de saldo oficial. '+m.creditCarry.pending.join(' | '),
|
||
(m.input.creditCarry?.reference??'')+'; '+(m.input.creditCarry?.allocationReference??'')+'; '+m.creditCarry.source]as[string,string,string,string]
|
||
]:[]),
|
||
...(m.pecDeduction?[[
|
||
'IRPC — L8','PEC — dedução limitada à colecta',
|
||
'Estado: '+stateLabel(m.input.pecPayments?.state??'')+'; parcelas disponíveis: '+show(m.pecDeduction.available)+'; dedução: '+show(m.pecDeduction.deducted)+'; colecta após PEC, antes de retenções/PPC: '+show(m.pecDeduction.afterPec)+'; parcela seleccionada não deduzida: '+show(m.pecDeduction.remaining)+'. Excedente sem reembolso PEC; transporte/caducidade não automáticos. Subtotais não resolvem informação em falta.',
|
||
(m.input.pecPayments?.reference??'')+'; '+m.pecDeduction.source
|
||
]as[string,string,string,string]]:[]),
|
||
['IRPC — L8','Saldo anual simulado',
|
||
'Estado: '+status+'. Retenções conhecidas: '+show(m.knownWithholdings)+'; PPC conhecidos: '+show(m.knownPpc)+'; saldo aritmético: '+show(m.arithmeticBalance)+'; a pagar: '+show(m.payable)+'; a recuperar: '+show(m.recoverable)+'. '+m.pending.join(' | ')+' '+m.warnings.join(' '),m.source],
|
||
['IRPC — L8','Estados dos comprovativos',
|
||
'Retenções sofridas: '+stateLabel(m.input.withholdings.state)+'; PPC: '+stateLabel(m.input.ppc.state)+'. Subtotais conhecidos não substituem informação em falta.',m.input.withholdings.reference+'; '+m.input.ppc.reference],
|
||
['IRPC — L8','PEC e créditos separados',
|
||
(m.input.pecPayments?'Declaração PEC anterior conservada (substituída nesta execução pelo detalhe comprovado): ':'PEC: ')+stateLabel(m.input.pec.state)+'; montante declarado: '+(m.input.pec.amount||'Não preenchido')+(m.input.internationalCredits?'; resumo anterior de créditos conservado,substituído nesta execução pelo detalhe: ':'; créditos: ')+stateLabel(m.input.credits.state)+'; montante declarado: '+(m.input.credits.amount||'Não preenchido')+'. Sem dedução, transporte ou reembolso presumidos. '+m.limitations.join(' '),m.input.pec.reference+'; '+m.input.credits.reference]
|
||
];
|
||
}catch(e){return[['IRPC — L8','Não recuperável',String(e),'Originais e históricos conservados.']];}
|
||
}
|
||
export function annualHtml(d:Record<string,unknown>){const b=annualBook(d);if(!b?.result||!annualCurrent(d,b))throw Error('Calcule e guardeL8 com as fontes actuais antes de exportar.');const esc=(s:string)=>s.replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]!));return'<!doctype html><html lang="pt"><meta charset="utf-8"><title>IRPC — L8 saldo anual simulado</title><h1>IRPC — colecta e saldo anual simulado</h1><p>Não é declaração submetida,pagamento efectuado ou reembolso recebido.</p>'+annualReportRows(d).map(r=>'<section><h2>'+esc(r[1])+'</h2><p>'+esc(r[2])+'</p><small>'+esc(r[3])+'</small></section>').join('')+'</html>';}
|