87 lines
27 KiB
TypeScript
87 lines
27 KiB
TypeScript
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<string,string>;amounts:Record<string,FiscalFraction|null>;details:Record<string,string>;source:string};
|
||
export type ResidualBook={version:1;kind:ResidualKind;input:Record<string,string>;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<string,string>=>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<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 não recuperáveis; originais preservados.');return s;}
|
||
function maps(d:Record<string,unknown>):Record<string,Record<string,ResidualBook>>{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<string,string[]>=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<string,unknown>,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:String(d.legal_name??d.organization_name??''),nuit:String(d.nuit??''),address:String(d.address??'')});}b={version:1,kind,input};}validate(b,kind);return b;}
|
||
export function saveIspcResidualBook(d:Record<string,unknown>,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<string,unknown>,kind:ResidualKind,i:Record<string,string>):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<string,unknown>,b:ResidualBook)=>JSON.stringify(['ispc-residual/1',acquisitionScope(d),b.kind,b.input,dependency(d,b.kind,b.input)]);
|
||
export const ispcResidualCurrent=(d:Record<string,unknown>,b:ResidualBook)=>!!b.result&&b.result.key===key(d,b);
|
||
function occasional(i:Record<string,string>,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<string,unknown>,i:Record<string,string>,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<string,unknown>,i:Record<string,string>,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<string,unknown>,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<string,string>={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<string,unknown>):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<string,string>)[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,unknown>):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)=>String(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<string,string>)[b.input.externalStatus]};${b.input.externalRef};${b.input.externalDate}`],['Falta',r.missing.join('; ')||'Nenhum dado deste recorte']];return`<!doctype html><html lang="pt"><meta charset="utf-8"><title>PreparaçãoISPC—B–Geral</title><style>body{font:16px system-ui;margin:32px}td,th{border:1px solid #777;padding:10px;vertical-align:top}table{border-collapse:collapse;width:100%}</style><h1>Preparação de campos ISPC—Modelo B–Geral</h1><p>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.</p><table>${fields.map(([a,c])=>`<tr><th>${esc(a)}</th><td>${esc(c)}</td></tr>`).join('')}</table><p>${esc(r.source)}</p><p>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.</p></html>`;}
|