2026-05-27 Gate 2 ship. U4's closing-session PDF reshaped to match Odoo's "Daily Sales Report X/Z" layout per Narong's 2026-05-27 Telegram. Sales grouped by category × product × variants × qty/total; Payments per method; Discounts (count + total); Session Control with separate "Number of transactions" (paid + partial_refund) and "Transactions Refunded" (fully reversed) lines so refunded orders don't inflate the count or the cups-sold totals. NIX Cash section preserved, placed under Session Control. Order-list table removed.
Intl.DateTimeFormat in tenant timezone).refunded_qty is subtracted before grouping (fully-refunded order = every line at refunded_qty=qty → all net to 0 → drops out; partial refund subtracts just the refunded portion).orders.tax_usd); rendering an incomplete breakdown would mislead. Future slice with new cafe.tax_rates table when needed.COUNT(*) FILTER (...).nix-cafe/lib/db/orders.ts — 3 new exports: listSalesByCategoryForSession (joins through H4-X views; groups in app layer by category/product/variants combo; nets refunded qty), sumDiscountsForSession (count + total), countOrdersForSession (returns { transactions, refunded } via two FILTER clauses)nix-cafe/lib/reports/closing_session_pdf.ts — new layout, X/Z title, Cash section relocatednix-cafe/app/api/cafe/sessions/[sessionId]/closing-session-pdf/route.ts — wires new DAOs, formats asOfDate via Intl.DateTimeFormat with tenant timezone, passes store address lines| ✓ | Find a closed lumiere session that has orders (Z test) — used session b967eb21, tx=1 refunded=0 |
| ✓ | SSO-login lumiere owner |
| ✓ | Z test: fetch closing-session PDF for the closed session (200 / application/pdf / closing-session- filename / %PDF magic) |
| ✓ | Closed session PDF: title 'Daily Sales Report Z' present + all 5 section headings (Sales / Payments / Discounts / Session Control / Cash) + 'Number of transactions' + 'Transactions Refunded' + 'As of' |
| ✓ | Closed session PDF: legacy 'Order#' / 'Methods' column headers are GONE — order-list table removed |
| ✓ | Counts in PDF body match raw SQL: Number of transactions: 1 + Transactions Refunded: 0 |
| ✓ | Find an open lumiere session for the X test |
| ✓ | X test: open-session PDF has title 'Daily Sales Report X' (not Z) — title flip verified end-to-end |
| ✓ | No 5xx HTTP responses during the suite |
Fetched live from lumiere-coffee.nixtech.app via authenticated GET. Open inline below or download.
| test-phase1-prod.mjs | 11/11 |
| test-phase2-sso-outdoor-prod.mjs | 6/6 |
| test-m1-prod.mjs | 10/10 |
| test-r7-prod.mjs | 14/14 |
| test-r8-prod.mjs | 4/4 |
| test-phase2-cafe-multishop-prod.mjs (solo) | 6/6 |
narongix chain ran sequentially. phase2-cafe-multishop ran solo per feedback_phase2_cafe_multishop_solo_retry — first-attempt green (8th validation of the rule).
node test-u7-prod.mjs > "test-screenshots/prod-slice-u7/_run.log" 2>&1 before the directory existed → silent exit code 1. Same exact failure mode as U6 Gate 2 yesterday. Saved a durable rule: feedback_mkdir_before_bash_redirect. Pre-mkdir prepend is the one-line fix.COUNT(*) FILTER (WHERE state IN ('paid', 'partial_refund')) made the bash → node -e helper return empty string silently. Already documented in AGENTS.md as a known gotcha. Switched the two affected queries to temp .mjs probes that knex.raw() directly with no escape chain. New helper prodSqlProbe(sqlNoQuotes, label) writes a one-shot ESM script next to migrate.js, runs it, deletes it. Could be a memory note but it's already in AGENTS.md.feedback_pdf_lib_cold_worker_5xx_first_call. The retry-once-after-3s pattern was wired in the test from day 1 and absorbed it. By the time the test reran with the fixed SQL helper, the worker was warm and the route returned 200 first try.