POS multi-staff & permissions
A real shop has more than one person at the counter. LaunchMyStore POS supports a shared-drawer shift model so a single register can be passed between cashiers across a business day, with every action attributed to the person who did it.
The role tiers
POS layers a permission scope on top of your store’s broader staff roles. Four tiers:
merchant— you, the owner. Everything unlocked.Admin— trusted operations lead. Same caps as merchant for POS.manager— floor manager. Can refund, void, close shifts, cap discount at 50%.staff— cashier. Can ring sales, accept payment, open the drawer for change — but cannot refund, void, or close the shift.
The scope matrix
| Capability | Owner | Admin | Manager | Cashier |
|---|---|---|---|---|
| Refund | Y | Y | Y | N |
| Void | Y | Y | Y | N |
| Max discount % | Unlimited | Unlimited | 50 | 10 |
| Open cash drawer | Y | Y | Y | Y |
can_close_shift | Y | Y | Y | N |
can_edit_price | Y | Y | Y | N |
can_view_activity | Y | Y | Y | N |
can_pair_hardware | Y | Y | Y | N |
All four tiers can open the cash drawer (cashiers need to make change). But only the upper three can close the shift — otherwise a cashier could walk out mid-day and lock the drawer numbers.
Adding POS staff
- Open Settings → Staff.
- Click Add staff.
- Fill in email, name, role (
Admin/Manager/Cashier). - Optionally set a POS PIN — a 4-6 digit code the cashier types at the register to clock in. Faster than email + password every shift handoff.
- Save. The staffer receives an invite email and lands in your store after accepting.
POS PINs are short-lived (typed at the terminal, never sent to a customer’s browser) and rotate independently of the staff’s admin login password.
The shared-drawer model
One physical register = one shift = one cash drawer. When a second cashier shows up for their afternoon block, they don’t open a new shift — they join the existing one as a contributor.
Each shift tracks its opener plus a list of additional cashiers who joined and (optionally) clocked out. For each contributor, the register records who they are, when they joined, and when they clocked out — preserving the full handoff timeline for the day.
How clock-in works
When Bob arrives mid-day:
- Bob taps Switch cashier on the register.
- Bob enters his POS PIN.
- Backend adds Bob to
contributorswith the current timestamp. - The cart resets; Bob starts ringing as himself.
Every order from this moment is attributed to Bob. Reports per-cashier work because the active cashier is recorded on every order, refund, and cash movement.
How clock-out works
Bob ends his shift block without closing the day:
- Bob taps Clock out.
- Backend stamps
leftAton Bob’s contributor entry. - The shift stays open for the next cashier.
Why this matters for “current shift” lookups
The register’s “current shift” lookup matches the shift when you are the opener OR an active contributor. If we filtered owner-only, a cashier who clocked in but didn’t open the shift would see a fake “Shift closed” card right after recording a sale — the cart still has items but the UI thinks no shift exists. The contributor-aware match prevents this.
Force-close: when a cashier abandons the drawer
Sometimes a cashier walks out without closing — or their browser crashes on a flaky tablet and they go home. The shift sits open overnight. A manager can force-close:
- Endpoint:
POST /pos/shifts/:id/force-close - Body:
{ closingDeclared?, reason? }— ifclosingDeclaredis omitted, defaults to the system-computedexpectedCash(so variance = 0). - The Z-report is generated with
forceClosed: trueand the reason recorded. - A
shift.force_closestaff-audit row is written so the activity log shows who did it and why.
The force-close UI is rolling out to the admin Shifts list; until then this is a manager-only API call.
Auditing who did what
Every meaningful action writes to the staff audit log:
shift.open,shift.close,shift.force_closeshift.movement.sale,shift.movement.refund,shift.movement.drop,shift.movement.paid_in,shift.movement.paid_out- Order create, refund, void
Open POS → More → Activity (manager-only) to see the day’s timeline grouped by cashier.
Common issues
- Cashier sees “Shift closed” right after a sale — they never clocked in. Have them tap Switch cashier on the register and enter their PIN.
- Switching cashier doesn’t reset the cart — the register’s local cart cache persists in the browser. The register flow clears it automatically when you switch cashiers; if you’re scripting tests, clear browser storage between POS identities.
- Wrong cashier’s name on a receipt — the previous cashier didn’t clock out. The Z-report still attributes correctly per-order.
- 403 on refund as a cashier — expected. Hand the till to a manager.
FAQ
Can two cashiers share one shift?
Yes — that’s the whole point of the contributor model. The opener is the “owner”; everyone else clocks in as a contributor. The shift is one drawer, but every order is stamped with the cashier who rang it.
Do contributors split the variance at close?
No — variance is per-drawer, not per-cashier. The Z-report attributes orders per cashier so you can see who handled what volume, but the cash count is a single shop-wide number.
What’s the difference between a Manager and an Admin?
Functionally on POS, they’re identical. Admin is a broader admin role on the whole platform (can see store settings, billing). manager is POS-floor scoped. Pick whichever fits the human.
Can I disable the 10% discount cap for cashiers?
Not from the UI. The caps are part of the role definition. Promote the cashier to Manager (50% cap) or Admin (uncapped) if they need more leeway.
Can a cashier reset their own PIN?
Not yet — PIN management is done by an Owner or Admin from Settings → Staff. Self-service PIN reset is on the roadmap.
What if I want manager approval for big discounts but let the cashier ring the sale?
That’s the manager-override pattern. When a cashier hits their cap, the register prompts for a manager PIN; once entered, the discount applies under the manager’s audit trail. The cashier keeps ringing as themselves — only the discount line is attributed to the manager.