Skip to content

Commit

Permalink
+ (Event) Fixed issue where a first time attendance workflow could be…
Browse files Browse the repository at this point in the history
… launched multiple times, or not at all, when checking into multiple schedules at the same time. (Fixes #5184)
  • Loading branch information
ethan-sparkdevnetwork committed Jun 26, 2023
1 parent b632ae3 commit 1b70656
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 53 deletions.
98 changes: 57 additions & 41 deletions Rock/Model/Event/Attendance/Attendance.SaveHook.cs
Expand Up @@ -40,6 +40,8 @@ internal class SaveHook : EntitySaveHook<Attendance>

private int? preSavePersonAliasId { get; set; }

private bool previousDidAttendValue { get; set; }

/// <summary>
/// Method that will be called on an entity immediately before the item is saved by context
/// </summary>
Expand All @@ -49,7 +51,6 @@ protected override void PreSave()

_isDeleted = State == EntityContextState.Deleted;

bool previousDidAttendValue;
bool previouslyDeclined;

if ( State == EntityContextState.Added )
Expand All @@ -67,11 +68,19 @@ protected override void PreSave()
// if the record was changed to Declined, queue a GroupScheduleCancellationTransaction in PostSaveChanges
_declinedScheduledAttendance = ( previouslyDeclined == false ) && Entity.IsScheduledPersonDeclined();

if ( previousDidAttendValue == false && Entity.DidAttend == true )
{
var launchMemberAttendedGroupWorkflowMsg = GetLaunchMemberAttendedGroupWorkflowMessage();
launchMemberAttendedGroupWorkflowMsg.Send();
}
/*
06/21/2023 ETD
Launch the workflow in post save to avoid a race condition between the bus message and the saving of the Attendance record.
The LaunchMemberAttendedGroupWorkflow needs to be run post save to work correctly.
if ( previousDidAttendValue == false && Entity.DidAttend == true )
{
var launchMemberAttendedGroupWorkflowMsg = GetLaunchMemberAttendedGroupWorkflowMessage();
launchMemberAttendedGroupWorkflowMsg.Send();
}
*/


var attendance = this.Entity;

Expand Down Expand Up @@ -116,8 +125,7 @@ protected override void PreSave()
RockContext.ExecuteAfterCommit( () =>
{
// Use the fast queue for this because it is real-time.
new SendAttendanceRealTimeNotificationsTransaction( Entity.Guid, State == EntityContextState.Deleted )
.Enqueue( true );
new SendAttendanceRealTimeNotificationsTransaction( Entity.Guid, State == EntityContextState.Deleted ).Enqueue( true );
} );
}

Expand Down Expand Up @@ -152,6 +160,13 @@ protected override void PostSave()
StreakTypeService.HandleAttendanceRecord( Entity.Id );
}

// Do this in post save to avoid a race condition between the bus message and the saving of the Attendance record. See engineering note in PreSave().
if ( previousDidAttendValue == false && Entity.DidAttend == true )
{
var launchMemberAttendedGroupWorkflowMsg = GetLaunchMemberAttendedGroupWorkflowMessage();
launchMemberAttendedGroupWorkflowMsg.Send();
}

var rockContext = ( RockContext ) this.RockContext;

if ( PersonAttendanceHistoryChangeList?.Any() == true )
Expand Down Expand Up @@ -226,44 +241,45 @@ private bool ShouldSendRealTimeMessage()
private LaunchMemberAttendedGroupWorkflow.Message GetLaunchMemberAttendedGroupWorkflowMessage()
{
var launchMemberAttendedGroupWorkflowMsg = new LaunchMemberAttendedGroupWorkflow.Message();
if ( State != EntityContextState.Deleted )
if ( State == EntityContextState.Deleted )
{
return launchMemberAttendedGroupWorkflowMsg;
}

// Get the attendance record
var attendance = Entity as Attendance;

// If attendance record is not valid or the DidAttend is false
if ( attendance == null || ( attendance.DidAttend.GetValueOrDefault( false ) == false ) )
{
// Get the attendance record
var attendance = Entity as Attendance;
return launchMemberAttendedGroupWorkflowMsg;
}

// If attendance record is valid and the DidAttend is true (not null or false)
if ( attendance != null && ( attendance.DidAttend == true ) )
{
// Save for all adds
bool valid = State == EntityContextState.Added;
// Save for all adds
bool valid = State == EntityContextState.Added;

// If not an add, check previous DidAttend value
if ( !valid )
{
// Only use changes where DidAttend was previously not true
valid = ( bool? ) Entry.OriginalValues.GetReadOnlyValueOrDefault( "DidAttend", false ) != true;
}
// If not an add, check previous DidAttend value
if ( !valid )
{
// Only use changes where DidAttend was previously not true
valid = ( bool? ) Entry.OriginalValues.GetReadOnlyValueOrDefault( "DidAttend", false ) != true;
}

if ( valid )
{
var occ = attendance.Occurrence;
if ( occ == null )
{
occ = new AttendanceOccurrenceService( new RockContext() ).Get( attendance.OccurrenceId );
}
if ( valid )
{
var occ = attendance.Occurrence ?? new AttendanceOccurrenceService( new RockContext() ).Get( attendance.OccurrenceId );

if ( occ != null )
{
// Save the values
launchMemberAttendedGroupWorkflowMsg.GroupId = occ.GroupId;
launchMemberAttendedGroupWorkflowMsg.AttendanceDateTime = occ.OccurrenceDate;
launchMemberAttendedGroupWorkflowMsg.PersonAliasId = attendance.PersonAliasId;

if ( occ.Group != null )
{
launchMemberAttendedGroupWorkflowMsg.GroupTypeId = occ.Group.GroupTypeId;
}
}
if ( occ != null )
{
// Save the values
launchMemberAttendedGroupWorkflowMsg.GroupId = occ.GroupId;
launchMemberAttendedGroupWorkflowMsg.AttendanceDateTime = occ.OccurrenceDate;
launchMemberAttendedGroupWorkflowMsg.PersonAliasId = attendance.PersonAliasId;
launchMemberAttendedGroupWorkflowMsg.AttendanceId = attendance.Id;

if ( occ.Group != null )
{
launchMemberAttendedGroupWorkflowMsg.GroupTypeId = occ.Group.GroupTypeId;
}
}
}
Expand Down
45 changes: 33 additions & 12 deletions Rock/Tasks/LaunchMemberAttendedGroupWorkflow.cs
Expand Up @@ -107,25 +107,38 @@ public override void Execute( Message message )
// Check to see if trigger is only specific to first time visitors
if ( qualifierParts.Length > 4 && qualifierParts[4].AsBoolean() )
{
// Get the person from person alias
// Get the person from person alias, must match person because alias used in attendance record might be different
int personId = new PersonAliasService( rockContext )
.Queryable().AsNoTracking()
.Queryable()
.AsNoTracking()
.Where( a => a.Id == message.PersonAliasId.Value )
.Select( a => a.PersonId )
.FirstOrDefault();

// Check if there are any other attendances for this group/person and if so, do not launch workflow
if ( new AttendanceService( rockContext )
.Queryable().AsNoTracking()
.Count( a => a.Occurrence.GroupId.HasValue &&
a.Occurrence.GroupId.Value == message.GroupId.Value &&
a.PersonAlias != null &&
a.PersonAlias.PersonId == personId &&
a.DidAttend.HasValue &&
a.DidAttend.Value ) > 1 )
// Get the attendance record, skip the trigger if one is not found (shouldn't happen)
var attendanceService = new AttendanceService( rockContext );
var attendance = attendanceService.Get( message.AttendanceId.Value );
if ( attendance == null )
{
launchIt = false;
continue;
}

// Count earlier attendances for the person/group, do not include this attendance. Match using either StartDateTime or CreatedDateTime in case the record was edited which would update the StartDateTime.
int count = attendanceService
.Queryable()
.AsNoTracking()
.Count( a => a.Id != message.AttendanceId
&& ( a.StartDateTime < attendance.StartDateTime || a.CreatedDateTime < attendance.CreatedDateTime )
&& a.Occurrence.GroupId.HasValue
&& a.Occurrence.GroupId.Value == message.GroupId.Value
&& a.PersonAlias != null
&& a.PersonAlias.PersonId == personId
&& a.DidAttend.HasValue
&& a.DidAttend.Value );

// Launch the workflow if this is the first attendance for the person/group
launchIt = count == 0;

}

// If first time flag was not specified, or this is a first time visit, launch the workflow
Expand Down Expand Up @@ -203,6 +216,14 @@ public sealed class Message : BusStartedTaskMessage
/// Gets or sets the attendance date time.
/// </summary>
public DateTime? AttendanceDateTime { get; set; }

/// <summary>
/// Gets or sets the attendance identifier.
/// </summary>
/// <value>
/// The attendance identifier.
/// </value>
public int? AttendanceId { get; set; }
}
}
}

0 comments on commit 1b70656

Please sign in to comment.