Case notes
Case note: ending the monthly empties-balance argument
Give every outlet its own running container ledger inside the ordering app, and the monthly empties argument mostly disappears. Each delivery and pickup writes a signed, timestamped movement to a shared event log; the outlet's balance is simply the sum of that log, visible to them in real time. A disputed number stops being one person's memory against another's and becomes a query against events both sides can see.
Why does the empties balance become a monthly argument?
Returnable containers are money, not packaging. Deposits typically run 15-20 percent of a keg's replacement cost (as of February 2026), which means a few hundred unaccounted kegs is a real receivable, not a rounding error. The Brewers Association puts the cost of keg loss at $0.46 to $1.37 per barrel of annual keg production, and estimates a total direct capital charge to craft brewers of $5.3 million to $15.8 million annually. Somebody carries that cost, and the somebody is decided by whoever keeps the better records.
The trouble is how the balance is kept. In the incumbent model, reconciliation happens once a month off an activity report reconstructed after the fact, the pattern documented in route-accounting suites such as Encompass Technologies' distributor finance module. The brewer's warehouse holds one number, the outlet remembers another, and the driver's paper ticket is somewhere in a cab. By the time the statement lands, nobody can reconstruct which of thirty deliveries the two missing kegs belonged to. And because the deposit rarely covers the full replacement cost, wholesalers are famously reluctant to bill a retailer for a lost keg and sour the relationship, so the balance quietly drifts, and next month the same argument runs again.
What did we actually change?
One thing: we stopped storing the balance and started deriving it. In the platform we run for the client, every physical container event, a full keg delivered, an empty collected, a damaged shell written off, emits a signed movement onto the same event bus that already carries orders and stock. Each movement records the container type, quantity, direction, the outlet, the driver's device, and the timestamp, and it is signed at the point of capture so it can be replayed but not quietly edited later.
The outlet sees the resulting balance in its own app, next to its orders, updated as the truck pulls away. Deposits move with the containers: each movement produces the matching deposit posting, which lands in the client's unchanged SAP financial core exactly as our other value events do, the same approach we use to replace the commerce layer without touching SAP FI. And because deposit rules and container types differ by country, the whole scheme is data rather than code, in the spirit of launching a new market as configuration.
| Dimension | Monthly activity report | Per-outlet event ledger |
|---|---|---|
| Source of truth | A statement reconstructed after the fact | The signed movement log |
| When the balance is known | Once a month, in arrears | Continuously, in the app |
| How disputes are handled | Memory against memory | A query against shared events |
| How errors are corrected | A manual adjustment | A reversing movement, audited |
How do you fix a disputed empties balance?
The fix is to make the balance a consequence of an event log rather than a figure a person maintains. Concretely, in order:
- Model the container, not the line item. Give each returnable type an asset identity with a location and an owner, so a movement can only ever shift it between real places.
- Sign every movement at capture. Delivery, pickup, swap, write-off, each records who, where, when, and on which device, at the moment it happens. Make it offline-tolerant and idempotent so a re-sync never double-counts.
- Derive the balance, never store it. The outlet balance is the fold of its movements. There is no editable balance field left to argue about.
- Show the outlet its own ledger. Put the running balance and the recent movements in the outlet's app, so a disagreement surfaces the day it happens, not on a statement six weeks later.
- Correct with a reversing event, never an edit. A miscount is resolved by posting a compensating movement with a reason; the original stays visible and the trail stays intact.
- Post deposits to finance as containers move. Each movement emits its deposit delta to SAP, so the container ledger and the receivable can never drift apart.
What did the numbers do, and what did not go smoothly?
We report these as our own operational observations, not as claims about the client's business. Across the outlets on the ledger, empties disputes measured per 1,000 deliveries fell from low double digits into the low single digits over the first two quarters, and the disputes that remained resolved in minutes, a lookup, instead of the days a paper trace used to take. Because the container ledger is just more events on a bus the team already watches, it added no new operational surface; it rides the single on-call rota for the whole stack like everything else.
What did not go smoothly was the opening balances. We seeded each outlet's starting count from the incumbent monthly reports, and those reports turned out to be exactly as unreliable as the problem we were there to fix. For a meaningful minority of outlets the seeded number was wrong on day one, which produced an early wave of "disputes" that were really just our own bad starting data. We had to run a manual reconciliation sweep, agree a corrected opening balance per outlet, and post it as a signed adjustment before the derived balances meant anything at all. An honest ledger inherits whatever dishonesty you migrate into it.
The interesting part is what a trustworthy container ledger makes possible next. Once every shell's movements are signed and queryable, deposit terms can flex per outlet without a spreadsheet, dwell-time alerts can flag kegs sitting too long before they turn into losses, and the same log can feed a returns forecast the brewery can actually plan against. The argument was never really about two missing kegs; it was about the absence of a shared memory. Once both sides have one, the balance stops being a monthly negotiation and starts being infrastructure.
Frequently asked questions
How is the empties balance calculated if it isn't stored anywhere?
It is derived. The outlet balance is the running fold of every signed container movement, deliveries, pickups, swaps and write-offs, on the shared event bus. There is no editable balance field, so there is nothing to disagree about. To see how a number arose, you replay the log rather than trust a remembered figure.
Does this replace our SAP finance system?
No. The container ledger lives in the commerce layer; the SAP financial core stays in place. Each movement emits its deposit posting to SAP as a value event, so the returnables ledger and the deposit receivable stay in step without duplicating the books. It is coexistence, not replacement.
What happens when a driver scans a pickup that the outlet disputes?
The disputed movement is already in the log with its device, location and timestamp, visible to both sides. You do not argue from memory; you look. If it is genuinely wrong, you post a reversing movement with a reason: the original stays visible and the correction is auditable. Most disputes close the same day.
Stuck with exactly this?
BrewOS builds the full route-to-market stack for global brewers — order capture, stock, loyalty, returnables, delivery and analytics on one event bus, run by a team of ~20 engineers. Bring us the feature that’s been stuck the longest and we’ll show you how we’d ship it in days.
Book a 30-minute walkthrough