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.
cafe_pos_configs_pending_push_idx widened to sync_state IN ('pending_create','pending_update'). No column changes.renameRegister → pending_update → cron drain → pos.config[11].name updated in Odoo → NIX flipped back to synced. Then setRegisterActive(false) → pending_update → cron drain → pos.config[11].active=false in Odoo.'PUSH-TEST-… [TEST]' + is_active=false; Odoo: 'PUSH-TEST-…' + active=true) — exactly the drift this task fixes. The connector pushed NIX's state and the two sides now match.pos.config.id=11 is now named z-archived-test-register-2026-05-14 and archived (active=false). Operator can hard-delete in backoffice if desired.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
pos.config.id=11 found
register id=b25d1b8a-78ce-… name='PUSH-TEST-1778724508344 [TEST]' isActive=falsepos.config[11] readable
Odoo pos.config[11] before: name='PUSH-TEST-1778724508344' active=truerenameRegister flips sync_state → pending_update (name written, retries reset)
sync_state → pending_update, name written, retries=0sync_state back to 'synced'
cron drained the rename — pos.config.write succeeded, row marked syncedpos.config[11].name updated — rename round-trip confirmed
Odoo pos.config[11].name === 'z-archived-test-register-2026-05-14'setRegisterActive(false) flips sync_state → pending_update
sync_state → pending_update, is_active=falsesync_state back to 'synced'
cron drained the deactivate — pos.config.write succeeded, row marked syncedpos.config[11].active === false — deactivate round-trip confirmed
read with context active_test:false — record archived in Odoo1. 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.
| test-phase1-prod.mjs | 11/11 |
| test-phase2-sso-outdoor-prod.mjs | 6/6 |
| test-phase2-cafe-multishop-prod.mjs | 6/6 |
| test-m1-prod.mjs | 10/10 |
| test-r7-prod.mjs | 14/14 |
| test-r8-prod.mjs | 4/4 |
| Total regression | 51/51 |
| + pos-config-update round-trip | 8/8 |
| Effective total | 59/59 |
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.