← All tests

Promotion type selector + % Discount STAGING

From Narong's "Polishing Work" list (Notion, 2026-07-17). Verified on lumiere.staging 7/7 + staging smoke 5/5.

"Promotions — Require users to choose Promotion Type. Default: BOGO, % Discount"
Until now every promotion was silently a BOGO — the config form had no type field at all. This adds an explicit Promotion Type choice and introduces % Discount as a second type. A migration adds cafe.promotions.discount_percent NUMERIC(5,2), kept nullable so "no percentage" (a BOGO) stays distinguishable from "0% off". promo_type already existed, so every pre-existing row is already a valid 'bogo' — no backfill needed.
Scope: config + migration only — the POS apply-side for % Discount is deliberately NOT wired yet. We're waiting on Narong for what should happen when a shop has several promotions active at once (there's one "Apply Promotion" button today). Until he answers, a % promo is inert at the till rather than half-applied, so money logic on the live POS path is byte-for-byte unchanged — which the last check below proves.

The type selector (defaults to BOGO) → % reveals a percent field

type selector, BOGO selected
BOGO is the default; no percent field.
type selector, percent selected
Choosing % Discount reveals the percentage input.

Validation, persistence, and the list badge

percent validation
0 and >100 rejected (same rule client + server).
percent promo created
Saved as promo_type='percent', discount_percent=15 (asserted in the DB).
list badges
The row shows "15% off" — previously it printed the raw bogo string.

Regression guard — the live money path is untouched

BOGO still applies
With a % promo present, the existing BOGO still applies exactly as before: 2×$2 + 2×$5 → total $7.

Checks (lumiere.staging, Starter)

Modal shows a type selector, defaulting to BOGO (no percent field)pass
Choosing % Discount reveals the percent fieldpass
Percent validation rejects 0 and >100pass
Create 15% → DB has promo_type='percent' + discount_percent=15pass
List badge reads "15% off", no raw type stringpass
BOGO still applies unchanged with a % promo present ($7)pass
Migration via migrate.js (the prod path) — idempotent both wayspass
Staging smoke5/5

nix-cafe staging d1dd6d7 · nix-OS-backend staging 9ea31f1 (migration 20260720120000_cafe_promotions_discount_percent, already applied to nix_db_staging). Parked for the scheduled prod promote — it rides the same node migrate.js run the n1 POS-user-role migration already needs.