diff --git a/docs/superpowers/plans/2026-08-30-wfsales-pos-implementation.md b/docs/superpowers/plans/2026-08-30-wfsales-pos-implementation.md index 5d504c3..b6f1c73 100644 --- a/docs/superpowers/plans/2026-08-30-wfsales-pos-implementation.md +++ b/docs/superpowers/plans/2026-08-30-wfsales-pos-implementation.md @@ -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.