66 lines
14 KiB
TypeScript
66 lines
14 KiB
TypeScript
import {acquisitionScope,readAcquisitionStore} from './iva-acquisitions.ts';
|
||
|
||
export const goodsAllocationVersion='goods-allocation-base/1';
|
||
export const goodsAllocationSource='CIVA3(3)e/f,Lei13/2016PDF6–7/p1426(264–265):afectação permanente de bens a uso próprio/particular/alheio,transmissão gratuita,afectação a sector isento ou ao activo imobilizado de bens20(1)a,com dedução total/parcial anterior nos bens/elementos;excepção de amostras/ofertas de pequeno valor na alíneae. Art15(2)b,PDF13/p1426(271):preço de aquisição ou,na sua falta,preço de custo,reportados ao momento da operação. Art20(1)a,PDF15/p1426(273). Lei22/2022PDF3mantém15(2)b e20(1)a;Lei10/2025mantém3(3)e/f e não altera15/20. Recorte2026/MZN,sem nova dedução/campo11,regularização,taxa,IVA ou totaisModeloA. Uso temporário de bens como serviçoP089 é distinto.';
|
||
export const goodsAllocationTreatments=[{id:'permanent_private_goods',label:'Afectação permanente a uso próprio,particular ou fins alheios',clause:'e'},{id:'free_goods_transfer',label:'Transmissão gratuita de bens',clause:'e'},{id:'exempt_sector_goods',label:'Afectação de bens a sector de actividade isento',clause:'f'},{id:'fixed_asset_20a',label:'Afectação ao activo imobilizado de bens20(1)a',clause:'f'}] as const;
|
||
export const goodsAllocationConditions=[['businessGoods','São bens da empresa identificados,não uma prestação de serviços ou venda onerosa normal'],['priorDeduction','Houve dedução total ou parcial anterior nos bens ou elementos que os constituem,com referência histórica'],['factsCorrespond','Objecto,destino,operação,data eâmbito correspondem ao percurso identificado']] as const;
|
||
export const allocationOwnConditions={
|
||
permanent_private_goods:[['permanentAllocation','A afectação dos bens é permanente,não mero uso temporário'],['privatePurpose','Uso próprio do titular,pessoal ou fins alheios à empresa identificado'],['notSmallGift','Não se trata de amostra ou oferta de pequeno valor abrangida pela excepção3(3)e']],
|
||
free_goods_transfer:[['gratuitousTransfer','Está identificada uma transmissão gratuita dos bens'],['notSmallGift','Não se trata de amostra ou oferta de pequeno valor abrangida pela excepção3(3)e']],
|
||
exempt_sector_goods:[['exemptSector','Os bens são afectados ao sector de actividade isento identificado']],
|
||
fixed_asset_20a:[['fixedAsset','Está identificada a afectação ao activo imobilizado'],['goods20a','Os bens correspondem aos referidos em20(1)a,com âmbito e referência identificados']],
|
||
} as const;
|
||
export function allocationSpecificConditions(treatment:string):ReadonlyArray<readonly[string,string]>{return allocationOwnConditions[treatment as keyof typeof allocationOwnConditions]??[];}
|
||
type Choice=''|'yes'|'no';type Condition=typeof goodsAllocationConditions[number][0];
|
||
export type GoodsAllocationRow={id:string;treatment:string;description:string;period:string;origin:'projected'|'effective';currency:string;operationDate:string;operationRef:string;documentRef:string;documentDate:string;goodsRef:string;deductionRef:string;destinationRef:string;exceptionRef:string;asset20aRef:string;valuationMode:string;acquisitionPrice:string;acquisitionRef:string;currentCost:string;costRef:string;valuationDate:string;valuationScope:string;evidence:string;conditions:Partial<Record<string,Choice>>};
|
||
type LineResult={id:string;status:'determined'|'not_applicable'|'indeterminate';classification:'assimilated_goods'|'not_applicable'|'indeterminate';baseAmount:string|null;vat:null;rate:null;reason:string};
|
||
export type GoodsAllocationCalculation={version:string;revision:number;calculatedAt:string;opportunity:string;location:string;sourceKey:string;source:string;inputs:GoodsAllocationRow[];rows:LineResult[]};
|
||
export type GoodsAllocationBook={rows:GoodsAllocationRow[];result?:GoodsAllocationCalculation;history?:GoodsAllocationCalculation[]};
|
||
type Store=ReturnType<typeof readAcquisitionStore>&{regulatedGoodsAllocations?:Record<string,GoodsAllocationBook>};
|
||
export function newGoodsAllocation(id:string,currency:string):GoodsAllocationRow{return{id,treatment:'',description:'',period:'',origin:'projected',currency,operationDate:'',operationRef:'',documentRef:'',documentDate:'',goodsRef:'',deductionRef:'',destinationRef:'',exceptionRef:'',asset20aRef:'',valuationMode:'',acquisitionPrice:'',acquisitionRef:'',currentCost:'',costRef:'',valuationDate:'',valuationScope:'',evidence:'',conditions:{}};}
|
||
export function switchAllocationTreatment(row:GoodsAllocationRow,treatment:string):GoodsAllocationRow{return{...row,treatment,conditions:Object.fromEntries(goodsAllocationConditions.map(([key])=>[key,row.conditions[key]??'']))};}
|
||
export function switchAllocationValuation(row:GoodsAllocationRow,valuationMode:string):GoodsAllocationRow{return{...row,valuationMode,conditions:{...row.conditions,valuationConfirmed:'',acquisitionUnavailable:''}};}
|
||
|
||
function readStore(draft:Record<string,unknown>):Store{
|
||
const store=readAcquisitionStore(draft.modelo_a_snapshot) as Store;
|
||
if(store.regulatedGoodsAllocations!==undefined&&(!store.regulatedGoodsAllocations||typeof store.regulatedGoodsAllocations!=='object'||Array.isArray(store.regulatedGoodsAllocations)))throw new Error('Afectações de bens não recuperáveis;originais preservados.');
|
||
const fields=Object.keys(newGoodsAllocation('','')).filter(key=>key!=='conditions');
|
||
for(const book of Object.values(store.regulatedGoodsAllocations??{}))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 GoodsAllocationRow]!=='string')||!row.conditions||typeof row.conditions!=='object'||Array.isArray(row.conditions)))throw new Error('Linhas de afectações de bens não recuperáveis;originais preservados,sem substituição por lista vazia.');
|
||
return store;
|
||
}
|
||
export function goodsAllocationBook(draft:Record<string,unknown>):GoodsAllocationBook{return readStore(draft).regulatedGoodsAllocations?.[acquisitionScope(draft)]??{rows:[]};}
|
||
export function saveGoodsAllocationBook(draft:Record<string,unknown>,book:GoodsAllocationBook):string{const current=readStore(draft),encoded=JSON.stringify({...current,regulatedGoodsAllocations:{...current.regulatedGoodsAllocations,[acquisitionScope(draft)]:book}});readStore({...draft,modelo_a_snapshot:encoded});return encoded;}
|
||
function decimal(raw:unknown):bigint|null{if(typeof raw!=='string'||!/^\d{1,15}(\.\d{1,6})?$/.test(raw.trim()))return null;const[whole,fraction='']=raw.trim().split('.');return BigInt(whole)*1000000n+BigInt(fraction.padEnd(6,'0'));}
|
||
function money(n:bigint):string{return`${n/1000000n}.${(n%1000000n).toString().padStart(6,'0').replace(/0+$/,'').padEnd(2,'0')}`;}
|
||
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 evaluateGoodsAllocation(row:GoodsAllocationRow,currency:unknown):LineResult{
|
||
const result:LineResult={id:row.id,status:'indeterminate',classification:'indeterminate',baseAmount:null,vat:null,rate:null,reason:''};const stop=(reason:string,status:LineResult['status']='indeterminate')=>({...result,status,classification:status==='not_applicable'?'not_applicable' as const:result.classification,reason});
|
||
const rule=goodsAllocationTreatments.find(t=>t.id===row.treatment);if(!rule)return stop('Identifique afectação permanente,transmissão gratuita,sector isento ou activo imobilizado20(1)a.');
|
||
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ês2026,data correspondente e dados efectivos ou pressupostos expressos.');
|
||
const conditions=[...goodsAllocationConditions,...allocationSpecificConditions(row.treatment)];if(conditions.some(([key])=>row.conditions[key]==='no'))return stop('Este percurso de transmissão assimilada não se aplica aos factos declarados;conferir natureza,dedução histórica ou excepção,não presumir isenção/IVAzero.','not_applicable');
|
||
if(conditions.some(([key])=>row.conditions[key]!=='yes'))return stop('Factos próprios ou dedução anterior por esclarecer;desconhecido não é não aplicável ou zero. Dedução histórica não é nova dedução concedida.');
|
||
if(!row.description.trim()||!row.operationRef.trim()||!row.documentRef.trim()||!validDate(row.documentDate)||!row.goodsRef.trim()||!row.deductionRef.trim()||!row.destinationRef.trim()||!row.evidence.trim())return stop('Identifique bens,operação,documento/data,dedução histórica,destino eâmbito factual ou pressupostos expressos;reutilize referências,sem certificado/aprovador novo.');
|
||
if(rule.clause==='e'&&!row.exceptionRef.trim())return stop('Identifique os factos/referência que distinguem a operação da excepção de amostras/ofertas de pequeno valor,ou pressuposto expresso;sem limiar inventado.');
|
||
if(row.treatment==='fixed_asset_20a'&&!row.asset20aRef.trim())return stop('Identifique enquadramento dos bens20(1)a eafectação ao activo imobilizado,com referência/âmbito.');
|
||
result.classification='assimilated_goods';
|
||
if(currency!=='MZN'||row.currency!==currency)return stop('Operação3(3)'+rule.clause+' identificada;moeda fora do recorteMZN ou divergente,base indeterminada,sem conversão silenciosa.');
|
||
if(!['acquisition','cost'].includes(row.valuationMode))return stop('Operação3(3)'+rule.clause+' identificada;base15(2)b por esclarecer:preço aquisição ou,na sua falta demonstrada,preço custo,reportados ao momento da operação.');
|
||
if(row.conditions.valuationConfirmed!=='yes'||!validDate(row.valuationDate)||row.valuationDate!==row.operationDate||!row.valuationScope.trim())return stop('Operação assimilada identificada;base por esclarecer. Confirme preço de aquisição/custo reportado ao momento desta operação,data/âmbito ecomposição sem próprioIVA;não usar custo histórico automaticamente.');
|
||
if(row.valuationMode==='cost'&&row.conditions.acquisitionUnavailable!=='yes')return stop('Operação assimilada identificada;não substituir aquisição desconhecida por custo. O custo15(2)b só é usado na falta demonstrada de preço aquisição;conferir esse facto.');
|
||
const raw=row.valuationMode==='acquisition'?row.acquisitionPrice:row.currentCost,ref=row.valuationMode==='acquisition'?row.acquisitionRef:row.costRef;const amount=decimal(raw);
|
||
if(amount===null||!ref.trim())return stop('Operação assimilada identificada;indique o preço e referência correspondentes ao modo escolhido,reportados ao momento da operação;desconhecido não é zero.');
|
||
return{...result,status:'determined',baseAmount:money(amount),reason:`P124:transmissão assimilada3(3)${rule.clause};P088:base15(2)b=${money(amount)}MZN,${row.valuationMode==='acquisition'?'preço aquisição':'preço custo,na falta demonstrada de preço aquisição'},reportado a${row.operationDate},${row.origin==='projected'?'sob pressupostos expressos':'segundo dados efectivos declarados'}. Dedução anterior total/parcial é facto histórico dos bens/elementos,não nova dedução/campo11. Não confundir afectação/transmissão de bens comserviço por uso temporárioP089. Sem calcular IVA,taxa,regularização ou totaisModeloA.`};
|
||
}
|
||
|
||
function key(draft:Record<string,unknown>,book:GoodsAllocationBook):string{return JSON.stringify({version:goodsAllocationVersion,scope:acquisitionScope(draft),location:draft.selection_labels??{},currency:draft.currency,rows:book.rows});}
|
||
export function goodsAllocationCurrent(draft:Record<string,unknown>,book:GoodsAllocationBook):boolean{return!!book.result&&book.result.sourceKey===key(draft,book);}
|
||
export function recordGoodsAllocations(draft:Record<string,unknown>,book:GoodsAllocationBook):GoodsAllocationBook{
|
||
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:GoodsAllocationCalculation={version:goodsAllocationVersion,revision:(book.result?.revision??0)+1,calculatedAt:new Date().toISOString(),opportunity:acquisitionScope(draft),location:JSON.stringify(draft.selection_labels??{}),sourceKey:key(draft,book),source:goodsAllocationSource,inputs:structuredClone(book.rows),rows:book.rows.map(row=>evaluateGoodsAllocation(row,draft.currency))};
|
||
const next={...book,result,history:[...(book.history??[]),...(book.result?[book.result]:[])]};saveGoodsAllocationBook(draft,next);return next;
|
||
}
|
||
export function goodsAllocationReportRows(draft:Record<string,unknown>):Array<[string,string,string,string]>{
|
||
try{const book=goodsAllocationBook(draft),result=book.result;if(!result)return[];return result.inputs.map((row,index)=>{const line=result.rows[index],rule=goodsAllocationTreatments.find(t=>t.id===row.treatment);return['IVA mensal',`Afectação P124/baseP088 — ${row.period} — ${row.id}`,`${row.description};classificação:${line.classification==='assimilated_goods'?'Transmissão assimilada de bens':line.classification==='not_applicable'?'Percurso não aplicável':'Indeterminada'};base:${line.baseAmount??'Indeterminada'} ${row.currency};IVA: Indeterminado;${line.reason}`,`${result.opportunity};${result.location};${goodsAllocationCurrent(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'};${rule?.label??'Percurso por indicar'};operação ${row.operationRef},${row.operationDate};bens ${row.goodsRef};dedução histórica ${row.deductionRef};destino ${row.destinationRef};${rule?.clause==='e'?`excepção amostras/ofertas ${row.exceptionRef};`:''}${row.treatment==='fixed_asset_20a'?`enquadramento20(1)a ${row.asset20aRef};`:''}base ${row.valuationMode};${row.valuationMode==='cost'?`custo ${row.currentCost},${row.costRef};falta aquisição ${row.conditions.acquisitionUnavailable??'por indicar'}`:`aquisição ${row.acquisitionPrice},${row.acquisitionRef}`};data/âmbito base ${row.valuationDate},${row.valuationScope};documento ${row.documentRef},${row.documentDate};condições ${[...goodsAllocationConditions,...allocationSpecificConditions(row.treatment)].map(([key,label])=>label+': '+(row.conditions[key]==='yes'?'Sim':row.conditions[key]==='no'?'Não':'Por indicar')).join('; ')};fundamento ${row.evidence};sem nova dedução/campo11/IVA/regularização/totaisModeloA.`];});}
|
||
catch{return[['IVA mensal','Afectação de bens','Dados não recuperáveis','Originais preservados;sem base ou classificação por defeito.']];}
|
||
}
|