Everything below is a real defect we found and fixed in production on a multi-store franchise, not a hypothetical. They're not the things vendors demo and they're not on anybody's comparison chart, but every one is a money bug — it changes a number a person gets paid or a legal entity gets charged, silently, with nothing on screen looking broken.
The honest framing: these are not features to buy. They are correctness properties any multi-location system has to get right, and most systems get most of them right most of the time. The value of knowing them is that you can go test your current system in an hour.
Why four or five stores is the threshold
Under about four locations, three things happen to be true, and the software relies on them by accident:
- Almost nobody works two stores, so "which store do these hours belong to?" has an obvious answer.
- An owner can eyeball the whole week on one screen, so a wrong number gets caught before payroll.
- The stores are usually one legal entity with one pay convention.
Past four or five, all three break at once — and franchise groups routinely hold stores in separate LLCs, so the store an hour is filed against determines which bank account pays for it. That's the moment "whose store was that shift?" stops being a display question and becomes an accounting one.
Bug 1: hours land on the wrong store's timesheet
The shape of it: a person is on the roster at store A (their home store) and is scheduled to work shifts at store B this week. Open store A's timesheet and their entire cross-store rota is sitting there — store B's 7.5 and 8.5 hour shifts showing as store A's hours. Open store B and the person isn't listed at all.
The cause is almost always the same: internally, the scheduled hours are looked up by person when they need to be looked up by person and store. One key too few. Everything a person is scheduled anywhere gets attached to the first row that mentions them, and that first row is their home store — because "home store" is the field the roster is built from.
Why it's a money bug, not cosmetic. Three consequences stack:
- The wrong entity pays. If stores sit in separate LLCs, store A's payroll subtotal is inflated by hours store B consumed. That's a real inter-company misallocation that your bookkeeper either untangles later or doesn't notice.
- Double counting risk. If the hours also correctly appear at store B, you've paid for the same 16 hours twice. If they only appear at A, the person still gets paid — so nothing looks wrong from their side, which is why it survives.
- The approver can't do their job. Store A's manager sees hours for a person who wasn't in the building. Either they rubber-stamp it — and the control is now fake — or they escalate every week.
The fix is structural: a punch's store, and a scheduled shift's store, must be recorded on the punch and the shift — never inferred from the person. Our scheduling grid now builds one section per store with the person appearing in each store they're scheduled, and timesheets split rows per punch location.
Bug 2: week totals silently report only the last store you selected
This one is nastier because the number it shows is plausible. Select four stores in a multi-store week view, and the totals bar reports hours for whichever store you picked most recently — or zero, if that store has no shifts that week.
The mechanism: each store's grid loads its own data, and it was already loaded, so a selection change that wipes and re-requests only makes the newly-added store report back. The already-mounted stores never re-run. You get a total that is arithmetically correct for a subset nobody asked for.
In the owner's words: "it's pulling from the last store selected" — zero hours across a week where thirteen shifts sat visibly on the grid below. The fix was keying each store's data by store and week so nothing stale can leak, never wiping on a selection change, and printing "N of M stores counted" next to the total.
That last part is the transferable lesson: an aggregate across a variable set must state how many members it summed. A bare "86.5 hours" cannot be checked. "86.5 hours · 4 of 4 stores counted" can. Any total on any screen you use to make a payroll decision should tell you what it included, and if yours doesn't, treat it as unverified.
Why it's a money bug. Labour percentage is the number multi-site operators manage the week with. A total silently missing three stores reads as "we're running lean" and you don't send anyone home. Or it reads as zero and you assume the schedule didn't publish. Both lead to a real staffing decision made on a fake number.
Running more than four locations? If you want to see these four checks run against your own store list and roster, book a demo — bring last week's schedule and we'll reconcile it live.
Bug 3: salaried is a property of the person AND the store
This is the one nobody predicts, and I'll admit we got it wrong twice before we got it right.
The naive model is a flag on the employee: salaried or hourly. That's what most systems do, and it's wrong in a franchise group, because the same person can be salaried at one location and hourly at another — a manager on salary at the two stores they run, picking up hourly cover at a third. Not an edge case: we found two such people in one roster, one salaried at three stores and hourly at a fourth.
Read the flag on the employee record and the person shows as hourly everywhere, so their salaried stores generate hourly line items. Go the other way — "salaried anywhere ⇒ salaried everywhere" — and the real punches at their hourly store get zeroed, so they're not paid for cover shifts they actually worked. Both are wrong, in opposite directions.
The correct model is a per-(person, store) record. Salary belongs to the specific placement. The rule we settled on: if there's a salary record for that person at that store, they're salaried there; only if they have no salary record anywhere at all do you fall back to the flag on the person.
| Person / store | Store 1 | Store 2 | Store 3 |
|---|---|---|---|
| Manager A | Salary | Salary | Hourly (cover) |
| Manager B | — | Salary | Salary |
| Associate C | Hourly | Hourly | — |
Why it's a money bug. Get it wrong one way and a salaried person's week generates hourly line items on top of their salary — you pay twice for the same time. Get it wrong the other way and their genuine cover shifts at the hourly store get zeroed, so you underpay someone who showed up. Also, downstream: salary must appear only under the entity that actually carries it, or one LLC's subtotal absorbs another's payroll. The weekly workbook we build for the bookkeeper follows this rule exactly — per-store sections, salary rows only where the placement exists. It's a workbook a human checks and files; it isn't a payroll processor and it never moves money.
Bug 4: cross-store shift copying
The everyday operation at five-plus stores: the week's pattern is nearly identical to last week's, and half the cover shifts are the same person at the same three sites. So the two features that actually save an hour every Monday are copy-week and paste-a-shift-somewhere-else.
Both are quietly dangerous, in specific ways:
- Copy-week has to de-duplicate. Press it twice — or press it after someone already built half the week — and you've got two overlapping shifts on one person, which means double scheduled hours and an approver who can't tell which one is real.
- Dragging a shift across stores must be a copy, not a move. Moving is right within a store (a shift changed day); across stores, the intent is nearly always "they're also covering here." Getting this backwards silently un-staffs the original store, and you find out when nobody opens.
- Editing a published shift has to un-publish it. If a change leaves the shift in the published state, the person's phone still shows the old time. The safe behaviour is to return it to draft so a human has to publish again — mildly annoying, and much better than someone arriving three hours late in good faith.
- A shift copied across stores must carry the new store, not the old one. This is Bug 1 again, arriving through a different door.
We ended up with a multi-select store picker (each selected store gets its own editable grid), a clipboard that pastes across stores, and hold-a-modifier-to-copy on drag. The point isn't that our arrangement is the only right one — it's that if your system can't express "the same person, at three stores, this week" on one screen, your managers will do it in a spreadsheet — and the spreadsheet is where hours go wrong.
How to test your own system this week
An hour of work, no vendor required:
- Pick a person who works two stores. Schedule them 8 hours at store B only. Now open store A's timesheet. If anything at all shows for them, you have Bug 1.
- Select several stores in whatever week view you have. Add and remove stores from the selection and watch the total. Add them in a different order and compare. If the number changes for the same selection, you have Bug 2 — and check whether the total tells you how many stores it counted.
- Find someone salaried at one site and hourly at another (or create a test person that shape). Check that their hourly store's real punches survive and their salaried store doesn't generate hourly lines.
- Copy a week twice. Then count the shifts. Then drag one shift to another store and check the original store still has its coverage.
- Edit a published shift and check what the employee's phone shows.
If your current system passes all five, genuinely: don't switch for this. Plenty of tools handle multi-site correctly — see how we line up against Homebase for the feature-by-feature version. Our claim is narrower: these are the properties worth checking before you sign anything, because they're invisible in a demo and expensive in production. They all live in our workforce and store ops product because a real franchise hit every one and we fixed them one at a time.