From 508ecaed5b7746a3b0eff061ff46583434c6657d Mon Sep 17 00:00:00 2001 From: Stephen Lee Date: Mon, 6 Jul 2026 19:54:12 -0400 Subject: [PATCH] ROCK-8700: Fix double check-in caused by redundant Add after AddOrUpdate Rock core's AttendanceService.AddOrUpdate returns the existing tracked attendance when one exists for the same person + occurrence. The extra attendanceService.Add() flipped that entity to Added, so EF inserted a duplicate row and dropped the update closing the old attendance, leaving two active attendances (double check-in). - SaveAttendance.cs: remove the redundant Add (AddOrUpdate already adds new records); existing records now update in place. - DataHelper.cs (RoomScanner): comment only. The identical-looking Add in CloneAttendance is intentional clone-by-readd and must not be removed. --- Plugins/org.secc.FamilyCheckin/Workflows/SaveAttendance.cs | 6 +++++- Plugins/org.secc.RoomScanner/Utilities/DataHelper.cs | 7 +++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/Plugins/org.secc.FamilyCheckin/Workflows/SaveAttendance.cs b/Plugins/org.secc.FamilyCheckin/Workflows/SaveAttendance.cs index 311349add..1d01fb012 100644 --- a/Plugins/org.secc.FamilyCheckin/Workflows/SaveAttendance.cs +++ b/Plugins/org.secc.FamilyCheckin/Workflows/SaveAttendance.cs @@ -182,7 +182,11 @@ public override bool Execute( RockContext rockContext, Rock.Model.WorkflowAction } }; - attendanceService.Add( attendance ); + // NOTE: Do not call attendanceService.Add( attendance ) here. AddOrUpdate already + // adds new records to the context. When AddOrUpdate returns an EXISTING attendance + // (same person + occurrence), calling Add would flip the tracked entity's state to + // Added, causing EF to INSERT a duplicate row and silently drop the update that + // closed the old attendance — the double check-in bug (ROCK-8700). attendances.Add( attendance ); } } diff --git a/Plugins/org.secc.RoomScanner/Utilities/DataHelper.cs b/Plugins/org.secc.RoomScanner/Utilities/DataHelper.cs index b92375ddb..01952b2fb 100644 --- a/Plugins/org.secc.RoomScanner/Utilities/DataHelper.cs +++ b/Plugins/org.secc.RoomScanner/Utilities/DataHelper.cs @@ -203,6 +203,13 @@ public static void CloneAttendance( Attendance attendance, bool isSubroom, Locat { newAttendance.ForeignId = null; } + // NOTE (ROCK-8700): This Add looks redundant (AddOrUpdate already adds new records), but it is + // intentional here. When AddOrUpdate returns an EXISTING attendance row (e.g. subroom scan where + // the target occurrence matches the source attendance's occurrence), Add flips the tracked entity + // to Added so EF INSERTs a fresh row — that new row is the "clone", while the source attendance + // (tracked by the caller's context) is closed below/by the caller. Removing this Add would cause + // the same row to be reopened and then closed, checking the person out instead of moving them. + // This is safe here because the source row is always closed afterward, so only one row stays active. attendanceService.Add( newAttendance ); var stayedFifteenMinutes = ( Rock.RockDateTime.Now - attendance.StartDateTime ) > new TimeSpan( 0, 15, 0 ); attendance.DidAttend = stayedFifteenMinutes;