mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-07 08:25:43 +00:00
Replace wagon/locomotive readiness with yard tracking, assign unassigned bookings from origin-yard fleet, standardize rates on USD with CBE ETB conversion, and update fleet/scheduling UI
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
|
||||
@Injectable()
|
||||
export class CbeExchangeService {
|
||||
private readonly logger = new Logger(CbeExchangeService.name);
|
||||
private cachedRate: number | null = null;
|
||||
private cacheExpiresAt = 0;
|
||||
|
||||
constructor(private readonly configService: ConfigService) {}
|
||||
|
||||
/**
|
||||
* Returns the current CBE USD→ETB exchange rate.
|
||||
* Fetches live from CBE_EXCHANGE_API_URL, caches for CBE_EXCHANGE_CACHE_TTL_MS,
|
||||
* and falls back to CBE_EXCHANGE_FALLBACK_RATE when the API is unreachable.
|
||||
*/
|
||||
async getUsdToEtbRate(): Promise<number> {
|
||||
const now = Date.now();
|
||||
|
||||
if (this.cachedRate !== null && now < this.cacheExpiresAt) {
|
||||
return this.cachedRate;
|
||||
}
|
||||
|
||||
const apiUrl = this.configService.get<string>('app.cbeExchange.apiUrl') ?? '';
|
||||
const fallbackRate = this.configService.get<number>('app.cbeExchange.fallbackRate') ?? 130;
|
||||
const cacheTtlMs = this.configService.get<number>('app.cbeExchange.cacheTtlMs') ?? 3_600_000;
|
||||
|
||||
if (!apiUrl) {
|
||||
this.logger.warn(
|
||||
`CBE_EXCHANGE_API_URL not configured — using fallback rate ${fallbackRate} ETB/USD`,
|
||||
);
|
||||
return fallbackRate;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(apiUrl, {
|
||||
signal: AbortSignal.timeout(8_000),
|
||||
headers: { Accept: 'application/json' },
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`CBE API responded with status ${response.status}`);
|
||||
}
|
||||
|
||||
const json = await response.json();
|
||||
const rate = this.parseRate(json);
|
||||
|
||||
if (!rate || !Number.isFinite(rate) || rate <= 0) {
|
||||
throw new Error(`Invalid rate value parsed from CBE API response: ${rate}`);
|
||||
}
|
||||
|
||||
this.cachedRate = rate;
|
||||
this.cacheExpiresAt = now + cacheTtlMs;
|
||||
this.logger.log(`CBE USD→ETB rate refreshed: ${rate}`);
|
||||
return rate;
|
||||
} catch (err) {
|
||||
this.logger.error(
|
||||
`Failed to fetch CBE exchange rate — using fallback ${fallbackRate} ETB/USD. Error: ${(err as Error).message}`,
|
||||
);
|
||||
|
||||
if (this.cachedRate !== null) {
|
||||
this.logger.warn(`Using previously cached CBE rate: ${this.cachedRate}`);
|
||||
return this.cachedRate;
|
||||
}
|
||||
|
||||
return fallbackRate;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the USD→ETB selling rate from the CBE API JSON response.
|
||||
* CBE API typically returns an array of currency objects.
|
||||
* Adjust this method if the API shape differs.
|
||||
*
|
||||
* Expected shape (one common format):
|
||||
* [ { currency: "USD", selling: "130.50", ... }, ... ]
|
||||
*/
|
||||
private parseRate(json: unknown): number | null {
|
||||
if (Array.isArray(json)) {
|
||||
const usdEntry = json.find(
|
||||
(entry: unknown) =>
|
||||
typeof entry === 'object' &&
|
||||
entry !== null &&
|
||||
(
|
||||
(entry as Record<string, unknown>)['currency'] === 'USD' ||
|
||||
(entry as Record<string, unknown>)['Currency'] === 'USD'
|
||||
),
|
||||
) as Record<string, unknown> | undefined;
|
||||
|
||||
if (!usdEntry) return null;
|
||||
|
||||
const selling =
|
||||
usdEntry['selling'] ??
|
||||
usdEntry['Selling'] ??
|
||||
usdEntry['sellingRate'] ??
|
||||
usdEntry['rate'] ??
|
||||
usdEntry['Rate'];
|
||||
|
||||
return selling !== undefined ? Number(selling) : null;
|
||||
}
|
||||
|
||||
if (typeof json === 'object' && json !== null) {
|
||||
const obj = json as Record<string, unknown>;
|
||||
const selling =
|
||||
obj['selling'] ??
|
||||
obj['Selling'] ??
|
||||
obj['sellingRate'] ??
|
||||
obj['usdToEtb'] ??
|
||||
obj['rate'];
|
||||
return selling !== undefined ? Number(selling) : null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user