← All tests

Multi-register Bundle 1 — schema + backfill (local)

2026-05-13 Gate 1 local. First of two bundles enabling multi-register per shop on Starter (and decoupling cafe.* from Odoo's bigint as primary key). This bundle is migration + backfill ONLY — no code change, no behavior change. Adds new columns to cafe.pos_configs (shop_id, sequence_prefix, sync_state, sync_error_message) and a parallel pos_config_uuid UUID column to cafe.sessions, cafe.orders, cafe.shop_pos_configs, cafe.pos_sequences. Backfills every legitimate row. Legacy int pos_config_id columns stay populated + authoritative; Bundle 2 will cut readers to the UUID; Bundle 3 (deferred) drops the int.

15/15 local checks passed.

Code surface — files touched

nix-outdoor-sales-backend:
  migrations/20260513100000_cafe_multi_register.ts      (new — knex; local dev)
  migrate.js                                             (appended — prod runner)

nix-cafe:
  lib/db/schema.ts                                       (added 4 columns to cafePosConfigs,
                                                          added posConfigUuid to 4 tables)

Migration backfill logic

1. cafe.pos_configs: ADD COLUMNS shop_id, sequence_prefix, sync_state, sync_error_message
2. Pro enrich:  UPDATE pc.shop_id = sp.shop_id  FROM cafe.shop_pos_configs sp
                   WHERE pc.shop_id IS NULL AND pc.odoo_pos_config_id IS NOT NULL
                     AND sp.pos_config_id = pc.odoo_pos_config_id
3. Starter seed: INSERT INTO cafe.pos_configs (tenant_id, shop_id, name, sync_state)
                 SELECT s.tenant_id, s.id, 'Register 1', 'synced'
                   FROM commerce.shops s
                  WHERE NOT EXISTS (SELECT 1 FROM cafe.pos_configs pc
                                     WHERE pc.tenant_id = s.tenant_id AND pc.shop_id = s.id)
4. ADD pos_config_uuid UUID NULL on sessions/orders/shop_pos_configs/pos_sequences
5. Backfill sessions/orders: JOIN cafe.pos_configs ON same tenant_id AND
   (odoo_pos_config_id = sessions.pos_config_id   -- Pro match
    OR shop_id = sessions.shop_id)                 -- Starter match
6. Backfill shop_pos_configs: prefer odoo_pos_config_id match; fall back
   to shop_id match (handles seeds / dev DBs with orphan rows)
7. Backfill pos_sequences: prefer odoo_pos_config_id; fall back to
   bridge via any session with the same pos_config_id

Orphans intentionally left NULL:
  - cafe.orders rows where pos_config_id IS NULL (pre-R5.2 history)
  - cafe.pos_sequences rows with no matching session (dead history)

Diagnostic counts (local seed DB)

Loading…

cafe.pos_configs

Loading…

cafe.sessions

Loading…

cafe.orders

Loading…

cafe.shop_pos_configs

Loading…

cafe.pos_sequences

Loading…

Next: Bundle 2

Bundle 2 (separate gate cycle, ~30-40 files):
  - Switch every DAO/route/action/test to read pos_config_uuid instead of int.
  - Set NOT NULL + FK on pos_config_uuid → cafe.pos_configs.id.
  - New /cafe/settings/registers admin page (per-shop dropdown, create/rename/prefix/deactivate).
  - Starter POS landing renders N cards per shop (currently 1:1 via shopToConfigId).
  - StarterTopBar register picker when shop has > 1 register.
  - formatPosOrderNumber extended with sequence_prefix (e.g. FRONT-001, DRIVE-001).
  - settings-nav.tsx adds "Registers" entry between Store and Currency & Tax.
  - New multi-register prod test + full regression sweep.

Bundle 3 (deferred):
  - Drop legacy int pos_config_id columns.
  - Rename pos_config_uuid → pos_config_id everywhere.
  - Drop shopToConfigId helper.
  - Can live indefinitely in the dual-column state if Bundle 2 ships stable.

Scope deliberately NOT in this work:
  - The Odoo connector that pushes NIX-created Pro registers to pos.config.create.
    Bundle 2 sets sync_state='pending_create' on those rows; the connector is
    its own future task (mirrors R11.5's session-close-move drain shape).
  - Per-register payment method restrictions, cross-register session merge.