Every trading day, each restaurant's books should show the money taken and the money earned agreeing to the penny. On roughly one day in sixteen they did not. This is what was wrong, what it was costing, and what the fix is worth — measured by running the real job over ninety days of real trading.
Put plainly: on nine days out of ten that were wrong, the books now close. Of the 122 days still open, only 32 are worth more than ten cents — the rest are till rounding.
A day that does not balance cannot be signed off. Someone has to open it, compare it against the till, and work out which figure is missing — and until they do, that restaurant's period is not closed.
Across these ninety days that was 1,191 restaurant-days needing investigation — about thirteen every single day. After the fix it is 122 across the whole period, and only 32 of those are worth more than ten cents: roughly one day every third day that genuinely needs a human.
The second cost is subtler and worse. Where the two records of a duplicated restaurant traded figures between them, the same day could reconcile differently depending on when you looked. That is not a number anyone can defend in a review, and it is the reason R2 is a requirement rather than a nicety.
Four requirements. Everything below is judged against them.
Ten restaurants were set up twice in the system, as two separate customer records, and both were importing from the same till. Because the two records competed for the same payments and refunds, a refund could belong to one record in the morning and the other by the afternoon. Over time this had moved 3,387 refunds, 4,069 payouts and 2,628 cash-drawer shifts between records. Nineteen pairs have been affected historically; ten still share a till today.
It also meant a single card payment could be attached to both records' copies of an order. That is the R3 hazard: the nightly job removes orders the till reports as voided, and removing an order removes its payments — so cancelling one restaurant's order could quietly delete the other's payment. In a sample of 20,000 orders, 11,469 payments had two owners.
When a guest was refunded, the tip came back too. The summary kept crediting the original tip, so the day showed income the restaurant no longer had.
A catering fee or auto-gratuity is charged inside the customer's card payment, so it arrived as money taken — but nothing recorded it as money earned. Every order carrying one left the day short by exactly that amount. This was the single largest cause.
A handful of records receive refunds while none of their sales reach the system. Those days cannot balance, because half the transaction is not there. This one is deliberately left visible rather than fixed — see below.
The real nightly job was run over the same ninety days twice — once as it behaves today, once with the fixes on — against the same copy of live data. These are measured outcomes, not estimates.
| Stage | Days not balancing | Clean | Discrepancy |
|---|---|---|---|
| Books as they stand | 1,191 | 93.70% | $70,276.50 |
| Counting refunded tips correctly | 890 | 95.29% | $67,032.09 |
| Recording service charges as earned | 122 | 99.35% | $2,379.45 |
| Change | Days fixed | Days broken | Correct days altered | Money involved |
|---|---|---|---|---|
| Refunded tips | 301 | 0 | 0 | $4,027.21 |
| Service charges | 768 | 0 | 0 | $64,752.64 |
| Both together | 1,069 | 0 | 0 | $67,897.05 |
Of 18,900 restaurant-days, 17,827 came out identical — not just still balancing, but line for line the same figures. Every day that changed was already wrong. This was checked at the level of individual lines (category, amount to the cent, and which account it posts to), not just each day's bottom line, because a day can keep balancing while the figures inside it move.
Three days from the period. In each case the correction equals the discrepancy exactly — which is what you would expect if the fix is recording something real that was recorded nowhere.
| A catering fee — NGPA, 4 June | Before | After |
|---|---|---|
| Service charges | not recorded | 1,344.86 |
| Card payments | 16,914.26 | 16,914.26 |
| Day out by | 1,344.86 | 0.00 |
The guests paid the catering fee inside their card payments. Nothing in the books said the restaurant had earned it, so the day was short by exactly the fee.
| A refunded tip — NGFL, 19 May | Before | After |
|---|---|---|
| Tips | 238.46 | 70.42 |
| Card refunds | 168.04 | 168.04 |
| Day out by | −168.04 | 0.00 |
Guests were refunded $168.04, tips included. The books kept crediting the full original tip. The corrected figure matches the refund to the penny.
| One restaurant, two records — 11 May | Record A | Record B |
|---|---|---|
| Orders that day | 221 | 221 |
| Refunds that day | none | 2 — $2,232.29 |
| Day out by | 2,232.29 | 0.00 |
The same restaurant, twice in the system. Both records held the same 221 orders, but the refunds existed only once. The record without them showed the sales being returned and no refunds against them, and was out by exactly what its twin was holding. Across its whole history that record held 158,535 orders and five refunds.
The duplicated restaurants needed more than the arithmetic fixes, and this is the part with a real choice in it.
Orders were always recorded per customer record, so both copies of a restaurant built their own order history. Refunds and payouts were not — only one record ever held each of them. So one copy shows returns with no refunds against them, and the other looks fine.
Rather than invent duplicate records, the fix asks the till again. Once each record carries its own identity, replaying ninety days of history means each one imports its own copy and the two sets of books converge on their own — no code inventing figures that someone then has to trust.
The alternative was to retire one record of each pair. That was measured too, and it is worse: it leaves 279 days and $7,790.54 open, and it forces a decision about whose trading history to abandon. Replaying instead leaves 122 days and $2,379.45, and no record is lost.
The refund import asked the till for a restaurant's refunds and read only the first page of the answer. The till sends a hundred at a time, so any location with more than a hundred refunds silently returned a hundred — and the response looked complete. This was quietly truncating the nightly refund import for every busy location, not only the duplicated ones. Now fixed.
| Remaining discrepancy | Days | Amount |
|---|---|---|
| Real trading days — small, mostly rounding | 106 | $1,151.80 |
| Refunds for restaurants whose sales never arrive | 16 | $1,227.65 |
Of the 106 trading days, only three are on the duplicated restaurants, and all three are understood: two are a day where the till recorded $6,358.99 of card payments against $6,059.57 of orders — a genuine gap in the till's own figures, not a fault in the books — and one is an auto-gratuity booked as a service charge. The remaining 103 days come to $502.96 spread across 190 restaurants, mostly a few cents each.
That $502.96 has been identical every time this was measured, across several rebuilds with deliberately different preparation. It is the floor this work reaches.
The 16 remaining days belong to two restaurants — NG4S and NGPS — that receive refunds while none of their sales are imported. Their books cannot close until the sales arrive. These are deliberately left visibly out of balance (R4). The arithmetic could be made to agree in one line, and that change was written, measured and then removed: an unbalanced day is the only signal anyone gets that a restaurant's sales are missing. Closing it would remove the alarm and leave the fire.
| Step | Cost | Risk |
|---|---|---|
| Deploy the change | minutes | None on its own — the two accounting changes are off per restaurant by default |
| Re-label existing records | ~38 min | Runs with imports paused; newest month first, so stopping early is safe |
| Replay 90 days for the 10 duplicated restaurants | ~5.9 hrs | Overnight job; reads from the till only |
| Recompute the books, then switch on per restaurant | ~25 min | Reversible — switching a restaurant back off restores today's behaviour exactly |
The whole sequence is one overnight maintenance window. No customer record is retired and no trading history is discarded. The accounting changes go on a few restaurants at a time, and switching one back off is the rollback.