62 lines
11 KiB
TypeScript
62 lines
11 KiB
TypeScript
import {acquisitionScope,readAcquisitionStore} from './iva-acquisitions.ts';
|
||
|
||
export const fuelBaseVersion='regulated-fuel-base/1';
|
||
export const fuelBaseSource='CIVA republicado Lei13/2016,15(2)(i),PDF13/p1426(271): nas transmissões de combustíveis cujo preço é fixado por Autoridade Pública,efectuadas por revendedores,o valor da contraprestação determinado nos termos dos números1 e5 não inclui a Taxa sobre os Combustíveis. Art15(5)/(6),PDF14/p1426(272),composição/exclusões,sem próprioIVA. Lei22/2022PDF3mantém15(2)(i)/(1)/(5)/(6)por remissão;Lei10/2025não altera15. Recorte2026/MZN;base apenas,sem presumir preço regulado,valorTC,incidência,taxaIVA,IVA,dedução/campo11 ou totaisModeloA.';
|
||
export const fuelBaseTreatments=[{id:'fuel_tax_included',label:'Montante sem IVA ainda inclui a Taxa sobre Combustíveis'},{id:'fuel_tax_excluded',label:'Montante sem IVA já exclui a Taxa sobre Combustíveis'}] as const;
|
||
export const fuelBaseConditions=[
|
||
['taxableFuelResale','Está identificada uma transmissão de combustíveis sujeita a IVA,efectuada por revendedor; não é qualquer aquisição ou prestação de serviços'],
|
||
['publicFixedPrice','O preço deste combustível é fixado pela Autoridade Pública identificada na referência aplicável'],
|
||
['priceScopeMatches','A referência do preço e o respectivo âmbito abrangem este combustível,operação,local e data; não se presume um preço actual para outro período'],
|
||
['netComposition','O montante indicado exclui o próprio IVA e reflecte a composição aplicável do artigo15(1)/(5)/(6),sem omissões ou duplicações'],
|
||
['fuelTaxTreatment','O tratamento escolhido corresponde à composição do montante:TC ainda incluída ou já excluída; a mesma TC não será subtraída duas vezes'],
|
||
] as const;
|
||
type Choice=''|'yes'|'no';type Condition=typeof fuelBaseConditions[number][0];
|
||
export type FuelBaseRow={id:string;treatment:string;description:string;period:string;origin:'projected'|'effective';currency:string;operationDate:string;priceRef:string;priceScope:string;priceFrom:string;priceTo:string;documentRef:string;documentDate:string;consideration:string;fuelTax:string;fuelTaxRef:string;compositionRef:string;evidence:string;conditions:Partial<Record<Condition,Choice>>};
|
||
type LineResult={id:string;status:'determined'|'not_applicable'|'indeterminate';baseAmount:string|null;vat:null;rate:null;reason:string};
|
||
export type FuelBaseCalculation={version:string;revision:number;calculatedAt:string;opportunity:string;location:string;sourceKey:string;source:string;inputs:FuelBaseRow[];rows:LineResult[]};
|
||
export type FuelBaseBook={rows:FuelBaseRow[];result?:FuelBaseCalculation;history?:FuelBaseCalculation[]};
|
||
type Store=ReturnType<typeof readAcquisitionStore>&{regulatedFuelBases?:Record<string,FuelBaseBook>};
|
||
export function newFuelBase(id:string,currency:string):FuelBaseRow{return{id,treatment:'',description:'',period:'',origin:'projected',currency,operationDate:'',priceRef:'',priceScope:'',priceFrom:'',priceTo:'',documentRef:'',documentDate:'',consideration:'',fuelTax:'',fuelTaxRef:'',compositionRef:'',evidence:'',conditions:{}};}
|
||
export function switchFuelTaxTreatment(row:FuelBaseRow,treatment:string):FuelBaseRow{return{...row,treatment,conditions:{...row.conditions,fuelTaxTreatment:''}};}
|
||
function readStore(draft:Record<string,unknown>):Store{
|
||
const store=readAcquisitionStore(draft.modelo_a_snapshot) as Store;
|
||
if(store.regulatedFuelBases!==undefined&&(!store.regulatedFuelBases||typeof store.regulatedFuelBases!=='object'||Array.isArray(store.regulatedFuelBases)))throw new Error('Bases de combustíveis não recuperáveis;originais preservados.');
|
||
const fields=Object.keys(newFuelBase('','')).filter(key=>key!=='conditions');
|
||
for(const book of Object.values(store.regulatedFuelBases??{}))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 FuelBaseRow]!=='string')||!row.conditions||typeof row.conditions!=='object'||Array.isArray(row.conditions)))throw new Error('Linhas de combustíveis não recuperáveis;originais preservados,sem substituição por lista vazia.');
|
||
return store;
|
||
}
|
||
export function fuelBaseBook(draft:Record<string,unknown>):FuelBaseBook{return readStore(draft).regulatedFuelBases?.[acquisitionScope(draft)]??{rows:[]};}
|
||
export function saveFuelBaseBook(draft:Record<string,unknown>,book:FuelBaseBook):string{const current=readStore(draft),encoded=JSON.stringify({...current,regulatedFuelBases:{...current.regulatedFuelBases,[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 evaluateFuelBase(row:FuelBaseRow,currency:unknown):LineResult{
|
||
const result:LineResult={id:row.id,status:'indeterminate',baseAmount:null,vat:null,rate:null,reason:''};const stop=(reason:string,status:LineResult['status']='indeterminate')=>({...result,status,reason});
|
||
if(!fuelBaseTreatments.some(item=>item.id===row.treatment))return stop('Indique se o montante sem IVA ainda inclui a TC ou já a exclui;não se presume a composição.');
|
||
if(currency!=='MZN'||row.currency!==currency)return stop('Moeda fora do âmbito MZN ou divergente do projecto;sem conversão silenciosa.');
|
||
if(!/^2026-(0[1-9]|1[0-2])$/.test(row.period)||!['projected','effective'].includes(row.origin))return stop('Indique mês em2026 e dados efectivos declarados ou pressupostos de projecção;sem inferir do orçamento.');
|
||
const failed=fuelBaseConditions.filter(([key])=>row.conditions[key]==='no');if(failed.length)return stop('Não se aplica esta base especial de combustíveis: '+failed.map(([,label])=>label).join('; ')+'. Não é isenção nem IVA zero.','not_applicable');
|
||
const missing=fuelBaseConditions.filter(([key])=>row.conditions[key]!=='yes');if(missing.length)return stop('Condições por indicar: '+missing.map(([,label])=>label).join('; ')+'. Desconhecido não significa não aplicável ou zero.');
|
||
if(!row.description.trim()||!row.priceRef.trim()||!row.priceScope.trim()||!row.documentRef.trim()||!row.compositionRef.trim()||!row.evidence.trim()||!validDate(row.documentDate))return stop('Identifique combustível/transmissão,referência e âmbito do preço,documento/data,composição e fundamento ou pressupostos expressos;reutilize referências existentes,sem novo certificado/aprovador.');
|
||
if(!validDate(row.operationDate)||row.operationDate.slice(0,7)!==row.period||!validDate(row.priceFrom)||row.operationDate<row.priceFrom||(row.priceTo!==''&&(!validDate(row.priceTo)||row.priceFrom>row.priceTo||row.operationDate>row.priceTo)))return stop('Identifique a data da transmissão no mês escolhido e o intervalo aplicável do preço que a abrange. Sem aplicar preço de outro período;na projecção indique pressupostos temporais expressos. O fim pode ficar vazio quando não existe termo fixado,com a correspondência temporal confirmada.');
|
||
const amount=decimal(row.consideration);if(amount===null)return stop('Indique o montante da contraprestação sem IVA;valor desconhecido ou inválido não é zero.');
|
||
let base=amount,formula='montante já sem TC,sem nova subtracção';
|
||
if(row.treatment==='fuel_tax_included'){
|
||
const tc=decimal(row.fuelTax);if(tc===null||!row.fuelTaxRef.trim())return stop('Identifique o montante exacto e a referência da TC incluída nesta contraprestação,ou pressuposto expresso;sem taxa TC presumida ou valor desconhecido igual a zero.');
|
||
if(tc>amount)return stop('A TC indicada excede a contraprestação sem IVA;reveja a composição e os montantes,sem base negativa ou zero substituto.');
|
||
base=amount-tc;formula=`${money(amount)} − ${money(tc)} de TC`;
|
||
}
|
||
return{...result,status:'determined',baseAmount:money(base),reason:`Base15(2)(i): ${formula} = ${money(base)}MZN,${row.origin==='projected'?'nas condições expressamente assumidas na projecção':'nos dados efectivos declarados'}. Preço/âmbito/período e composição identificados;sem determinar automaticamente preço ou taxaTC. Base não é imposto;IVA/taxa,dedução/campo11 e totaisModeloA não são determinados por este cálculo.`};
|
||
}
|
||
function key(draft:Record<string,unknown>,book:FuelBaseBook):string{return JSON.stringify({version:fuelBaseVersion,scope:acquisitionScope(draft),location:draft.selection_labels??{},currency:draft.currency,rows:book.rows});}
|
||
export function fuelBaseCurrent(draft:Record<string,unknown>,book:FuelBaseBook):boolean{return!!book.result&&book.result.sourceKey===key(draft,book);}
|
||
export function recordFuelBases(draft:Record<string,unknown>,book:FuelBaseBook):FuelBaseBook{
|
||
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:FuelBaseCalculation={version:fuelBaseVersion,revision:(book.result?.revision??0)+1,calculatedAt:new Date().toISOString(),opportunity:acquisitionScope(draft),location:JSON.stringify(draft.selection_labels??{}),sourceKey:key(draft,book),source:fuelBaseSource,inputs:structuredClone(book.rows),rows:book.rows.map(row=>evaluateFuelBase(row,draft.currency))};
|
||
const next={...book,result,history:[...(book.history??[]),...(book.result?[book.result]:[])]};saveFuelBaseBook(draft,next);return next;
|
||
}
|
||
export function fuelBaseReportRows(draft:Record<string,unknown>):Array<[string,string,string,string]>{
|
||
try{const book=fuelBaseBook(draft),result=book.result;if(!result)return[];return result.inputs.map((row,index)=>['IVA mensal',`Base P095 — ${row.period} — ${row.id}`,`${row.description};base tributável: ${result.rows[index].baseAmount??'Indeterminada'} ${row.currency};IVA: Indeterminado;${result.rows[index].reason}`,`${result.opportunity};${result.location};${fuelBaseCurrent(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'};transmissão ${row.operationDate};preço ${row.priceRef};âmbito ${row.priceScope};intervalo ${row.priceFrom} a ${row.priceTo||'sem termo fixado,correspondência temporal confirmada'};${fuelBaseTreatments.find(item=>item.id===row.treatment)?.label??'Composição por indicar'};contraprestação ${row.consideration} ${row.currency};${row.treatment==='fuel_tax_included'?`TC incluída ${row.fuelTax} ${row.currency},referência ${row.fuelTaxRef}`:'TC já excluída,sem segunda subtracção'};composição ${row.compositionRef};documento ${row.documentRef},${row.documentDate};condições ${fuelBaseConditions.map(([key,label])=>label+': '+(row.conditions[key]==='yes'?'Sim':row.conditions[key]==='no'?'Não':'Por indicar')).join('; ')};fundamento ${row.evidence};não preenche totaisModeloA ou campo11.`]);}
|
||
catch{return[['IVA mensal','Base de combustíveis','Dados não recuperáveis','Originais preservados;sem substituir por zero.']];}
|
||
}
|