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;