56 lines
12 KiB
TypeScript
56 lines
12 KiB
TypeScript
import {acquisitionScope,readAcquisitionStore} from './iva-acquisitions.ts';
|
|
|
|
export const ownNameVersion='own-name-chains/1';
|
|
export const ownNameSource='CIVA3(3)c,Lei13/2016PDF6/p1426(264):transferências em execução de contrato de comissão,incluindo consignação;comissário considerado comprador na comissão de venda,comitente na de compra. Art4(3),PDF7/p1426(265):mandatário emnome próprio é adquirente eprestador do serviço sucessivamente. Lei10/2025PDF12mantém3(3)c e não altera4. Base de bens15(2)e/P091já entregue,não recalculada ou somada neste percurso. Recorte2026:circuito/contraprestações originais separados,sem localização,isenção,taxa,IVA,dedução/campo11 ou totaisModeloA presumidos.';
|
|
export const ownNameTreatments=[{id:'goods_sale_commission',branch:'P122',label:'P122 — Comissão de venda de bens',kind:'goods',direction:'sale'},{id:'goods_purchase_commission',branch:'P122',label:'P122 — Comissão de compra de bens',kind:'goods',direction:'purchase'},{id:'service_sale_mandate',branch:'P128',label:'P128 — Mandato de venda/prestação de serviços',kind:'services',direction:'sale'},{id:'service_purchase_mandate',branch:'P128',label:'P128 — Mandato de aquisição de serviços',kind:'services',direction:'purchase'}] as const;
|
|
export const ownNameConditions=[['ownName','O comissário/mandatário actua emnome próprio,não apenas emnome do mandante'],['forAnother','Actua por conta de outrem identificado ecom relação documentada ou pressuposto expresso'],['factsCorrespond','Objecto,partes,referências eperíodo correspondem à operação identificada']] as const;
|
|
const goodsConditions=[['goodsNature','São transferências de bens,não serviço autónomo de intermediação'],['commissionContract','As transferências executam contrato de comissão identificado,conforme3(3)c,incluindo consignação quando aplicável'],['directionConfirmed','Papéis e duas relações correspondem ao sentido de venda ou compra escolhido']] as const;
|
|
const serviceConditions=[['serviceNature','Está identificada uma prestação de serviços,não transferência de bens'],['mandateContract','A prestação é efectuada por intervenção de mandatário emnome próprio,com mandato identificado4(3)'],['successiveService','Mandatário é adquirente eprestador do serviço sucessivamente;referências distinguem as duas prestações'],['directionConfirmed','Papéis e duas relações correspondem ao sentido de venda/prestação ou aquisição escolhido']] as const;
|
|
export function ownNameSpecificConditions(treatment:string):ReadonlyArray<readonly[string,string]>{const rule=ownNameTreatments.find(t=>t.id===treatment);return rule?.kind==='goods'?goodsConditions:rule?.kind==='services'?serviceConditions:[];}
|
|
type Choice=''|'yes'|'no';type Condition=typeof ownNameConditions[number][0];
|
|
export type OwnNameRow={id:string;treatment:string;description:string;period:string;origin:'projected'|'effective';operationDate:string;operationRef:string;documentRef:string;documentDate:string;principalRef:string;agentRef:string;thirdPartyRef:string;contractRef:string;subjectRef:string;currency:string;firstAmount:string;secondAmount:string;firstLegRef:string;secondLegRef:string;existingBaseRef:string;scope:string;evidence:string;conditions:Partial<Record<string,Choice>>};
|
|
type Leg={supplier:string;acquirer:string;consideration:string;currency:string;reference:string};
|
|
type LineResult={id:string;status:'determined'|'not_applicable'|'indeterminate';legs:Leg[];baseAmount:null;vat:null;rate:null;reason:string};
|
|
export type OwnNameCalculation={version:string;revision:number;calculatedAt:string;opportunity:string;location:string;sourceKey:string;source:string;inputs:OwnNameRow[];rows:LineResult[]};
|
|
export type OwnNameBook={rows:OwnNameRow[];result?:OwnNameCalculation;history?:OwnNameCalculation[]};
|
|
type Store=ReturnType<typeof readAcquisitionStore>&{ownNames?:Record<string,OwnNameBook>};
|
|
export function newOwnName(id:string,currency=''):OwnNameRow{return{id,treatment:'',description:'',period:'',origin:'projected',operationDate:'',operationRef:'',documentRef:'',documentDate:'',principalRef:'',agentRef:'',thirdPartyRef:'',contractRef:'',subjectRef:'',currency,firstAmount:'',secondAmount:'',firstLegRef:'',secondLegRef:'',existingBaseRef:'',scope:'',evidence:'',conditions:{}};}
|
|
export function switchOwnNameTreatment(row:OwnNameRow,treatment:string):OwnNameRow{return{...row,treatment,conditions:Object.fromEntries(ownNameConditions.map(([key])=>[key,row.conditions[key]??'']))};}
|
|
|
|
function readStore(draft:Record<string,unknown>):Store{
|
|
const store=readAcquisitionStore(draft.modelo_a_snapshot) as Store;
|
|
if(store.ownNames!==undefined&&(!store.ownNames||typeof store.ownNames!=='object'||Array.isArray(store.ownNames)))throw new Error('Circuitos emnome próprio não recuperáveis;originais preservados.');
|
|
const fields=Object.keys(newOwnName('')).filter(key=>key!=='conditions');
|
|
for(const book of Object.values(store.ownNames??{}))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 OwnNameRow]!=='string')||!row.conditions||typeof row.conditions!=='object'||Array.isArray(row.conditions)))throw new Error('Linhas de circuitos emnome próprio não recuperáveis;originais preservados,sem substituição por lista vazia.');
|
|
return store;
|
|
}
|
|
export function ownNameBook(draft:Record<string,unknown>):OwnNameBook{return readStore(draft).ownNames?.[acquisitionScope(draft)]??{rows:[]};}
|
|
export function saveOwnNameBook(draft:Record<string,unknown>,book:OwnNameBook):string{const current=readStore(draft),encoded=JSON.stringify({...current,ownNames:{...current.ownNames,[acquisitionScope(draft)]:book}});readStore({...draft,modelo_a_snapshot:encoded});return encoded;}
|
|
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 evaluateOwnName(row:OwnNameRow,currency:unknown):LineResult{
|
|
const result:LineResult={id:row.id,status:'indeterminate',legs:[],baseAmount:null,vat:null,rate:null,reason:''};const stop=(reason:string,status:LineResult['status']='indeterminate')=>({...result,status,reason});
|
|
const rule=ownNameTreatments.find(t=>t.id===row.treatment);if(!rule)return stop('Identifique bens/serviços e o sentido da comissão/mandato;sem inferir pelo pagamento de uma comissão.');
|
|
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 required=[...ownNameConditions,...ownNameSpecificConditions(row.treatment)];if(required.some(([key])=>row.conditions[key]==='no'))return stop('Este circuito emnome próprio não se aplica aos factos declarados;rever mandato/comissão/natureza/papéis,sem classificar mera intermediação como duas operações ou presumir isenção/IVAzero.','not_applicable');
|
|
if(required.some(([key])=>row.conditions[key]!=='yes'))return stop('Natureza,actuação emnome próprio ou papéis por esclarecer;desconhecido não é não aplicável ou zero.');
|
|
if(!row.description.trim()||!row.operationRef.trim()||!row.documentRef.trim()||!validDate(row.documentDate)||!row.scope.trim()||!row.evidence.trim()||!row.subjectRef.trim()||!row.contractRef.trim()||![row.principalRef,row.agentRef,row.thirdPartyRef,row.firstLegRef,row.secondLegRef].every(v=>!!v.trim()))return stop('Identifique objecto,mandante/comitente,comissário/mandatário,terceiro,contrato,duas relações,operação/documento/data eâmbito;reutilize referências ou pressupostos expressos,sem certificado/aprovador novo.');
|
|
if(typeof currency!=='string'||!/^[A-Z]{3}$/.test(currency)||row.currency!==currency)return stop('Identifique moeda original coerente com o contexto;sem converter ou reinterpretarUSDcomoMZN.');
|
|
const amount=(v:string):string|null=>/^\d{1,15}(\.\d{1,6})?$/.test(v.trim())?v.trim():null;const first=amount(row.firstAmount),second=amount(row.secondAmount);
|
|
if(first===null||second===null)return stop('Identifique separadamente as contraprestações originais das duas relações;desconhecido não é zero. Não inferir uma delas da comissão ou somá-las como uma única base.');
|
|
const before=rule.direction==='sale'?row.principalRef:row.thirdPartyRef,after=rule.direction==='sale'?row.thirdPartyRef:row.principalRef;
|
|
const legs=[{supplier:before,acquirer:row.agentRef,consideration:first,currency:row.currency,reference:row.firstLegRef},{supplier:row.agentRef,acquirer:after,consideration:second,currency:row.currency,reference:row.secondLegRef}];
|
|
return{...result,status:'determined',legs,reason:`${rule.branch}:${rule.kind==='goods'?`art3(3)c,comissão de${rule.direction==='sale'?'venda:comissário considerado comprador':'compra:comitente considerado comprador'}`:'art4(3),mandatário emnome próprio considerado adquirente eprestador do serviço sucessivamente'}. ${row.origin==='projected'?'Circuito sob pressupostos expressos.':'Circuito segundo dados efectivos declarados.'} Duas relações com objecto/partes/contraprestações originais separadas,sem soma ou compensação automática. ${rule.kind==='goods'?'BaseP091/15(2)e já entregue permanece separada e não é recalculada.':'Não transformar este circuito emcomissão autónoma ou aplicar15(2)e de bens aos serviços.'} Sem determinar localização,isenção,base,taxa,IVA,dedução/campo11 ou totaisModeloA.`};
|
|
}
|
|
|
|
function key(draft:Record<string,unknown>,book:OwnNameBook):string{return JSON.stringify({version:ownNameVersion,scope:acquisitionScope(draft),location:draft.selection_labels??{},currency:draft.currency,rows:book.rows});}
|
|
export function ownNameCurrent(draft:Record<string,unknown>,book:OwnNameBook):boolean{return!!book.result&&book.result.sourceKey===key(draft,book);}
|
|
export function recordOwnNames(draft:Record<string,unknown>,book:OwnNameBook):OwnNameBook{
|
|
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:OwnNameCalculation={version:ownNameVersion,revision:(book.result?.revision??0)+1,calculatedAt:new Date().toISOString(),opportunity:acquisitionScope(draft),location:JSON.stringify(draft.selection_labels??{}),sourceKey:key(draft,book),source:ownNameSource,inputs:structuredClone(book.rows),rows:book.rows.map(row=>evaluateOwnName(row,draft.currency))};
|
|
const next={...book,result,history:[...(book.history??[]),...(book.result?[book.result]:[])]};saveOwnNameBook(draft,next);return next;
|
|
}
|
|
export function ownNameReportRows(draft:Record<string,unknown>):Array<[string,string,string,string]>{
|
|
try{const book=ownNameBook(draft),result=book.result;if(!result)return[];return result.inputs.map((row,index)=>{const line=result.rows[index],rule=ownNameTreatments.find(t=>t.id===row.treatment);return['IVA mensal',`Circuito ${rule?.branch??'emnome próprio'} — ${row.period} — ${row.id}`,`${row.description};${line.status==='determined'?'Circuito identificado':line.status==='not_applicable'?'Circuito não aplicável':'Circuito indeterminado'};IVA: Indeterminado;${line.reason};${line.legs.map((leg,i)=>`Relação${i+1}:${leg.supplier} → ${leg.acquirer},contraprestação original ${leg.consideration} ${leg.currency},referência ${leg.reference}`).join('; ')};sem soma das duas relações`,`${result.opportunity};${result.location};${ownNameCurrent(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};objecto ${row.subjectRef};contrato ${row.contractRef};mandante/comitente ${row.principalRef};agente ${row.agentRef};terceiro ${row.thirdPartyRef};relações ${row.firstLegRef}/${row.secondLegRef};${rule?.kind==='goods'?`referênciaP091existente ${row.existingBaseRef||'não indicada;sem nova base calculada'};`:''}âmbito ${row.scope};documento ${row.documentRef},${row.documentDate};condições ${[...ownNameConditions,...ownNameSpecificConditions(row.treatment)].map(([key,label])=>label+': '+(row.conditions[key]==='yes'?'Sim':row.conditions[key]==='no'?'Não':'Por indicar')).join('; ')};fundamento ${row.evidence};sem IVA/dedução/campo11/totaisModeloA.`];});}
|
|
catch{return[['IVA mensal','Circuito emnome próprio','Dados não recuperáveis','Originais preservados;sem circuito ou valor por defeito.']];}
|
|
}
|