The Total That Added Dollars To Shillings
For about a year one of our reports added US dollars to Kenyan shillings and printed the result with no currency label on it. The repair was easy. Deciding what a single honest figure even looks like was not.
The AR/AP Aging report had a total row. The total row worked the way total rows do: it took the outstanding amount from every invoice and added them up. Every invoice also carried the currency it was issued in, and the total row did not read that column. So a business with receivables in three currencies got one number that was the arithmetic sum of three incompatible quantities, printed without so much as a currency code beside it to indicate what it was supposed to be.
Nobody reported it. That is the part worth sitting with. A wrong total in a report is not like a crash — it renders, it is plausibly sized, it goes into a board pack, and it is only ever caught by someone who already knows what the right answer is and was checking.
The shape of the defect, made searchable
A money column summed across rows while a currency column sits unread on the same table. Once we wrote it down that way we could look for it everywhere, and that turned one bug into a survey: twenty-two tables carry a currency column, and there are seventy-five money aggregations across twenty-five controllers and services.
Most of them were fine, and why that is not reassuring
The majority of those seventy-five aggregations are safe, and safe by accident rather than by design: their table is single-currency by construction. Purchase orders, for instance, have no currency column at all, so nothing there can be mixed. The sum is right because the alternative was never possible.
That is a fragile kind of correctness. It holds only until somebody adds the column — at which point every aggregation over that table silently becomes wrong, with no test failing and no error raised, because the code was never wrong about currency so much as innocent of it. The exposure concentrates on one table above all: customer invoices, where a currency is written on every single row and read by very little of what adds those rows up.
So the honest status is not "fixed". It is that the reports and dashboard surfaces where a mixed total was actually being printed have been repaired and now go through one shared helper, and that the pattern has a name and a search, and that the remaining work is known rather than discovered. Anybody using our reporting against multi-currency receivables should read the next section as a description of the convention, not as a guarantee that no unrepaired instance exists.
What a single honest figure looks like
The interesting problem is not detecting the fault, it is deciding what to print instead. A report footer has room for one number. The data has three. There are only a few available answers and most of them are worse than they look.
Four ways to print one figure over three currencies
Not converting is the deliberate part and it is the one that gets argued about, so it is worth defending. A converted total looks the most useful of the four and is the least stable. It requires a decision about which rate and as at when; run the same closed period twice a month apart and the figure moves, which means it can never be tied back to anything; and no ledger anywhere contains it. A number that changes when nothing changed is not a total, it is an estimate wearing a total's clothes.
The weakness in our own answer
Here is the part a changelog would leave out. "Largest" sounds objective and is not. Without a conversion rate — and we have just spent a section explaining why we refuse to use one — the only ordering available is by nominal amount, and nominal amounts across currencies are not comparable at all.
Where nominal ordering gets it backwards
The mitigation is that any surface with a base currency passes it as the preferred one, and the preferred currency then leads whenever it is present. That is better on two counts: it is the currency the reader thinks in, and it is stable — the headline does not reshuffle as balances move. Nominal ordering survives only as the fallback for sequencing the disclosed remainder, and is never used to assert that one exposure is bigger than another.
It is still a real limitation and it is published here rather than buried in a code comment because a reader in a multi-currency market deserves to know which figure on the screen is a fact and which is a convention.
The rule that follows from all of this
One figure means the largest single-currency slice with its code and a disclosure of what else exists. A whole report means scoping the report to one currency. Those are different answers to different questions, and the mistake is using the first where the second was needed — a footer is not a substitute for a filter.
The question to ask anybody, including us
This is an unusually easy class of defect to test for and an unusually hard one to notice by accident, which is a bad combination — it means it survives indefinitely in products nobody has specifically probed.
- Raise two invoices in two currencies and open the aging report. Then look at the total row. This takes four minutes and it is the entire test. If the footer shows one unlabelled number, you have found it.
- Ask what rate a converted total used, and as at when. If a vendor converts, that question has a correct answer and they should have it ready. "The current rate" is not an answer for a closed period.
- Run the same closed month twice, a few weeks apart. A total for a period that has ended should be identical both times. If it moved, a rate is baked into it.
- Ask which of their reports are currency-aware and which are merely currency-innocent. The distinction is whether the table can hold mixed currencies at all. Innocent is fine until the column arrives.
Ours: the aging report footer printed one unlabelled mixed number until August 2026 and now shows a labelled slice with a disclosure; we do not convert, so there is no rate to name; closed periods do not move between viewings, which is a direct consequence of that refusal; and the currency-aware set is the reports and dashboard surfaces routed through the shared helper, with customer invoices named above as where the remaining exposure sits.
The reason to publish the survey figure rather than just the fix is that seventy-five aggregations is a number that invites a follow-up question, and we would rather be asked it than have it discovered. A vendor telling you their multi-currency handling is complete is telling you they have not counted.
Two related arguments sit next to this one. Statutory Record Retention And As-Of Reporting is the other way a report can be confidently wrong — right arithmetic over the wrong moment, rather than wrong arithmetic over the right one. And the same discipline applied to the underlying figures rather than the footer is what the reporting and BI tooling is built on: a dataset that knows which currency each row is in is the precondition for every total above it being honest.