149 lines
5.3 KiB
TypeScript
149 lines
5.3 KiB
TypeScript
export const fxTempoSource =
|
|
'Orientação operacional expressa da Tempo,25/10/2026:compra de moeda→taxa de compra;venda→taxa de venda;operações comerciais→taxa média de referência do Banco de Moçambique na data de submissão indicada. Interpretar compra/venda pela perspectiva do boletim. Excepções fiscais específicas conservam disposição,data,taxa e âmbito;sem recálculo automático dos históricos.';
|
|
export type FxSelection = {
|
|
issuer?: string;
|
|
nature: '' | 'purchase' | 'sale' | 'commercial';
|
|
submissionDate: string;
|
|
bulletinDate: string;
|
|
bulletinRef: string;
|
|
source: string;
|
|
sourceCurrency: string;
|
|
direction: '' | 'mzn_per_unit' | 'units_per_mzn';
|
|
bulletinPerspective: string;
|
|
relation: '' | 'same' | 'opposite';
|
|
buy: string;
|
|
sell: string;
|
|
mean: string;
|
|
mode: 'general' | 'exception';
|
|
exceptionRef: string;
|
|
exceptionScope: string;
|
|
exceptionDate: string;
|
|
exceptionRate: string;
|
|
applied?: {
|
|
column: string;
|
|
rate: string;
|
|
date: string;
|
|
source: string;
|
|
selectedAt: string;
|
|
selectionKey: string;
|
|
};
|
|
};
|
|
export function emptyFxSelection(): FxSelection {
|
|
return {
|
|
nature: '',
|
|
submissionDate: '',
|
|
bulletinDate: '',
|
|
bulletinRef: '',
|
|
source: '',
|
|
sourceCurrency: '',
|
|
direction: '',
|
|
bulletinPerspective: '',
|
|
relation: '',
|
|
buy: '',
|
|
sell: '',
|
|
mean: '',
|
|
mode: 'general',
|
|
exceptionRef: '',
|
|
exceptionScope: '',
|
|
exceptionDate: '',
|
|
exceptionRate: '',
|
|
};
|
|
}
|
|
function date(s: string) {
|
|
const d = new Date(s + 'T00:00:00Z');
|
|
return (
|
|
/^\d{4}-\d{2}-\d{2}$/.test(s) &&
|
|
Number.isFinite(d.getTime()) &&
|
|
d.toISOString().slice(0, 10) === s
|
|
);
|
|
}
|
|
function positive(s: string) {
|
|
return /^\d{1,15}(\.\d{1,8})?$/.test(s) && Number(s) > 0;
|
|
}
|
|
export function fxSelectionKey(s: FxSelection): string {
|
|
const { applied, ...inputs } = s;
|
|
void applied;
|
|
return JSON.stringify(inputs);
|
|
}
|
|
export function selectFx(s: FxSelection): NonNullable<FxSelection['applied']> {
|
|
if (
|
|
!['purchase', 'sale', 'commercial'].includes(s.nature) ||
|
|
!date(s.submissionDate) ||
|
|
!s.bulletinRef.trim() ||
|
|
!s.source.trim() ||
|
|
!s.sourceCurrency.trim() ||
|
|
!['mzn_per_unit', 'units_per_mzn'].includes(s.direction)
|
|
)
|
|
throw Error(
|
|
'Indique natureza,data de submissão,moeda,direcção e boletim/fonte;sem usar taxa actual ouzero por defeito.',
|
|
);
|
|
let column = '',
|
|
rate = '',
|
|
day = '';
|
|
if (s.mode === 'exception') {
|
|
if (
|
|
!s.exceptionRef.trim() ||
|
|
!s.exceptionScope.trim() ||
|
|
!date(s.exceptionDate) ||
|
|
!positive(s.exceptionRate)
|
|
)
|
|
throw Error(
|
|
'Excepção exige disposição fiscal,âmbito,data e taxa identificados;os restantes percursos continuam disponíveis.',
|
|
);
|
|
column = 'exception';
|
|
rate = s.exceptionRate;
|
|
day = s.exceptionDate;
|
|
} else {
|
|
if (
|
|
s.mode !== 'general' ||
|
|
s.bulletinDate !== s.submissionDate ||
|
|
!date(s.bulletinDate)
|
|
)
|
|
throw Error(
|
|
'Use boletim da data de submissão indicada;ausência de boletim não autoriza usar a taxa mais recente.',
|
|
);
|
|
if (s.nature === 'commercial') {
|
|
let officialUrl = false;
|
|
try {
|
|
officialUrl = ['bancomoc.mz', 'www.bancomoc.mz'].includes(
|
|
new URL(s.source).hostname,
|
|
);
|
|
} catch {
|
|
/* An identified official local exemplar does not require another validation. */
|
|
}
|
|
if (!officialUrl && s.issuer?.trim() !== 'Banco de Moçambique')
|
|
throw Error(
|
|
'Operação comercial usa taxa média de referência do Banco de Moçambique;identifique a origemBM do boletim arquivado ou a suaURL,sem exigir nova validação da fonte aceite.',
|
|
);
|
|
column = 'mean';
|
|
rate = s.mean;
|
|
} else {
|
|
if (
|
|
!s.bulletinPerspective.trim() ||
|
|
!['same', 'opposite'].includes(s.relation)
|
|
)
|
|
throw Error(
|
|
'Identifique a perspectiva indicada no boletim e a relação com a natureza declarada;sem inverter a operação do cliente por presunção.',
|
|
);
|
|
column =
|
|
(s.nature === 'purchase') === (s.relation === 'same')
|
|
? 'buy'
|
|
: 'sell';
|
|
rate = column === 'buy' ? s.buy : s.sell;
|
|
}
|
|
day = s.submissionDate;
|
|
}
|
|
if (!positive(rate))
|
|
throw Error(
|
|
'Taxa da coluna aplicável ausente ou inválida;não calcular média a partir de compra/venda.',
|
|
);
|
|
return {
|
|
column,
|
|
rate,
|
|
date: day,
|
|
source: `${s.bulletinRef};${s.source};perspectiva:${s.bulletinPerspective || 'média de referênciaBM'};natureza:${s.nature};relação:${s.relation || 'não aplicável à média'};${s.mode === 'exception' ? s.exceptionRef + ';' + s.exceptionScope : fxTempoSource}`,
|
|
selectedAt: new Date().toISOString(),
|
|
selectionKey: fxSelectionKey(s),
|
|
};
|
|
}
|