Open a tip pool for a period, define percentage splits per role (60% servers / 30% kitchen / 10% bar), and the platform distributes the total across staff in proportion to their clocked hours in each role. One transaction, one audit row, zero spreadsheets.
A tip pool is a single distribution event for a defined period — typically a shift, a day, or a week. You open the pool with a periodStart and periodEnd; the platform sums every COMPLETED BillPayment.tipAmount in that window; you bind a TipDistributionRule (configurable percentage per role, summing to 100); and on Distribute the platform writes per-staff TipPoolDistribution rows in proportion to each staff member's clocked hours in their role during the period. Pool moves OPEN → DISTRIBUTED → LOCKED.
The whole transaction is atomic. The total, the rule, the per-staff splits, and the StaffAuditLog row all land in a single $transaction — there's no half-distributed state, no partial rows, no 'tip pool is in a weird state' bugs. After distribution, lock the pool to prevent further mutation. The data the venue exports for payroll matches what staff see on their own My Wages page, by design.
The pool moves OPEN → DISTRIBUTED in a single $transaction with the per-staff rows. If anything fails, nothing applies. No half-distributed pools, no orphan rows, no manual cleanups.
Two servers worked the same Friday — one for 4 hours, one for 8. Headcount-based splits give them the same share, which feels unfair. Hours-in-role splits give the 8-hour server twice the share. The math is transparent; staff can verify their own row.
Percentage splits per role (SERVER, KITCHEN, BAR, HOST) are venue-configurable. Sum across roles must equal 100 — enforced at the API layer. We don't pre-load a 'default' split because tip-pool ethics differ by country and venue type.
The pool total is a sum over completed BillPayments in the period — the same canonical source the operator already uses for payment reports. Tips get to staff exactly the way the customer paid them; no double-counting, no missed channels.
Open Staff → Tip Pool → Rules. Create a rule with a name (e.g. 'Standard 60/30/10'), an applies-to filter (ALL / DINE_IN / DELIVERY), and the per-role percentages. Sum across rows must equal 100; the API rejects 99 or 101.
Open Staff → Tip Pool, click + Open period. Pick periodStart, periodEnd, and the rule to apply. The pool starts in OPEN status with totalCents = 0 — the actual sum gets computed at distribution time.
Click Distribute on the pool card. The platform sums BillPayment.tipAmount for COMPLETED payments in the window, aggregates clocked minutes per staff per role from ClockEvents, computes per-row percentage shares, and writes everything atomically. Pool moves to DISTRIBUTED.
Once you've reviewed the distribution, click Lock — the pool moves to LOCKED and no further mutations are allowed. Export the per-staff splits as CSV for your payroll provider; staff see their own share on /admin/profile/wages.
The full distribution math + the per-staff row inserts + the StaffAuditLog entry land in one Prisma transaction. Either everything commits or nothing does — there's no 'half-distributed' state to clean up manually.
Inside each role bucket (e.g. SERVER: £498 from a £830 pool at 60%), the share splits proportionally to each staff member's clocked minutes in that role during the period. Last-row residual handles cents rounding so the sum equals the bucket exactly.
Rules are first-class entities — name them, mark one default, archive old ones. The applies-to filter (ALL / DINE_IN / DELIVERY) lets you run different splits for dine-in nights vs delivery shifts.
Once you're confident the distribution is correct, lock the pool. The row's status becomes LOCKED and no further mutations are allowed. Past pools that have been paid out should always be locked — it's the audit cleanest signal.
Friday night ends with £830 in tip totals. Open a TipPool for the shift period, distribute against the 60/30/10 rule. Five servers split £498 by hours, three kitchen by £249, one bar by £83. CSV export goes to the accountant Monday morning.
Some venues distribute weekly across all sources (dine-in + delivery + pickup tips). Open a 7-day pool with a rule whose appliesToSource = ALL. Distribute Sunday night; staff see their share on Monday in My Wages.
Delivery drivers get 100% of delivery tips (rule A); dine-in servers/kitchen split dine-in tips 60/40 (rule B). Open two pools per period, one with each rule, and the system distributes both correctly without bleeding sources.
A 200-person catering event includes an automatic 18% service charge that flows into the tip totals. Open a one-off pool for that event, bind your standard rule, distribute. The catering team gets their share alongside the regular service team.
Open Staff → Tip Pool, see every distributed pool from the month with totals, rule names, and per-staff splits. Lock the ones you've reviewed; investigate the ones with surprising splits before locking.
Staff don't have to ask 'what's my share?'. They open My Wages on their phone and see every TipPoolDistribution row attributed to them. The math is transparent; the percent share is shown; trust is built.
The tip-pool problem isn't 'how do we calculate the split?' — it's 'how do we make the split believable?'. Black-box payroll software that hands a server a number with no breakdown breeds suspicion ('how do you know it's right?'). Ordering.Tools' tip pool answers that with transparency: every distribution row carries the staff member's hours, their role, the percentage share, and the resulting cents. Staff see the math; managers don't have to defend it; the audit log makes any past distribution reproducible.
Headcount-based tip splits are the simplest mental model — 'three servers, split equally'. But they punish the staff member who covered an extra hour and reward the one who left at 21:00 sharp. Hours-in-role splits restore proportional fairness: clocked-in time is the canonical measure of presence, and the system already has it from ClockEvents. The implementation is one SQL aggregation per pool — no hand-counting, no spreadsheets.
If a manager creates a tip rule with rows summing to 99% (forgot one role), the next distribution silently leaves 1% of the pool unallocated — confusing for staff, embarrassing for the manager. Ordering.Tools enforces sum-to-100 at three layers: the rule-creation API rejects 99 with a 400; the distribution API re-checks before computing splits; the unit tests assert it. The result is no silent rounding errors, no orphan pennies, no 'where did £8 go?' Slack messages.
Where do the tips actually come from? In Ordering.Tools every customer payment writes a BillPayment row with tipAmount as a separate decimal column. The tip pool aggregates COMPLETED BillPayments only — refunded payments are excluded automatically. PENDING and AUTHORIZED states don't count until they complete. This means a customer who tipped on a card that later bounced doesn't enter the pool, and a refunded order doesn't either. Tips that get to staff are tips the venue actually received — the chain of custody is one query deep and fully reproducible.
Define the split, open a pool, hit Distribute. Staff see their share on their phone. Premium feature, included in Staff Management.