Files
Investidor-V2/resources/js/lib/iva-foreign-operations.ts
T

51 lines
12 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 {acquisitionScope,readAcquisitionStore} from './iva-acquisitions.ts';
import {goodsLocationBook,goodsLocationCurrent} from './iva-goods-location.ts';
import {serviceLocationBook,serviceLocationCurrent} from './iva-service-location.ts';
import {listedServiceBook,listedServiceCurrent} from './iva-listed-services.ts';
import {currencyEventBook,currencyEventCurrent} from './iva-currency-events.ts';
import {digitalBook,digitalCurrent} from './iva-digital.ts';
export const foreignOperationVersion='foreign-operations/1';
export const foreignOperationSource='CIVA6,Lei13/2016PDF7–8 e redacção2025:reutilizar localização já apurada.19(1)bii,2016PDF15/p1426(273),mantidoLei10/2025PDF13:operações no estrangeiro que seriam tributáveis se efectuadas no território nacional;19(2)alterado2025 mantém exclusões próprias. Enquadramento potencial,não dedução concedida/campo11.Não confundir fora do território com isenção/IVAzero,moeda estrangeira,cliente estrangeiro ou caixa06.';
type Choice=''|'yes'|'no';
export type ForeignOperationRow={id:string;sourceKind:''|'goods'|'services'|'listed'|'digital';sourceRowId:string;territorialPart:''|'outside'|'national';eventRowId:string;hypotheticalTaxable:Choice;hypotheticalRef:string;excluded19_2:Choice;scope:string;evidence:string;};
type Source={id:string;description:string;period:string;operationRef:string;scope:string;status:string;territory:string|null;current:boolean;revision:number;source:string;reason:string;};
type LineResult={id:string;status:'determined'|'not_applicable'|'indeterminate';territory:string|null;relation19:'potential'|'not_demonstrated'|'excluded_19_2'|'indeterminate';source:Source|null;event:Record<string,unknown>|null;deduction:null;baseAmount:null;vat:null;rate:null;reason:string};
export type ForeignOperationCalculation={version:string;revision:number;calculatedAt:string;opportunity:string;sourceKey:string;source:string;inputs:ForeignOperationRow[];rows:LineResult[]};
export type ForeignOperationBook={rows:ForeignOperationRow[];result?:ForeignOperationCalculation;history?:ForeignOperationCalculation[]};
type Store=ReturnType<typeof readAcquisitionStore>&{foreignOperations?:Record<string,ForeignOperationBook>};
export function newForeignOperation(id:string):ForeignOperationRow{return{id,sourceKind:'',sourceRowId:'',territorialPart:'',eventRowId:'',hypotheticalTaxable:'',hypotheticalRef:'',excluded19_2:'',scope:'',evidence:''};}
export function switchForeignSource(row:ForeignOperationRow,sourceKind:ForeignOperationRow['sourceKind'],sourceRowId=''):ForeignOperationRow{return{...row,sourceKind,sourceRowId,territorialPart:'',eventRowId:'',hypotheticalTaxable:'',excluded19_2:''};}
export function foreignSources(draft:Record<string,unknown>,kind:ForeignOperationRow['sourceKind']):Source[]{
if(kind==='digital'){const b=digitalBook(draft),r=b.result;return r?r.inputs.map((input,i)=>({id:input.id,description:input.description,period:input.period,operationRef:input.operationRef,scope:input.scope,status:r.rows[i].status,territory:r.rows[i].territory,current:digitalCurrent(draft,b),revision:r.revision,source:r.source,reason:r.rows[i].reason})):[];}
if(kind==='goods'){const b=goodsLocationBook(draft),r=b.result;return r?r.inputs.map((input,i)=>({id:input.id,description:input.description,period:input.period,operationRef:input.operationRef,scope:input.scope,status:r.rows[i].status,territory:r.rows[i].territory,current:goodsLocationCurrent(draft,b),revision:r.revision,source:r.source,reason:r.rows[i].reason})):[];}
if(kind==='services'){const b=serviceLocationBook(draft),r=b.result;return r?r.inputs.map((input,i)=>({id:input.id,description:input.description,period:input.period,operationRef:input.operationRef,scope:input.scope,status:r.rows[i].status,territory:r.rows[i].territory,current:serviceLocationCurrent(draft,b),revision:r.revision,source:r.source,reason:r.rows[i].reason})):[];}
if(kind==='listed'){const b=listedServiceBook(draft),r=b.result;return r?r.inputs.map((input,i)=>({id:input.id,description:input.description,period:input.period,operationRef:input.operationRef,scope:input.scope,status:r.rows[i].status,territory:r.rows[i].territory,current:listedServiceCurrent(draft,b),revision:r.revision,source:r.source,reason:r.rows[i].reason})):[];}
return[];
}
export function foreignEvents(draft:Record<string,unknown>):Array<{id:string;description:string;operationRef:string;period:string;current:boolean;revision:number;status:string;eventDate:string|null;dueEvents:unknown[];remainingPending:boolean}>{const b=currencyEventBook(draft),r=b.result;return r?r.inputs.filter(x=>x.treatment!=='foreign_scenario').map(input=>{const line=r.rows[r.inputs.findIndex(x=>x.id===input.id)];return{id:input.id,description:input.description,operationRef:input.operationRef,period:input.period,current:currencyEventCurrent(draft,b),revision:r.revision,status:line.status,eventDate:line.eventDate,dueEvents:line.dueEvents,remainingPending:line.remainingPending};}):[];}
function readStore(draft:Record<string,unknown>):Store{const s=readAcquisitionStore(draft.modelo_a_snapshot) as Store;if(s.foreignOperations!==undefined&&(!s.foreignOperations||typeof s.foreignOperations!=='object'||Array.isArray(s.foreignOperations)))throw new Error('Operações no estrangeiro não recuperáveis;originais conservados.');const fields=Object.keys(newForeignOperation(''));for(const b of Object.values(s.foreignOperations??{}))if(!b||!Array.isArray(b.rows)||(b.history!==undefined&&!Array.isArray(b.history))||b.rows.some(r=>!r||fields.some(k=>typeof r[k as keyof ForeignOperationRow]!=='string')))throw new Error('Linhas de operações no estrangeiro não recuperáveis;sem lista vazia por defeito.');return s;}
export function foreignOperationBook(draft:Record<string,unknown>):ForeignOperationBook{return readStore(draft).foreignOperations?.[acquisitionScope(draft)]??{rows:[]};}
export function saveForeignOperationBook(draft:Record<string,unknown>,book:ForeignOperationBook):string{const s=readStore(draft),encoded=JSON.stringify({...s,foreignOperations:{...s.foreignOperations,[acquisitionScope(draft)]:book}});readStore({...draft,modelo_a_snapshot:encoded});return encoded;}
export function evaluateForeignOperation(draft:Record<string,unknown>,row:ForeignOperationRow):LineResult{
const result:LineResult={id:row.id,status:'indeterminate',territory:null,relation19:'indeterminate',source:null,event:null,deduction:null,baseAmount:null,vat:null,rate:null,reason:''};const stop=(reason:string,status:LineResult['status']='indeterminate')=>({...result,status,reason});
try{const source=foreignSources(draft,row.sourceKind).find(s=>s.id===row.sourceRowId);if(!source)return stop('Escolha uma classificação de localização já guardada nesta oportunidade. Prepare-a nas secções existentes;sem nova classificação por endereço,nacionalidade ou moeda.');result.source=source;
if(!source.current||source.status!=='determined'||!source.territory)return stop('Localização alterada ou indeterminada;resultado anterior conservado,sem o tratar como classificação actual.');result.territory=source.territory;
if(!/^2026-(0[1-9]|1[0-2])$/.test(source.period)||!row.scope.trim()||!row.evidence.trim())return stop('Identifique âmbito da análise e factos/pressupostos;reutilize operação/período/documentos da localização.');
if(source.territory==='national')return stop('Localização reutilizada aponta para Moçambique;P002operação no estrangeiro não se aplica neste âmbito.Não equivale a isenção nem decide tributação interna.','not_applicable');
if(source.territory==='partial'){
if(!['outside','national'].includes(row.territorialPart))return stop('Transporte tem parcela nacional e exterior;delimite a parcela desta análise,sem transformar todo o trajecto em estrangeiro.');
if(row.territorialPart==='national'){result.territory='national';return stop('Análise limitada à parcela nacional do trajecto;P002estrangeiro não se aplica a essa parcela.','not_applicable');}result.territory='outside_portion';
}
if(row.eventRowId){const event=foreignEvents(draft).find(e=>e.id===row.eventRowId);if(!event||!event.current||event.status!=='determined'||event.operationRef!==source.operationRef||event.period!==source.period)return stop('Evento escolhido não está actual ou não corresponde à mesma operação/período;conserve localização e corrija apenas a associação.Não inferir evento pela data do documento.');result.event=event;}
if(!row.hypotheticalRef.trim()||!['yes','no'].includes(row.hypotheticalTaxable))return stop('Operação/parcela estrangeira identificada;fundamente se seria tributável caso efectuada em Moçambique19(1)bii.Não presumir esse facto pela localização ou pela taxa estrangeira.');
if(row.hypotheticalTaxable==='no'){result.relation19='not_demonstrated';return stop('Operação no estrangeiro identificada;pressuposto de tributabilidade interna19(1)bii não demonstrado pelos factos declarados.Isto não decide outros enquadramentos de dedução.','determined');}
if(!['yes','no'].includes(row.excluded19_2))return stop('Tributabilidade interna hipotética indicada;conferir exclusões19(2),incluindo17-A e pagamentos15(6)c.Sem presumir dedução.');
if(row.excluded19_2==='yes'){result.relation19='excluded_19_2';return stop('Operação no estrangeiro identificada;exclusão19(2) declarada e fundamentada afasta este enquadramento potencial.Não calcula ou regulariza dedução.','determined');}
result.relation19='potential';return stop('Operação/parcela no estrangeiro reutilizada da localização;seria tributável em Moçambique segundo fundamento declarado,sem exclusão19(2) indicada.Enquadramento potencial19(1)bii,dependente das demais condições18/20 e âmbito dos bens/serviços utilizados.Não é concessão/cálculo de dedução,campo11,isenção ou IVA zero.','determined');
}catch{return stop('Localização/eventos de origem não recuperáveis;originais preservados,sem inferir classificação ou valores.');}
}
function key(draft:Record<string,unknown>,book:ForeignOperationBook):string{return JSON.stringify({version:foreignOperationVersion,scope:acquisitionScope(draft),location:draft.selection_labels??{},rows:book.rows,dependencies:book.rows.map(row=>({source:foreignSources(draft,row.sourceKind).find(s=>s.id===row.sourceRowId),event:row.eventRowId?foreignEvents(draft).find(e=>e.id===row.eventRowId):null}))});}
export function foreignOperationCurrent(draft:Record<string,unknown>,book:ForeignOperationBook):boolean{try{return!!book.result&&book.result.sourceKey===key(draft,book);}catch{return false;}}
export function recordForeignOperations(draft:Record<string,unknown>,book:ForeignOperationBook):ForeignOperationBook{if(!book.rows.length||new Set(book.rows.map(r=>r.id)).size!==book.rows.length)throw new Error('Indique identidades distintas;originais conservados.');const result:ForeignOperationCalculation={version:foreignOperationVersion,revision:(book.result?.revision??0)+1,calculatedAt:new Date().toISOString(),opportunity:acquisitionScope(draft),sourceKey:key(draft,book),source:foreignOperationSource,inputs:structuredClone(book.rows),rows:book.rows.map(r=>evaluateForeignOperation(draft,r))};const next={...book,result,history:[...(book.history??[]),...(book.result?[book.result]:[])]};saveForeignOperationBook(draft,next);return next;}
export function foreignOperationReportRows(draft:Record<string,unknown>):Array<[string,string,string,string]>{try{const b=foreignOperationBook(draft),r=b.result;if(!r)return[];return r.inputs.map((row,i)=>{const line=r.rows[i];return['IVA mensal',`P002 — ${line.source?.period??'Período indeterminado'} — ${row.id}`,`${line.source?.description??'Origem por escolher'};${line.reason};dedução/IVA:Indeterminados`,`${r.opportunity};${foreignOperationCurrent(draft,b)?'Resultado actual':'Origens/entradas alteradas;resultado histórico conservado'};versão${r.revision};${r.calculatedAt};${r.source};localização${row.sourceKind}/${row.sourceRowId},versão${line.source?.revision??'indeterminada'};${line.source?.reason??''};operação${line.source?.operationRef??''};âmbito origem${line.source?.scope??''};parcela${row.territorialPart};evento${row.eventRowId||'Sem associação necessária'},${line.event?JSON.stringify(line.event):'Não apurado por esta análise'};hipótese interna${row.hypotheticalTaxable},fundamento${row.hypotheticalRef};exclusão19(2)${row.excluded19_2};âmbito${row.scope};factos${row.evidence};sem base/IVA/dedução/campo11/totaisModeloA.`];});}catch{return[['IVA mensal','P002','Dados não recuperáveis','Originais preservados;sem classificação por defeito.']];}}