feat(validations): merchant (bar/lavazh) ticket validations end-to-end
In-park merchants discharge customers' parking: a merchant user scans the ticket on their device (/validate; validation:create + program↔user binding) and applies their program — comp / first-N-minutes free / amount-off (capped, typed at scan) / percent. All money stays at the booth: the quote folds live validations in a canonical order (timeCredit → percent → fixed → comp, net floors at 0, Σ lines ≡ gross − net), the payment records gross/discount and CONSUMES the validation ids (an overstay's fresh period never re-applies them), the receipt prints the gross → lines → net story, and the Z/X-report carries discountTotalMinor leakage. Every apply/void is a signed, attributed ledger event (refId = append-only void); program config is /setup/site master data (Bar/Lavazh checkboxes + right-column panel, tabs when both) whose saves sign config_change. Migration 0024 + reset-db drift-guard entries; 8 route integration tests + priceSession fold suite. See wiki/concepts/validation-discounts.md for the full design record. Claude-Session: https://claude.ai/code/session_01YYkpEsLmoQPaize5ec3oUm
This commit is contained in:
@@ -80,6 +80,8 @@ function receiptFigures(
|
||||
currency?: string;
|
||||
tender?: "cash" | "card";
|
||||
graceExitMin?: number;
|
||||
grossMinor?: number;
|
||||
validationLines?: { label: string; discountMinor: number }[];
|
||||
};
|
||||
return {
|
||||
ticketId,
|
||||
@@ -89,6 +91,9 @@ function receiptFigures(
|
||||
currency: p.currency ?? "ALL",
|
||||
tender: p.tender === "card" ? "card" : "cash",
|
||||
graceExitMin: typeof p.graceExitMin === "number" ? p.graceExitMin : null,
|
||||
// Merchant validations, as settled on the signed payment (gross → lines → net).
|
||||
grossMinor: typeof p.grossMinor === "number" ? p.grossMinor : null,
|
||||
validationLines: Array.isArray(p.validationLines) ? p.validationLines : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user