One official exchange rate, written into every client system the day it publishes

Records the National Bank of Cambodia's daily official KHR/USD rate and writes it into a client's ERP or POS. Started as a small Odoo module, now a standalone subscription product.

RoleEnd to end
StatusLive
RevenueSubscription
Writes intoFive named systems

The situation

Businesses in Cambodia price and report in two currencies, and the official KHR/USD rate that accounting has to use is set once a day by the National Bank of Cambodia. Somebody looks it up and types it into the ERP, or the point of sale, or both. Miss a day and the books are wrong in a way that is tedious to unpick later.

It began as a small Odoo module for one client. The same manual step existed in every other system those clients ran, so the module became a product.

What I did

End to end: the fetch and validation against the central bank, the rate store, the connection model and the schedule each client chooses, the integrations into each supported system, the alerting, the client dashboard, and the subscription that pays for it.

System diagram

FIG. 01 — ONE RATE IS FETCHED, CHECKED AND STORED ONCE, THEN RUN OUT TO EVERY ACTIVE CONNECTION ON ITS OWN SCHEDULE.

The rules in the flow are only visible in three places

The flow decides what each connection gets and what happens when one of them does not take it. None of that is any use to the operator unless it can be seen, so the same logic surfaces as three screens: the rate as it was fetched, the connections it went out to, and the history of what each one did with it.

FIG. 02 · WHAT THE FLOW PRODUCES3 OF 9 STEPS SHOWN
01
OPERATOR · RATE SOURCEPHONE
RielSync operator view on a phone, showing the fetched National Bank of Cambodia rate with its date and validation check.
EXPAND ⤢, expand
02
OPERATOR · CONNECTIONSDESKTOP
RielSync operator connections list on desktop, showing each client system with a status indicator and one connection inactive.
EXPAND ⤢, expand
03
OPERATOR · SYNC HISTORYDESKTOP
RielSync sync history on desktop, showing a per-day record of writes with a push failure flagged against one connection.
EXPAND ⤢, expand
01 · ONE SOURCEThe official rate is fetched once, from the National Bank of Cambodia. Every connection downstream reads from that record rather than holding its own copy.
02 · ONE RUN PER CONNECTIONA run is per connection, not per client. Each reports its own result, so partial success shows as partial rather than as a failed sync.
03 · REASON AGAINST EVERY DAYThe client dashboard records which days received a rate and which did not, with a reason. A stale rate explains itself.

FIG. 02 — ONE RATE, MANY CONNECTIONS, AND WHAT THE OPERATOR SEES WHEN ONE OF THEM DOES NOT TAKE IT.

Key decisions and tradeoffs

Three decisions that shaped the build, the alternative in each case, and what the choice cost.

Decision 01SCHEDULING

Four hourly attempts in the evening, rather than a retry loop

ALTERNATIVEfetch in the morning and retry on failure until a rate comes back.WHY NOTthe official rate is published in the afternoon Cambodia time and applies to the next business date. A morning fetch is asking for yesterday. The run starts at 18:00 and repeats hourly to 22:00, and the four attempts are the retry.COSTno rate by the end of the window is treated as "not released" rather than as a fault, which is correct at weekends and on public holidays but means a genuinely late publication is missed until the next day.
Decision 02SCOPE

No backfill

ALTERNATIVEcatch a connection up on the days it missed once it comes back.WHY NOTa client gets the rates their subscription was active for. Rates are dated facts, and quietly writing a run of historic ones into a live ledger is not a favour.COSTthe flow has no reconciliation stage at all. A connection that was down for three days never receives those three rates; the dashboard shows the gap and the reason, and that is the whole remedy.
Decision 03ALERTING

Credential failures email the client, write failures email me

WHYthe asymmetry follows who can fix it. Credentials are managed by the client in their own dashboard, so the alert has to reach them. A failed write is mine, and telling the client about it only transfers alarm.ALSOfailures are isolated. One bad connection does not affect others on the same client or anywhere else.COSTa client can hold a stale rate without being told directly, which is why the dashboard carries a full sync history with a reason against every day rather than a green tick.

Current status

Live and running daily on a subscription. The client does nothing in the flow. They touch RielSync only to resolve a payment issue or to add, update or remove a connection.

A stale rate is a normal state, not a fault. No rate is released at weekends or on public holidays, so the client system holds the previous day's rate as the latest known, and the dashboard says why.

Five systems are supported today, and others are possible after a check against their API.

Supported systems and sources

TargetWriteHistory
OdooRate and date, idempotentAccumulates
QuickBooks OnlineRate and date, idempotentAccumulates
Zoho BooksRate and date, idempotentAccumulates
SAP Business OneRate and date, idempotentAccumulates
SambaPOSSingle value field, overwrittenStamped per ticket

The rate is read from the National Bank of Cambodia public site, with the official rate API as a fallback. A rate that fails the plausibility threshold or the date check is not stored. It raises an email alert for me to look at instead.

Have a system that half works and nobody wants to touch?

A systems review puts the situation, the cost and the options in writing.