71 lines
11 KiB
TypeScript
71 lines
11 KiB
TypeScript
import { acquisitionScope, readAcquisitionStore } from './iva-acquisitions.ts';
|
||
|
||
export const publicBaseVersion='public-authority-base/1';
|
||
export const publicBaseSource='CIVA republicado Lei13/2016,15(2)(d),PDF13/p1426(271): base das transmissões de bens e prestações de serviços resultantes de actos de autoridades públicas é a indemnização ou qualquer outra compensação. Art15(3)/(4),PDF14/p1426(272): componente não monetária pelo valor normal;15(5)/(6),composição/exclusões,incluindo exclusão do próprio IVA e indemnização judicial por incumprimento de contratos. Lei22/2022PDF3 mantém15(2)(d)/(3–6)por remissão;Lei10/2025não altera15. Recorte2026/MZN,determinação da base;sem inferir incidência,taxa,isenção,IVA,dedução/campo11 ou totaisModeloA.';
|
||
export const publicBaseTreatments=[
|
||
{id:'public_authority_indemnity',label:'Indemnização por acto de autoridade pública'},
|
||
{id:'public_authority_compensation',label:'Outra compensação por acto de autoridade pública'},
|
||
] as const;
|
||
export const publicBaseConditions=[
|
||
['taxableDomesticSupply','Está identificada uma transmissão de bens ou prestação de serviços sujeita a IVA no território nacional; não basta existir um pagamento ou uma entidade pública'],
|
||
['authorityAct','A transmissão ou prestação resulta do acto da autoridade pública identificado'],
|
||
['compensationLink','O montante e as componentes indicadas correspondem à indemnização ou compensação desta operação'],
|
||
['notJudicialContractBreach','Não se trata de quantia recebida a título de indemnização declarada judicialmente por incumprimento total ou parcial de contrato, excluída pelo artigo15(6)(a)'],
|
||
['netComposition','Os valores indicados excluem o próprio IVA e reflectem a composição aplicável dos números5/6 do artigo15, sem omissões nem duplicações; a referência explica a composição'],
|
||
] as const;
|
||
type Condition=typeof publicBaseConditions[number][0];
|
||
type Choice=''|'yes'|'no';
|
||
export type PublicBaseRow={id:string;treatment:string;description:string;operationNature:''|'goods'|'services';period:string;origin:'projected'|'effective';currency:string;authority:string;actRef:string;documentRef:string;documentDate:string;compensationForm:''|'money'|'non_money'|'mixed';moneyAmount:string;nonMoneyValue:string;valuationRef: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 PublicBaseCalculation={version:string;revision:number;calculatedAt:string;opportunity:string;location:string;sourceKey:string;source:string;inputs:PublicBaseRow[];rows:LineResult[]};
|
||
export type PublicBaseBook={rows:PublicBaseRow[];result?:PublicBaseCalculation;history?:PublicBaseCalculation[]};
|
||
type Store=ReturnType<typeof readAcquisitionStore>&{publicAuthorityBases?:Record<string,PublicBaseBook>};
|
||
export function newPublicBase(id:string,currency:string):PublicBaseRow{return{id,treatment:'',description:'',operationNature:'',period:'',origin:'projected',currency,authority:'',actRef:'',documentRef:'',documentDate:'',compensationForm:'',moneyAmount:'',nonMoneyValue:'',valuationRef:'',compositionRef:'',evidence:'',conditions:{}};}
|
||
function readStore(draft:Record<string,unknown>):Store{
|
||
const store=readAcquisitionStore(draft.modelo_a_snapshot) as Store;
|
||
if(store.publicAuthorityBases!==undefined&&(!store.publicAuthorityBases||typeof store.publicAuthorityBases!=='object'||Array.isArray(store.publicAuthorityBases)))throw new Error('Bases de actos públicos não recuperáveis; originais preservados.');
|
||
const fields=Object.keys(newPublicBase('','')).filter(key=>key!=='conditions');
|
||
for(const book of Object.values(store.publicAuthorityBases??{})){
|
||
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 PublicBaseRow]!=='string')||!row.conditions||typeof row.conditions!=='object'||Array.isArray(row.conditions)))throw new Error('Linhas de base tributável não recuperáveis; originais preservados,sem substituição por lista vazia.');
|
||
}
|
||
return store;
|
||
}
|
||
export function publicBaseBook(draft:Record<string,unknown>):PublicBaseBook{return readStore(draft).publicAuthorityBases?.[acquisitionScope(draft)]??{rows:[]};}
|
||
export function savePublicBaseBook(draft:Record<string,unknown>,book:PublicBaseBook):string{
|
||
const current=readStore(draft),encoded=JSON.stringify({...current,publicAuthorityBases:{...current.publicAuthorityBases,[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 evaluatePublicBase(row:PublicBaseRow,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(!publicBaseTreatments.some(item=>item.id===row.treatment))return stop('Escolha indemnização ou outra compensação por acto de autoridade pública; um pagamento genérico não determina esta base.');
|
||
if(currency!=='MZN'||row.currency!==currency)return stop('Moeda fora do âmbito MZN ou divergente do projecto; originais conservados,sem conversão.');
|
||
if(!/^2026-(0[1-9]|1[0-2])$/.test(row.period)||!['projected','effective'].includes(row.origin))return stop('Indique o período em2026 e dados efectivos declarados ou pressupostos de projecção; não são inferidos do orçamento.');
|
||
const failed=publicBaseConditions.filter(([key])=>row.conditions[key]==='no');
|
||
if(failed.length)return stop('Não se aplica esta determinação de base: '+failed.map(([,label])=>label).join('; ')+'. Não é isenção nem IVA zero; conserve o enquadramento próprio da operação.','not_applicable');
|
||
const missing=publicBaseConditions.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(!['goods','services'].includes(row.operationNature)||!row.description.trim()||!row.authority.trim()||!row.actRef.trim()||!row.documentRef.trim()||!row.compositionRef.trim()||!row.evidence.trim()||!validDate(row.documentDate))return stop('Identifique a operação,natureza,autoridade,acto,documento/data e fundamento da composição,ou pressupostos expressos; reutilize referências existentes,sem novo certificado.');
|
||
if(!['money','non_money','mixed'].includes(row.compensationForm))return stop('Indique se a compensação é em dinheiro,em espécie ou mista; ausência de resposta não elimina uma componente.');
|
||
const cash=row.compensationForm==='non_money'?0n:decimal(row.moneyAmount),kind=row.compensationForm==='money'?0n:decimal(row.nonMoneyValue);
|
||
if(cash===null||kind===null)return stop('Indique os valores sem IVA das componentes aplicáveis; montante em falta ou inválido não é zero.');
|
||
if(row.compensationForm!=='money'&&!row.valuationRef.trim())return stop('Identifique o fundamento do valor normal da componente não monetária,conforme15(3)/(4),ou o pressuposto expresso; não é avaliado pelo portal.');
|
||
const base=money(cash+kind);
|
||
return{...result,status:'determined',baseAmount:base,reason:`Base15(2)(d): componente monetária ${money(cash)} + valor normal não monetário ${money(kind)} = ${base}MZN,${row.origin==='projected'?'nas condições expressamente assumidas na projecção':'nos dados efectivos declarados'}. Componentes sem IVA,não duplicadas,composição15(5)/(6) identificada. Base não é imposto; incidência/taxa/IVA/dedução/campo11 não são determinados por este cálculo.`};
|
||
}
|
||
function key(draft:Record<string,unknown>,book:PublicBaseBook):string{return JSON.stringify({version:publicBaseVersion,scope:acquisitionScope(draft),location:draft.selection_labels??{},currency:draft.currency,rows:book.rows});}
|
||
function natureLabel(value:string):string{return value==='goods'?'Transmissão de bens':value==='services'?'Prestação de serviços':'Por indicar';}
|
||
function formLabel(value:string):string{return value==='money'?'Em dinheiro':value==='non_money'?'Em espécie':value==='mixed'?'Mista — dinheiro e espécie':'Por indicar';}
|
||
export function publicBaseCurrent(draft:Record<string,unknown>,book:PublicBaseBook):boolean{return!!book.result&&book.result.sourceKey===key(draft,book);}
|
||
export function recordPublicBases(draft:Record<string,unknown>,book:PublicBaseBook):PublicBaseBook{
|
||
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:PublicBaseCalculation={version:publicBaseVersion,revision:(book.result?.revision??0)+1,calculatedAt:new Date().toISOString(),opportunity:acquisitionScope(draft),location:JSON.stringify(draft.selection_labels??{}),sourceKey:key(draft,book),source:publicBaseSource,inputs:structuredClone(book.rows),rows:book.rows.map(row=>evaluatePublicBase(row,draft.currency))};
|
||
const next={...book,result,history:[...(book.history??[]),...(book.result?[book.result]:[])]};savePublicBaseBook(draft,next);return next;
|
||
}
|
||
export function publicBaseReportRows(draft:Record<string,unknown>):Array<[string,string,string,string]>{
|
||
try{const book=publicBaseBook(draft),result=book.result;if(!result)return[];return result.inputs.map((row,index)=>['IVA mensal',`Base P090 — ${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}; ${publicBaseCurrent(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'}; ${publicBaseTreatments.find(item=>item.id===row.treatment)?.label??row.treatment}; natureza ${natureLabel(row.operationNature)}; autoridade ${row.authority}; acto ${row.actRef}; documento ${row.documentRef},${row.documentDate}; forma ${formLabel(row.compensationForm)}; ${row.compensationForm!=='non_money'?`dinheiro ${row.moneyAmount} ${row.currency}; `:''}${row.compensationForm!=='money'?`valor normal não monetário ${row.nonMoneyValue} ${row.currency},fundamento ${row.valuationRef}; `:''}composição ${row.compositionRef}; condições ${publicBaseConditions.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 actos públicos','Dados não recuperáveis','Originais preservados;sem substituir por zero.']];}
|
||
}
|