feat(exchange-settings): one fallback rate per currency

exchange_settings was a single row holding the USD->ETB fallback only.
Restructured to one row per foreign currency (adds a currency column,
migration 3850000000000) so DJF gets its own fallback rate, source and
sync timestamp instead of a parallel column. Service/controller/DTO
follow: get/loadFallbackRate/saveFallbackRate/setManualRate all take a
currency now, GET /exchange-settings returns the list, and
PATCH /exchange-settings/:currency sets one. Per-currency manual-rate
ceiling (USD ~10,000, DJF ~100) replaces the old fixed bound.

Adds a spec exercising the multi-currency CBE parse and the USD<->DJF
pivot against a fixture payload.

Claude-Session: https://claude.ai/code/session_01CZy77vCWhka3pnmVF9NDkL
This commit is contained in:
ghost2023
2026-09-04 11:52:18 +03:00
parent 17deb434ae
commit 21dc28a708
7 changed files with 285 additions and 77 deletions

View File

@@ -0,0 +1,42 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
/**
* `exchange_settings` was a single-row table holding the USD→ETB fallback
* only. Restructures it to one row per currency so DJF (and any future
* currency) gets its own fallback rate, source and sync timestamp instead of
* a parallel column per currency.
*/
export class ExchangeSettingsPerCurrency3850000000000 implements MigrationInterface {
name = 'ExchangeSettingsPerCurrency3850000000000';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE freight.exchange_settings ADD COLUMN IF NOT EXISTS currency varchar(5);
`);
// The single pre-existing row was always the USD→ETB fallback.
await queryRunner.query(`
UPDATE freight.exchange_settings SET currency = 'USD' WHERE currency IS NULL;
`);
await queryRunner.query(`
ALTER TABLE freight.exchange_settings ALTER COLUMN currency SET NOT NULL;
`);
await queryRunner.query(`
CREATE UNIQUE INDEX IF NOT EXISTS uq_exchange_settings_currency
ON freight.exchange_settings (currency) WHERE deleted_at IS NULL;
`);
// Seed the DJF row at the CBE-quoted DJF→ETB rate observed 2026-09-04, so
// pricing has a usable fallback before the first successful CBE fetch.
await queryRunner.query(`
INSERT INTO freight.exchange_settings (id, currency, fallback_rate, fallback_source, created_at, updated_at)
SELECT uuid_generate_v4(), 'DJF', 0.9203, 'AUTO', now(), now()
WHERE NOT EXISTS (SELECT 1 FROM freight.exchange_settings WHERE currency = 'DJF');
`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DELETE FROM freight.exchange_settings WHERE currency = 'DJF'`);
await queryRunner.query(`DROP INDEX IF EXISTS freight.uq_exchange_settings_currency`);
await queryRunner.query(`ALTER TABLE freight.exchange_settings ALTER COLUMN currency DROP NOT NULL`);
await queryRunner.query(`ALTER TABLE freight.exchange_settings DROP COLUMN IF EXISTS currency`);
}
}