51 lines
12 KiB
TypeScript
51 lines
12 KiB
TypeScript
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.']];}}
|