Files
Investidor-V2/resources/js/lib/iva-public-authority-base.ts
T

71 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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.']];}
}