import {selectFx,fxSelectionKey,type FxSelection} from './iva-fx-selection.ts'; import {currencyEventBook,newCurrencyEvent} from './iva-currency-events.ts'; import {convertCents} from './business-financial-plan.ts'; import {decimalFraction,fraction,type FiscalFraction} from './fiscal-rational.ts'; import {closureFingerprint} from './iva-closure.ts'; export type IrpcFxLink={id:string;fingerprint:string}; export type IrpcFxAmount={currency:string;fxSelection?:FxSelection;fxLink?:IrpcFxLink}; export type IrpcFxEvidence={originalAmount:string;originalCurrency:string;convertedCurrency:'MZN';column:string;rate:string;date:string;direction:string;source:string;selectionKey:string;rounding:string}; export function irpcFxSources(d:Record){return currencyEventBook(d).rows.filter(r=>r.fxSelection).map(r=>({id:r.id,label:`${r.description||r.id} — ${r.sourceCurrency} — ${r.fxSelection?.applied?.rate??'por aplicar'}`,fingerprint:closureFingerprint(r),selection:structuredClone(r.fxSelection!)}));} export function irpcFxDependency(d:Record,input:IrpcFxAmount):string{ if(input.currency==='MZN'||!input.fxLink)return''; const source=irpcFxSources(d).find(s=>s.id===input.fxLink!.id); return !source?'Selecção cambial de origem indisponível;vínculo e histórico conservados.':source.fingerprint!==input.fxLink.fingerprint?'Selecção cambial de origem alterada;compare a nova selecção antes de a aplicar às retenções.':''; } export function irpcFxRow(id:string,input:IrpcFxAmount){const s=input.fxSelection,a=s?.applied;return{...newCurrencyEvent(id,'MZN'),sourceCurrency:input.currency,fxSelection:s,fxDirection:s?.direction??'',fxRate:a?.rate??'',fxDate:a?.date??'',fxSource:a?.source??''};} export function convertIrpcAmount(d:Record,amount:string,input:IrpcFxAmount):{value:FiscalFraction;evidence:IrpcFxEvidence|null}{ const original=decimalFraction(amount);if(input.currency==='MZN')return{value:original,evidence:null}; const issue=irpcFxDependency(d,input);if(issue)throw Error(issue); const s=input.fxSelection;if(!s)throw Error('Reutilize ou aplique a selecção cambial já entregue:moeda original,boletim,coluna,data e sentido;sem taxa actual por defeito.'); const selected=selectFx(s),a=s.applied; if(!a||a.selectionKey!==fxSelectionKey(s)||a.rate!==selected.rate||a.date!==selected.date||a.source!==selected.source||a.column!==selected.column||s.sourceCurrency!==input.currency)throw Error('Selecção cambial alterada,não confirmada ou de outra moeda;use a conferência e confirmação existentes,sem recalcular históricos.'); const [whole,places='']=selected.rate.split('.'),rate=BigInt(whole+places.padEnd(8,'0')),scale=100000000n; const micro=BigInt(original.numerator)*1000000n/BigInt(original.denominator); const converted=s.direction==='mzn_per_unit'?convertCents(micro,rate,scale):convertCents(micro,scale,rate); return{value:fraction(converted,1000000n),evidence:{originalAmount:amount,originalCurrency:input.currency,convertedCurrency:'MZN',column:selected.column,rate:selected.rate,date:selected.date,direction:s.direction,source:selected.source,selectionKey:a.selectionKey,rounding:'Mesmo convertCents entregue;conversão à unidade de0,000001MZN;apresentação separada,não regra nova de arredondamento do imposto.'}}; } export function irpcFxEvidenceLabel(e:IrpcFxEvidence):string{const column=({buy:'Compra',sell:'Venda',mean:'Média de referência BM',exception:'Excepção fiscal identificada'} as Record)[e.column]??e.column;const direction=e.direction==='mzn_per_unit'?'MZN por '+e.originalCurrency:e.originalCurrency+' por MZN';return`Original: ${e.originalAmount} ${e.originalCurrency}; coluna: ${column}; taxa: ${e.rate}; cotação: ${direction}; data cambial: ${e.date}; fonte: ${e.source.split(';perspectiva:')[0]}. Montante e selecção originais conservados.`;} export function updateIrpcFx(input:IrpcFxAmount,selection:FxSelection):Partial{return{fxSelection:selection,fxLink:input.fxSelection&&fxSelectionKey(input.fxSelection)===fxSelectionKey(selection)?input.fxLink:undefined};}