2026-05-13 Gate 1 local. Second of two bundles toward multi-register per shop. Bundle 2 ships the user-visible feature: register admin at /cafe/settings/registers, multi-card Starter POS landing, top-bar register picker, sequence_prefix in order numbers, plus dual-write of pos_config_uuid on every new session + order INSERT.
pos_configs.shop_id for single-shop tenants (closes Bundle 1's get-coffee gap); delete redundant auto-seeded 'Register 1' rows on shops that already have Pro registers (gated by 4 NOT EXISTS checks).cafe.pos_configs.pos_config_int_id BIGINT backfilled to odoo_pos_config_id (Pro) or shopToConfigId(shop_id) (Starter). Bundle 2 callers use this as the legacy-int alias; Bundle 3 drops the old int columns from sessions/orders.cafe.pos_config_int_id_seq START 1_000_000_000 for new registers. 10^9 range never collides with Odoo's pos.config.id (≤ 10^6) or shopToConfigId's 31-bit hashes (< 2^31).(tenant_id, pos_config_int_id) and (tenant_id, shop_id, name) WHERE shop_id IS NOT NULL. Multi-shop tenants can have a 'Register 1' per shop; can't have two on the same shop.lib/db/pos_configs.ts: listRegistersForShop, getRegisterByIntId, getRegisterById, createRegister, renameRegister, setRegisterPrefix, setRegisterActive.lib/actions/registers.ts — gated by nix_cafe.settings.edit, with duplicate-name error translation./cafe/settings/registers — per-shop dropdown, register list with prefix chip, modal add/edit, deactivate button. Plus Registers entry in SettingsNav.cafe.pos_configs cards per shop (from buildStarterLanding) instead of synthesizing one.OpenShiftForm mirrors the same picker pre-shift.posConfigUuid and dual-writes it on the new session row.registerPrefix > shopCode > POS{id}. Multi-register shops can tell receipts apart (e.g. F-0001 vs D-0001).?configId via cafe.pos_configs.pos_config_int_id first, falls back to the legacy shopToConfigId scan — works for both old + new register rows.Loading…
Loading…
nix-outdoor-sales-backend: migrations/20260513110000_cafe_multi_register_features.ts (new) migrate.js (entry added) nix-cafe: lib/db/schema.ts (posConfigIntId column) lib/db/pos_configs.ts (5 new DAOs) lib/db/starter_landing.ts (N cards per shop) lib/db/cafe_sessions.ts (posConfigUuid on OpenSessionInput) lib/db/orders.ts (posConfigUuid on CreateOrderInput) lib/db/pos_sequences.ts (formatPosOrderNumber + registerPrefix) lib/actions/registers.ts (new — 4 server actions) lib/actions/starter_shift.ts (openStarterShift accepts posConfigUuid) lib/actions/drafts.ts (registerPrefix in order-number mint) app/(authed)/settings/registers/page.tsx (new — admin page) app/(authed)/settings/registers/registers-client.tsx (new — client component) app/(authed)/settings/_components/settings-nav.tsx (Registers entry) app/(authed)/pos/page.tsx (Pro createOpenNixSession dual-writes uuid) app/(authed)/pos/starter-register-page.tsx (resolves register from ?config or first active) app/(authed)/pos/_components/starter-top-bar.tsx (register picker) app/(authed)/pos/_components/open-shift-form.tsx (register picker + posConfigUuid in submit) app/(pos-fullscreen)/pos/register/[configId]/page.tsx (resolve via getRegisterByIntId) app/api/cafe/orders/route.ts (registerPrefix lookup + dual-write) lib/actions/register.ts (Pro openShift dual-writes uuid)
1. Migration applied locally (Batch 38, 1 migration).
2. Drizzle schema typechecks (npx tsc --noEmit, clean).
3. DAO + landing probe — 8/8 green:
- pos_config_int_id column exists (bigint)
- sequence start = 1_000_000_000
- unique (tenant, pos_config_int_id) partial index in place
- unique (tenant, shop, name) partial index in place
- 100% of pos_configs have pos_config_int_id
- listRegistersForShop + createRegister + rename + setPrefix + deactivate
+ duplicate-name rejection: all round-trip cleanly
- buildStarterLanding emits a card per pos_config (verified by creating
a 2nd register and confirming both appear in the landing)
- formatPosOrderNumber precedence: registerPrefix > shopCode > POS{n}
4. Local dev-server route smoke not run — dev server hangs on first compile
on this Win machine (known issue per project_dev_server_bypass_routes_500.md);
visual verification of /cafe/settings/registers + multi-card landing
happens at Gate 2 on prod where the full SSO + Worker stack is healthy.
1. Commit backend (migration + migrate.js) + cafe (everything else).
2. Push to karouna-dev on both repos.
3. Apply prod migration via `node migrate.js` against Supabase.
4. Wait for CF Pages auto-deploy (~90s).
5. Run prod multi-register flow on demo (Starter):
- Login as owner@demo.com
- Navigate to /cafe/settings/registers
- Verify Register 1 exists for demo's single shop
- Add a 2nd register named "TEST-FRONT" with prefix "F"
- Navigate to /cafe/pos — verify 2 cards under demo's shop
- Click TEST-FRONT card → lockable fullscreen opens
- Cleanup: deactivate the test register
6. Verify on get-coffee (Pro):
- /cafe/settings/registers shows 5 Pro registers (Bakery Shop, CM, TK, TSP,
Fresh Clean) all linked to get-coffee's single shop (enrichment step worked)
- Redundant 'Register 1' for get-coffee is gone
7. Full regression sweep (51 + new):
- phase1, phase2-sso-outdoor, phase2-cafe-multishop, m1, r7, r8 (sequential).
8. Publish multi-register-bundle2-prod gallery.
9. Stop for approval.
Bundle 3 (deferred):
- 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.
Out of scope:
- The Odoo connector that pushes new NIX-created Pro registers to pos.config.create.
Bundle 2 creates Pro registers with sync_state='pending_create'; the future
connector picks them up and writes odoo_pos_config_id (mirrors R11.5's
session-close-move drain).