← All tests

Multi-register Bundle 3 — final cutover (local)

2026-05-14 Gate 1 local. Final phase of the multi-register arc. Bundle 1 added pos_config_uuid alongside the legacy int + dual-wrote it. Bundle 2 shipped the user-visible features (admin page, N cards, picker, prefix). Bundle 3 completes the cutover: drops the legacy int pos_config_id columns, renames pos_config_uuidpos_config_id, enforces NOT NULL + FK, and refactors ~14 files to operate on UUIDs.

19/19 local probe PASS. Typecheck clean.

What changes per table

cafe.sessions:
  BEFORE  pos_config_id INTEGER NOT NULL          (legacy, Odoo int / shopToConfigId)
          pos_config_uuid UUID NULL               (Bundle 1 dual-column)
  AFTER   pos_config_id UUID NOT NULL             FK → cafe.pos_configs.id

cafe.orders:
  BEFORE  pos_config_id INTEGER NULL              (legacy + pre-R5.2 history)
          pos_config_uuid UUID NULL
  AFTER   pos_config_id UUID NULL                 FK → cafe.pos_configs.id

cafe.shop_pos_configs:
  BEFORE  pos_config_id INTEGER NOT NULL          PK part + unique
          pos_config_uuid UUID NULL
  AFTER   pos_config_id UUID NOT NULL             PK part + unique + FK

cafe.pos_sequences:
  BEFORE  pos_config_id INTEGER NOT NULL          PK part
          pos_config_uuid UUID NULL
  AFTER   pos_config_id UUID NOT NULL             PK part + FK

cafe.pos_configs:
  UNCHANGED  pos_config_int_id BIGINT (URL identifier; Pro = Odoo's pos.config.id,
                                      Starter = shopToConfigId hash / sequence)

Files touched (~14 cafe + 1 backend migration)

nix-outdoor-sales-backend:
  migrations/20260514100000_cafe_multi_register_bundle3.ts  (new)
  migrate.js                                                (entry added)

nix-cafe:
  lib/db/schema.ts                       (4 tables → pos_config_id: uuid + FK)
  lib/db/cafe_sessions.ts                (OpenSessionInput.posConfigId: string;
                                          listSessionsForDay JOINs pos_configs
                                          to surface legacy int)
  lib/db/orders.ts                       (CreateOrderInput.posConfigId: string;
                                          aggregateRevenueByPosConfig JOINs)
  lib/db/pos_sequences.ts                (advanceSequence/peekSequence take UUID;
                                          formatPosOrderNumber renamed first arg
                                          to posConfigIntId for clarity)
  lib/db/pos_configs.ts                  (fetchSessionSummariesFromMirror JOINs)
  lib/db/manager_live.ts                 (Pro landing JOINs sessions ↔ pos_configs)
  lib/db/odoo_sync.ts                    (listPendingCreates JOINs to get Odoo
                                          int from UUID; .posConfigId in
                                          PendingCreate stays int)
  lib/db/order_refunds_push.ts           (same JOIN pattern)
  lib/db/shops.ts                        (getShopForPosConfig + helpers source
                                          from pos_configs directly, not
                                          shop_pos_configs)
  lib/db/team.ts                         (shop→register list from pos_configs)
  lib/db/starter_landing.ts              (queries sessions by UUID)
  lib/actions/register.ts                (Pro openShift + PIN unlock paths
                                          resolve UUID via getRegisterByIntId)
  lib/actions/starter_shift.ts           (only posConfigUuid path remains)
  lib/actions/drafts.ts                  (uses register UUID for sequence;
                                          register int for receipt prefix)
  app/(authed)/pos/page.tsx              (Pro path resolves register UUID first)
  app/(authed)/pos/starter-register-page.tsx
  app/(pos-fullscreen)/pos/register/[configId]/page.tsx (drops findShopIdForConfigId)
  app/api/cafe/orders/route.ts           (resolves register, separates UUID
                                          (sessions/sequence) from int (prefix))
  scripts/seed-supabase-lumiere-demo.ts  (resolves pos_configs UUID per shop
                                          before inserts)
  lib/starter-config.ts                  (DELETED — shopToConfigId no longer used)

Local probe — 19/19

Schema shape (8/8):
  ✓ cafe.sessions.pos_config_id is UUID NOT NULL
  ✓ cafe.orders.pos_config_id is UUID NULL (pre-R5.2 history)
  ✓ cafe.shop_pos_configs.pos_config_id is UUID NOT NULL
  ✓ cafe.pos_sequences.pos_config_id is UUID NOT NULL
  ✓ no legacy pos_config_uuid column on any of the 4 tables

FK constraints (4/4):
  ✓ sessions_pos_config_uuid_fkey
  ✓ orders_pos_config_uuid_fkey
  ✓ shop_pos_configs_pos_config_uuid_fkey
  ✓ pos_sequences_pos_config_uuid_fkey

PKs recreated (2/2):
  ✓ shop_pos_configs PK on (shop_id, pos_config_id UUID)
  ✓ pos_sequences    PK on (pos_config_id UUID, business_date)

Indexes (4/4):
  ✓ cafe_orders_legacy_order_number_unique
  ✓ cafe_orders_tenant_config_date_seq_unique
  ✓ cafe_sessions_pos_config_state_idx
  ✓ uq_cafe_shop_pos_configs_pos_config_id

DAO round-trip (1/1):
  ✓ getRegisterByIntId + getOpenNixSessionForConfig + advanceSequence
    + formatPosOrderNumber + getRegisterById all work with UUIDs end-to-end

Local diagnostic counts

Loading…

⚠ Deploy ordering caveat for Gate 2

Bundle 3 changes column types — there's a brief unavailability window during
the cutover that's hard to eliminate:

  Push → CF Pages deploys (~60-90s) → New code expects UUID column
  Run migrate → DB renames + drops (~10s)

Old code expects int; new code expects UUID. Either ordering has a window where
the code/schema mismatch causes 500s on POS routes.

For the 3-tenant dev environment this is acceptable. Plan: push code first,
then run migrate immediately after the deploy lands. Window ≈ 10-15s where
POS register routes will 500 between deploy-end and migrate-finish.

Production users would see "Register couldn't load — try again" for that
window; ringing orders is paused for ~15s. No data corruption risk
because the migration is idempotent and FK-checked.

Gate 2 plan

1. Push backend (migration) + cafe (refactor) on karouna-dev.
2. Wait for CF Pages deploy (~60-90s).
3. Run prod migrate via node migrate.js against Supabase.
4. Run prod multi-register test (10/10 baseline from Bundle 2).
5. Run full regression sweep — phase1 / phase2-sso-outdoor / phase2-cafe-multishop
   / m1 / r7 / r8 sequential per R8.2 single-session rule.
6. Publish multi-register-bundle3-prod gallery.
7. Stop for approval.

Bundle 3 closes the multi-register arc. After this lands, there is no Bundle 4
on the books — the schema is in its final shape (UUID throughout, FKs enforced,
shopToConfigId helper retired).

Out of scope (still — separate future task):
  The Odoo connector that pushes new NIX-created Pro registers to
  pos.config.create. Bundle 2 sets sync_state='pending_create'; the connector
  drains them.