0017. Shifts and Z-reports
- Status: Accepted
- Date: 2026-10-27
Context
Section titled “Context”At the end of the day a shop counts its cash drawer and prints a Z-report: what was sold, what tax was charged, how customers paid, and whether the drawer is over or short. A shop may have several devices, and a table opened before a shift change may be paid after it. Each sale has to be counted exactly once, without coordination between devices and without a server.
Decision
Section titled “Decision”-
A shift belongs to one device and its cash drawer. It opens with a float, records cash paid in and out with a reason, and closes with the cash counted. Each device numbers its own shifts 1, 2, 3…; the number is the Z-report number.
-
Shifts are events (
shift.opened,shift.cash_movement,shift.closed), projected like orders: pure, idempotent, and an event that cannot apply is kept as a conflict. -
Money is reported where it moved; a sale is reported where it closed. Payments, closes and refunds carry the
shiftIdof the device’s open shift. A Z-report counts:- sales (bill totals, SST per code and rate, service charge, discounts, voided lines): from the orders that closed in the shift;
- payments, refunds and cash rounding: from those taken in the shift.
An order opened in one shift and paid in the next is reported once, in the second.
-
The report is computed, never stored.
shiftReport()inpackages/corerebuilds it from the events, so a reprint, the hub and the back office all get the same numbers. -
Expected cash is the opening float plus cash payments (after rounding and change), minus cash refunds, plus cash in, minus cash out. The variance is counted minus expected.
-
Settings (ADR 0014):
- each device can require an open shift to take payments (on by default, so every sale lands in a Z-report; order-taking phones can turn it off);
- each outlet can choose a blind close (off by default), which hides the expected cash while counting and on X-reports, until the Z-report prints.
Consequences
Section titled “Consequences”- Payments taken with shifts switched off belong to no shift. They still count in daily sales reports (M7), but in no Z-report.
- Open orders do not block closing a shift; the close screen warns about them, and they count in whichever shift they are paid in.
shiftIdwas added to three existing payloads as an optional field without a schema version bump, which was safe before any release. Later payload changes need a new schema version.- The event store copies
payload.shiftIdinto an indexed column, so a report reads one shift’s events without scanning the log.
For AI tools: llms.txt ·llms-full.txtopenkedai.com