Files
Investidor-V2/resources/js/lib/iva-acquisition-allocation.ts
T

56 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type {Acquisition,AcquisitionResult} from './iva-acquisitions.ts';
import {acquisitionScope} from './iva-acquisitions.ts';
import {evaluateAcquisitionBC} from './iva-acquisition-bc.ts';
import {reducedBaseBook,reducedBaseCurrent,reducedBaseTreatments,evaluateReducedBase} from './iva-reduced-bases.ts';
import {decimalFraction,fraction,fractionLabel,fractionPresentation,subtractFractions,type FiscalFraction} from './fiscal-rational.ts';
export const acquisitionAllocationVersion='acquisition-allocation/1';
export const acquisitionAllocationSource='L2-D: CIVA22(1–8),Lei13/2016 PDF16/p1426(274);Lei10/2025,18(6),PDF13/p2180(13).22(4)numerador anual semIVA com direito18/19;denominador anual semIVA de todas operações,incluindo fora de campo;22(5)exclui vendas de activo usado e operações imobiliárias/financeiras acessórias.22(8)quociente à centésima imediatamente superior.22(2)afectação real com comunicação prévia;22(6–7)provisório,regularização anual emE. Não substitui condições18–20 ou calcula restituiçãoL3.';
type Choice=''|'yes'|'no';
export type AcquisitionAllocation={version:1;method:''|'actual'|'pro_rata'|'reduced';numerator:string;denominator:string;unit:string;referenceYear:string;basis:''|'prior_year'|'initial_changed';scopeRef:string;source:string;communicationRef:string;declarationRef:string;actualRequired:Choice;noExcludedPayments:Choice;facts:Record<string,Choice>;reducedId:string;reducedFingerprint:string};
export function newAcquisitionAllocation(period:string):AcquisitionAllocation{return{version:1,method:'',numerator:'',denominator:'',unit:'',referenceYear:String(Number(period.slice(0,4))-1),basis:'',scopeRef:'',source:'',communicationRef:'',declarationRef:'',actualRequired:'',noExcludedPayments:'',facts:{},reducedId:'',reducedFingerprint:''};}
export function validAcquisitionAllocation(v:unknown):v is AcquisitionAllocation{if(!v||typeof v!=='object'||Array.isArray(v))return false;const d=v as AcquisitionAllocation;return d.version===1&&['','actual','pro_rata','reduced'].includes(d.method)&&['','prior_year','initial_changed'].includes(d.basis)&&['numerator','denominator','unit','referenceYear','scopeRef','source','communicationRef','declarationRef','reducedId','reducedFingerprint'].every(k=>typeof d[k as keyof AcquisitionAllocation]==='string')&&[d.actualRequired,d.noExcludedPayments].every(v=>['','yes','no'].includes(v))&&!!d.facts&&typeof d.facts==='object'&&!Array.isArray(d.facts)&&Object.values(d.facts).every(v=>['','yes','no'].includes(v));}
export function allocationReducedChoices(draft:Record<string,unknown>){return reducedBaseBook(draft).rows.map(row=>({id:row.id,label:`${row.description||row.id} — ${row.period} — ${reducedBaseTreatments.find(t=>t.id===row.treatment)?.label??row.treatment}`,row}));}
export function adoptAllocationReduced(draft:Record<string,unknown>,d:AcquisitionAllocation,id:string):AcquisitionAllocation{const source=allocationReducedChoices(draft).find(r=>r.id===id);if(!source)throw Error('Base reduzida de origem indisponível;originais preservados.');return {...d,reducedId:id,reducedFingerprint:JSON.stringify(source.row),facts:{...d.facts,scopeConfirmed:''}};}
export function allocationSourceKey(draft:Record<string,unknown>,rows:Acquisition[]):string{return rows.some(r=>r.d)?JSON.stringify({version:acquisitionAllocationVersion,reducedOperations:reducedBaseBook(draft).rows,sources:rows.filter(r=>r.d?.method==='reduced').map(r=>({row:allocationReducedChoices(draft).find(s=>s.id===r.d?.reducedId)?.row??null,current:reducedBaseCurrent(draft,reducedBaseBook(draft))}))}):'';}
export function allocationHasUnconsolidatedOutputs(draft:Record<string,unknown>,period:string):boolean{return reducedBaseBook(draft).rows.some(r=>r.period===period);}
type Decision={ratio:FiscalFraction|null;rawRatio?:FiscalFraction;reason:string;source:string};
export function acquisitionAllocationDecision(draft:Record<string,unknown>|undefined,row:Acquisition):Decision{
const d=row.d;const stop=(reason:string):Decision=>({ratio:null,reason,source:acquisitionAllocationSource});
if(!d||!validAcquisitionAllocation(d))return stop('Método/parâmetrosD não recuperáveis;sem percentagem por defeito.');
if(!draft||draft.currency!=='MZN'||row.sourceCurrency!=='MZN'||!/^2026-(0[1-9]|1[0-2])$/.test(row.period))return stop('ÂmbitoD2026/MZN ou contexto em falta.');
if(!d.scopeRef.trim()||!d.source.trim()||d.facts.scopeConfirmed!=='yes')return stop('Âmbito da aquisição/actividade,fonte e correspondência por confirmar;sem inferir da oportunidade.');
if(d.method==='reduced'){
if(row.allocation!=='taxable_only')return stop('18(6) neste percurso exige afectação exclusiva à operação de base reduzida;combinação com afectação mista ainda não demonstrada.');
const b=reducedBaseBook(draft),source=b.rows.find(r=>r.id===d.reducedId);if(!source||!reducedBaseCurrent(draft,b)||JSON.stringify(source)!==d.reducedFingerprint)return stop('Origem de base reduzida alterada/incompleta:rever e adoptar expressamente;histórico conservado.');
const result=evaluateReducedBase(source,draft.currency),rule=reducedBaseTreatments.find(r=>r.id===source.treatment);if(!rule||result.status!=='determined')return stop('Condições da base reduzida de origem ainda indeterminadas;não basta escolher percentagem.');
return{ratio:fraction(BigInt(rule.percent),100n),reason:`18(6):proporção específica${rule.percent}% reutilizada de${rule.branch}/${source.id};aplicada uma vez ao IVA elegível,sem converter percentagem de base em taxaIVA.`,source:acquisitionAllocationSource+';'+b.result!.source};
}
if(row.allocation!=='mixed')return stop('Método22 exige afectação mista expressamente identificada;conserve a afectação exclusiva quando aplicável.');
if(d.facts.rightBasis!=='yes')return stop('Identifique a parte/operações com direito18/19;isenção ou sector não estabelecem o direito.');
if(!/^\d{1,15}(\.\d{1,6})?$/.test(d.numerator)||!/^\d{1,15}(\.\d{1,6})?$/.test(d.denominator))return stop('Numerador/denominador em falta ou inválidos;sem percentagem presumida.');
const n=decimalFraction(d.numerator),den=decimalFraction(d.denominator);if(BigInt(den.numerator)===0n)return stop('Denominador zero:não existe quociente aplicável.');
const rawRatio=fraction(BigInt(n.numerator)*BigInt(den.denominator),BigInt(n.denominator)*BigInt(den.numerator));if(BigInt(rawRatio.numerator)>BigInt(rawRatio.denominator))return stop('Numerador excede denominador;resolver bases sem limitar silenciosamente a100%.');
if(d.method==='actual'){
if(!d.unit.trim()||d.facts.actualScope!=='yes'||d.facts.communication!=='yes'||!d.communicationRef.trim()||d.facts.conditionsMet!=='yes')return stop('Afectação real22(2):critério/unidade,repartição efectiva,comunicação prévia e condições concretamente aplicáveis por completar;sem criar aprovação adicional.');
return{ratio:rawRatio,rawRatio,reason:`Afectação real22(2):${d.numerator}/${d.denominator} ${d.unit};critério e comunicação${d.communicationRef}. Fracção real exacta,sem arredondamento de pro rata.`,source:acquisitionAllocationSource};
}
if(d.method!=='pro_rata')return stop('Escolha afectação real,pro rata provisório ou proporção específica18(6).');
if(d.actualRequired==='yes')return stop('Está identificada imposição de afectação real22(3);não aplicar pro rata em substituição.');
if(d.facts.annualScope!=='yes'||d.facts.netExclusions!=='yes')return stop('Confirmar totais anuais semIVA e âmbito22(4–5):inclui fora de campo;exclui vendas de activo usado e imobiliárias/financeiras acessórias. Não usar só vendas de um mês como total anual.');
const year=Number(row.period.slice(0,4));
if(d.basis==='prior_year'){if(d.referenceYear!==String(year-1))return stop('Pro rata provisório22(6) usa operações do ano anterior ao exercício;sem substituir pelo mês corrente.');}
else if(d.basis==='initial_changed'){if(d.referenceYear!==String(year)||d.facts.initialChanged!=='yes'||!d.declarationRef.trim())return stop('Estimativa22(7):início/alteração substancial,ano e referência da declaração de início/alterações por identificar.');}
else return stop('Indique base provisória22(6) ou estimativa expressa22(7);regularização final22(6) segueE.');
const numerator=BigInt(rawRatio.numerator),denominator=BigInt(rawRatio.denominator);const hundredths=(numerator*100n+denominator-1n)/denominator;
return{ratio:fraction(hundredths,100n),rawRatio,reason:`Pro rata provisório22(4/6/7/8):quociente${fractionLabel(rawRatio)}→${hundredths}% (quociente à centésima superior). Base anual${d.referenceYear},${d.basis==='prior_year'?'ano anterior':'estimativa declarada'}. Acerto anual emE,sem recalcular históricos.`,source:acquisitionAllocationSource};
}
export function evaluateAcquisitionAllocation(row:Acquisition,currency:string,registered:Choice,draft:Record<string,unknown>|undefined,raw:AcquisitionResult):AcquisitionResult{
const decision=acquisitionAllocationDecision(draft,row),d=row.d!;
const unknown=(reason:string):AcquisitionResult=>({...raw,deductible:null,reason,eligibility:{status:'indeterminate',reason,source:decision.source}});
if(!decision.ratio)return unknown(decision.reason);if(!row.bc)return unknown('Complete a natureza/condiçõesB/C da aquisição antes da proporção;sem isenção de condições documentais.');
const base=evaluateAcquisitionBC(row,currency,registered,draft,raw,{right:{status:'eligible',reason:decision.reason,source:decision.source,scopeId:draft?acquisitionScope(draft):undefined},noExcludedPayments:d.noExcludedPayments});
if(base.deductible===null||base.eligibility?.status!=='eligible')return base;
const before=base.deductibleExact??decimalFraction(base.deductible);const exact=fraction(BigInt(before.numerator)*BigInt(decision.ratio.numerator),BigInt(before.denominator)*BigInt(decision.ratio.denominator));const reason=base.reason+' '+decision.reason;
return {...base,deductible:fractionPresentation(exact).decimal,deductibleExact:exact,nonDeductibleExact:subtractFractions(decimalFraction(base.vat!),exact),reason,eligibility:{...base.eligibility,status:'eligible',reason},allocationCalculation:{version:acquisitionAllocationVersion,ratio:decision.ratio,rawRatio:decision.rawRatio??decision.ratio,before,source:decision.source}};
}