At a Glance
Owner: Product Admin · Table:tb_exchange_rate· Used by: PR / PO / GRN / pricelist / costing · Feed: daily (manual or cron)

Exchange Rate stores the dated history of currency-to-base-currency rates. Every priced document (PR / PO / GRN / pricelist) snapshots the rate effective on its date at submit and freezes it for the life of the document — re-approval does NOT re-fetch.
Maintained by Product Admin (typically each morning). Read by the costing engine for FX revaluation on credit notes (COST_CALC_005) and period close.
| Task | Where | Notes |
|---|---|---|
| Enter today's rate for all enabled currencies | Configuration → Exchange Rate → Bulk daily | Idempotent on (currency, date) — safe to re-run |
| Enter one rate (single currency) | Configuration → Exchange Rate → Manual single-row | Pick currency, pick date, enter rate |
| Check which rate a document used | Open the PO/GRN, look at the line FX field | Document freezes rate at submit time |
| Refresh FX on a draft PO | PO line → Refresh FX action | PR and GRN do NOT have this action |
| Fix a wrong rate on a posted document | Cannot edit in-place | Raise a manual journal voucher per finance policy |
| Enable automated daily feed | Configure micro-cronjobs FX job |
Idempotent — safe to re-run; updates the "current" cache too |
| Symptom / Message | Cause | Action |
|---|---|---|
| "Exchange rate must be > 0" | Rate entered as zero or negative | Re-enter a positive number |
| "Effective date too far in future" | at_date > today + 1 day (configurable horizon) |
Use today's date for the daily entry |
| "Currency must be active" | tb_currency.is_active = false for the chosen code |
Activate first under master-data/currency |
| Duplicate rate for the same date | A row already exists for (currency, at_date) |
Edit the existing row; do NOT insert a second |
| "Period is closed" | at_date falls inside a closed system-config/period |
Cannot back-fill into a closed period; raise a JV |
| Document shows "rate not in history" warning | No tb_exchange_rate row at or before document date for that currency |
Add a backdated rate, then re-open the document so resolution can run |
Decimal(15, 5); document line totals round to 2 decimals (money) per the rounding convention.tb_currency.exchange_rate is a cache of the most-recent tb_exchange_rate row. New documents resolve via the dated history first; only if no row matches the date does the cache act as fallback (with a warning on the document).Source: tenant schema.
tb_exchange_rate| Field | Prisma Type | Nullable | Description |
|---|---|---|---|
id |
String @db.Uuid |
No | Primary key. |
at_date |
DateTime? @db.Timestamptz(6) |
Yes | Effective date (defaults now()). Applies from this date until superseded. |
currency_id |
String? @db.Uuid |
Yes | FK to tb_currency. |
currency_code |
String? @db.VarChar(3) |
Yes | Denormalised display copy (USD, THB). |
currency_name |
String? @db.VarChar |
Yes | Denormalised display copy. |
exchange_rate |
Decimal? @db.Decimal(15, 5) |
Yes | Rate against BU default currency (default 1). |
note |
String? @db.VarChar |
Yes | Free text (e.g. "Daily fix from BoT"). |
info, dimension |
Json? |
Yes | Standard metadata. |
| Audit columns | — | Yes | created_*, updated_*, deleted_*. |
Constraints: @@unique([at_date, currency_id, deleted_at]) map exchangerate_at_date_currency_u — one rate per (currency, date). Index on (at_date, currency_id). FK on currency_id onDelete: NoAction so rate history survives a currency soft-delete.
(at_date, currency_id). Re-entering the same day = update the existing row.exchange_rate > 0; at_date <= today + 1 day (configurable horizon); currency_id must reference an active currency.Decimal(15, 5); line totals round to money precision (2 dp) per the rounding convention.at_date <= document_date for the document currency. If no row exists, fall back to tb_currency.exchange_rate and flag the document.at_date inside a closed period are rejected.tb_currency.default_currency_id is the implicit "to" side of every rate.COST_CALC_005 (credit-note FX revaluation) and period close read here.../carmen-turborepo-backend-v2/packages/prisma-shared-schema-tenant/prisma/schema.prisma — tb_exchange_rate (lines ~744-768).../carmen-inventory-frontend/app/(root)/config/exchange-rate/.../micro-cronjobs/ — daily FX feed.