A number that is confidently wrong is worse than one that is missing.
A blank cell gets investigated. A total that is neatly formatted, carries no warning and happens to be nonsense gets pasted into a board pack. Two of the three controls on this page were written because we shipped exactly that, found it, and fixed the class rather than the instance — which is the part worth telling you about.
Our aging report summed dollars into shillings and printed the result with no currency on it.
It had been doing it for a while, and nothing looked wrong. The report ran, the columns lined up, the total was formatted to two decimal places like every other total in the product. It was only found when a workspace that trades in more than one currency looked closely at a figure that felt slightly high. The sweep that followed found the same shape on the dashboard, in the top-customers report and in credit-hold exposure — all of them sitting on one shared helper that assumed every amount was in the same currency and had two decimal places.
Why nothing caught it
Every workspace we had looked at closely traded in one currency, and in one currency the broken code and the correct code produce identical output. The bug was invisible from the inside and obvious from a market we had not sat in. That is the usual shape of this class of defect, and it is why the fix was a rule rather than a patch.
Why a converted total would not have fixed it
The tempting repair is to convert everything to one currency and add it up. But a converted total needs a decision about which rate and as at when; it moves between two viewings of the same closed period; and it cannot be reconciled against any ledger, because no ledger holds it. You would have replaced a wrong number with an unstable one.
What replaced it
One rule, applied everywhere a single money figure is shown: display the largest single-currency total, label it with its code, and disclose how many other currencies sit behind it. No rate is required, the figure is true at every viewing, and the reader knows there is more — which the old single number never told them.
Rule one
Never add unlike currencies. Never convert to make them like.
The rule has two halves and the second is the one people argue with, so it is worth being explicit about what it costs and what it buys. It costs you a single all-in number. It buys you a number that can be reconciled, that does not move, and that nobody has to caveat in a meeting.
Where a single figure has to be shown
A dashboard tile, a summary line, a card at the top of a report — anywhere the layout has room for one number. The largest single-currency slice leads, labelled, with a disclosure of the remainder alongside it.
Any surface that knows the workspace's base currency passes it through, and that currency then leads whenever it is present. This matters more than it sounds. Without a rate the only available ordering is by nominal amount, and nominal amounts are not comparable: 215,000 shillings sorts above 38,400 dollars while being worth a fraction of it. Leading with the currency the reader thinks in is both more meaningful and more stable, because it does not reshuffle as balances move.
Where a whole report is involved
A report is scoped to one currency rather than summarised across several. You choose the currency, the rows are the rows in that currency, and the totals at the bottom are real totals of real rows. Running it three times for three currencies gives you three reports you can each hand to somebody, which is more useful than one you have to explain.
The nominal ordering described opposite is only ever used to sequence the disclosed remainder. It is never used to claim one exposure is larger than another, because without a rate that claim cannot be made honestly.
If you want a consolidated figure across currencies at a rate you control, that is a legitimate thing to want and it is in the ledger below as work we would do — with the rate and its as-at date stored on the record, so the number can be reproduced later. What we will not do is produce it silently.
Rule two
“UGX 1,234.56” is not a rounding infelicity. It is an amount that cannot exist.
Money was stored at two decimal places in 104 columns and formatted with two decimal places everywhere, on an assumption nobody had ever written down: that every currency has two. Of the 137 currencies the product ships tax profiles for, 105 do. 32 do not, and they miss in both directions.
26
currencies at zero places
The larger half, and the one nobody had noticed. It covers 38 of the 188 country rows we ship. XOF alone is the eight UEMOA states and XAF the six CEMAC states — so this is most of francophone West and Central Africa, plus Japan, Korea, Vietnam, Indonesia, Iceland and Hungary.
ALL BIF CLP COP DJF GNF HUF IDR IQD ISK JPY KMF KRW LAK LBP MGA MMK PKR PYG RWF SOS UGX VND VUV XAF XOF
6
currencies at three places
The half that had been noticed, because it is the one that visibly loses money. A Tunisian tax total of 201.900 was being stored and displayed as 201.90 — a real difference, silently discarded on every line.
BHD JOD KWD LYD OMR TND
105
currencies at two places
The assumption, which was right three-quarters of the time. Being right three-quarters of the time is what let it live for as long as it did: any single-market workspace could run for years without meeting the other quarter.
The place count comes from the internationalisation data already bundled with the framework rather than a table we keep by hand, because a hand-kept list is a second copy of a fact somebody else maintains. One consequence worth knowing before you check our figures against a standards document: that data answers “how many digits is this currency written with in practice”, which for a few currencies with an inflated-away subunit is fewer than the formal standard still assigns. The sharpest case is the Iraqi dinar, formally three places and written with none.
Rule three
There are five ways to pay a supplier. A control on one of them is theatre.
This is the control that finance teams ask for and that is most often built wrongly: refuse to release payment when the goods received do not agree with the order. The wrong way to build it is a check in the screen where you noticed the problem. The other four routes stay open, and whoever needs to get paid finds one.
WebPay from the purchase order
APIProgrammatic release
ManualRecording a payment made elsewhere
M-PesaB2B and B2C transfers
PaystackCard and bank rails
one gate, one definition of “may this be paid”
Those five paths write into two different ledgers, which is the other half of why a per-screen check could never work. Every one of them now calls the same gate before releasing anything, so the answer to “can this be paid” is defined in one place and cannot differ by route.
What the gate decides
One of these blocks by default and one only warns, and we would defend the difference.
A control that is right in principle and wrong on the first day gets switched off on the second. These defaults are chosen so that a working business can install the product on a Monday without anything legitimate being refused.
Blocks
The received quantity disagrees with the order
This is the control being asked for. What arrived does not match what was ordered, so the invoice sitting behind it is not yet trustworthy and the payment does not go. It blocks by default, on every one of the five routes.
Warns
Nothing has been received yet
Deposits, prepayments and proforma terms are legitimate and common, and a product that refused them outright would break working businesses on the day it shipped. So this warns rather than blocks — and an organisation whose policy is that nothing is paid before receipt turns the setting on and it blocks.
Overrides
Somebody decides to pay past a failed match
Possible, and it should be. It requires a permission that is deliberately not the same one as approving a purchase order — approving an order is routine work, paying past a failed match is not, and collapsing the two would hand the second to everyone who legitimately has the first. It also requires a written reason, and it is stamped with who did it and when.
Read this one carefully
Closing a period here records that you closed it. It does not yet refuse a backdated entry.
There is a period close in the product. It holds the month, the status, who closed it and when, any notes they left, and — if the month was reopened — who reopened it and when. That is a genuine and useful control record, and for a great many workspaces the discipline of having to reopen a month in front of colleagues is the whole of what they needed.
What it is
A soft close. The state is recorded per month, per workspace, with a full history of closes and reopens that you can read back. Nothing about the record is guessed or derived; each row is an action somebody took.
Used as intended it does real work: it establishes when a month was declared final, gives you an audit answer to “when did we close January”, and makes a reopen a visible event rather than a quiet one.
What it is not, and we are saying so here rather than in a footnote
Nothing consults that record before writing a journal entry. A backdated entry into a closed month is not refused today — it is posted, and the close record sits alongside it saying the month was closed. If your control requirement is that the system must refuse, this does not meet it yet.
A hard lock is a well-understood piece of work on top of a record that already exists, and it is in the ledger below. We would rather tell you now than have you discover it in an audit.
This is the sort of thing that normally reaches a customer as “period close” on a feature list and is understood as a lock. If you are comparing products on this row, ask each of them the same question: does closing a period refuse a posting into it, or record that it was closed?
Before you quote this to a controller
The finance-control rows, answered exactly.
Accounting controls — what the arithmetic actually does
What AWRA OpsHub does today
One money figure is never a sum of unlike currencies — the largest single-currency total leads, labelled, with the remainder disclosed rather than absorbed.
No silent conversion anywhere, so a figure reads the same at every viewing and can be reconciled against a ledger that actually holds it.
Decimal places read from the currency, across all 137 shipped currencies — 26 at zero places, 6 at three, 105 at two.
A base currency leads when the surface knows one, so ordering is meaningful rather than an artefact of nominal size.
A single payment gate across all five payment routes and both payment ledgers, with one definition of whether an order may be paid.
A quantity discrepancy blocks payment by default; payment before receipt warns by default and blocks when you turn the setting on.
Override of the payment gate behind its own permission, separate from purchase-order approval, requiring a written reason and stamped with who and when.
A period close record holding the month, the status, who closed it, who reopened it, when, and their notes.
A hash-sealed audit trail underneath all of it, with no delete button in the interface.
More we can add to your workspace
A hard period lock that refuses a posting into a closed month, turning the close record we already keep into an enforced boundary.
A consolidated multi-currency total at a rate you control, with the rate and its as-at date stored on the record so the figure can be reproduced months later.
Automatic revaluation of open foreign-currency balances at period end, with the gain or loss posted for you.
Segregation-of-duties rules across finance actions, refusing a role that could both raise a payment and release it.
Approval routing by value, where an amount over a threshold you set has to collect a named second signature before the payment gate is even consulted.
A maker-checker step on journal entries, so an entry is drafted by one person and posted by another.
Recurring journal templates for the accruals and prepayments that are the same every month.
A close checklist that tracks which reconciliations are done before the month can be declared final.
Where we point you to a specialist
We will not decide whether a charge is recoverable, deductible or capital. That is an adviser's judgement about your circumstances, and a software vendor willing to make it in a sales meeting is selling you a liability rather than a feature.
We point you to your accountant on which basis of accounting your figures should be prepared on. The system records what happened and when; how that is presented in a statutory account is their determination.
We will not sign a statutory return on your behalf. Where we file to a revenue authority — Kenya's eTIMS is the live example — we transmit what you issued and record what came back, and the declaration remains yours.
We will not produce a single all-in figure across currencies without a rate you chose. Not because it is hard, but because the resulting number cannot be reconciled to anything and reads as though it can.
Everything in the middle column sits on top of controls that already exist — the close record, the payment gate, the currency layer. If one of those rows is the one your controller keeps asking about, tell us and we will scope it.
Adjacent
These controls decide whether a number is trustworthy. These pages are what you do with it.
Run inventory, procurement, assets, sales, and field work with approved AWRA guidance for setup, migration, integrations, security, pricing, and support.
We use necessary cookies for secure sessions. With your permission, we also use cookies and browser storage for preferences, analytics, and demo engagement. Privacy Policy
AWRA OpsHub
Cookie settings
Cookie Consent Manager
Necessary cookies stay on for login, CSRF protection, and security. You can choose the optional categories below.
Overview
General Information
AWRA uses cookies and browser storage to keep public pages secure, remember selected preferences, measure website performance, and manage demo engagement prompts.
You can choose whether functional and marketing engagement storage apply. Analytics measurement is always active in this AWRA setup.
These settings apply to AWRA public website experiences such as the homepage, feature pages, pricing calculator, blog, help center, and request-demo page. Authenticated dashboard and vendor portal sessions still rely on required security cookies.
Required
Always active
Functional
Optional
Analytics
Always active
Marketing
Optional
For more context on privacy handling, open the Privacy Policy.
Required Cookies
Required Cookies
Always Active
Required cookies and storage support basic website delivery, secure sessions, request protection, and remembering the consent choice itself.
These cannot be switched off from this manager because disabling them would break login/session behavior, form protection, or the ability to remember the privacy choice you save.
Cookie details
Session security: keeps secure server sessions working while browsing AWRA.
CSRF protection: helps verify form submissions and protect requests.
Consent record: stores the preference decision so the banner does not keep asking after a choice is saved.
Examples: Laravel session cookies, CSRF tokens, and the AWRA consent preference record.
Duration: session security can expire with the browser/session; saved consent can last longer so the same browser remembers the choice.
Functional Cookies
Functional Cookies
Functional storage improves the public website experience by remembering interface choices, helper states, dismissed notices, and short-lived interaction preferences.
Turning this off does not stop secure required cookies or analytics. It only limits optional convenience memory.
If disabled, AWRA may show some helper prompts again or forget non-essential display choices. Core public pages, contact forms, and request-demo forms still work.
Cookie details
UI preferences: remembered display choices and helper states where available.
Dismissed notices: session-level or preference-level memory for notices the visitor has closed.
Frequency helpers: optional browser storage that prevents repeated prompts when allowed.
Examples: localStorage or sessionStorage values for dismissed banners, guide/helper states, and lightweight public-page preferences.
Effect when off: AWRA avoids optional convenience memory unless it is also allowed through marketing and engagement preferences.
Analytics Cookies
Analytics Cookies
Always Active
Analytics helps AWRA understand public page performance, traffic patterns, and content usefulness so we can improve the marketing website.
This category does not by itself enable demo popups, exit-intent prompts, or advertising pixels. Those are controlled by Marketing & engagement.
In this AWRA setup, Google Analytics and Google Tag Manager measurement are treated as mandatory website measurement and remain active.
Cookie details
Google Analytics / GTM: measures aggregate traffic and page activity.
Performance insight: helps identify which public pages, docs, and demo paths visitors use.
Operational signal: supports website quality decisions without enabling demo popups by itself.
Examples: Google measurement identifiers such as GA/GTM tags and related browser identifiers set by Google scripts.
Use: page views, source/referrer trends, public content performance, and product education page effectiveness.
Marketing & engagement
Marketing & engagement
Marketing and engagement storage supports demo prompts, exit-intent prompts, campaign attribution, and future advertising pixels.
When disabled, AWRA will not auto-open demo or exit-intent popups. CTA buttons can still open a form because that is a direct visitor action.
When enabled, AWRA can remember that a visitor already saw, dismissed, or submitted a demo prompt so the same popup is not repeated aggressively.
Cookie details
Demo prompt memory: tracks whether an auto prompt or exit prompt was recently dismissed.
Demo submission memory: avoids asking again after a visitor submits a demo request.
Campaign context: keeps source page, referrer, and UTM context available for demo requests.
Examples: popup frequency caps, demo-submitted flags, engagement source fields, and UTM/referrer context.
Effect when off: auto demo prompts and exit-intent prompts stay blocked; normal navigation and manually clicked CTA buttons still work.