Skip to content

feat(facility): track badge prints and report yearly gaps#962

Open
jee7s wants to merge 1 commit into
mainfrom
feat/badge-print-tracking
Open

feat(facility): track badge prints and report yearly gaps#962
jee7s wants to merge 1 commit into
mainfrom
feat/badge-print-tracking

Conversation

@jee7s

@jee7s jee7s commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

What & why

Physical ID badges get printed for people, but nothing recorded that a badge
was printed, when, or by whom — so reprints and the yearly badge refresh
were tracked by memory. This adds a durable record of badge prints and a Facility
Ops report answering "who has / hasn't had a badge printed in year X?"

Design: checkin-app/docs/designs/BADGE_PRINT_TRACKING.md.

Decisions (from the product interview)

  • Mark-printed + report, not an in-app print/render flow and not derived from
    scans. Recording a print is a deliberate human act, decoupled from generating a
    badge PDF (which already exists on Print ID Badges). Both alternatives are
    written up as rejected in the design doc.
  • New BadgePrint model (personId, printedAt, printedById, optional
    note) — a sibling of RawBadgeLog. Append-only; multiple rows per person are
    normal (reprints, yearly refresh). The report de-dupes to one row per person
    with the most recent date + a count.
  • Placement: Facility Ops, not Membership Ops — badge printing is a
    physical-facility concern and a direct sibling of the existing Raw Badge Events
    / Print ID Badges tabs, same isSysadmin|isBoardMember gate. (Rationale in the
    doc.)
  • "Needs a badge this year" = anyone who checked in (Visit) that year.
    Defensible from existing data (came to the facility → should have a badge; never
    came → doesn't need one). Read only as a where filter, so no Visit rows are
    returned and the route auth drift-guard's edge rule doesn't apply.
  • Calendar-year (UTC) window for v1. The membership-year / timezone-aware
    boundary is written up as the deferred alternative.

Changes

  • BadgePrint model + additive migration 20260709020000_badge_print_tracking
    (new table only — two FKs to Person, two indexes; no data change).
  • GET/POST /api/facility/badge-prints (sysadmin/board): POST marks people
    printed (single + bulk in one call); GET returns the yearly report (printed +
    gap lists).
  • New Badge Prints tab under Facility Ops: year picker, gap list with per-row
    and bulk mark-printed, and the printed list.
  • Regenerated security/generated/classifications.ts (BadgePrint field tiers);
    new page/route registered in pageRegistry + authzRegistry.

Testing

  • Unit: badgePrints.test.ts — year-window boundaries (half-open, adjacent
    years, in/out of range).
  • Page: badge-prints/__tests__/page.test.tsx — renders printed + gap lists,
    single and bulk mark POST the right body.
  • Integration: badge-prints/__tests__/route.integration.test.ts (7 tests) —
    authz 401/403 (GET + POST), single + bulk mark with the caller as actor, report
    printed/gap correctness across the 2025↔2026 boundary, and reprint/duplicate
    behavior (two rows, one report row, count 2).
  • Full pre-commit suite green (1715 tests); tsc --noEmit and
    eslint --max-warnings 0 clean.

Deferred

Membership-year/timezone-aware boundary, editing/deleting a print row,
registering the report route through the security registry, per-person print
history UI. All noted in the design doc.

🤖 Generated with Claude Code

Add a durable record of physical ID badge prints and a Facility Ops report
answering "who has / hasn't had a badge printed in year X".

- New BadgePrint model (personId, printedAt, printedById, optional note),
  sibling of RawBadgeLog. Append-only; reprints/yearly refresh add rows.
- POST /api/facility/badge-prints marks people printed (single + bulk, same
  call). GET returns the calendar-year report: printed (grouped per person with
  last date, recorder, count) and the gap list (people who visited that year
  with no print). Both sysadmin/board gated.
- New "Badge Prints" tab under Facility Ops with a year picker, gap list with
  per-row + bulk mark-printed, and the printed list.
- Additive migration (new table only). Calendar-year (UTC) window; membership-
  year/timezone boundary deferred. See docs/designs/BADGE_PRINT_TRACKING.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@jee7s jee7s added the post-first-release Deferred until after the first production release label Jul 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

post-first-release Deferred until after the first production release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant