docs(pos): record implementation deviations and rollout blockers

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Dave 2026-08-31 01:51:56 +02:00
parent c56961d7d9
commit 8e6a6deac9

View file

@ -2030,3 +2030,59 @@ Not automatable (this is a static PWA with no existing test harness in this repo
- **Spec coverage:** superuser-credential removal (Tasks 10-11), stock_items/stock_ledger-backed catalog and checkout (Tasks 3-4, 6-7, 12-13), pos_sales/pos_sale_items data model (manual prerequisite + Task 4), account-sale delivery slip without a new workflow record (Task 17), history/cashup off pos_sales (Tasks 15-16), POS_API_KEY auth (Task 2, wired into every route) — all covered.
- **Deviation from spec, called out explicitly at the top:** `customer_fulfillment` reused instead of a new `pos_sale` ledger reason, removing a manual PB-admin step. `customers` search also moved behind a new `/api/v1/pos/customers` route (Task 17) — the spec didn't call this out explicitly, but it was a direct-PB call in the original mockup that would otherwise still need the removed superuser credentials; folding it into the POS API surface was necessary to actually satisfy "no PocketBase credentials shipped to the client."
- Letterhead branding on the receipt is intentionally dropped rather than wired up — it's cosmetic, wasn't asked for in the design's goals, and would need another new endpoint; noted inline in Task 14 as a place to extend later if wanted.
---
## Implementation notes (2026-08-31)
Implemented on branch `feat/wfsales-pos`. Two deliberate deviations from the
plan as written, both agreed with the user before any code was written:
1. **All four tender types, not two.** `pos.html` offers Cash, Card, EFT and
Account. The plan's backend accepted only `cash`/`card` and coerced
everything else to `cash`, which would have booked account (pay-later) and
EFT sales as money in the drawer. `PosPaymentMethod` is now
`cash | card | eft | account`; only `cash` records `cash_given`/
`change_given`, the rest record zero. Stock decrements for all four —
including `account`, since the goods physically leave on a delivery note.
`cashup.html` already rendered four method rows, so under the original plan
the EFT and Account rows would have been permanently R0.
2. **Checkout is idempotent via `client_ref`.** The plan queued a sale on *any*
checkout failure, including a request the server had already committed but
whose response was lost. `flushQueue()` would then re-post it, duplicating
the sale and decrementing stock twice — contradicting the design's "a sale is
never double-decremented". The till now sends its local sale id as
`client_ref`; `checkoutSale` returns the existing record with
`duplicate: true` and performs no writes when that key is already present.
Smaller adjustments made along the way:
- `searchCustomersForPos` + `GET /api/v1/pos/customers` landed with the other
routes (Task 8) rather than inside Task 17, since it is backend work.
- The delivery-note price toggle drives the Bluetooth thermal print as well as
the on-screen slip, and the printed slip now carries the company name.
- `index.html` distinguishes a rejected API key from being offline, instead of
showing "Offline" for both.
- PB filter strings built by hand are quote-escaped (`escapePbValue`).
### Verification status
- 35 POS unit/route tests pass; `tsc --noEmit` clean for all POS files.
- Pre-existing suite failures (9 files) were confirmed identical at the merge
base — this branch introduces none.
- Live smoke test against the dev server: all 9 static files serve from
`/pos/*`, all four endpoints 401 without a valid key, and `/catalog` +
`/customers` return real PocketBase data with one.
### Blocked before Task 20 can run
- `pos_sales` / `pos_sale_items` collections do not exist yet (`/sales` returns
500). Per the prerequisite at the top of this plan, plus two additions:
`payment_method` must include `eft` and `account`, and `pos_sales` needs a
`client_ref` text field with a unique index.
- **No sellable stock.** Of 373 active `stock_items`, zero have a
`selling_price` and zero have a `markup`; only 98 have a `cost_price`. The POS
prices from `selling_price`, so today every line would ring up at R0.00. This
needs a pricing decision before the till can take real money — see the handover
notes for the options.