62 lines
12 KiB
TypeScript
62 lines
12 KiB
TypeScript
import {acquisitionScope,readAcquisitionStore} from './iva-acquisitions.ts';
|
||
|
||
export const commissionBaseVersion='commission-goods-base/1';
|
||
export const commissionBaseSource='CIVA republicado Lei13/2016,15(2)(e),PDF13/p1426(271): nas transmissões de bens entre comitente e comissário,preço de venda acordado pelo comissário diminuído da comissão;entre comissário e comitente,preço de compra acordado pelo comissário aumentado da comissão. Art3(3)(c),PDF6/p1426(264): transferências em execução de contrato de comissão,incluindo consignação;na comissão de venda considera-se comprador o comissário,na de compra o comitente. Art15(5)/(6),PDF14,p1426(272),composição/exclusões,sem próprioIVA;regra especial de leilão15(2)(g)não substituída. Lei22/2022PDF3mantém15(2)(e);Lei10/2025PDF12mantém3(3)(c)por remissão e não altera15. Recorte2026/MZN,base de bens;sem incidência,taxa,IVA,serviço autónomo,dedução/campo11 ou totaisModeloA presumidos.';
|
||
export const commissionBaseTreatments=[
|
||
{id:'commission_sale_transfer',label:'Comitente → comissário — comissão de venda',direction:'Comitente para comissário',priceLabel:'Preço de venda acordado pelo comissário,antes da dedução da comissão,sem IVA',operator:'−'},
|
||
{id:'commission_purchase_transfer',label:'Comissário → comitente — comissão de compra',direction:'Comissário para comitente',priceLabel:'Preço de compra acordado pelo comissário,antes do acréscimo da comissão,sem IVA',operator:'+'},
|
||
] as const;
|
||
export const commissionBaseConditions=[
|
||
['taxableDomesticGoods','Está identificada uma transmissão de bens sujeita a IVA no território nacional; não é um serviço autónomo de intermediação nem qualquer pagamento de comissão'],
|
||
['commissionContract','A transferência destes bens entre comitente e comissário executa o contrato de comissão identificado,conforme3(3)(c),incluindo consignação quando aplicável'],
|
||
['directionConfirmed','A operação e os papéis correspondem ao sentido escolhido:comissão de venda(comitente para comissário)ou de compra(comissário para comitente)'],
|
||
['netComposition','O preço acordado pelo comissário e a comissão desta operação estão identificados sem o próprio IVA,com composição15(5)/(6)aplicável; o preço ainda não sofreu o ajuste da comissão efectuado por esta fórmula'],
|
||
['notSpecialAuction','Esta operação não está abrangida pela regra especial das vendas em leilão do artigo15(2)(g); essa base não é substituída pela comissão genérica'],
|
||
] as const;
|
||
type Condition=typeof commissionBaseConditions[number][0];
|
||
type Choice=''|'yes'|'no';
|
||
export type CommissionBaseRow={id:string;treatment:string;description:string;period:string;origin:'projected'|'effective';currency:string;principal:string;agent:string;agreementRef:string;agreedPrice:string;priceRef:string;commissionAmount:string;commissionRef:string;compositionRef:string;documentRef:string;documentDate: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 CommissionBaseCalculation={version:string;revision:number;calculatedAt:string;opportunity:string;location:string;sourceKey:string;source:string;inputs:CommissionBaseRow[];rows:LineResult[]};
|
||
export type CommissionBaseBook={rows:CommissionBaseRow[];result?:CommissionBaseCalculation;history?:CommissionBaseCalculation[]};
|
||
type Store=ReturnType<typeof readAcquisitionStore>&{commissionBases?:Record<string,CommissionBaseBook>};
|
||
export function newCommissionBase(id:string,currency:string):CommissionBaseRow{return{id,treatment:'',description:'',period:'',origin:'projected',currency,principal:'',agent:'',agreementRef:'',agreedPrice:'',priceRef:'',commissionAmount:'',commissionRef:'',compositionRef:'',documentRef:'',documentDate:'',evidence:'',conditions:{}};}
|
||
export function switchCommissionDirection(row:CommissionBaseRow,treatment:string):CommissionBaseRow{return{...row,treatment,conditions:{...row.conditions,directionConfirmed:''}};}
|
||
function readStore(draft:Record<string,unknown>):Store{
|
||
const store=readAcquisitionStore(draft.modelo_a_snapshot) as Store;
|
||
if(store.commissionBases!==undefined&&(!store.commissionBases||typeof store.commissionBases!=='object'||Array.isArray(store.commissionBases)))throw new Error('Bases de comissão não recuperáveis;originais preservados.');
|
||
const fields=Object.keys(newCommissionBase('','')).filter(key=>key!=='conditions');
|
||
for(const book of Object.values(store.commissionBases??{}))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 CommissionBaseRow]!=='string')||!row.conditions||typeof row.conditions!=='object'||Array.isArray(row.conditions)))throw new Error('Linhas de comissão não recuperáveis;originais preservados,sem substituição por lista vazia.');
|
||
return store;
|
||
}
|
||
export function commissionBaseBook(draft:Record<string,unknown>):CommissionBaseBook{return readStore(draft).commissionBases?.[acquisitionScope(draft)]??{rows:[]};}
|
||
export function saveCommissionBaseBook(draft:Record<string,unknown>,book:CommissionBaseBook):string{const current=readStore(draft),encoded=JSON.stringify({...current,commissionBases:{...current.commissionBases,[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 evaluateCommissionBase(row:CommissionBaseRow,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});
|
||
const rule=commissionBaseTreatments.find(item=>item.id===row.treatment);
|
||
if(!rule)return stop('Escolha a transferência de bens em comissão de venda ou de compra; comissão desconhecida não determina uma base.');
|
||
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 o período em2026 e dados efectivos declarados ou pressupostos de projecção; não se infere o período do orçamento.');
|
||
const failed=commissionBaseConditions.filter(([key])=>row.conditions[key]==='no');if(failed.length)return stop('Não se aplica esta base de comissão: '+failed.map(([,label])=>label).join('; ')+'. Não é isenção nem IVA zero.','not_applicable');
|
||
const missing=commissionBaseConditions.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.principal.trim()||!row.agent.trim()||!row.agreementRef.trim()||!row.priceRef.trim()||!row.commissionRef.trim()||!row.compositionRef.trim()||!row.documentRef.trim()||!row.evidence.trim()||!validDate(row.documentDate))return stop('Identifique bens,comitente,comissário,contrato,documento/data e referências do preço,comissão e composição,ou pressupostos expressos;reutilize referências existentes,sem novo certificado.');
|
||
const price=decimal(row.agreedPrice),commission=decimal(row.commissionAmount);if(price===null||commission===null)return stop('Indique preço acordado e comissão sem IVA;montante desconhecido ou inválido não é zero. Não se presume percentagem,base de percentagem ou valor da comissão.');
|
||
if(row.treatment==='commission_sale_transfer'&&commission>price)return stop(`Preço menos comissão produz diferença aritmética negativa de −${money(commission-price)}MZN. Valores conservados;este recorte não a apresenta como base fiscal determinada nem a substitui por zero ou valor absoluto.`);
|
||
const base=money(row.treatment==='commission_sale_transfer'?price-commission:price+commission);
|
||
return{...result,status:'determined',baseAmount:base,reason:`Base15(2)(e),${rule.direction}: preço ${money(price)} ${rule.operator} comissão ${money(commission)} = ${base}MZN,${row.origin==='projected'?'nas condições expressamente assumidas na projecção':'nos dados efectivos declarados'}. Valores sem IVA,ajuste efectuado uma única vez. 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:CommissionBaseBook):string{return JSON.stringify({version:commissionBaseVersion,scope:acquisitionScope(draft),location:draft.selection_labels??{},currency:draft.currency,rows:book.rows});}
|
||
export function commissionBaseCurrent(draft:Record<string,unknown>,book:CommissionBaseBook):boolean{return!!book.result&&book.result.sourceKey===key(draft,book);}
|
||
export function recordCommissionBases(draft:Record<string,unknown>,book:CommissionBaseBook):CommissionBaseBook{
|
||
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:CommissionBaseCalculation={version:commissionBaseVersion,revision:(book.result?.revision??0)+1,calculatedAt:new Date().toISOString(),opportunity:acquisitionScope(draft),location:JSON.stringify(draft.selection_labels??{}),sourceKey:key(draft,book),source:commissionBaseSource,inputs:structuredClone(book.rows),rows:book.rows.map(row=>evaluateCommissionBase(row,draft.currency))};
|
||
const next={...book,result,history:[...(book.history??[]),...(book.result?[book.result]:[])]};saveCommissionBaseBook(draft,next);return next;
|
||
}
|
||
export function commissionBaseReportRows(draft:Record<string,unknown>):Array<[string,string,string,string]>{
|
||
try{const book=commissionBaseBook(draft),result=book.result;if(!result)return[];return result.inputs.map((row,index)=>['IVA mensal',`Base P091 — ${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};${commissionBaseCurrent(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'};${commissionBaseTreatments.find(item=>item.id===row.treatment)?.label??row.treatment};comitente ${row.principal};comissário ${row.agent};contrato ${row.agreementRef};preço ${row.agreedPrice} ${row.currency},referência ${row.priceRef};comissão ${row.commissionAmount} ${row.currency},referência ${row.commissionRef};composição ${row.compositionRef};documento ${row.documentRef},${row.documentDate};condições ${commissionBaseConditions.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 comissão','Dados não recuperáveis','Originais preservados;sem substituir por zero.']];}
|
||
}
|