Commit graph

8 commits

Author SHA1 Message Date
049f433930 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>
2026-07-20 07:40:05 +00:00
1ba3f9ad1c Ticket vouchers now return remaining and decrement on use
The ticket-vouchers lookup previously returned the tier entitlement
every time, so a donor could keep claiming free tickets by re-
submitting the form. It now subtracts vouchers already consumed:

  remaining = entitled - used

where `used` is the sum of the Vouchers column across that donor's
prior ticket orders (each checkout stores what it applied). Response
gains entitled/used/remaining; `vouchers` is now the remaining count
the form should grant. Consumption is implicit — no counter to keep in
sync — and resets by zeroing/deleting the Vouchers value on the order
row in NocoDB.

- nocodb: findByEmail + vouchersUsedByEmail (case-insensitive).
- 8 new tests (36 total). Doc updated with the new response + reset.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 22:11:08 +00:00
267957d333 Non-food vendors get no entry ticket
Only food vendors receive gate passes. The /vendor-webhook/non-food
endpoint now acknowledges the submission ({"status":"ignored"}) and
issues nothing, instead of creating a 1-pass ticket — kept as a safe
no-op so an accidentally-wired FluentForms feed doesn't 404. Food
webhook unchanged. Doc + tests updated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 21:57:22 +00:00
7296555964 Vendor webhooks, free kids through 12, multi-origin lookup CORS
Children now free through age 12:
- Scannable/paid ticket total = adults + youth 13-16 only; kids 12 &
  under (0-4, 5-9, 10-12) are stored but not counted (charging starts
  at 13). computeTotal + freeKidsCount in fields.ts, webhook guard,
  scan/admin badges, event-report labels, docs, and personas updated.

Vendor booth webhooks (vendors.beartariacampgrounds.com):
- New /vendor-webhook/food (2 named pass-holders) and
  /vendor-webhook/non-food (1 pass-holder), reusing WEBHOOK_SECRET.
  Booth name -> ticket title; each named person = one entry pass;
  tagged with a "Food Vendor"/"Vendor" Ticket Type (badge on scan +
  event-report rollup). Idempotent + QR email like the attendee hook.
- Extracted shared FluentForms parsing (nameGroup/qty/selected/
  addressLine/readDonor) into fluentforms.ts; attendee webhook now
  imports it. 13 new unit tests.

Public lookup CORS is now a comma-separated allowlist; the caller's
Origin is echoed only if it matches. tickets + vendors both allowed
on donor-eligibility and ticket-vouchers.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 05:13:07 +00:00
b7c77afe02 Rework webhook + model for the 2026 Tickets form
New form schema: up to 10 named adults, youth 13-16, kids 10-12 / 5-9 / 0-4,
donor tier/vouchers (from the lookups), parking/RV/UTV/ice payment fields.
- Scannable total = adults + youth + kids 10-12 + kids 5-9 (kids 0-4 free).
- Store + display adult names on a good scan; show donor tier, UTV, vouchers.
- Ice: payment_ice = 1-4 tickets ($20 each), 1 ticket = 3 bags.
- New NocoDB 2026 schema (with Id PK); webhook parses compound names,
  quantity/payment fields (nested objects or money strings).
- Our webhook keeps sending the QR ticket email (FluentForms sends the receipt);
  from address is now info@beartariacampgrounds.com.
Updated /test personas, /webhook-doc, tests, and the app display.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 03:23:06 +00:00
d293e53ee6 Add Check-in / Ice / Banquet scan modes
- Ice mode: prepaid ice bags (Ice Total / Ice Redeemed columns) redeemed
  independently of ticket check-ins; grab all bags at once or some now.
- Banquet mode: donor total (online + offline) looked up by the ticket's
  email via the Donors Master List, with a manual email override. New
  DonorService + POST /api/banquet.
- Redeem generalized over a resource (tickets|ice); audit records ice actions.
- App gains a mode selector; webhook maps ice_bags (defaults to
  ICE_BAGS_DEFAULT when only a boolean ice option is present).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 04:07:51 +00:00
b36d63a6a4 Add check-in audit logging + in-app history view
Every successful check-in/undo writes a row to the "2026 Ticket Audit Logs"
NocoDB table (timestamp, people, code, action, name, remaining-after).
Non-fatal: audit failures never block a gate check-in. New GET /api/audit
endpoint (global or per-code). Admin panel gains a global "Recent check-ins"
panel and per-ticket history. Audit table is optional via NOCODB_AUDIT_TABLE_ID.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 03:53:30 +00:00
3397e3e3ec Initial Camp Scan ticketing system
Backend (Fastify + TS): FluentForms webhook -> NocoDB row + QR + MailerSend
email; PIN auth; scan/lookup/redeem with per-code serialization; reusable QR
codes with count-based check-in; admin search.

App (Expo, one codebase): Android APK + iPhone PWA. Login, camera scanner
(native + web barcode-detector split), green/red overlay with sound + haptics,
admin lookup/redeem. Session token persisted per device.

Ops: multi-stage Dockerfile serving API + PWA same-origin, compose bound to
127.0.0.1; Forgejo Actions runner + tag-triggered signed APK build for Obtainium.
Docs in README.md and INSTALL.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 03:47:59 +00:00