62 lines
12 KiB
TypeScript
62 lines
12 KiB
TypeScript
import {acquisitionScope,readAcquisitionStore} from './iva-acquisitions.ts';
|
||
|
||
export const goodsLocationVersion='goods-territoriality/1';
|
||
export const goodsLocationSource='CIVA republicado Lei13/2016,6(1)/(2),PDF7/p1426(265):localização dos bens no território nacional ao iniciar transporte/expedição para o adquirente ou,sem transporte/expedição,ao pôr à sua disposição. Não obstante6(1),são também tributáveis transmissões pelo importador e subsequentes de bens transportados/expedidos do estrangeiro,antes da importação. Lei10/2025PDF12mantém6(1)/(2),altera6(7–9)quanto a digitais;recorte2026,bens corpóreos,não digitais. Endereço/nacionalidade do cliente não substituem localização dos bens. Classificação territorial apenas,não isenção/taxa/IVA/dedução/campo11/direitos aduaneiros ou totaisModeloA.';
|
||
export const goodsLocationTreatments=[{id:'goods_with_transport',label:'Bens com transporte ou expedição para o adquirente'},{id:'goods_without_transport',label:'Bens sem transporte ou expedição'},{id:'goods_before_import',label:'Transmissão pelo importador ou subsequente,antes da importação'}] as const;
|
||
export const goodsLocationConditions=[
|
||
['goodsSupply','Está identificada uma transmissão de bens corpóreos,não uma prestação de serviços ou bens digitais abrangidos por regras próprias'],
|
||
['factsCorrespond','Os factos,referências e âmbito correspondem a esta operação e período,não apenas ao endereço ou nacionalidade do adquirente'],
|
||
] as const;
|
||
export const goodsOwnConditions={
|
||
goods_with_transport:[['transportToBuyer','Os bens são transportados ou expedidos para o adquirente'],['notBeforeImport','Não corresponde à transmissão pré-importação abrangida pelo artigo6(2)']],
|
||
goods_without_transport:[['noTransport','Não há expedição ou transporte dos bens para esta transmissão'],['notBeforeImport','Não corresponde à transmissão pré-importação abrangida pelo artigo6(2)']],
|
||
goods_before_import:[['fromAbroad','Os bens são transportados ou expedidos do estrangeiro'],['nationalImport','Está identificada a importação desses bens para o território nacional'],['importerChain','A transmissão é feita pelo importador ou é subsequente na cadeia desses bens'],['beforeImport','Esta transmissão ocorre antes da importação identificada']],
|
||
} as const;
|
||
export function ownGoodsConditions(treatment:string):ReadonlyArray<readonly[string,string]>{return goodsOwnConditions[treatment as keyof typeof goodsOwnConditions]??[];}
|
||
type Choice=''|'yes'|'no';type Condition=typeof goodsLocationConditions[number][0];
|
||
export type GoodsLocationRow={id:string;treatment:string;description:string;period:string;origin:'projected'|'effective';operationDate:string;operationRef:string;documentRef:string;documentDate:string;goodsPlace:string;placeCountry:string;placeRef:string;transportRef:string;importRef:string;chainRef:string;scope:string;evidence:string;conditions:Partial<Record<string,Choice>>};
|
||
type LineResult={id:string;status:'determined'|'not_applicable'|'indeterminate';territory:'national'|'outside'|null;baseAmount:null;vat:null;rate:null;reason:string};
|
||
export type GoodsLocationCalculation={version:string;revision:number;calculatedAt:string;opportunity:string;location:string;sourceKey:string;source:string;inputs:GoodsLocationRow[];rows:LineResult[]};
|
||
export type GoodsLocationBook={rows:GoodsLocationRow[];result?:GoodsLocationCalculation;history?:GoodsLocationCalculation[]};
|
||
type Store=ReturnType<typeof readAcquisitionStore>&{goodsLocations?:Record<string,GoodsLocationBook>};
|
||
export function newGoodsLocation(id:string):GoodsLocationRow{return{id,treatment:'',description:'',period:'',origin:'projected',operationDate:'',operationRef:'',documentRef:'',documentDate:'',goodsPlace:'',placeCountry:'',placeRef:'',transportRef:'',importRef:'',chainRef:'',scope:'',evidence:'',conditions:{}};}
|
||
export function switchGoodsTreatment(row:GoodsLocationRow,treatment:string):GoodsLocationRow{return{...row,treatment,conditions:Object.fromEntries(goodsLocationConditions.map(([key])=>[key,row.conditions[key]??'']))};}
|
||
|
||
function readStore(draft:Record<string,unknown>):Store{
|
||
const store=readAcquisitionStore(draft.modelo_a_snapshot) as Store;
|
||
if(store.goodsLocations!==undefined&&(!store.goodsLocations||typeof store.goodsLocations!=='object'||Array.isArray(store.goodsLocations)))throw new Error('Bases de contraprestação não recuperáveis;originais preservados.');
|
||
const fields=Object.keys(newGoodsLocation('')).filter(key=>key!=='conditions');
|
||
for(const book of Object.values(store.goodsLocations??{}))if(!book||!Array.isArray(book.rows)||(book.history!==undefined&&!Array.isArray(book.history))||book.rows.some(row=>!row||fields.some(key=>typeof row[key as keyof GoodsLocationRow]!=='string')||!row.conditions||typeof row.conditions!=='object'||Array.isArray(row.conditions)))throw new Error('Linhas de contraprestação não recuperáveis;originais preservados,sem substituição por lista vazia.');
|
||
return store;
|
||
}
|
||
export function goodsLocationBook(draft:Record<string,unknown>):GoodsLocationBook{return readStore(draft).goodsLocations?.[acquisitionScope(draft)]??{rows:[]};}
|
||
export function saveGoodsLocationBook(draft:Record<string,unknown>,book:GoodsLocationBook):string{const current=readStore(draft),encoded=JSON.stringify({...current,goodsLocations:{...current.goodsLocations,[acquisitionScope(draft)]:book}});readStore({...draft,modelo_a_snapshot:encoded});return encoded;}
|
||
function validDate(value:string):boolean{const d=new Date(value+'T00:00:00Z');return /^\d{4}-\d{2}-\d{2}$/.test(value)&&Number.isFinite(d.getTime())&&d.toISOString().slice(0,10)===value;}
|
||
export function evaluateGoodsLocation(row:GoodsLocationRow):LineResult{
|
||
const result:LineResult={id:row.id,status:'indeterminate',territory:null,baseAmount:null,vat:null,rate:null,reason:''};const stop=(reason:string,status:LineResult['status']='indeterminate')=>({...result,status,reason});
|
||
const rule=goodsLocationTreatments.find(t=>t.id===row.treatment);if(!rule)return stop('Identifique transporte,ausência de transporte ou transmissão antes da importação;desconhecido não determina território.');
|
||
if(!/^2026-(0[1-9]|1[0-2])$/.test(row.period)||!['projected','effective'].includes(row.origin)||!validDate(row.operationDate)||row.operationDate.slice(0,7)!==row.period)return stop('Identifique mês em2026 e data da operação correspondentes,com dados efectivos declarados ou pressupostos expressos de projecção.');
|
||
const required=[...goodsLocationConditions,...ownGoodsConditions(row.treatment)];const failed=required.filter(([key])=>row.conditions[key]==='no');if(failed.length)return stop('Este percurso não se aplica: '+failed.map(([,label])=>label).join('; ')+'. Rever o percurso,não classificar automaticamente como fora do território ou isento.','not_applicable');
|
||
if(required.some(([key])=>row.conditions[key]!=='yes'))return stop('Confirme os factos próprios deste percurso;desconhecido não é fora do território,isenção ou IVA zero.');
|
||
if(!row.description.trim()||!row.operationRef.trim()||!row.documentRef.trim()||!validDate(row.documentDate)||!row.scope.trim()||!row.evidence.trim())return stop('Identifique bens/operação,documento/data,âmbito e factos ou pressupostos expressos;reutilize referências existentes,sem certificado/aprovador novo.');
|
||
if(row.treatment==='goods_before_import'){
|
||
if(!row.transportRef.trim()||!row.importRef.trim()||!row.chainRef.trim())return stop('Identifique transporte/expedição do estrangeiro,importação nacional e posição da transmissão na cadeia,ou pressupostos expressos;não inferir a condição pelo endereço do adquirente.');
|
||
return{...result,status:'determined',territory:'national',reason:`Art6(2):transmissão pelo importador ou subsequente de bens transportados/expedidos do estrangeiro,antes da importação nacional identificada;abrangida territorialmente emMoçambique,${row.origin==='projected'?'nas condições expressamente assumidas na projecção':'nos dados efectivos declarados'}. A localização física anterior no estrangeiro não afasta por si esta regra. Classificação não determina isenção,base,taxa,IVA,dedução/campo11,direitos ou totaisModeloA.`};
|
||
}
|
||
if(!row.goodsPlace.trim()||!row.placeRef.trim()||!['MZ','foreign'].includes(row.placeCountry))return stop('Identifique onde os bens se encontram no momento relevante e respectiva referência:início do transporte/expedição ou colocação à disposição sem transporte;endereço do cliente não substitui esse facto.');
|
||
if(row.treatment==='goods_with_transport'&&!row.transportRef.trim())return stop('Identifique o transporte/expedição correspondente ao início referido,ou pressuposto expresso.');
|
||
const territory=row.placeCountry==='MZ'?'national':'outside';return{...result,status:'determined',territory,reason:`Art6(1):bens ${territory==='national'?'situados emMoçambique':'situados fora deMoçambique'} ${row.treatment==='goods_with_transport'?'ao iniciar o transporte/expedição para o adquirente':'ao serem postos à disposição do adquirente,sem transporte/expedição'},${row.origin==='projected'?'nas condições expressamente assumidas na projecção':'nos dados efectivos declarados'}. Factos de6(2)conferidos como não aplicáveis a este percurso. ${territory==='outside'?'Fora deste âmbito territorial não equivale a isenção ou taxa/IVA zero.':'Abrangência territorial não confirma isenção,taxa ou IVA.'} Classificação não determina base,dedução/campo11,direitos ou totaisModeloA.`};
|
||
}
|
||
|
||
function key(draft:Record<string,unknown>,book:GoodsLocationBook):string{return JSON.stringify({version:goodsLocationVersion,scope:acquisitionScope(draft),location:draft.selection_labels??{},rows:book.rows});}
|
||
export function goodsLocationCurrent(draft:Record<string,unknown>,book:GoodsLocationBook):boolean{return!!book.result&&book.result.sourceKey===key(draft,book);}
|
||
export function recordGoodsLocations(draft:Record<string,unknown>,book:GoodsLocationBook):GoodsLocationBook{
|
||
if(!book.rows.length||new Set(book.rows.map(row=>row.id)).size!==book.rows.length)throw new Error('Indique operações com identificadores distintos;originais conservados.');
|
||
const result:GoodsLocationCalculation={version:goodsLocationVersion,revision:(book.result?.revision??0)+1,calculatedAt:new Date().toISOString(),opportunity:acquisitionScope(draft),location:JSON.stringify(draft.selection_labels??{}),sourceKey:key(draft,book),source:goodsLocationSource,inputs:structuredClone(book.rows),rows:book.rows.map(row=>evaluateGoodsLocation(row))};
|
||
const next={...book,result,history:[...(book.history??[]),...(book.result?[book.result]:[])]};saveGoodsLocationBook(draft,next);return next;
|
||
}
|
||
export function goodsLocationReportRows(draft:Record<string,unknown>):Array<[string,string,string,string]>{
|
||
try{const book=goodsLocationBook(draft),result=book.result;if(!result)return[];return result.inputs.map((row,index)=>['IVA mensal',`Localização P105 — ${row.period} — ${row.id}`,`${row.description};classificação territorial: ${result.rows[index].territory==='national'?'Moçambique':result.rows[index].territory==='outside'?'Fora do território nacional':'Indeterminada'};IVA: Indeterminado;${result.rows[index].reason}`,`${result.opportunity};${result.location};${goodsLocationCurrent(draft,book)?'Resultado actual':'Entradas alteradas;resultado histórico conservado'};versão${result.revision};${result.calculatedAt};${result.source};${row.origin==='projected'?'Projecção':'Dados efectivos declarados'};${goodsLocationTreatments.find(t=>t.id===row.treatment)?.label??'Percurso por indicar'};operação ${row.operationRef},${row.operationDate};${row.treatment==='goods_before_import'?`transporte ${row.transportRef};importação ${row.importRef};cadeia ${row.chainRef}`:`local dos bens ${row.goodsPlace},${row.placeCountry};referência ${row.placeRef}${row.treatment==='goods_with_transport'?`;transporte ${row.transportRef}`:''}`};âmbito ${row.scope};documento ${row.documentRef},${row.documentDate};condições ${[...goodsLocationConditions,...ownGoodsConditions(row.treatment)].map(([key,label])=>label+': '+(row.conditions[key]==='yes'?'Sim':row.conditions[key]==='no'?'Não':'Por indicar')).join('; ')};fundamento factual ${row.evidence};não determina isenção/IVA/campo11/totaisModeloA.`]);}
|
||
catch{return[['IVA mensal','Localização de bens','Dados não recuperáveis','Originais preservados;sem classificar por defeito.']];}
|
||
}
|