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_uuid → pos_config_id, enforces NOT NULL + FK, and refactors ~14 files to operate on UUIDs.
pos_config_id: uuid with .references(() => cafePosConfigs.id). Dual-column phase is over.posConfigId: number to posConfigId: string (UUID). Helpers like getOpenNixSessionForConfig, advanceSequence, createOpenNixSession, createNativeOrder all take UUIDs now.listPendingCreates + listPendingPartialRefunds innerJoin cafe.pos_configs to surface Odoo's pos.config.id (still int) from the new UUID column.aggregateRevenueByPosConfig, listSessionsForDay): innerJoin cafe.pos_configs to return the URL-friendly int as posConfigId for label lookups on the dashboard/reports pages.cafePosConfigs.odooPosConfigId, group results back by int for the existing landing UI.findShopIdForConfigId fallback (Bundle 2's enrichment ensures every register has shop_id).lib/starter-config.ts — shopToConfigId + findShopIdForConfigId are no longer referenced anywhere.ON DELETE RESTRICT — pos_configs rows with referencing sessions/orders/etc can't be hard-deleted (admin UI deactivate-only).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)
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)
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
Loading…
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.
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.