fix(subs): price out-of-window charge from minutes actually parked, not a fixed entry stamp
An out-of-window subscriber entry stamped a FIXED windowOwedMinor = the whole gap to window-open (e.g. 800 ALL for a 13:21 arrival to a 20:00 window) and deferred it to exit. That over-charged anyone who left before the window opened — a 1-hour visit was billed as 6.5 hours. The amount isn't knowable at entry: a subscriber may enter early, leave after an hour, come and go several times before the window opens, and linger past window-close. They should pay only for the minutes actually parked outside the window (capped at the window edges) — exactly what minutesOutsideWindow already computes. So the entry now stamps a MARKER only (outOfWindow: true + windowTariffVersionId for reproducible pricing), no fixed amount. The exit gate and booth quote price it live via windowOwedBetween(entry → settle-time), which already caps at the window edges (early entry stops accruing at window-open; the in-window portion of a crossing stay is free; the late-exit tail keeps accruing until payment). Both already called that one function, so they agree. - subscription-flow: entry stamps outOfWindow marker; the advisory slip is now a scannable out-of-window TICKET (Code128 + QR of the occurrence id). - shared LedgerPayload: add outOfWindow; mark windowOwedMinor/windowGap*/ windowCurrency deprecated read-only (historic signed events still type-check). - BoothScreen: window-charge badge keys on outOfWindow (or the old stamp). - ActiveSessions: drop the always-on "Open barrier" for subscribers — the assist-open / window-charge payment live in the pay modal, so the list can't one-click past an unpaid out-of-window charge. Verified the live model on a DB copy: 13:21→14:30 = 200 ALL; 19:55(in grace)→ 23:00 = 0; 19:00→21:30 (crosses into window) = 100 ALL. Existing signed occurrences left untouched (immutable). build+lint 14/14, shared 87/87. Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
This commit is contained in:
@@ -112,9 +112,12 @@ function eventBadges(p: LedgerEvent["payload"]): string[] {
|
||||
if (p.permitRefused) keys.push("booth.badgeSubRefused");
|
||||
if (p.ticketPrinted === false) keys.push("booth.badgeNoTicket");
|
||||
if (p.subscriptionSale) keys.push("booth.badgeSubSale");
|
||||
// Subscriber entered/exited outside their plan's allowed window → owes a deferred
|
||||
// transient charge, collected (gated) at exit. Flag it so the operator KNOWS now.
|
||||
if (typeof p.windowOwedMinor === "number" && p.windowOwedMinor > 0) keys.push("booth.badgeWindowCharge");
|
||||
// Subscriber entered outside their plan's allowed window → will owe a transient charge
|
||||
// for the minutes actually parked out-of-window, priced + collected (gated) at exit.
|
||||
// Flag it so the operator KNOWS now. (`windowOwedMinor` is the old fixed-amount stamp,
|
||||
// kept so historic events still badge.)
|
||||
if (p.outOfWindow === true || (typeof p.windowOwedMinor === "number" && p.windowOwedMinor > 0))
|
||||
keys.push("booth.badgeWindowCharge");
|
||||
if (p.source === "manual" && !p.subscriptionSale) keys.push("booth.badgeManualOpen");
|
||||
return keys;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user