← All tests

Odoo pos.config rename/deactivate round-trip PROD

2026-05-14 on Supabase prod + get-coffee Odoo. Multi-register arc follow-up — the update half of the NIX → Odoo connector SHIPPED end-to-end. Register renames and deactivations now propagate to Odoo: the DAO flips sync_state='pending_update' in a transaction, the existing odoo-sync cron tick drains it via pos.config.write({name, active}) within ~60s. Same retry/backoff/dead-letter shape as the create queue. Gate 2 doubled as the PUSH-TEST cleanup — get-coffee's stale pos.config.id=11 was the fixture: renamed to a disposable name, then deactivated. Both halves round-tripped to Odoo cleanly.

8/8 prod round-trip + 51/51 regression = 59/59 green. Zero regressions.

Commits shipped

nix-outdoor-sales-backend:
  48de307   feat: widen pos_configs drain index to cover pending_update state
            (migration 20260514130000 + migrate.js entry)

nix-cafe:
  6e229da   feat: round-trip register rename + deactivate to Odoo via pos.config.write
            - lib/odoo/pos_config.ts            buildPosConfigUpdatePayload + updatePosConfigInOdoo
            - lib/db/pos_configs_push.ts         update-queue DAOs + markPosConfigPushed divergence check
            - lib/db/pos_configs.ts              renameRegister + setRegisterActive flip sync_state
            - app/api/cafe/cron/odoo-sync/route.ts  pending_update drain loop

End-to-end round-trip on get-coffee — 8/8

  1. Setup — get-coffee register linked to Odoo pos.config.id=11 found register id=b25d1b8a-78ce-… name='PUSH-TEST-1778724508344 [TEST]' isActive=false
  2. Setup — Odoo client built, pos.config[11] readable Odoo pos.config[11] before: name='PUSH-TEST-1778724508344' active=true
  3. renameRegister flips sync_state → pending_update (name written, retries reset) sync_state → pending_update, name written, retries=0
  4. Cron tick drains the rename → NIX sync_state back to 'synced' cron drained the rename — pos.config.write succeeded, row marked synced
  5. Odoo pos.config[11].name updated — rename round-trip confirmed Odoo pos.config[11].name === 'z-archived-test-register-2026-05-14'
  6. setRegisterActive(false) flips sync_state → pending_update sync_state → pending_update, is_active=false
  7. Cron tick drains the deactivate → NIX sync_state back to 'synced' cron drained the deactivate — pos.config.write succeeded, row marked synced
  8. Odoo pos.config[11].active === false — deactivate round-trip confirmed read with context active_test:false — record archived in Odoo

How it works

1. Admin renames or deactivates a register in /cafe/settings/registers.
   renameRegister / setRegisterActive run in a transaction: local write,
   then queueOdooUpdate flips sync_state='pending_update' (+ resets
   retry/backoff) — but ONLY for rows that:
     • have an odoo_pos_config_id (skips Starter + not-yet-created Pro)
     • aren't 'pending_create' (the create connector handles those)
   setRegisterPrefix is deliberately NOT propagated — Odoo's pos.config
   has no sequence_prefix field, and NIX overrides order names in the
   R5 push payload, so Odoo's sequence never fires for NIX orders.

2. Cron fires every minute → /api/cafe/cron/odoo-sync. After the
   pending_create drain, a new pending_update drain loop runs:
     a. listPendingPosConfigUpdates(tenant) — rows with retries < 5 and
        next_attempt_at due, ordered oldest-first.
     b. updatePosConfigInOdoo(odoo, odooId, {name, active}) →
        pos.config.write([id], payload).
     c. markPosConfigUpdateSynced — flip sync_state='synced', clear
        retry/error.

3. On failure: markPosConfigUpdateFailed bumps retries with the shared
   backoff (1/5/15/60/240 min); dead-letters to 'sync_error' at 5.

Create-flight race: if a rename/deactivate lands while a register is
still 'pending_create', the rename DAO leaves it alone — but
markPosConfigPushed now takes the name it pushed, re-reads the row
after the create lands, and re-queues as 'pending_update' if name or
active drifted. So a mid-flight edit is never lost.

Regression sweep — 51/51

test-phase1-prod.mjs11/11
test-phase2-sso-outdoor-prod.mjs6/6
test-phase2-cafe-multishop-prod.mjs6/6
test-m1-prod.mjs10/10
test-r7-prod.mjs14/14
test-r8-prod.mjs4/4
Total regression51/51
+ pos-config-update round-trip8/8
Effective total59/59

What this closes

The multi-register arc's last two open follow-ups + a cleanup:

  ✓ Rename round-trip to Odoo — NIX-side rename now propagates to
    Odoo's pos.config within ~60s (was: NIX renamed, Odoo stayed stale).
  ✓ Deactivate round-trip — NIX-side deactivation archives the Odoo
    pos.config too.
  ✓ Stale PUSH-TEST artifact — get-coffee's pos.config.id=11 renamed to
    a disposable name + archived, used as the Gate 2 fixture.

Still out of scope (no current need):
  • setRegisterPrefix → Odoo: NIX-only by design (see "How it works").
  • A general per-field update connector with its own queue — the
    name+active write covers every field that meaningfully round-trips
    today.