58 lines
12 KiB
TypeScript
58 lines
12 KiB
TypeScript
import {acquisitionScope,readAcquisitionStore} from './iva-acquisitions.ts';
|
|
export const propertyTransferVersion='property-transfers/1';
|
|
export const propertyTransferSource='CIVA Lei13/2016:3(3)a,PDF6/p1426(264),entrega material em locação com cláusula vinculante de transferência da propriedade;3(5),PDF7/p1426(265),cessão onerosa/gratuita de estabelecimento,totalidade ou parte de património que constitua ramo independente,adquirente2(1)a que pratique operações com direito à dedução;15(1),PDF13/p1426(271),contraprestação obtida/a obter.Lei22/2022PDF3 mantém15(1);Lei10/2025PDF12 mantém3(3)a/3(5),não altera15.P087não usa15(2)a,referente a consignação3(3)d.';
|
|
export const propertyTransferTreatments=[{id:'binding_lease',branch:'P087',label:'Locação com transferência vinculativa da propriedade'},{id:'business_onerous',branch:'P127',label:'Cessão onerosa de estabelecimento/património autónomo'},{id:'business_free',branch:'P127',label:'Cessão gratuita de estabelecimento/património autónomo'}] as const;
|
|
export const propertyTransferConditions={binding_lease:[['materialDelivery','Entrega material dos bens em execução do contrato de locação'],['bindingProperty','Contrato contém cláusula vinculante para ambas as partes de transferência da propriedade']],business_onerous:[['onerous','Cessão a título oneroso identificada'],['autonomousBusiness','Estabelecimento,totalidade ou parte de património susceptível de constituir ramo de actividade independente'],['acquirer2a','Adquirente é ou passa,pela aquisição,a sujeito passivo2(1)a'],['deductionActivities','Adquirente pratica operações que concedem direito à dedução']],business_free:[['gratuitous','Cessão a título gratuito identificada'],['autonomousBusiness','Estabelecimento,totalidade ou parte de património susceptível de constituir ramo de actividade independente'],['acquirer2a','Adquirente é ou passa,pela aquisição,a sujeito passivo2(1)a'],['deductionActivities','Adquirente pratica operações que concedem direito à dedução']]} as const;
|
|
export function transferConditions(t:string):ReadonlyArray<readonly[string,string]>{return propertyTransferConditions[t as keyof typeof propertyTransferConditions]??[];}
|
|
type Choice=''|'yes'|'no';
|
|
export type PropertyTransferRow={id:string;treatment:string;description:string;period:string;origin:'projected'|'effective';operationDate:string;operationRef:string;documentRef:string;documentDate:string;goodsRef:string;scope:string;evidence:string;contractRef:string;propertyClauseRef:string;acquirerRef:string;autonomousRef:string;activitiesRef:string;consideration:string;considerationRef:string;currency:string;compositionConfirmed:Choice;conditions:Partial<Record<string,Choice>>};
|
|
type LineResult={id:string;status:'determined'|'not_applicable'|'indeterminate';classification:'goods_transmission'|'tax_not_exigible_3_5'|'not_applicable'|'indeterminate';baseAmount:string|null;vat:null;rate:null;reason:string};
|
|
export type PropertyTransferCalculation={version:string;revision:number;calculatedAt:string;opportunity:string;location:string;sourceKey:string;source:string;inputs:PropertyTransferRow[];rows:LineResult[]};
|
|
export type PropertyTransferBook={rows:PropertyTransferRow[];result?:PropertyTransferCalculation;history?:PropertyTransferCalculation[]};
|
|
type Store=ReturnType<typeof readAcquisitionStore>&{propertyTransfers?:Record<string,PropertyTransferBook>};
|
|
export function newPropertyTransfer(id:string,currency:string):PropertyTransferRow{return{id,treatment:'',description:'',period:'',origin:'projected',operationDate:'',operationRef:'',documentRef:'',documentDate:'',goodsRef:'',scope:'',evidence:'',contractRef:'',propertyClauseRef:'',acquirerRef:'',autonomousRef:'',activitiesRef:'',consideration:'',considerationRef:'',currency,compositionConfirmed:'',conditions:{}};}
|
|
export function switchPropertyTreatment(row:PropertyTransferRow,treatment:string):PropertyTransferRow{return{...row,treatment,conditions:{},compositionConfirmed:''};}
|
|
function readStore(draft:Record<string,unknown>):Store{
|
|
const store=readAcquisitionStore(draft.modelo_a_snapshot) as Store;
|
|
if(store.propertyTransfers!==undefined&&(!store.propertyTransfers||typeof store.propertyTransfers!=='object'||Array.isArray(store.propertyTransfers)))throw new Error('Transmissões de propriedade não recuperáveis;originais preservados.');
|
|
const fields=Object.keys(newPropertyTransfer('','')).filter(k=>k!=='conditions');
|
|
for(const book of Object.values(store.propertyTransfers??{}))if(!book||!Array.isArray(book.rows)||(book.history!==undefined&&!Array.isArray(book.history))||book.rows.some(row=>!row||fields.some(k=>typeof row[k as keyof PropertyTransferRow]!=='string')||!row.conditions||typeof row.conditions!=='object'||Array.isArray(row.conditions)))throw new Error('Linhas não recuperáveis;originais conservados,sem lista vazia por defeito.');
|
|
return store;
|
|
}
|
|
export function propertyTransferBook(draft:Record<string,unknown>):PropertyTransferBook{return readStore(draft).propertyTransfers?.[acquisitionScope(draft)]??{rows:[]};}
|
|
export function savePropertyTransferBook(draft:Record<string,unknown>,book:PropertyTransferBook):string{const store=readStore(draft),encoded=JSON.stringify({...store,propertyTransfers:{...store.propertyTransfers,[acquisitionScope(draft)]:book}});readStore({...draft,modelo_a_snapshot:encoded});return encoded;}
|
|
function validDate(s:string):boolean{const n=Date.parse(s+'T00:00:00Z');return /^\d{4}-\d{2}-\d{2}$/.test(s)&&Number.isFinite(n)&&new Date(n).toISOString().slice(0,10)===s;}
|
|
function decimal(raw:string):bigint|null{if(!/^\d{1,15}(\.\d{1,6})?$/.test(raw.trim()))return null;const[w,f='']=raw.trim().split('.');return BigInt(w)*1000000n+BigInt(f.padEnd(6,'0'));}
|
|
function money(n:bigint):string{return`${n/1000000n}.${(n%1000000n).toString().padStart(6,'0').replace(/0+$/,'').padEnd(2,'0')}`;}
|
|
export function evaluatePropertyTransfer(row:PropertyTransferRow,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',classification:LineResult['classification']=status==='not_applicable'?'not_applicable':result.classification)=>({...result,status,classification,reason});
|
|
if(!propertyTransferTreatments.some(t=>t.id===row.treatment))return stop('Identifique locação vinculativa ou cessão onerosa/gratuita de estabelecimento.');
|
|
if(!validDate(row.operationDate)||!/^2026-(0[1-9]|1[0-2])$/.test(row.period)||row.period!==row.operationDate.slice(0,7)||!['projected','effective'].includes(row.origin))return stop('Identifique data/mês2026 e dados efectivos ou pressupostos expressos.');
|
|
if(![row.description,row.operationRef,row.documentRef,row.goodsRef,row.scope,row.evidence,row.contractRef].every(v=>!!v.trim())||!validDate(row.documentDate))return stop('Identifique objecto,operação,contrato/documento/data e âmbito factual ou pressupostos;reutilize referências,sem certificado/aprovador novo.');
|
|
const conditions=transferConditions(row.treatment);
|
|
if(conditions.some(([k])=>row.conditions[k]==='no'))return stop(row.treatment==='binding_lease'?'Não estão reunidos os factos de3(3)a;não converter automaticamente opção de compra em transferência vinculativa.':'Não estão reunidos os requisitos de não exigibilidade3(5);classificar a transmissão pelos restantes percursos aplicáveis,sem isenção/IVA zero.','not_applicable');
|
|
if(conditions.some(([k])=>row.conditions[k]!=='yes'))return stop('Factos próprios por esclarecer;desconhecido não é não aplicável.');
|
|
if(row.treatment==='binding_lease'){
|
|
if(!row.propertyClauseRef.trim())return stop('Identifique cláusula vinculante para ambas as partes;mera opção de compra não a demonstra.');
|
|
result.classification='goods_transmission';
|
|
if(currency!=='MZN'||row.currency!==currency)return stop('Transmissão3(3)a identificada;base em moeda fora do recorteMZN ou divergente,sem conversão silenciosa.');
|
|
const value=decimal(row.consideration);
|
|
if(value===null||!row.considerationRef.trim()||row.compositionConfirmed!=='yes')return stop('Transmissão3(3)a identificada;base15(1) por esclarecer. Identifique contraprestação obtida/a obter e composição correspondente,sem próprioIVA e sem voltar a subtrair componentes já excluídas;não somar rendas/valor residual automaticamente.');
|
|
return{...result,status:'determined',baseAmount:money(value),reason:`P087:entrega material em locação com cláusula vinculativa3(3)a;base15(1)=${money(value)}MZN,contraprestação obtida/a obter segundo composição e âmbito identificados.Não é consignação3(3)d/15(2)a,nem mera opção de compra.Não calcula IVA,exigibilidade,nova dedução/campo11 ou totaisModeloA.`};
|
|
}
|
|
if(![row.acquirerRef,row.autonomousRef,row.activitiesRef].every(v=>!!v.trim()))return stop('Identifique adquirente2(1)a,conjunto susceptível de ramo independente e operações que conferem direito à dedução;simples venda de activos não basta.');
|
|
return stop('P127:cessão '+(row.treatment==='business_onerous'?'onerosa':'gratuita')+'3(5) identificada como transmissão de bens cujo imposto não é devido nem exigível,segundo conjunto/adquirente/operações e âmbito demonstrados.Não é isenção genérica,nem taxa/base zero.Não concede dedução ao adquirente;qualidade e operações são factos do enquadramento.Sem condição adicional de continuidade,certificado ou aprovador inventados.','determined','tax_not_exigible_3_5');
|
|
}
|
|
function key(draft:Record<string,unknown>,book:PropertyTransferBook):string{return JSON.stringify({version:propertyTransferVersion,scope:acquisitionScope(draft),location:draft.selection_labels??{},currency:draft.currency,rows:book.rows});}
|
|
export function propertyTransferCurrent(draft:Record<string,unknown>,book:PropertyTransferBook):boolean{return!!book.result&&book.result.sourceKey===key(draft,book);}
|
|
export function recordPropertyTransfers(draft:Record<string,unknown>,book:PropertyTransferBook):PropertyTransferBook{
|
|
if(!book.rows.length||new Set(book.rows.map(r=>r.id)).size!==book.rows.length)throw new Error('Indique operações com identificadores distintos;originais conservados.');
|
|
const result:PropertyTransferCalculation={version:propertyTransferVersion,revision:(book.result?.revision??0)+1,calculatedAt:new Date().toISOString(),opportunity:acquisitionScope(draft),location:JSON.stringify(draft.selection_labels??{}),sourceKey:key(draft,book),source:propertyTransferSource,inputs:structuredClone(book.rows),rows:book.rows.map(row=>evaluatePropertyTransfer(row,draft.currency))};
|
|
const next={...book,result,history:[...(book.history??[]),...(book.result?[book.result]:[])]};savePropertyTransferBook(draft,next);return next;
|
|
}
|
|
export function propertyTransferReportRows(draft:Record<string,unknown>):Array<[string,string,string,string]>{
|
|
try{const book=propertyTransferBook(draft),r=book.result;if(!r)return[];return r.inputs.map((row,i)=>['IVA mensal',`${propertyTransferTreatments.find(t=>t.id===row.treatment)?.branch??'Transmissão'} — ${row.period} — ${row.id}`,`${row.description};${r.rows[i].reason};base:${r.rows[i].baseAmount??'Indeterminada'} ${row.currency};IVA:Indeterminado`,`${r.opportunity};${r.location};${propertyTransferCurrent(draft,book)?'Resultado actual':'Entradas alteradas;resultado histórico conservado'};versão${r.revision};${r.calculatedAt};${r.source};${row.origin};operação ${row.operationRef},${row.operationDate};documento ${row.documentRef},${row.documentDate};bens ${row.goodsRef};âmbito ${row.scope};contrato ${row.contractRef};${row.treatment==='binding_lease'?`cláusula ${row.propertyClauseRef};contraprestação ${row.consideration},${row.considerationRef};composição ${row.compositionConfirmed}`:`adquirente ${row.acquirerRef};conjunto autónomo ${row.autonomousRef};operações adquirente ${row.activitiesRef}`};condições${JSON.stringify(row.conditions)};fundamento ${row.evidence};sem nova dedução/campo11/IVA/margem automática.`]);}
|
|
catch{return[['IVA mensal','Transmissões de propriedade','Dados não recuperáveis','Originais conservados;sem classificação por defeito.']];}
|
|
}
|