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.
shop_id WHERE shop_id IS NOT NULL.shop_id resolved from cafe.shop_pos_configs JOIN on odoo_pos_config_id. Local has 0 Pro rows; prod get-coffee has them and will exercise this path on Gate 2.'Register 1' per commerce.shops that didn't have a cafe.pos_configs row yet — 6 rows seeded locally, 1 per shop. Future migrations that flip a Pro tenant from Starter inherit this row.sessions, orders, shop_pos_configs, pos_sequences) gain a nullable pos_config_uuid UUID column. Backfilled by joining cafe.pos_configs via either odoo_pos_config_id (Pro) or shop_id (Starter).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)
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)
Loading…
Loading…
Loading…
Loading…
Loading…
Loading…
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.