Customer story · Workforce ops
Replacing Deputy across eight franchise stores — and eight legal entities.
An 8-location shipping and print franchise. 32 active employees. A separate LLC per store, so “payroll” was never one payroll. Scheduling and time tracking were on Deputy; the part Deputy couldn't do — assembling all of it into the workbook their accountant actually pays from — was done by hand every week. This is what we built instead, including the parts that were wrong the first time.
01 — The starting point
The tool worked. The seams didn't.
Deputy did scheduling and punches fine. The problem was everything on either side of it. Eight stores meant eight separate legal entities, each with its own payroll subtotals, and one person can work at two of them in the same week. Timesheets came out of one system, pay rates lived in a spreadsheet, leave hours lived in someone's head, and every Friday a human retyped it all into a workbook with per-entity sections and hand-kept formulas.
Meanwhile the counter had its own stack of paper: a custom-box quote form, freight and auto-transport forms, a stamp inventory log, a returns carton sheet. None of that is workforce software's problem — except that it's the same tablet, the same employees, and the same owner asking why he has four systems and a clipboard.
So the brief wasn't “replace Deputy.” It was: one tablet at the counter, one phone app for employees, and one export that lands in the accountant's inbox looking like the file she already reconciles.
02 — Identity and the clock
A shared tablet needs to know who's standing at it
- PIN identity, not loginsEvery counter transaction starts with a 4-digit PIN. PINs are hashed, employees set their own from their phone, and weak or duplicate codes are refused. No shared password taped under the till.
- One-time device claim per storeA tablet claims itself once with a per-store code and holds a long-lived device token after that. The owner can revoke a device from the Stores tab when a tablet walks.
- Break pairing, not raw punchesIn, break start, break end, out — paired into worked hours with breaks deducted, flagged when a day is missing its out punch. An open shift today reads “in now”; only a prior day's open punch counts as an exception.
- Manager fixes are audited, kiosk punches are immutableA manager can add or correct the four punches for a day from one drawer, in shift order. Those rows are tagged as manager-entered and can be deleted; a punch made at the kiosk cannot be, by anyone.
- Punches belong to the store they happened atSomeone who works two stores gets two timesheet rows, and the weekly export splits by punch location — because two LLCs are paying for two different sets of hours.
The time-clock detail that mattered most wasn't a feature, it was a timezone. Manager-entered times were briefly stored as typed rather than converted from Eastern to UTC, which shifts a corrected punch by four or five hours. It was caught before it touched real payroll — the franchise had no manager punches to backfill — and every manual time now goes through one Eastern-to-UTC conversion with a day view that reads back exactly what's stored.
03 — Scheduling
Person per store, not person per company
The first scheduling build treated a week as one grid. That's wrong for this business: the unit of scheduling is a person at a store, and a manager thinks in one store at a time while the owner thinks in all eight. So the rota was rebuilt around a multi-select store picker — each selected store gets its own editable week grid with its own draft-and-publish lifecycle, its own copy-week, and a clipboard that pastes shifts across stores. Editing a published shift puts it back in draft, because a schedule change nobody was told about isn't a schedule.
Employees confirm shifts from their phone. A published shift shows a red dot until the person taps confirm, then green — so “did they see it?” stops being a phone call. Published scheduled hours also flow into the timesheet as a Scheduled row beside the Actual row, which is how a manager spots a no-show without doing arithmetic.
Two bugs here are worth telling because both were found by an owner, not a test. The top-of-page hours total once reported only the last store you'd added to the picker, because already-mounted store grids never re-ran their loader on a selection change. And someone who works two stores had their entire cross-store rota attached to the first row, so an unscheduled store's timesheet filled up with the other store's hours. Both were keying bugs — one map keyed by employee instead of employee-and-store — and both are now pinned by tests.
04 — Payroll
We didn't replace the workbook. We generated it.
The deliverable is a real .xlsx file, built server-side, that mirrors the workbook the owner was already maintaining: a section per legal entity, hourly rows carrying hours and overtime straight from punches, live SUM formulas so a hand-edit still totals correctly, blank leave columns for the ones they fill in by hand, and a trailing new-hires section. There's a layout switch, because the owner wanted person-major rather than entity-major once he saw both: a salary section then an hourly section, with mixed people appearing in each.
Leave hours — vacation, sick, holiday, bereavement, paternity/maternity, and a free-text “other” with a reason — are entered per employee per store per week and land in their own columns, with the reason appended to the comments cell. Any row whose pay changed that week, or that has leave hours or profile notes, is highlighted so the accountant's eye goes straight to it. Pay changes themselves are a ledger: every rate move is logged, and the owner gets a history chart.
The thing we got wrong, and the reason this section exists: “salaried” is per store, not per person. A long-tenured employee turned out to be salaried at three stores and hourly at a fourth, with a company-wide record that said hourly. A global flag is wrong. A per-employee flag is wrong. Salary is a property of the row that says this person, at this store — so the rule became: a payroll profile with a salary amount at that location means salary there, and the company-level pay kind only applies to someone with no salary profile anywhere. That single rule now drives the workbook, the timesheet's zeroed salary rows, and the schedule's salary-versus-hourly split, so the three can't disagree.
One more detail, which is the kind of thing nobody specs: the generated file initially rendered with enormous guessed row heights in macOS Quick Look, because the sheet shipped without a dimension declaration. An owner's first impression of a payroll file is the preview pane. It's fixed.
05 — The employee's phone
Everything HR sends, in one app with a PIN they set themselves
- Today's shift, and a confirm buttonHome screen is the shift card plus their stats. Unconfirmed published shifts show a red confirm button that turns into a green check.
- Chat that reaches roles, not just peopleThreads address an individual, a group, or a role — everyone, managers, owners. Group threads can be named. Notifications land in the portal bell and as web push on the phone.
- Documents in three completion modesDigital signature, wet ink, or upload. A wet-ink document prints with merge fields filled, gets signed on paper, and is scanned back — it shows on an “awaiting scan-back” list until it is. An upload document collects a file, like a certification card.
- Write-ups acknowledged from the phoneThe drawer mirrors their paper form — verbal, written, final, offense types, corrective plan, consequences — and the employee acknowledges by name and timestamp via a signing link instead of a countersigned photocopy.
- An ideas boxAnyone can submit one idea-thread; owners and GMs move it through reviewed, implemented, rewarded, or declined with a note. It exists because good counter ideas were dying in group texts.
- A help button on the kioskPIN plus a note posts into a single reused thread with the owners and pushes to their phones — the counter's escalation path when a customer is standing there.
Documents are categorized and hand-ordered, and an owner can notify every employee who hasn't signed an in-scope document with a seven-day signing link. Access itself is granular: managers and GMs are scoped to specific stores, individual areas can be revoked per person, and the scope fails closed — a manager with no stores assigned sees nothing until the owner assigns them. Related reading: staff management and the workforce ops solution.
06 — The counter
The real story of fit: we rebuilt their paper
Workforce software got them off Deputy. The counter tooling is why they stayed, and every piece of it started as a form on a clipboard.
- Custom box quoting, priced by dimension. Length plus width plus height times a rate, with padding added per dimension by wall type — standard, fragile, or custom — and a side-by-side comparison of double versus triple wall that the associate can flip between. Optional materials and service add-ons stack onto the box price as line items rather than replacing it. Photos of the item can be captured, the customer's name is required, a pricing disclaimer must be ticked, and a signature is taken on a fullscreen pad. Box dimensions stay hidden from the customer-facing screen until a shipping choice is made.
- Freight and auto-transport forms, field for field. Rebuilt from their paper originals, including per-item condition, whether the item is in the store (which makes photos a blocker rather than a suggestion), specific-date handling with the extra-charge warning, and the personal-belongings disclaimer verbatim. A box quote can hand off to a freight quote carrying the finished box dimensions and weights.
- Returns carton processing. Cartons are witnessed by two or three distinct PINs, tracking numbers and return codes are scanned or wedged in from a handheld, malformed codes are refused with the exact scanned text shown, and the export matches the format their returns partner expects — one carton row followed by one row per return code.
- Stamp inventory logs that match their workbook. Books and rolls are separate counters, opening and closing counts per day, and sold is derived as the difference — so the log is a full editable month grid where correcting one cell re-flows every downstream number. A closing count is refused outright if that day has no opening count, because a difference computed from nothing poisons every day after it. Pricing, cost, sales and margin columns are owner-only, and each day is valued at the price that applied on that day, so a later price change never restates history.
Quotes email the owners as one batched send and move through a pipeline — new, quoted, won or lost — with badge counts in the sidebar. All of it works from the tablet at the counter and from the portal on a laptop, using the same forms.
07 — Honest wrinkles
The two things that make this believable
The camera couldn't read the carrier's barcodes, and we blamed the wrong thing. A manager reported that at one store, tracking labels “just don't read.” We tightened validation, then loosened it, then added an override checkbox — all reasonable, all irrelevant. The label he sent us passed validation fine. The camera had simply never decoded it. Two real defects: the scanner component was restarting itself mid-decode because its effect depended on inline callbacks that changed identity on every render, and the video stream had no resolution constraint, so the browser handed it 480p. A long, dense carrier tracking barcode has too few pixels per bar at counter distance at that resolution — while the short return codes decoded fine, which is exactly the symptom we'd been told and hadn't understood. Fixed with a 1080p constraint, continuous focus where the device supports it, a guide rail on screen, and support for the handheld keyboard-wedge scanners a shipping counter already owns, because hardware beats a tablet camera every time. We also added distinct audio tones for accepted, captured, and refused scans — synthesized in the browser, no audio files to 404 on a kiosk — since a warning rendered behind a fullscreen scanner overlay is a warning nobody sees.
“Salaried” was per store all along. Covered above, and it deserves repeating as a category of lesson: the data model you'd design from the org chart is not the data model the payroll actually follows. We shipped a global flag, then an employee-level flag, and both were wrong in ways that only show up when a real person is salaried at three stores and hourly at a fourth. The fix was to let the payroll profile rows — which already existed, per employee per store — be the source of truth.
There have been others: revoking a permission stopped the API but left the tab in the sidebar, because navigation was built from role alone and never consulted per-user revokes. A stamp log grouped its days by UTC date, so any count taken after 7pm Eastern landed on the next day's line. A missing import in an always-mounted component rendered the whole portal blank for signed-in users — the build didn't catch it, because an undefined identifier is a runtime error, not a compile error. Each one is now a test, a lint gate, or an authenticated smoke check. That's the actual difference between software that runs a business and software that demos well.
08 — Questions
Straight answers.
Does this replace Deputy completely, or sit next to it?
Completely, for this franchise. Scheduling, the time clock, timesheets, and the payroll export all live in one place, and the roster was imported from their Deputy export — 32 active employees, with each store's Deputy code kept on the store record so the owner could still reconcile against old reports.
How does payroll work when one person is salaried at one store and hourly at another?
Salary is stored per employee per store, not per person. A payroll profile row at a location with a salary amount means that store pays them salary; a location without one pays them from their punches. The workbook and the timesheet both follow that rule, so an hourly store's real hours survive while the salaried store shows the literal 0 their accountant expects.
Why a real .xlsx instead of a CSV?
Because the owner already had a workbook that worked, with per-entity sections and subtotals. The export builds a genuine .xlsx with those sections and live SUM formulas so the file opens looking like the file they were already using — and rows that changed that week are highlighted so nobody has to diff it by eye.
What does something like this cost?
Workforce and operations work is quoted per site and headcount rather than published as a flat rate, because the variable is how much of your existing paperwork has to be rebuilt field-for-field. The retail platform price is published; ops builds are scoped on a call. See pricing.
09 — Next step
Bring the spreadsheet you hate.
If your week ends with someone retyping timesheets into a workbook, that's the demo. Book 20 minutes and bring the file — the interesting question is always what your paper looks like.
Workforce & ops pricing quoted per site and headcount · no contracts