← All tests

Multi-register Bundle 2 — feature enablement PROD

2026-05-13 on Supabase prod. Second of two bundles toward multi-register per shop + UUID-decoupling from Odoo's bigint. Bundle 2 ships the user-visible feature: /cafe/settings/registers admin, Starter POS landing renders N cards per shop, top-bar register picker, sequence_prefix on order numbers, dual-write of pos_config_uuid on session+order INSERTs.

10/10 prod test PASS. 51/51 regression green. 61/61 total. Bundle 1's get-coffee data-shape gap closed.

Get-coffee data state — 5 Pro registers under 1 shop, no redundant Register 1

SELECT pc.name, pc.odoo_pos_config_id, pc.pos_config_int_id, pc.shop_id, sess, ord
  FROM cafe.pos_configs pc + counts
 WHERE tenant = get-coffee:

┌─────────────────────────────────────┬─────────┬───────────────────┬──────┬──────┐
│ name                                │ odoo id │ pos_config_int_id │ sess │ ord  │
├─────────────────────────────────────┼─────────┼───────────────────┼──────┼──────┤
│ Bakery Shop                         │       4 │ 4                 │    4 │    9 │
│ Get Coffee CM (Chhuk Meas)          │       5 │ 5                 │    5 │   29 │
│ Get Coffee TK (Toul Kork)           │       6 │ 6                 │    9 │   87 │
│ Get Coffee TSP (Toul Svay Prey)     │       7 │ 7                 │   49 │   45 │
│ Fresh Clean Shop                    │       8 │ 8                 │    2 │    1 │
└─────────────────────────────────────┴─────────┴───────────────────┴──────┴──────┘

All 5 rows shop_id = 809e3f7f-... (single get-coffee shop, enriched by step 1).
Orders sum: 9 + 29 + 87 + 45 + 1 = 171 (re-mapped by step 6 from redundant
Register 1's UUID; sums match the original count of orders that previously
landed on the wrong row).

Regression sweep — 51/51 + 10 new

test-multi-register-bundle2-prod.mjs (NEW)10/10
test-phase1-prod.mjs11/11
test-phase2-sso-outdoor-prod.mjs6/6
test-phase2-cafe-multishop-prod.mjs6/6
test-m1-prod.mjs10/10
test-r7-prod.mjs14/14
test-r8-prod.mjs4/4
Total61/61

Reusable lessons surfaced in this gate cycle

1. Bundle 1's OR-clause backfill (match by Odoo id OR by shop_id) could
   non-deterministically pick either when both branches matched — 171 orders
   landed on the wrong UUID on prod get-coffee. Bundle 2 added a remap step
   that scans for "rows currently pointing at a redundant Register 1 with a
   matching Pro alternative" and re-targets them. Lesson: in any future
   multi-table backfill, prefer NOT-EXISTS ordering over OR conditions.

2. Drizzle wraps pg errors in a "Failed query: ..." Error whose message
   doesn't carry the pg constraint name. The .cause chain *does* have the
   pg error with code='23505' + .constraint, but it's not 100% reliable.
   For unique-violation translation (e.g. duplicate names), pre-check the
   collision before INSERT rather than parsing the thrown error. Cleaner
   user-facing message, no error-format coupling.

3. Hyperdrive caches every SELECT for ~60s; admin pages that mutate then
   render need their DAOs wrapped in db.transaction(). The Bundle 2 admin
   page kept failing read-after-write until listRegistersForShop +
   getRegisterById + getRegisterByIntId were all wrapped. Mirrors the
   R9 + telegram-token incidents documented in
   project_hyperdrive_cache_stale_reads.md.

4. Demo's plan_code is 'cafe_pro' (not Starter as I assumed pre-test).
   The Starter tenant on prod is **lumiere-coffee** (5 shops, no Odoo).
   For any new Starter prod test, use owner@lumiere-coffee.com.

5. Bundle 1's INSERT-Register-1 check (`NOT EXISTS shop_id = s.id`) fired
   wrongly for tenants whose pos_configs had NULL shop_id. Bundle 2 added
   a "single-shop tenant enrichment" step (`pos_configs.shop_id = the
   single shop`) BEFORE the seed in subsequent runs — same idempotent
   behavior, cleaner outcome.

Bundle 3 (deferred — not yet scheduled)

- Set NOT NULL + FK on pos_config_uuid for sessions/shop_pos_configs/pos_sequences.
- Drop legacy int pos_config_id columns from those tables.
- Rename pos_config_uuid → pos_config_id throughout.
- Drop shopToConfigId helper.

Can sit indefinitely in the dual-column state — Bundle 2 dual-writes the
UUID on every new INSERT, so the legacy int is increasingly redundant but
not breaking anything. Bundle 3 is "tidy up when convenient", not urgent.

Separately out-of-scope (separate future task):
  The Odoo connector that pushes new NIX-created Pro registers to
  pos.config.create. Bundle 2 sets sync_state='pending_create' on those
  rows; today they're usable in NIX but invisible in Odoo until the
  connector drains them (mirrors R11.5's session-close-move drain).