Fix ice bag count + default ice check-in to 1 bag
The form sends payment_ice as a descriptive label, e.g. "One Ice
ticket good for one bag per day (3 total bags)". The old parser pulled
the first number ("3") and treated it as 3 tickets, then multiplied by
3 bags/ticket → 9 bags for one ice ticket (18 for two). New
iceBagsFromPayment reads the "(N total bags)" the label states
directly, with worded-count and numeric dollar/count fallbacks for
forward compatibility. 1 ice → 3 bags, 2 → 6. 5 new tests.
Scanner: Ice mode now defaults the check-in count to 1 (a bag at a
time) instead of all remaining bags; staff can bump it up.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
49e45ff94c
commit
049f433930
4 changed files with 64 additions and 9 deletions
|
|
@ -56,6 +56,34 @@ export function addressLine(v: any): string | undefined {
|
|||
return undefined;
|
||||
}
|
||||
|
||||
const NUMBER_WORDS: Record<string, number> = { one: 1, two: 2, three: 3, four: 4, five: 5, six: 6 };
|
||||
|
||||
/**
|
||||
* Total bags of ice from the `payment_ice` field. The form sends a descriptive
|
||||
* option label, e.g. "One Ice ticket good for one bag per day (3 total bags)",
|
||||
* so the reliable signal is the "(N total bags)" the label states. Falls back to
|
||||
* a worded ticket count ("Two Ice tickets" → 2 × bagsPerTicket), then to a
|
||||
* numeric dollar-total/ticket-count for forward compatibility.
|
||||
*/
|
||||
export function iceBagsFromPayment(
|
||||
value: unknown,
|
||||
opts: { bagsPerTicket: number; ticketPrice: number },
|
||||
): number {
|
||||
const { bagsPerTicket, ticketPrice } = opts;
|
||||
const s = typeof value === "string" ? value : "";
|
||||
// Preferred: the label states the total bags directly.
|
||||
const bagsMatch = s.match(/(\d+)\s*total\s*bags/i);
|
||||
if (bagsMatch) return Math.max(0, parseInt(bagsMatch[1], 10));
|
||||
// Worded ticket count: "One Ice ticket", "Two Ice tickets".
|
||||
const wordMatch = s.match(/\b(one|two|three|four|five|six)\b\s+ice/i);
|
||||
if (wordMatch) return NUMBER_WORDS[wordMatch[1].toLowerCase()] * bagsPerTicket;
|
||||
// Numeric fallback: a dollar total (>= price) → tickets; else a small count.
|
||||
const n = qty(value);
|
||||
if (n <= 0) return 0;
|
||||
const tickets = n >= ticketPrice ? Math.round(n / ticketPrice) : Math.round(n);
|
||||
return Math.max(0, tickets) * bagsPerTicket;
|
||||
}
|
||||
|
||||
/** Donor status from the hidden lookup fields + the "are you a donor?" radio. */
|
||||
export function readDonor(body: Record<string, any>): { isDonor: boolean; donorTier: string } {
|
||||
const donorTier = String(body.donor_tier ?? "").trim();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue