Skip to content

feat(check-in): check-in zones - #651

Merged
harshtandiya merged 19 commits into
developfrom
feat/check-in-zones
Oct 25, 2026
Merged

harshtandiya merged 19 commits into
developfrom
feat/check-in-zones

Conversation

@harshtandiya

@harshtandiya harshtandiya commented Oct 23, 2026 •

Copy link
Copy Markdown
Collaborator

What changed

Check-in zones: named checkpoints of an event (a hall, a workshop, lunch, swag pickup) that front desk checks attendees into independently.

  • New Event Check-in Zone doctype. Every event gets a default "Main Entrance" zone that can be renamed but not disabled or deleted; a patch adds it to existing events and files existing check-ins under it.
  • Per zone: a repeat rule (once per day / once per event) and an optional list of ticket types allowed in.
  • Check-in API takes a zone (default zone when empty). Validate with no zone ("Any zone") returns the ticket's status at every zone.
  • Guests page: compact stats, a Check-in zones section (cards with progress; pick one to filter the list), a dot per zone on each guest row with a hover card, and a Check-in quick action. Zones are added and edited in a side drawer, here and in Event Settings.
  • Scanner: zone picker beside the search (remembered per device, plus "Any zone"); per-zone rows in the guest dialog. Picking a zone lists only the tickets it lets in. Attendance report gets a zone filter.
  • A check-in at any zone still blocks a refund.

Demo

1-guests-page 2-guests-zone-selected 3-guest-zone-hover 4-zone-drawer 5-check-in-page 6-check-in-any-zone 7-check-in-mobile

Testing

  • New tests: test_event_check_in_zone.py, zone cases in api/checkin/test_checkin.py and api/events/test_event_guests.py, refund block at a non-default zone.
  • Patch verified by bench migrate on a local site (1045 events, 178 check-ins backfilled).
  • Manual browser pass over settings, scanner (single zone, Any zone, undo per zone) and the Guests page. E2E check-in.spec.ts and Guest list specs pass.

Every event gets a 'Main Entrance' zone that cannot be deleted or disabled.
Check-ins record their zone, defaulting to the event's default zone, and a
patch backfills zones for existing events and check-ins.
The check-in endpoints take a zone. Each zone applies its own repeat rule
and optional ticket type list, and validate reports the ticket's state at
every enabled zone. The guest list and the attendance report read one zone,
the default zone when none is given.
A Check-in zones section lists the event's zones with their repeat rule,
ticket types and an enabled switch. A dialog adds, edits and deletes zones.
The scanner remembers a zone per device and event, or Any zone, and sends it
with every scan, check-in, undo and guest list fetch. The guest dialog shows
the attendee's state at each zone, with per-zone check-in and undo in Any
zone mode. Manage > Guests filters check-in status by zone.
set_only_once on the zone's event compared the autoincrement event name as
an int against its stored string, so every save of a zone failed. The zone
now refuses a move to another event in validate instead.
@github-actions

github-actions Bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Backend test coverage: 93%

Commit 6e8aa38. Test modules are excluded.

Coverage by file
Name Stmts Miss Cover
buzz/__init__.py 5 5 0%
buzz/analytics.py 32 2 94%
buzz/api/account/__init__.py 32 2 94%
buzz/api/account/exceptions.py 8 0 100%
buzz/api/account/schemas.py 7 0 100%
buzz/api/account/services.py 15 1 93%
buzz/api/auth/__init__.py 11 0 100%
buzz/api/auth/schemas.py 3 0 100%
buzz/api/booking/__init__.py 48 2 96%
buzz/api/booking/coupons.py 25 0 100%
buzz/api/booking/details.py 98 6 94%
buzz/api/booking/downloads.py 37 0 100%
buzz/api/booking/event_data.py 49 4 92%
buzz/api/booking/exceptions.py 34 0 100%
buzz/api/booking/guests.py 71 10 86%
buzz/api/booking/holds.py 64 1 98%
buzz/api/booking/my_bookings.py 9 3 67%
buzz/api/booking/schemas.py 47 0 100%
buzz/api/booking/services.py 233 16 93%
buzz/api/campaigns/__init__.py 34 19 44%
buzz/api/campaigns/exceptions.py 11 0 100%
buzz/api/campaigns/schemas.py 2 0 100%
buzz/api/checkin/__init__.py 21 0 100%
buzz/api/checkin/exceptions.py 20 0 100%
buzz/api/checkin/schemas.py 12 0 100%
buzz/api/checkin/services.py 107 2 98%
buzz/api/comments/__init__.py 29 0 100%
buzz/api/comments/activity.py 55 1 98%
buzz/api/comments/email_content.py 90 1 99%
buzz/api/comments/exceptions.py 17 0 100%
buzz/api/comments/schemas.py 17 0 100%
buzz/api/comments/services.py 85 3 96%
buzz/api/communications/__init__.py 16 0 100%
buzz/api/communications/exceptions.py 11 0 100%
buzz/api/communications/schemas.py 6 0 100%
buzz/api/communications/services.py 64 1 98%
buzz/api/communities/__init__.py 62 1 98%
buzz/api/communities/creation.py 26 1 96%
buzz/api/communities/exceptions.py 19 0 100%
buzz/api/communities/notifications.py 34 2 94%
buzz/api/communities/queries.py 36 0 100%
buzz/api/communities/reviews.py 39 1 97%
buzz/api/communities/schemas.py 14 0 100%
buzz/api/communities/submissions.py 54 1 98%
buzz/api/events/__init__.py 52 0 100%
buzz/api/events/collaborators.py 100 4 96%
buzz/api/events/coupons.py 41 0 100%
buzz/api/events/exceptions.py 44 0 100%
buzz/api/events/guest_zones.py 48 0 100%
buzz/api/events/schemas.py 107 0 100%
buzz/api/events/services.py 237 10 96%
buzz/api/events/taxes.py 19 0 100%
buzz/api/events/ticket_types.py 52 0 100%
buzz/api/events/zoom.py 15 1 93%
buzz/api/exceptions.py 23 0 100%
buzz/api/filters/conditions.py 106 2 98%
buzz/api/filters/exceptions.py 5 0 100%
buzz/api/filters/schemas.py 6 0 100%
buzz/api/forms/__init__.py 24 0 100%
buzz/api/forms/answers.py 77 14 82%
buzz/api/forms/exceptions.py 27 0 100%
buzz/api/forms/fields.py 82 3 96%
buzz/api/forms/schemas.py 7 0 100%
buzz/api/forms/services.py 119 1 99%
buzz/api/imports/__init__.py 13 0 100%
buzz/api/imports/exceptions.py 20 0 100%
buzz/api/imports/schemas.py 28 0 100%
buzz/api/imports/services.py 65 5 92%
buzz/api/imports/sponsors.py 165 2 99%
buzz/api/imports/spreadsheet.py 41 3 93%
buzz/api/listings/__init__.py 9 0 100%
buzz/api/listings/exceptions.py 4 0 100%
buzz/api/listings/schemas.py 3 0 100%
buzz/api/listings/services.py 33 0 100%
buzz/api/maps/__init__.py 20 0 100%
buzz/api/maps/exceptions.py 11 0 100%
buzz/api/maps/schemas.py 9 0 100%
buzz/api/maps/services.py 65 0 100%
buzz/api/notifications/__init__.py 12 0 100%
buzz/api/notifications/schemas.py 9 0 100%
buzz/api/notifications/services.py 47 0 100%
buzz/api/payments/__init__.py 10 1 90%
buzz/api/payments/currencies.py 7 2 71%
buzz/api/payments/schemas.py 2 0 100%
buzz/api/playground/__init__.py 24 1 96%
buzz/api/playground/exceptions.py 9 0 100%
buzz/api/playground/schemas.py 6 0 100%
buzz/api/playground/services.py 91 5 95%
buzz/api/proposals/__init__.py 18 0 100%
buzz/api/proposals/exceptions.py 5 0 100%
buzz/api/proposals/schemas.py 26 0 100%
buzz/api/proposals/services.py 106 1 99%
buzz/api/schemas.py 13 0 100%
buzz/api/sponsorships/__init__.py 41 0 100%
buzz/api/sponsorships/enquiries.py 74 2 97%
buzz/api/sponsorships/exceptions.py 22 0 100%
buzz/api/sponsorships/forms.py 64 2 97%
buzz/api/sponsorships/manage.py 22 1 95%
buzz/api/sponsorships/schemas.py 21 0 100%
buzz/api/sponsorships/services.py 67 1 99%
buzz/api/sponsorships/sponsors.py 29 0 100%
buzz/api/tags/__init__.py 9 0 100%
buzz/api/tags/desk.py 35 9 74%
buzz/api/tags/exceptions.py 8 0 100%
buzz/api/tags/schemas.py 2 0 100%
buzz/api/tags/services.py 56 0 100%
buzz/api/teams/__init__.py 51 2 96%
buzz/api/teams/calendar.py 28 0 100%
buzz/api/teams/exceptions.py 26 0 100%
buzz/api/teams/invitations.py 34 0 100%
buzz/api/teams/public_page.py 26 8 69%
buzz/api/teams/schemas.py 14 0 100%
buzz/api/teams/services.py 105 1 99%
buzz/api/tickets/__init__.py 15 0 100%
buzz/api/tickets/emails.py 14 9 36%
buzz/api/tickets/exceptions.py 29 0 100%
buzz/api/tickets/schemas.py 4 0 100%
buzz/api/tickets/services.py 85 0 100%
buzz/api/tickets/windows.py 12 0 100%
buzz/booking_permissions.py 47 3 94%
buzz/buzz/doctype/buzz_custom_field/buzz_custom_field.py 20 4 80%
buzz/buzz/doctype/buzz_notification_subscription/buzz_notification_subscription.py 3 0 100%
buzz/buzz/web_form/apply_for_sponsorship/apply_for_sponsorship.py 3 3 0%
buzz/buzz/web_form/propose_a_talk/propose_a_talk.py 3 3 0%
buzz/buzz_marketing/doctype/buzz_campaign/buzz_campaign.py 21 7 67%
buzz/comments.py 81 4 95%
buzz/emails.py 19 0 100%
buzz/events/banner_pattern.py 35 0 100%
buzz/events/buzz_events_listing.py 52 11 79%
buzz/events/countdown.py 13 0 100%
buzz/events/doctype/additional_event_page/additional_event_page.py 9 0 100%
buzz/events/doctype/buzz_event/buzz_event.py 283 22 92%
buzz/events/doctype/buzz_event_collaborator/buzz_event_collaborator.py 47 2 96%
buzz/events/doctype/buzz_event_form/buzz_event_form.py 11 4 64%
buzz/events/doctype/buzz_form_field/buzz_form_field.py 3 0 100%
buzz/events/doctype/buzz_price/buzz_price.py 10 0 100%
buzz/events/doctype/buzz_settings/buzz_settings.py 26 4 85%
buzz/events/doctype/buzz_tag/buzz_tag.py 30 2 93%
buzz/events/doctype/buzz_tag_link/buzz_tag_link.py 14 2 86%
buzz/events/doctype/buzz_team/buzz_team.py 102 3 97%
buzz/events/doctype/buzz_team/team_map.py 23 0 100%
buzz/events/doctype/buzz_team/team_page.py 99 2 98%
buzz/events/doctype/buzz_team_membership/buzz_team_membership.py 93 1 99%
buzz/events/doctype/buzz_team_settings/buzz_team_settings.py 45 0 100%
buzz/events/doctype/buzz_theme/buzz_theme.py 50 3 94%
buzz/events/doctype/buzz_theme/tokens.py 29 1 97%
buzz/events/doctype/buzz_theme_token/buzz_theme_token.py 3 0 100%
buzz/events/doctype/community_event_request/community_event_request.py 59 13 78%
buzz/events/doctype/event_category/event_category.py 9 0 100%
buzz/events/doctype/event_check_in/event_check_in.py 19 2 89%
buzz/events/doctype/event_check_in_zone/event_check_in_zone.py 40 0 100%
buzz/events/doctype/event_check_in_zone_ticket_type/event_check_in_zone_ticket_type.py 3 0 100%
buzz/events/doctype/event_cohost/event_cohost.py 4 0 100%
buzz/events/doctype/event_communication/event_communication.py 16 0 100%
buzz/events/doctype/event_external_link/event_external_link.py 4 0 100%
buzz/events/doctype/event_featured_speaker/event_featured_speaker.py 4 0 100%
buzz/events/doctype/event_feedback/event_feedback.py 4 0 100%
buzz/events/doctype/event_host/event_host.py 4 0 100%
buzz/events/doctype/event_payment_gateway/event_payment_gateway.py 4 0 100%
buzz/events/doctype/event_sponsor/event_sponsor.py 15 2 87%
buzz/events/doctype/event_talk/event_talk.py 8 0 100%
buzz/events/doctype/event_template/event_template.py 43 4 91%
buzz/events/doctype/event_template_add_on/event_template_add_on.py 4 0 100%
buzz/events/doctype/event_template_custom_field/event_template_custom_field.py 4 0 100%
buzz/events/doctype/event_template_ticket_category/event_template_ticket_category.py 4 0 100%
buzz/events/doctype/event_template_ticket_type/event_template_ticket_type.py 4 0 100%
buzz/events/doctype/event_topic/event_topic.py 18 5 72%
buzz/events/doctype/event_topic/topic_page.py 32 32 0%
buzz/events/doctype/event_topic_link/event_topic_link.py 3 0 100%
buzz/events/doctype/event_track/event_track.py 4 0 100%
buzz/events/doctype/event_user_preferences/event_user_preferences.py 4 4 0%
buzz/events/doctype/event_venue/details.py 37 1 97%
buzz/events/doctype/event_venue/event_venue.py 95 1 99%
buzz/events/doctype/event_venue/geocode.py 72 9 88%
buzz/events/doctype/event_venue/map_link.py 106 5 95%
buzz/events/doctype/event_venue_location/event_venue_location.py 3 0 100%
buzz/events/doctype/offline_payment_method/offline_payment_method.py 11 0 100%
buzz/events/doctype/schedule_item/schedule_item.py 4 0 100%
buzz/events/doctype/social_media_link/social_media_link.py 3 0 100%
buzz/events/doctype/speaker_profile/speaker_profile.py 8 0 100%
buzz/events/doctype/sponsor_enquiry_form/sponsor_enquiry_form.py 54 5 91%
buzz/events/doctype/sponsorship_tier/sponsorship_tier.py 27 1 96%
buzz/events/doctype/talk_speaker/talk_speaker.py 3 0 100%
buzz/events/doctype/utm_parameter/utm_parameter.py 4 0 100%
buzz/events/og_image.py 141 7 95%
buzz/events/og_text.py 66 1 98%
buzz/events/online_meeting.py 42 0 100%
buzz/events/report/event_attendance_summary/event_attendance_summary.py 65 32 51%
buzz/events/report/event_overview/event_overview.py 27 0 100%
buzz/hooks.py 41 0 100%
buzz/html_lines.py 66 1 98%
buzz/install.py 66 32 52%
buzz/integrations/zoom.py 3 3 0%
buzz/islands.py 9 1 89%
buzz/payments.py 96 33 66%
buzz/permissions.py 182 13 93%
buzz/playground/__init__.py 32 9 72%
buzz/playground/checklist.py 64 43 33%
buzz/playground/cleanup.py 43 5 88%
buzz/playground/demo_data.py 95 3 97%
buzz/playground/doctype/playground_email/playground_email.py 4 0 100%
buzz/playground/doctype/playground_sandbox/playground_sandbox.py 6 0 100%
buzz/playground/inbox.py 67 9 87%
buzz/proposals/description.py 68 0 100%
buzz/proposals/doctype/event_proposal/event_proposal.py 71 5 93%
buzz/proposals/doctype/proposal_speaker/proposal_speaker.py 4 0 100%
buzz/proposals/doctype/sponsorship_deck_item/sponsorship_deck_item.py 4 0 100%
buzz/proposals/doctype/sponsorship_enquiry/sponsorship_enquiry.py 81 5 94%
buzz/proposals/doctype/talk_proposal/talk_proposal.py 96 5 95%
buzz/proposals/doctype/talk_proposal_status/talk_proposal_status.py 4 0 100%
buzz/subscriptions.py 45 1 98%
buzz/tasks.py 4 4 0%
buzz/telemetry.py 46 0 100%
buzz/telemetry_scan.py 55 8 85%
buzz/ticketing/doctype/additional_field/additional_field.py 4 0 100%
buzz/ticketing/doctype/attendee_ticket_add_on/attendee_ticket_add_on.py 3 0 100%
buzz/ticketing/doctype/buzz_coupon_code/buzz_coupon_code.py 153 2 99%
buzz/ticketing/doctype/buzz_coupon_ticket_type/buzz_coupon_ticket_type.py 4 0 100%
buzz/ticketing/doctype/event_booking/event_booking.py 351 17 95%
buzz/ticketing/doctype/event_booking_attendee/event_booking_attendee.py 20 7 65%
buzz/ticketing/doctype/event_booking_refund/event_booking_refund.py 56 0 100%
buzz/ticketing/doctype/event_booking_refund_ticket/event_booking_refund_ticket.py 4 0 100%
buzz/ticketing/doctype/event_payment/event_payment.py 4 0 100%
buzz/ticketing/doctype/event_ticket/event_ticket.py 73 7 90%
buzz/ticketing/doctype/event_ticket_category/event_ticket_category.py 27 0 100%
buzz/ticketing/doctype/event_ticket_type/event_ticket_type.py 91 0 100%
buzz/ticketing/doctype/ticket_add_on/ticket_add_on.py 9 1 89%
buzz/ticketing/doctype/ticket_add_on_value/ticket_add_on_value.py 3 0 100%
buzz/ticketing/doctype/ticket_cancellation_item/ticket_cancellation_item.py 4 0 100%
buzz/ticketing/doctype/ticket_cancellation_request/ticket_cancellation_request.py 19 1 95%
buzz/ticketing/doctype/ticket_hold/ticket_hold.py 44 1 98%
buzz/ticketing/report/detailed_event_registrations/detailed_event_registrations.py 113 5 96%
buzz/ticketing/report/event_add_ons_overview/event_add_ons_overview.py 22 3 86%
buzz/uninstall.py 10 2 80%
buzz/utils.py 139 22 84%
buzz/www/community.py 24 0 100%
buzz/www/dashboard.py 27 3 89%
buzz/www/event/banner_color.py 20 6 70%
buzz/www/event/date_range.py 42 5 88%
buzz/www/event/dot_map.py 38 2 95%
buzz/www/event/index.py 163 6 96%
buzz/www/event/link_icons.py 1 0 100%
buzz/www/event/meta.py 64 5 92%
buzz/www/event/registration.py 84 15 82%
buzz/www/event/venue_map.py 35 1 97%
buzz/www/events.py 102 9 91%
buzz/www/events_sitemap.py 11 0 100%
buzz/www/site_header.py 40 1 98%
buzz/www/user_profile.py 36 0 100%
TOTAL 10011 681 93%

106 empty files skipped.

The guest list response now carries, for each enabled zone, how many
guests checked in out of the tickets it lets in, plus each guest's status
at every zone. Picking a zone narrows the list to the tickets it admits.
ZoneStatus moves to the events schemas to break an import cycle.
Zones open in the same side drawer as collaborators and ticket types.
The repeat rule becomes a segmented control and ticket types become
toggle chips, with help text that follows the choice.
- Compact stat cards (frappe-ui NumberCard and ChartCard) for
  registrations, check-ins and the ticket type mix.
- A Check-in zones section with a card per zone showing progress.
  Picking a card narrows the list and the stats to that zone; the edit
  and add actions open the zone drawer.
- Each guest row gets a dot per zone: green when checked in, hollow when
  the ticket is not allowed there.
- Quick actions lead with a Check-in tile, then copy link, manage zones
  and CSV export.
The default zone links to its event, so deleting an event failed the link
check. Zones now go with the event unless it has check-ins, which still
block the delete on their own link.
The legend was a list, so it counted as guest rows for assistive tech and
the e2e row count.
Hovering the zone dots on a guest row lists every zone with its check-in
time, or whether the guest has not checked in or is not allowed there,
under a count of the zones checked in.
The zone hover card rows are ItemListRow, so they match frappe-ui menus,
and the zone card bar is Progress instead of a hand-drawn bar.
The search takes three quarters of the row and the zone select the rest,
with a floor so a zone name stays readable on a phone. Scan stays in the
header.
- A zone always narrows the guest list to the tickets it lets in, so the
  front desk no longer lists guests it would refuse.
- Disabled zones cannot narrow the list.
- A zone only accepts ticket types of its own event.
- Check-in locks the ticket row, so two desks cannot record it twice.
- One query reads an existing check-in and its time.
- A check-in without a zone says so; zones are no longer renameable.
- The check-in dialog reads zone statuses from the guest row instead of
  validating the ticket again, and a check-in or undo refreshes the row.
- In Any zone mode the dialog stays open after a check-in.
- A check-in from an earlier day shows its date, not only the time.
- Zone cards hide edit and add for members who cannot edit the event.
- One zoneRulesLabel serves the zone cards and the settings panel.
# Conflicts:
#	buzz/api/events/__init__.py
#	buzz/api/events/services.py
#	buzz/patches.txt
#	dashboard/src/pages/manage/events/EventGuests.vue
The sidebar now carries a notifications count, so the first bare number on
the page is no longer the registration total.
On a phone the search and zone picker stack, and Scan becomes a solid
button floating at the bottom centre, where the thumb rests. Wider screens
keep the side-by-side row and the header button.
@harshtandiya
harshtandiya marked this pull request as ready for review October 25, 2026 20:20

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

@harshtandiya
harshtandiya merged commit 7e51629 into develop Oct 25, 2026
10 checks passed
@harshtandiya
harshtandiya deleted the feat/check-in-zones branch October 25, 2026 20:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant