Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
2e35cde
fix(auth): prevent concurrent PIN limit bypass race and preserve cros…
khaira777 Oct 18, 2026
c660712
no-mistakes(document): docs: document manager PIN concurrency and log…
khaira777 Oct 18, 2026
bcbb8be
fix(auth): add explicit manager selection to approval UI and kitchen …
khaira777 Oct 18, 2026
f369a5a
no-mistakes(review): fix approver deselection and support custom appr…
khaira777 Oct 18, 2026
679b9b6
no-mistakes(review): Use neutral fallback copy in approval select
khaira777 Oct 18, 2026
656bd60
no-mistakes(test): {"summary": "allow payment retry without manager P…
khaira777 Oct 18, 2026
ca1b754
no-mistakes(document): Document kitchen approver binding and resolve …
khaira777 Oct 18, 2026
0a0344c
fix(test): mock useMemo in orders-search test and align modal keypad …
khaira777 Oct 18, 2026
721beb2
fix(test): restore manager PINs in Scenario 4 finally block
khaira777 Oct 18, 2026
1d9c8a2
fix(e2e): capture and restore gate settings in approval-manager-selec…
khaira777 Oct 18, 2026
55cdba1
fix(e2e): prevent test state leakage in approval manager selection spec
khaira777 Oct 18, 2026
f037183
fix(e2e): capture and restore orders layout in approval manager selec…
khaira777 Oct 18, 2026
9dd2d07
Merge remote-tracking branch 'origin/main' into fm/flocafe-reconcile-…
khaira777 Oct 20, 2026
e0d4c06
Merge branch 'fm/flocafe-reconcile-1222-1229-r1' into work-1229
khaira777 Oct 20, 2026
df407af
Merge remote-tracking branch 'origin/main' into fm/flocafe-reconcile-…
khaira777 Oct 20, 2026
2407a00
Merge branch 'fm/flocafe-reconcile-1222-1229-r1' into work-1229
khaira777 Oct 20, 2026
19ff766
Merge remote-tracking branch 'origin/main' into fm/flocafe-reconcile-…
khaira777 Oct 20, 2026
7086353
Merge branch 'fm/flocafe-reconcile-1222-1229-r1' into work-1229
khaira777 Oct 20, 2026
79092e8
Merge remote-tracking branch 'origin/main' into work-1229
khaira777 Oct 20, 2026
0088b7d
Merge branch 'main' into fix/approval-manager-selection
khaira777 Oct 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions docs/architecture/authentication-and-authorization.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ list. `requireRole` (in `main/middleware/security.ts`) still exists, but a stati
(`tests/authorization-static-audit.test.ts`) fails the build if a new call site under
`main/routes/` uses it; the few remaining direct role checks are reviewed context policy, covered
in their own sections below (KDS station/category scope, refund approver identity, the
override-PIN holder for order-status and item cancel/void overrides, and the
override-PIN holder for order-status, kitchen-delivery, and item cancel/void overrides, and the
last-active-owner/staff-target rule).

**Resolution.** For every permission id, `resolveEffectivePermissions(userId)` evaluates, in
Expand Down Expand Up @@ -128,10 +128,11 @@ a router, in [`main/routes/index.ts`](../../main/routes/index.ts). A static sear
**Gates inside transaction bodies.** The most important case. Some endpoints perform the
permission check inside the `withTxn` callback, after the transaction opens, using
`hasPermission(actorId, id)` against state read in the same transaction.
`PATCH /api/orders/:id/status`, `PATCH /api/orders/:orderId/items/:itemId/cancel`, and the matching
`.../restore` endpoint in `main/routes/orders.ts` do this, and their override-PIN paths
additionally require the presenting user to satisfy `hasRole(role, ROLE_ACCESS.ownerManager)` - a
deliberate context-policy role check, not a permission, because the override PIN belongs to a
`PATCH /api/orders/:id/status`, `PATCH /api/orders/:orderId/items/:itemId/cancel`, the matching
`.../restore` endpoint in `main/routes/orders.ts`, and the bill payment kitchen-delivery override
in `main/routes/bills.ts` do this, and their override-PIN paths additionally require the presenting
user to satisfy `hasRole(role, ROLE_ACCESS.ownerManager)` - a deliberate context-policy role check,
not a permission, because the override PIN belongs to a
person acting in an owner/manager capacity regardless of what their individual permission grants
say. To prevent event loop starvation and transaction lock contention during CPU-intensive bcrypt
evaluations, manager PINs are verified asynchronously via `verifyPinAsync` outside the transaction,
Expand Down
2 changes: 1 addition & 1 deletion docs/architecture/frontend.md
Original file line number Diff line number Diff line change
Expand Up @@ -638,7 +638,7 @@ frontend/src/
│ ├── tables/ Floorplan editor, table move modal, table turnover badge
│ ├── ui/ shadcn/ui primitives
│ └── updates/ Update download prompt and install guard dialog
├── hooks/ Printer, KDS, theme, update, cash, and formatting hooks
├── hooks/ Printer, KDS, theme, update, cash, approver, and formatting hooks
├── lib/
│ ├── i18n/ Language registry, loader, and the 24 message files
│ ├── printer/ Encoders, document builders, warnings, WebUSB bridge,
Expand Down
4 changes: 4 additions & 0 deletions docs/architecture/order-lifecycle.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ same fields:
if either already has an associated customer, a different id is a `400`. Wallet payments require a
customer already associated with the bill or order, with enough points.
- `override_pin`: the manager override described below.
- `approver_id`: when supplying `override_pin`, the selected manager ID (`manager_id` and `user_id`
remain compatibility aliases).

Non-cash lines can never exceed the balance (`400`). Cash is applied up to the amount still owed and
the difference is recorded as change, so an over-tendered bill settles and its ledger line carries
Expand All @@ -161,6 +163,8 @@ Two gates sit between a valid request and the write:
- **Kitchen-delivery gate.** When `require_kitchen_delivered_before_settlement` is on, `kds_enabled`
is not `false`, and `billing_type` is not `prepaid`, paying while kitchen items are undelivered
is a `409` with code `KITCHEN_ITEMS_UNDELIVERED` unless a manager PIN is supplied as `override_pin`.
When an approver is explicitly supplied (`approver_id`, `manager_id`, or `user_id`), PIN
verification is restricted to that active manager without falling back to scanning other managers.
When all kitchen items are delivered, `override_pin` verification is bypassed. The override is
recorded in the order audit log.

Expand Down
4 changes: 2 additions & 2 deletions docs/reference/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -396,8 +396,8 @@ Bill generation, settlement, and completion are described end to end in
| `GET` | `/order/:orderId` | `ROLE_ACCESS.ownerManagerCashier` | path: `orderId` | - |
| `POST` | `/generate` | `ROLE_ACCESS.ownerManagerCashier` | body: `order_id` | See [Order lifecycle](../architecture/order-lifecycle.md#bills) for bill creation and re-sync behavior. |
| `POST` | `/:id/split-check` | `ROLE_ACCESS.ownerManagerCashier` | body: `checks` | Body `checks` must hold between 2 and 20 entries and the resulting split group is capped at 20 checks. An unpaid untouched dine-in check is divided by allocating every active order item quantity; a split-group child may be divided again while it stays unpaid with no payment recorded, keeping its bill id, bill number and group, while paid siblings keep their own rows and payments. `400` when a request does not allocate exactly the source's own quantities. `403` when `split_checks_enabled` is off. `409` when the source has a recorded payment, a legacy already-split bill is re-split, the source has no divisible allocation, or the resulting split group would exceed 20 checks. |
| `POST` | `/:id/payment` | `ROLE_ACCESS.ownerManagerCashier` | path: `id`; body: payment line object, `customer_id`; header: `Idempotency-Key` | See [Order lifecycle](../architecture/order-lifecycle.md#payments) for input, replay, gate, and settlement behavior. |
| `POST` | `/:id/payments` | `ROLE_ACCESS.ownerManagerCashier` | path: `id`; body: `payments`, `customer_id`; header: `Idempotency-Key` | See [Order lifecycle](../architecture/order-lifecycle.md#payments) for input, replay, gate, and settlement behavior. |
| `POST` | `/:id/payment` | `ROLE_ACCESS.ownerManagerCashier` | path: `id`; body: payment line object (accepts optional `override_pin`, `approver_id`, `manager_id`, `user_id`), `customer_id`; header: `Idempotency-Key` | See [Order lifecycle](../architecture/order-lifecycle.md#payments) for input, replay, gate, and settlement behavior. |
| `POST` | `/:id/payments` | `ROLE_ACCESS.ownerManagerCashier` | path: `id`; body: `payments`, `customer_id`, optional `override_pin`, `approver_id`, `manager_id`, `user_id`; header: `Idempotency-Key` | See [Order lifecycle](../architecture/order-lifecycle.md#payments) for input, replay, gate, and settlement behavior. |
| `POST` | `/:id/applyDiscount` | `ROLE_ACCESS.ownerManager` | path: `id`; body: `type`, `value`, `reason`, `override_pin`, `approver_id`, `manager_id`, `user_id` | `400` when the bill is paid (`paid`). `409` when the order is completed or cancelled (`order_not_discountable`), the bill is split (`split_check`) or refunded (`refunded`), or the discount would reduce the bill total below collected payments (`below_collected_funds`). Auto-settles the bill to `paid`, completes the order, and frees the table when remaining balance reaches zero on a partially paid bill (gated by kitchen delivery; see [Settlement and completion](../architecture/order-lifecycle.md#settlement-and-completion)). Audited as `order_discount_applied`. |
| `PATCH` | `/:id/charges` | `bills.discount.apply` | path: `id`; body: `charge_id`, `waived` or `applied` | `{ bill }` with updated charge breakdown and totals. Only for unpaid, unsplit bills with no payments; the charge must be active and apply to the order type. `409` when the order is cancelled. |
| `POST` | `/:id/markPrinted` | `bills.print` (`ROLE_ACCESS.ownerManager`) | path: `id` | Stamps `bills.printed_at`. |
Expand Down
Loading
Loading