← All tests

R11.4 — Cross-session refund aggregator PROD

Final piece of the bypass-Odoo-POS writer side. When a customer is refunded for an order rung in a PRIOR closed session (yesterday's), the refund cash physically exits TODAY's drawer — so the refund books to TODAY's close-move, not yesterday's. Per Narong's Option B decision (2026-05-11): mirrors Odoo POS's native behavior (refund creates a negative pos.order folded into next session_close); cash flow aligns with the physical drawer; less Odoo write volume than Option A's per-refund reverse entry.

1 cafe commit + 41/41 regression green.
No R11.4-specific prod test ran this Gate 2. The cross-session-refund code path is unreachable on prod until some tenant has bypass_odoo_pos=true, which doesn't happen until R11.5 cutover (separate future session). Gate 2 is regression-only — confirms the aggregator change didn't break any production read path that uses the existing fields. Real cross-session refund verification happens in R11.5 with an actual flag flip + real shift close on get-coffee.

Regression sweep — sequential per R8.2 rule

SuiteResultNotes
test-launchpad-fix-prod.mjs8/8Outdoor SSO bridge fix sanity
test-r7-prod.mjs14/14Dashboard + manager-live
test-r8-prod.mjs4/4Auth/security trio
test-cash-carryover-prod.mjs9/9cafe.sessions read path (same DAO we just extended)
test-phase2-cafe-multishop-prod.mjs6/6Cafe multi-shop

What ships

lib/db/cafe_sessions.ts — aggregateSalesAndPaymentsForSession extension:
  + Detect cafe.order_refunds where refunded_at ∈ session's open window
    AND original order.session_id != current session (cross-session only).
  + For each: subtract from gross + subtract from cash bucket (or first
    method / synthesized "Refund" bucket as fallback).
  + 2 new diagnostic fields on SessionMoveAggregate:
      crossSessionRefundCount
      crossSessionRefundTotalUsd

Same-session refunds still handled by the existing orders loop
(state='refunded' → subtract). Cross-session filter avoids double-counting.

No migration, no new column. Timestamp inference works because
cafe.sessions has a partial unique enforcing one open session per
pos_config_id — every refund.refunded_at lands in exactly one session
window per register.

What's deferred to R11.5 cutover (separate future session)

- SQL flip cafe.tenant_config.bypass_odoo_pos=true on get-coffee.
- Close a real shift on get-coffee POS.
- Wait one cron tick; watch the drain post an account.move to
  journal 48 "NIX Cafe Sales" in Odoo with the right credit + debit lines.
- Generate-and-diff against historical Odoo-auto-created moves
  (Strategy A from scoping memo).
- Issue a refund on a closed-session's order; verify it folds into the
  NEXT session's close-move per R11.4's design.
- Watch one week of bypass-mode operation in production.
- Eventually retire the legacy pos.order/pos.session push for the
  flipped tenant.