/// <summary> /// Executes the specified context. /// </summary> /// <param name="context">The context.</param> public void Execute( IJobExecutionContext context ) { var rockContext = new RockContext(); var groupRequirementService = new GroupRequirementService( rockContext ); var groupMemberRequirementService = new GroupMemberRequirementService( rockContext ); var groupMemberService = new GroupMemberService( rockContext ); // we only need to consider group requirements that are based on a DataView or SQL var groupRequirementQry = groupRequirementService.Queryable() .Where( a => a.GroupRequirementType.RequirementCheckType != RequirementCheckType.Manual ) .AsNoTracking(); var calculationExceptions = new List<Exception>(); int groupRequirementsCalculatedMemberCount = 0; foreach ( var groupRequirement in groupRequirementQry.Include( i => i.GroupRequirementType ).AsNoTracking().ToList() ) { try { var currentDateTime = RockDateTime.Now; var qryGroupMemberRequirementsAlreadyOK = groupMemberRequirementService.Queryable().Where( a => a.GroupRequirementId == groupRequirement.Id ); if ( groupRequirement.GroupRequirementType.CanExpire && groupRequirement.GroupRequirementType.ExpireInDays.HasValue ) { // Expirable: don't recalculate members that already met the requirement within the expiredays (unless they are flagged with a warning) var expireDaysCount = groupRequirement.GroupRequirementType.ExpireInDays.Value; qryGroupMemberRequirementsAlreadyOK = qryGroupMemberRequirementsAlreadyOK.Where( a => !a.RequirementWarningDateTime.HasValue && a.RequirementMetDateTime.HasValue && SqlFunctions.DateDiff( "day", a.RequirementMetDateTime, currentDateTime ) < expireDaysCount ); } else { // No Expiration: don't recalculate members that already met the requirement qryGroupMemberRequirementsAlreadyOK = qryGroupMemberRequirementsAlreadyOK.Where( a => a.RequirementMetDateTime.HasValue ); } var groupMemberQry = groupMemberService.Queryable().Where( a => a.GroupId == groupRequirement.GroupId ).AsNoTracking(); var personQry = groupMemberQry.Where( a => !qryGroupMemberRequirementsAlreadyOK.Any( r => r.GroupMemberId == a.Id ) ).Select( a => a.Person ); var results = groupRequirement.PersonQueryableMeetsGroupRequirement( rockContext, personQry, groupRequirement.GroupRoleId ).ToList(); groupRequirementsCalculatedMemberCount += results.Select( a => a.PersonId ).Distinct().Count(); foreach ( var result in results ) { // use a fresh rockContext per Update so that ChangeTracker doesn't get bogged down var rockContextUpdate = new RockContext(); groupRequirement.UpdateGroupMemberRequirementResult( rockContextUpdate, result.PersonId, result.MeetsGroupRequirement ); rockContextUpdate.SaveChanges(); } } catch ( Exception ex ) { calculationExceptions.Add( new Exception( string.Format( "Exception when calculating group requirement: {0} ", groupRequirement ), ex ) ); } } context.Result = string.Format( "Group member requirements re-calculated for {0} group members", groupRequirementsCalculatedMemberCount ); if ( calculationExceptions.Any() ) { throw new AggregateException( "One or more group requirement calculations failed ", calculationExceptions ); } }
/// <summary> /// Executes the specified context. /// </summary> /// <param name="context">The context.</param> public void Execute(IJobExecutionContext context) { var rockContext = new RockContext(); var groupRequirementService = new GroupRequirementService(rockContext); var groupMemberRequirementService = new GroupMemberRequirementService(rockContext); var groupMemberService = new GroupMemberService(rockContext); // we only need to consider group requirements that are based on a DataView or SQL var groupRequirementQry = groupRequirementService.Queryable() .Where(a => a.GroupRequirementType.RequirementCheckType != RequirementCheckType.Manual) .AsNoTracking(); var calculationExceptions = new List <Exception>(); int groupRequirementsCalculatedMemberCount = 0; foreach (var groupRequirement in groupRequirementQry.Include(i => i.GroupRequirementType).AsNoTracking().ToList()) { try { var currentDateTime = RockDateTime.Now; var qryGroupMemberRequirementsAlreadyOK = groupMemberRequirementService.Queryable().Where(a => a.GroupRequirementId == groupRequirement.Id); if (groupRequirement.GroupRequirementType.CanExpire && groupRequirement.GroupRequirementType.ExpireInDays.HasValue) { // Expirable: don't recalculate members that already met the requirement within the expiredays (unless they are flagged with a warning) var expireDaysCount = groupRequirement.GroupRequirementType.ExpireInDays.Value; qryGroupMemberRequirementsAlreadyOK = qryGroupMemberRequirementsAlreadyOK.Where(a => !a.RequirementWarningDateTime.HasValue && a.RequirementMetDateTime.HasValue && SqlFunctions.DateDiff("day", a.RequirementMetDateTime, currentDateTime) < expireDaysCount); } else { // No Expiration: don't recalculate members that already met the requirement qryGroupMemberRequirementsAlreadyOK = qryGroupMemberRequirementsAlreadyOK.Where(a => a.RequirementMetDateTime.HasValue); } var groupMemberQry = groupMemberService.Queryable().Where(a => a.GroupId == groupRequirement.GroupId).AsNoTracking(); var personQry = groupMemberQry.Where(a => !qryGroupMemberRequirementsAlreadyOK.Any(r => r.GroupMemberId == a.Id)).Select(a => a.Person); var results = groupRequirement.PersonQueryableMeetsGroupRequirement(rockContext, personQry, groupRequirement.GroupRoleId).ToList(); groupRequirementsCalculatedMemberCount += results.Select(a => a.PersonId).Distinct().Count(); foreach (var result in results) { // use a fresh rockContext per Update so that ChangeTracker doesn't get bogged down var rockContextUpdate = new RockContext(); groupRequirement.UpdateGroupMemberRequirementResult(rockContextUpdate, result.PersonId, result.MeetsGroupRequirement); rockContextUpdate.SaveChanges(); } } catch (Exception ex) { calculationExceptions.Add(new Exception(string.Format("Exception when calculating group requirement: {0} ", groupRequirement), ex)); } } context.Result = string.Format("Group member requirements re-calculated for {0} group members", groupRequirementsCalculatedMemberCount); if (calculationExceptions.Any()) { throw new AggregateException("One or more group requirement calculations failed ", calculationExceptions); } }
/// <summary> /// Executes the specified context. /// </summary> /// <param name="context">The context.</param> public void Execute(IJobExecutionContext context) { var rockContext = new RockContext(); var groupRequirementService = new GroupRequirementService(rockContext); var groupMemberRequirementService = new GroupMemberRequirementService(rockContext); var groupMemberService = new GroupMemberService(rockContext); var groupService = new GroupService(rockContext); // we only need to consider group requirements that are based on a DataView or SQL var groupRequirementQry = groupRequirementService.Queryable() .Where(a => a.GroupRequirementType.RequirementCheckType != RequirementCheckType.Manual) .AsNoTracking(); var calculationExceptions = new List <Exception>(); List <int> groupRequirementsCalculatedPersonIds = new List <int>(); foreach (var groupRequirement in groupRequirementQry.Include(i => i.GroupRequirementType).Include(a => a.GroupRequirementType.DataView).Include(a => a.GroupRequirementType.WarningDataView).AsNoTracking().ToList()) { // Only calculate group requirements for Active groups (if an inactive group becomes active again, this job will take care of re-calculating the requirements again) var groupQuery = groupService.Queryable().Where(a => a.IsActive); if (groupRequirement.GroupId.HasValue) { groupQuery = groupQuery.Where(g => g.Id == groupRequirement.GroupId); } else if (groupRequirement.GroupTypeId.HasValue) { groupQuery = groupQuery.Where(g => g.GroupTypeId == groupRequirement.GroupTypeId); } else { // shouldn't happen, but Group Requirement doesn't have a groupId or a GroupTypeId break; } var groupList = groupQuery.Select(a => new { a.Id, a.Name }).ToList(); var groupCount = groupList.Count(); foreach (var group in groupList) { context.UpdateLastStatusMessage($"Calculating group requirement '{groupRequirement.GroupRequirementType.Name}' for {group.Name}"); try { var currentDateTime = RockDateTime.Now; var qryGroupMemberRequirementsAlreadyOK = groupMemberRequirementService.Queryable().Where(a => a.GroupRequirementId == groupRequirement.Id && a.GroupMember.GroupId == group.Id); if (groupRequirement.GroupRequirementType.CanExpire && groupRequirement.GroupRequirementType.ExpireInDays.HasValue) { // Group requirement can expire: don't recalculate members that already met the requirement within the expire days (unless they are flagged with a warning) var expireDaysCount = groupRequirement.GroupRequirementType.ExpireInDays.Value; qryGroupMemberRequirementsAlreadyOK = qryGroupMemberRequirementsAlreadyOK.Where(a => !a.RequirementWarningDateTime.HasValue && a.RequirementMetDateTime.HasValue && SqlFunctions.DateDiff("day", a.RequirementMetDateTime, currentDateTime) < expireDaysCount); } else { // No Expiration: don't recalculate members that already met the requirement qryGroupMemberRequirementsAlreadyOK = qryGroupMemberRequirementsAlreadyOK.Where(a => a.RequirementMetDateTime.HasValue); } var groupMemberQry = groupMemberService.Queryable(); if (groupRequirement.GroupId.HasValue) { groupMemberQry = groupMemberQry.Where(g => g.GroupId == groupRequirement.GroupId); } else if (groupRequirement.GroupTypeId.HasValue) { groupMemberQry = groupMemberQry.Where(g => (g.Group.GroupTypeId == groupRequirement.GroupTypeId) && g.GroupId == group.Id); } else { // shouldn't happen, but Group Requirement doesn't have a groupId or a GroupTypeId break; } var personQry = groupMemberQry.Where(a => !qryGroupMemberRequirementsAlreadyOK.Any(r => r.GroupMemberId == a.Id)).Select(a => a.Person); var results = groupRequirement.PersonQueryableMeetsGroupRequirement(rockContext, personQry, group.Id, groupRequirement.GroupRoleId).ToList(); groupRequirementsCalculatedPersonIds.AddRange(results.Select(a => a.PersonId).Distinct()); foreach (var result in results) { try { // use a fresh rockContext per result so that ChangeTracker doesn't get bogged down using (var rockContextUpdate = new RockContext()) { groupRequirement.UpdateGroupMemberRequirementResult(rockContextUpdate, result.PersonId, group.Id, result.MeetsGroupRequirement); rockContextUpdate.SaveChanges(); } } catch (Exception ex) { calculationExceptions.Add(new Exception($"Exception when updating group requirement result: {groupRequirement} for person.Id { result.PersonId }", ex)); } } } catch (Exception ex) { calculationExceptions.Add(new Exception(string.Format("Exception when calculating group requirement: {0} ", groupRequirement), ex)); } } } context.UpdateLastStatusMessage($"{groupRequirementQry.Count()} group member requirements re-calculated for {groupRequirementsCalculatedPersonIds.Distinct().Count()} people"); if (calculationExceptions.Any()) { throw new AggregateException("One or more group requirement calculations failed ", calculationExceptions); } }
/// <summary> /// Groups the members not meeting requirements. /// </summary> /// <param name="groupId">The group identifier.</param> /// <param name="includeWarnings">if set to <c>true</c> [include warnings].</param> /// <returns></returns> public Dictionary<GroupMember, Dictionary<PersonGroupRequirementStatus, DateTime>> GroupMembersNotMeetingRequirements( int groupId, bool includeWarnings ) { Dictionary<GroupMember, Dictionary<PersonGroupRequirementStatus, DateTime>> results = new Dictionary<GroupMember, Dictionary<PersonGroupRequirementStatus, DateTime>>(); var rockContext = this.Context as RockContext; var groupRequirementService = new GroupRequirementService( rockContext ); var groupMemberService = new GroupMemberService( rockContext ); var groupMemberRequirementService = new GroupMemberRequirementService( rockContext ); var qryGroupRequirements = groupRequirementService.Queryable().Where( a => a.GroupId == groupId ); bool hasGroupRequirements = qryGroupRequirements.Any(); if ( !hasGroupRequirements ) { // if no group requirements, then there are no members that don't meet the requirements, so return an empty dictionary return new Dictionary<GroupMember, Dictionary<PersonGroupRequirementStatus, DateTime>>(); } var qryGroupMembers = groupMemberService.Queryable().Where( a => a.GroupId == groupId ); var qryGroupMemberRequirements = groupMemberRequirementService.Queryable().Where( a => a.GroupMember.GroupId == groupId ); // get a list of group member ids that don't meet all the requirements IQueryable<int> qryGroupMemberIdsThatLackGroupRequirements = qryGroupMembers.Where( a => !qryGroupRequirements.Select(x => x.Id).All( r => a.GroupMemberRequirements.Where( mr => mr.RequirementMetDateTime.HasValue ).Select( x => x.GroupRequirementId ).Contains( r ) ) ).Select(a => a.Id); IQueryable<GroupMember> qryMembersWithIssues; if ( includeWarnings ) { IQueryable<int> qryGroupMemberIdsWithRequirementWarnings = qryGroupMemberRequirements.Where( a => a.RequirementWarningDateTime != null || a.RequirementFailDateTime != null ).Select( a => a.GroupMemberId ).Distinct(); qryMembersWithIssues = qryGroupMembers.Where( a => qryGroupMemberIdsThatLackGroupRequirements.Contains( a.Id ) || qryGroupMemberIdsWithRequirementWarnings.Contains( a.Id ) ); } else { qryMembersWithIssues = qryGroupMembers.Where( a => qryGroupMemberIdsThatLackGroupRequirements.Contains( a.Id ) ); } var qry = qryMembersWithIssues.Select( a => new { GroupMember = a, GroupRequirementStatuses = qryGroupMemberRequirements.Where( x => x.GroupMemberId == a.Id ) } ); var currentDateTime = RockDateTime.Now; foreach (var groupMemberWithIssues in qry) { Dictionary<PersonGroupRequirementStatus, DateTime> statuses = new Dictionary<PersonGroupRequirementStatus, DateTime>(); // populate where the status is known foreach ( var requirementStatus in groupMemberWithIssues.GroupRequirementStatuses ) { PersonGroupRequirementStatus status = new PersonGroupRequirementStatus(); status.GroupRequirement = requirementStatus.GroupRequirement; status.PersonId = groupMemberWithIssues.GroupMember.PersonId; DateTime occuranceDate = new DateTime(); if ( requirementStatus.RequirementMetDateTime == null) { status.MeetsGroupRequirement = MeetsGroupRequirement.NotMet; occuranceDate = requirementStatus.RequirementFailDateTime ?? currentDateTime; } else if (requirementStatus.RequirementWarningDateTime.HasValue) { status.MeetsGroupRequirement = MeetsGroupRequirement.MeetsWithWarning; occuranceDate = requirementStatus.RequirementWarningDateTime.Value; } else { status.MeetsGroupRequirement = MeetsGroupRequirement.Meets; occuranceDate = requirementStatus.RequirementMetDateTime.Value; } statuses.Add( status, occuranceDate ); } // also add any groupRequirements that they don't have statuses for (and therefore haven't met) foreach (var groupRequirement in qryGroupRequirements) { if ( !statuses.Any( x => x.Key.GroupRequirement.Id == groupRequirement.Id) ) { PersonGroupRequirementStatus status = new PersonGroupRequirementStatus(); status.GroupRequirement = groupRequirement; status.PersonId = groupMemberWithIssues.GroupMember.PersonId; status.MeetsGroupRequirement = MeetsGroupRequirement.NotMet; statuses.Add( status, currentDateTime ); } } var statusesWithIssues = statuses.Where( a => a.Key.MeetsGroupRequirement != MeetsGroupRequirement.Meets ).ToDictionary( k => k.Key, v => v.Value ); if ( statusesWithIssues.Any() ) { results.Add( groupMemberWithIssues.GroupMember, statusesWithIssues ); } } return results; }