From d2d87a9b7f17d38af70a7048d0c5c5563ef30854 Mon Sep 17 00:00:00 2001 From: Mia Bennett Date: Tue, 7 Jul 2026 14:30:54 +0930 Subject: [PATCH] fix(visitor_mailer): visitor check-in notification (PPT-2535) --- drivers/place/visitor_mailer.cr | 7 ++ drivers/place/visitor_mailer_spec.cr | 131 +++++++++++++++++++++++++++ drivers/place/visitor_models.cr | 3 +- 3 files changed, 140 insertions(+), 1 deletion(-) diff --git a/drivers/place/visitor_mailer.cr b/drivers/place/visitor_mailer.cr index 8ee9dd5017..9acade1314 100644 --- a/drivers/place/visitor_mailer.cr +++ b/drivers/place/visitor_mailer.cr @@ -285,6 +285,13 @@ class Place::VisitorMailer < PlaceOS::Driver case guest_details in GuestCheckin + # the same signal is fired for check-out (state=false), only notify the + # host of arrivals. checkin is nilable for backwards compatibility. + if guest_details.checkin == false + logger.debug { "ignoring guest checkin event as the visitor was checked out" } + return + end + send_checkedin_email( @notify_checkin_template, guest_details.attendee_email, diff --git a/drivers/place/visitor_mailer_spec.cr b/drivers/place/visitor_mailer_spec.cr index 11517aa2bd..8c4bdc1d21 100644 --- a/drivers/place/visitor_mailer_spec.cr +++ b/drivers/place/visitor_mailer_spec.cr @@ -1373,4 +1373,135 @@ DriverSpecs.mock_driver "Place::VisitorMailer" do system(:Mailer)[:send_count].should eq count_before_induction_decline + 1 system(:Mailer)[:last_template].should eq ["visitor_invited", "custom_declined"] + + # ================================================================== + # visitor check-in tests (PPT-2535) + # ================================================================== + + # ------------------------------------------------------------------ + # Test 33: guest_event — visitor check-in sends the notify_checkin + # email to the host. Payloads mirror what staff-api emits + # from bookings.cr#guest_checkin and events.cr#guest_checkin. + # ------------------------------------------------------------------ + + count_before_checkin = system(:Mailer)[:send_count].as_i + checked_in_before = status[:users_checked_in]?.try(&.as_i64) || 0_i64 + + booking_checkin_payload = { + action: "checkin", + id: 900_i64, + checkin: true, + booking_id: 900_i64, + resource_id: "visitor@external.com", + resource_ids: ["visitor@external.com"], + event_title: "Catch Up", + event_summary: "Catch Up", + event_starting: now + 3600, + attendee_name: "Visitor One", + attendee_email: "visitor@external.com", + host: "host@example.com", + zones: ["zone-building", "zone-room"], + }.to_json + + publish("staff/guest/checkin", booking_checkin_payload) + sleep 1.0 + + system(:Mailer)[:send_count].should eq count_before_checkin + 1 + system(:Mailer)[:last_to].should eq "host@example.com" + system(:Mailer)[:last_template].should eq ["visitor_invited", "notify_checkin"] + status[:users_checked_in].should eq checked_in_before + 1 + + args_checkin = system(:Mailer)[:last_args] + args_checkin["visitor_email"].should eq "visitor@external.com" + args_checkin["host_email"].should eq "host@example.com" + args_checkin["event_title"].should eq "Catch Up" + + event_checkin_payload = { + action: "checkin", + id: 901_i64, + checkin: true, + system_id: "sys-room1", + event_id: "evt-900", + event_ical_uid: "ical-900", + host: "host@example.com", + resource: "room1@example.com", + event_title: "Catch Up", + event_summary: "Catch Up", + event_starting: now + 3600, + attendee_name: "Visitor One", + attendee_email: "visitor@external.com", + zones: ["zone-building", "zone-room"], + }.to_json + + publish("staff/guest/checkin", event_checkin_payload) + sleep 1.0 + + system(:Mailer)[:send_count].should eq count_before_checkin + 2 + system(:Mailer)[:last_to].should eq "host@example.com" + system(:Mailer)[:last_template].should eq ["visitor_invited", "notify_checkin"] + + # ------------------------------------------------------------------ + # Test 34: guest_event — check-in for an untitled booking (PPT-2535) + # bookings without a title or description signal + # event_title / event_summary as null. This previously failed + # to parse (event_summary was non-nilable) so the exception + # was swallowed and the host never received the notification. + # ------------------------------------------------------------------ + + count_before_untitled = system(:Mailer)[:send_count].as_i + errors_before_untitled = status[:error_count]?.try(&.as_i64) || 0_i64 + + untitled_checkin_payload = { + action: "checkin", + id: 902_i64, + checkin: true, + booking_id: 902_i64, + resource_id: "visitor@external.com", + resource_ids: ["visitor@external.com"], + event_title: nil, + event_summary: nil, + event_starting: now + 3600, + attendee_name: "Visitor One", + attendee_email: "visitor@external.com", + host: "host@example.com", + zones: ["zone-building", "zone-room"], + }.to_json + + publish("staff/guest/checkin", untitled_checkin_payload) + sleep 1.0 + + system(:Mailer)[:send_count].should eq count_before_untitled + 1 + system(:Mailer)[:last_to].should eq "host@example.com" + system(:Mailer)[:last_template].should eq ["visitor_invited", "notify_checkin"] + (status[:error_count]?.try(&.as_i64) || 0_i64).should eq errors_before_untitled + + # ------------------------------------------------------------------ + # Test 35: guest_event — visitor check-out (state=false) does NOT + # send the "visitor has arrived" email (PPT-2535) + # ------------------------------------------------------------------ + + count_before_checkout = system(:Mailer)[:send_count].as_i + checked_in_before_checkout = status[:users_checked_in].as_i64 + + checkout_payload = { + action: "checkin", + id: 903_i64, + checkin: false, + booking_id: 903_i64, + resource_id: "visitor@external.com", + resource_ids: ["visitor@external.com"], + event_title: "Catch Up", + event_summary: "Catch Up", + event_starting: now + 3600, + attendee_name: "Visitor One", + attendee_email: "visitor@external.com", + host: "host@example.com", + zones: ["zone-building", "zone-room"], + }.to_json + + publish("staff/guest/checkin", checkout_payload) + sleep 1.0 + + system(:Mailer)[:send_count].should eq count_before_checkout + status[:users_checked_in].should eq checked_in_before_checkout end diff --git a/drivers/place/visitor_models.cr b/drivers/place/visitor_models.cr index 6f60e2f01e..74e319987b 100644 --- a/drivers/place/visitor_models.cr +++ b/drivers/place/visitor_models.cr @@ -24,7 +24,8 @@ module Place property checkin : Bool? property event_title : String? - property event_summary : String + # nilable as bookings without a title or description signal null + property event_summary : String? property event_starting : Int64 property attendee_name : String? property attendee_email : String