cash out: shift reconciliation & tip pool app
A case study in building the tool I needed at close
The short version
Closing a bar shift means reconciling the till and splitting the tip pool while everyone is tired and ready to leave. At Bar Mini, both jobs lived on paper and in two separate Google Sheets. Cash, card totals, a kitchen tip-out, and an hourly split across the staff were all calculated by hand, with no guardrail if a number was mistyped.
Cash Out turns that paperwork into a live shift form. It totals sales and payments as numbers are entered, flags a variance the moment cash counted stops matching what sales say it should be, and calculates the tip pool instantly: 3% of gross to the kitchen, then the rest split by hours worked.
I designed and built it for the real conditions of the job: a shared iPad, late nights, unreliable wifi, and a workflow where the next bartender should never inherit the last shift's numbers.
The problem
Closing a bar shift means two jobs happen at once. You reconcile the till, cash and card totals against what actually rang in on sales, and you figure out how the tip pool splits across whoever worked. At Bar Mini both of those lived on paper and in two separate Google Sheets, filled in by hand at the end of a double when nobody wants to be doing arithmetic.
A kitchen tip-out carve-out, a per-hour split across three or four staff, and a variance check between cash counted and cash expected were all done on a phone calculator or eyeballed. I designed and built the fix myself because the problem was real, and because I wanted to take a project from a rough plan through an actual build that people use on a real shift.
Starting point
I had a Figma file laying out the screens, and two Google Sheets that already encoded the real rules: which sales categories mattered, how HST factored into gross, what counted as a card payment versus cash, and the exact tip math the bar already ran on. The build was not inventing a system. It was translating paperwork that already worked into something live.
Colors, spacing, and the title typeface came straight out of the Figma file rather than an approximation of it.
Designing for how the job actually works
The reconciliation math needed to happen the moment someone typed a number, not after they hit submit. The app totals sales and payments live and flags the variance as soon as cash counted stops matching what the sales say it should be.
The tip split follows the bar's actual policy: pull 3% of gross off the top for the kitchen, then divide what's left across staff by hours worked. That is two calculations a person used to do by hand, now computed the instant hours get entered.
Staff names get locked to whoever is actually on the schedule, with one open slot for a guest bartender. On a shared iPad passed between three people a night, one mistyped name can split that person's tips out of the pool entirely because the sheet no longer recognizes them. Locking the regular names down closed that hole without losing flexibility for a fill-in.
The original plan had an AM/PM toggle, carried over from the Figma mock. Once I built it, it was clear that field mapped to nothing: Bar Mini cashes out once a night, full stop. I pulled the toggle rather than keep a control on screen that did not correspond to how the bar runs. The form also clears itself after a successful submit, so the next bartender never inherits last night's numbers.
Iterating against real feedback
The settings screen where you paste the Google Sheets connection URL looked like it worked in every test, right up until it did not save anything on the actual iPad. The button was closing the dialog, but the save handler was attached to the wrong element. It only showed up once someone tried it on the real device.
A worse one: the locked employee names rendered in white text on a cream background. A broader style rule elsewhere in the stylesheet was overriding the label style, and it became obvious only on the actual screen. I also rebuilt the tip pool write logic after a guessed layout put submissions at the bottom of the Tip Sheet instead of in its fixed formatted block.
The offline cache nearly undid a fix before it shipped. It was pinned to a version name that never changed, so installed devices kept serving the old broken version. I now bump that version number with every release.
Building it
The backend is a Google Apps Script Web App instead of a database. It writes straight into the same two Google Sheets the bar already uses, so nobody has to learn a new system. The write logic checks each target cell first and skips it if a formula is already there, which lets the app add numbers without overwriting the sheet's own math.
It is packaged as a Progressive Web App, so it installs to the iPad's home screen and keeps working through a dead wifi patch, queuing a failed submit locally and retrying automatically when the connection returns. It is deployed through GitHub to Vercel, so a fix goes from my end to the bar's iPad in about a minute.
What this project is, for me
The parts that mattered were not the syntax. They were deciding what should be locked down versus left open on a shared device, when to compute something automatically instead of trusting a person to do the math at 2am, and when to say no to a feature, like direct receipt printing, because the actual infrastructure did not support it cleanly. Those are product decisions before they are code. I just happen to also be the one who builds and ships the result.