/// <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 errors = new List <string>(); var rockContext = new RockContext(); JobDataMap dataMap = context.JobDetail.JobDataMap; Guid? systemEmailGuid = dataMap.GetString("NotificationEmailTemplate").AsGuidOrNull(); if (systemEmailGuid.HasValue) { var selectedGroupTypes = new List <Guid>(); if (!string.IsNullOrWhiteSpace(dataMap.GetString("GroupTypes"))) { selectedGroupTypes = dataMap.GetString("GroupTypes").Split(',').Select(Guid.Parse).ToList(); } var notificationOption = dataMap.GetString("NotifyParentLeaders").ConvertToEnum <NotificationOption>(NotificationOption.None); var accountAbilityGroupGuid = dataMap.GetString("AccountabilityGroup").AsGuid(); var groupRequirementsQry = new GroupRequirementService(rockContext).Queryable(); // get groups matching of the types provided GroupService groupService = new GroupService(rockContext); var groups = groupService.Queryable().AsNoTracking() .Where(g => selectedGroupTypes.Contains(g.GroupType.Guid) && g.IsActive == true && groupRequirementsQry.Any(a => (a.GroupId.HasValue && a.GroupId == g.Id) || (a.GroupTypeId.HasValue && a.GroupTypeId == g.GroupTypeId))); foreach (var group in groups) { // check for members that don't meet requirements var groupMembersWithIssues = groupService.GroupMembersNotMeetingRequirements(group, true); if (groupMembersWithIssues.Count > 0) { // add issues to issue list GroupsMissingRequirements groupMissingRequirements = new GroupsMissingRequirements(); groupMissingRequirements.Id = group.Id; groupMissingRequirements.Name = group.Name; if (group.GroupType != null) { groupMissingRequirements.GroupTypeId = group.GroupTypeId; groupMissingRequirements.GroupTypeName = group.GroupType.Name; } groupMissingRequirements.AncestorPathName = groupService.GroupAncestorPathName(group.Id); // get list of the group leaders groupMissingRequirements.Leaders = group.Members .Where(m => m.GroupRole.ReceiveRequirementsNotifications) .Select(m => new GroupMemberResult { Id = m.Id, PersonId = m.PersonId, FullName = m.Person.FullName }) .ToList(); List <GroupMembersMissingRequirements> groupMembers = new List <GroupMembersMissingRequirements>(); foreach (var groupMemberIssue in groupMembersWithIssues) { GroupMembersMissingRequirements groupMember = new GroupMembersMissingRequirements(); groupMember.FullName = groupMemberIssue.Key.Person.FullName; groupMember.Id = groupMemberIssue.Key.Id; groupMember.PersonId = groupMemberIssue.Key.PersonId; groupMember.GroupMemberRole = groupMemberIssue.Key.GroupRole.Name; List <MissingRequirement> missingRequirements = new List <MissingRequirement>(); foreach (var issue in groupMemberIssue.Value) { MissingRequirement missingRequirement = new MissingRequirement(); missingRequirement.Id = issue.Key.GroupRequirement.GroupRequirementType.Id; missingRequirement.Name = issue.Key.GroupRequirement.GroupRequirementType.Name; missingRequirement.Status = issue.Key.MeetsGroupRequirement; missingRequirement.OccurrenceDate = issue.Value; switch (issue.Key.MeetsGroupRequirement) { case MeetsGroupRequirement.Meets: missingRequirement.Message = issue.Key.GroupRequirement.GroupRequirementType.PositiveLabel; break; case MeetsGroupRequirement.MeetsWithWarning: missingRequirement.Message = issue.Key.GroupRequirement.GroupRequirementType.WarningLabel; break; case MeetsGroupRequirement.NotMet: missingRequirement.Message = issue.Key.GroupRequirement.GroupRequirementType.NegativeLabel; break; } missingRequirements.Add(missingRequirement); } groupMember.MissingRequirements = missingRequirements; groupMembers.Add(groupMember); } groupMissingRequirements.GroupMembersMissingRequirements = groupMembers; _groupsMissingRequriements.Add(groupMissingRequirements); // add leaders as people to notify foreach (var leader in group.Members.Where(m => m.GroupRole.ReceiveRequirementsNotifications)) { NotificationItem notification = new NotificationItem(); notification.GroupId = group.Id; notification.Person = leader.Person; _notificationList.Add(notification); } // notify parents if (notificationOption != NotificationOption.None) { var parentLeaders = new GroupMemberService(rockContext).Queryable("Person").AsNoTracking() .Where(m => m.GroupRole.ReceiveRequirementsNotifications); if (notificationOption == NotificationOption.DirectParent) { // just the parent group parentLeaders = parentLeaders.Where(m => m.GroupId == group.ParentGroupId); } else { // all parents in the hierarchy var parentIds = groupService.GetAllAncestorIds(group.Id); parentLeaders = parentLeaders.Where(m => parentIds.Contains(m.GroupId)); } foreach (var parentLeader in parentLeaders.ToList()) { NotificationItem parentNotification = new NotificationItem(); parentNotification.Person = parentLeader.Person; parentNotification.GroupId = group.Id; _notificationList.Add(parentNotification); } } } } // send out notifications int recipients = 0; var notificationRecipients = _notificationList.GroupBy(p => p.Person.Id).ToList(); foreach (var recipientId in notificationRecipients) { var recipient = _notificationList.Where(n => n.Person.Id == recipientId.Key).Select(n => n.Person).FirstOrDefault(); if (!recipient.IsEmailActive || recipient.Email.IsNullOrWhiteSpace() || recipient.EmailPreference == EmailPreference.DoNotEmail) { continue; } var mergeFields = Rock.Lava.LavaHelper.GetCommonMergeFields(null); mergeFields.Add("Person", recipient); var notificationGroupIds = _notificationList .Where(n => n.Person.Id == recipient.Id) .Select(n => n.GroupId) .ToList(); var missingRequirements = _groupsMissingRequriements.Where(g => notificationGroupIds.Contains(g.Id)).ToList(); mergeFields.Add("GroupsMissingRequirements", missingRequirements); var emailMessage = new RockEmailMessage(systemEmailGuid.Value); emailMessage.AddRecipient(new RecipientData(recipient.Email, mergeFields)); var emailErrors = new List <string>(); emailMessage.Send(out emailErrors); errors.AddRange(emailErrors); recipients++; } // add accountability group members if (!accountAbilityGroupGuid.IsEmpty()) { var accountabilityGroupMembers = new GroupMemberService(rockContext).Queryable().AsNoTracking() .Where(m => m.Group.Guid == accountAbilityGroupGuid) .Select(m => m.Person); var emailMessage = new RockEmailMessage(systemEmailGuid.Value); foreach (var person in accountabilityGroupMembers) { var mergeFields = Rock.Lava.LavaHelper.GetCommonMergeFields(null); mergeFields.Add("Person", person); mergeFields.Add("GroupsMissingRequirements", _groupsMissingRequriements); emailMessage.AddRecipient(new RecipientData(person.Email, mergeFields)); recipients++; } var emailErrors = new List <string>(); emailMessage.Send(out emailErrors); errors.AddRange(emailErrors); } context.Result = string.Format("{0} requirement notification {1} sent", recipients, "email".PluralizeIf(recipients != 1)); if (errors.Any()) { StringBuilder sb = new StringBuilder(); sb.AppendLine(); sb.Append(string.Format("{0} Errors: ", errors.Count())); errors.ForEach(e => { sb.AppendLine(); sb.Append(e); }); string errorMessage = sb.ToString(); context.Result += errorMessage; var exception = new Exception(errorMessage); HttpContext context2 = HttpContext.Current; ExceptionLogService.LogException(exception, context2); throw exception; } } else { context.Result = "Warning: No NotificationEmailTemplate found"; } }
/// <summary> /// Binds the group members grid. /// </summary> protected void BindGroupMembersGrid() { if ( _group != null ) { pnlGroupMembers.Visible = true; lHeading.Text = string.Format( "{0} {1}", _group.GroupType.GroupTerm, _group.GroupType.GroupMemberTerm.Pluralize() ); if ( _group.GroupType.Roles.Any() ) { nbRoleWarning.Visible = false; rFilter.Visible = true; gGroupMembers.Visible = true; var rockContext = new RockContext(); GroupMemberService groupMemberService = new GroupMemberService( rockContext ); var qry = groupMemberService.Queryable( "Person,GroupRole", true ).AsNoTracking() .Where( m => m.GroupId == _group.Id ); // Filter by First Name string firstName = tbFirstName.Text; if ( !string.IsNullOrWhiteSpace( firstName ) ) { qry = qry.Where( m => m.Person.FirstName.StartsWith( firstName ) ); } // Filter by Last Name string lastName = tbLastName.Text; if ( !string.IsNullOrWhiteSpace( lastName ) ) { qry = qry.Where( m => m.Person.LastName.StartsWith( lastName ) ); } // Filter by role var validGroupTypeRoles = _group.GroupType.Roles.Select( r => r.Id ).ToList(); var roles = new List<int>(); foreach ( string role in cblRole.SelectedValues ) { if ( !string.IsNullOrWhiteSpace( role ) ) { int roleId = int.MinValue; if ( int.TryParse( role, out roleId ) && validGroupTypeRoles.Contains( roleId ) ) { roles.Add( roleId ); } } } if ( roles.Any() ) { qry = qry.Where( m => roles.Contains( m.GroupRoleId ) ); } // Filter by Status var statuses = new List<GroupMemberStatus>(); foreach ( string status in cblStatus.SelectedValues ) { if ( !string.IsNullOrWhiteSpace( status ) ) { statuses.Add( status.ConvertToEnum<GroupMemberStatus>() ); } } if ( statuses.Any() ) { qry = qry.Where( m => statuses.Contains( m.GroupMemberStatus ) ); } // Filter query by any configured attribute filters if ( AvailableAttributes != null && AvailableAttributes.Any() ) { var attributeValueService = new AttributeValueService( rockContext ); var parameterExpression = attributeValueService.ParameterExpression; foreach ( var attribute in AvailableAttributes ) { var filterControl = phAttributeFilters.FindControl( "filter_" + attribute.Id.ToString() ); if ( filterControl != null ) { var filterValues = attribute.FieldType.Field.GetFilterValues( filterControl, attribute.QualifierValues, Rock.Reporting.FilterMode.SimpleFilter ); var expression = attribute.FieldType.Field.AttributeFilterExpression( attribute.QualifierValues, filterValues, parameterExpression ); if ( expression != null ) { var attributeValues = attributeValueService .Queryable() .Where( v => v.Attribute.Id == attribute.Id ); attributeValues = attributeValues.Where( parameterExpression, expression, null ); qry = qry.Where( w => attributeValues.Select( v => v.EntityId ).Contains( w.Id ) ); } } } } _inactiveStatus = DefinedValueCache.Read( Rock.SystemGuid.DefinedValue.PERSON_RECORD_STATUS_INACTIVE ); SortProperty sortProperty = gGroupMembers.SortProperty; bool hasGroupRequirements = new GroupRequirementService( rockContext ).Queryable().Where( a => a.GroupId == _group.Id ).Any(); // If there are group requirements that that member doesn't meet, show an icon in the grid bool includeWarnings = false; var groupMemberIdsThatLackGroupRequirements = new GroupService( rockContext ).GroupMembersNotMeetingRequirements( _group.Id, includeWarnings ).Select( a => a.Key.Id ); List<GroupMember> groupMembersList = null; if ( sortProperty != null ) { groupMembersList = qry.Sort( sortProperty ).ToList(); } else { groupMembersList = qry.OrderBy( a => a.GroupRole.Order ).ThenBy( a => a.Person.LastName ).ThenBy( a => a.Person.FirstName ).ToList(); } // Since we're not binding to actual group member list, but are using AttributeField columns, // we need to save the workflows into the grid's object list gGroupMembers.ObjectList = new Dictionary<string, object>(); groupMembersList.ForEach( m => gGroupMembers.ObjectList.Add( m.Id.ToString(), m ) ); gGroupMembers.EntityTypeId = EntityTypeCache.Read( Rock.SystemGuid.EntityType.GROUP_MEMBER.AsGuid() ).Id; gGroupMembers.DataSource = groupMembersList.Select( m => new { m.Id, m.Guid, m.PersonId, Name = m.Person.NickName + " " + m.Person.LastName + ( hasGroupRequirements && groupMemberIdsThatLackGroupRequirements.Contains( m.Id ) ? " <i class='fa fa-exclamation-triangle text-warning'></i>" : string.Empty ) + ( !string.IsNullOrEmpty( m.Note ) ? " <i class='fa fa-file-text-o text-info'></i>" : string.Empty ), GroupRole = m.GroupRole.Name, m.GroupMemberStatus, RecordStatusValueId = m.Person.RecordStatusValueId, IsDeceased = m.Person.IsDeceased } ).ToList(); gGroupMembers.DataBind(); } else { nbRoleWarning.Text = string.Format( "{0} cannot be added to this {1} because the '{2}' group type does not have any roles defined.", _group.GroupType.GroupMemberTerm.Pluralize(), _group.GroupType.GroupTerm, _group.GroupType.Name ); nbRoleWarning.Visible = true; rFilter.Visible = false; gGroupMembers.Visible = false; } } else { pnlGroupMembers.Visible = false; } }
/// <summary> /// Binds the group members grid. /// </summary> protected void BindGroupMembersGrid(bool selectAll = false) { if (_group != null) { pnlGroupMembers.Visible = true; lHeading.Text = string.Format("{0} {1}", _group.GroupType.GroupTerm, _group.GroupType.GroupMemberTerm.Pluralize()); if (_group.GroupType.Roles.Any()) { nbRoleWarning.Visible = false; rFilter.Visible = true; gGroupMembers.Visible = true; var rockContext = new RockContext(); GroupMemberService groupMemberService = new GroupMemberService(rockContext); var qry = groupMemberService.Queryable("Person,GroupRole", true).AsNoTracking() .Where(m => m.GroupId == _group.Id); // Filter by First Name string firstName = tbFirstName.Text; if (!string.IsNullOrWhiteSpace(firstName)) { qry = qry.Where(m => m.Person.FirstName.StartsWith(firstName)); } // Filter by Last Name string lastName = tbLastName.Text; if (!string.IsNullOrWhiteSpace(lastName)) { qry = qry.Where(m => m.Person.LastName.StartsWith(lastName)); } // Filter by role var validGroupTypeRoles = _group.GroupType.Roles.Select(r => r.Id).ToList(); var roles = new List <int>(); foreach (string role in cblRole.SelectedValues) { if (!string.IsNullOrWhiteSpace(role)) { int roleId = int.MinValue; if (int.TryParse(role, out roleId) && validGroupTypeRoles.Contains(roleId)) { roles.Add(roleId); } } } if (roles.Any()) { qry = qry.Where(m => roles.Contains(m.GroupRoleId)); } // Filter by Status var statuses = new List <GroupMemberStatus>(); foreach (string status in cblStatus.SelectedValues) { if (!string.IsNullOrWhiteSpace(status)) { statuses.Add(status.ConvertToEnum <GroupMemberStatus>()); } } if (statuses.Any()) { qry = qry.Where(m => statuses.Contains(m.GroupMemberStatus)); } // Filter query by any configured attribute filters if (AvailableAttributes != null && AvailableAttributes.Any()) { var attributeValueService = new AttributeValueService(rockContext); var parameterExpression = attributeValueService.ParameterExpression; foreach (var attribute in AvailableAttributes) { var filterControl = phAttributeFilters.FindControl("filter_" + attribute.Id.ToString()); if (filterControl != null) { var filterValues = attribute.FieldType.Field.GetFilterValues(filterControl, attribute.QualifierValues, Rock.Reporting.FilterMode.SimpleFilter); var expression = attribute.FieldType.Field.AttributeFilterExpression(attribute.QualifierValues, filterValues, parameterExpression); if (expression != null) { var attributeValues = attributeValueService .Queryable() .Where(v => v.Attribute.Id == attribute.Id); attributeValues = attributeValues.Where(parameterExpression, expression, null); qry = qry.Where(w => attributeValues.Select(v => v.EntityId).Contains(w.Id)); } } } } _inactiveStatus = DefinedValueCache.Read(Rock.SystemGuid.DefinedValue.PERSON_RECORD_STATUS_INACTIVE); SortProperty sortProperty = gGroupMembers.SortProperty; bool hasGroupRequirements = new GroupRequirementService(rockContext).Queryable().Where(a => a.GroupId == _group.Id).Any(); // If there are group requirements that that member doesn't meet, show an icon in the grid bool includeWarnings = false; var groupMemberIdsThatLackGroupRequirements = new GroupService(rockContext).GroupMembersNotMeetingRequirements(_group.Id, includeWarnings).Select(a => a.Key.Id); List <GroupMember> groupMembersList = null; if (sortProperty != null) { groupMembersList = qry.Sort(sortProperty).ToList(); } else { groupMembersList = qry.OrderBy(a => a.GroupRole.Order).ThenBy(a => a.Person.LastName).ThenBy(a => a.Person.FirstName).ToList(); } // Since we're not binding to actual group member list, but are using AttributeField columns, // we need to save the workflows into the grid's object list gGroupMembers.ObjectList = new Dictionary <string, object>(); groupMembersList.ForEach(m => gGroupMembers.ObjectList.Add(m.Id.ToString(), m)); gGroupMembers.EntityTypeId = EntityTypeCache.Read(Rock.SystemGuid.EntityType.GROUP_MEMBER.AsGuid()).Id; var homePhoneType = DefinedValueCache.Read(Rock.SystemGuid.DefinedValue.PERSON_PHONE_TYPE_HOME); var cellPhoneType = DefinedValueCache.Read(Rock.SystemGuid.DefinedValue.PERSON_PHONE_TYPE_MOBILE); // If exporting to Excel, the selectAll option will be true, and home location should be calculated var homeLocations = new Dictionary <int, Location>(); if (selectAll) { foreach (var m in groupMembersList) { homeLocations.Add(m.Id, m.Person.GetHomeLocation(rockContext)); } } gGroupMembers.DataSource = groupMembersList .ToList().Select(m => new { m.Id, m.Guid, m.PersonId, Name = m.Person.NickName + " " + m.Person.LastName + (hasGroupRequirements && groupMemberIdsThatLackGroupRequirements.Contains(m.Id) ? " <i class='fa fa-exclamation-triangle text-warning'></i>" : string.Empty) + (!string.IsNullOrEmpty(m.Note) ? " <i class='fa fa-file-text-o text-info'></i>" : string.Empty), Email = m.Person.Email, HomePhone = homePhoneType != null ? m.Person.PhoneNumbers .Where(p => p.NumberTypeValueId.HasValue && p.NumberTypeValueId.Value == homePhoneType.Id) .Select(p => p.NumberFormatted) .FirstOrDefault() : string.Empty, CellPhone = cellPhoneType != null ? m.Person.PhoneNumbers .Where(p => p.NumberTypeValueId.HasValue && p.NumberTypeValueId.Value == cellPhoneType.Id) .Select(p => p.NumberFormatted) .FirstOrDefault() : string.Empty, HomeAddress = homeLocations.ContainsKey(m.Id) && homeLocations[m.Id] != null ? homeLocations[m.Id].FormattedAddress : string.Empty, Latitude = homeLocations.ContainsKey(m.Id) && homeLocations[m.Id] != null ? homeLocations[m.Id].Latitude : (double?)null, Longitude = homeLocations.ContainsKey(m.Id) && homeLocations[m.Id] != null ? homeLocations[m.Id].Longitude : (double?)null, GroupRole = m.GroupRole.Name, m.GroupMemberStatus, RecordStatusValueId = m.Person.RecordStatusValueId, IsDeceased = m.Person.IsDeceased }).ToList(); gGroupMembers.DataBind(); } else { nbRoleWarning.Text = string.Format( "{0} cannot be added to this {1} because the '{2}' group type does not have any roles defined.", _group.GroupType.GroupMemberTerm.Pluralize(), _group.GroupType.GroupTerm, _group.GroupType.Name); nbRoleWarning.Visible = true; rFilter.Visible = false; gGroupMembers.Visible = false; } } else { pnlGroupMembers.Visible = false; } }
/// <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> /// Binds the group members grid. /// </summary> protected void BindGroupMembersGrid( bool isExporting = false ) { if ( _group != null ) { pnlGroupMembers.Visible = true; lHeading.Text = string.Format( "{0} {1}", _group.GroupType.GroupTerm, _group.GroupType.GroupMemberTerm.Pluralize() ); if ( _group.GroupType.Roles.Any() ) { nbRoleWarning.Visible = false; rFilter.Visible = true; gGroupMembers.Visible = true; var rockContext = new RockContext(); if ( _group != null && _group.RequiredSignatureDocumentTemplateId.HasValue ) { Signers = new SignatureDocumentService( rockContext ) .Queryable().AsNoTracking() .Where( d => d.SignatureDocumentTemplateId == _group.RequiredSignatureDocumentTemplateId.Value && d.Status == SignatureDocumentStatus.Signed && d.BinaryFileId.HasValue && d.AppliesToPersonAlias != null ) .OrderByDescending( d => d.LastStatusDate ) .Select( d => d.AppliesToPersonAlias.PersonId ) .ToList(); } GroupMemberService groupMemberService = new GroupMemberService( rockContext ); var qry = groupMemberService.Queryable( "Person,GroupRole", true ).AsNoTracking() .Where( m => m.GroupId == _group.Id ); // Filter by First Name string firstName = tbFirstName.Text; if ( !string.IsNullOrWhiteSpace( firstName ) ) { qry = qry.Where( m => m.Person.FirstName.StartsWith( firstName ) || m.Person.NickName.StartsWith( firstName ) ); } // Filter by Last Name string lastName = tbLastName.Text; if ( !string.IsNullOrWhiteSpace( lastName ) ) { qry = qry.Where( m => m.Person.LastName.StartsWith( lastName ) ); } // Filter by role var validGroupTypeRoles = _group.GroupType.Roles.Select( r => r.Id ).ToList(); var roles = new List<int>(); foreach ( var roleId in cblRole.SelectedValues.AsIntegerList() ) { if ( validGroupTypeRoles.Contains( roleId ) ) { roles.Add( roleId ); } } if ( roles.Any() ) { qry = qry.Where( m => roles.Contains( m.GroupRoleId ) ); } // Filter by Group Member Status var statuses = new List<GroupMemberStatus>(); foreach ( string status in cblGroupMemberStatus.SelectedValues ) { if ( !string.IsNullOrWhiteSpace( status ) ) { statuses.Add( status.ConvertToEnum<GroupMemberStatus>() ); } } if ( statuses.Any() ) { qry = qry.Where( m => statuses.Contains( m.GroupMemberStatus ) ); } var genders = new List<Gender>(); foreach ( var item in cblGenderFilter.SelectedValues ) { var gender = item.ConvertToEnum<Gender>(); genders.Add( gender ); } if ( genders.Any() ) { qry = qry.Where( m => genders.Contains( m.Person.Gender ) ); } // Filter by Campus if ( cpCampusFilter.SelectedCampusId.HasValue ) { Guid familyGuid = new Guid( Rock.SystemGuid.GroupType.GROUPTYPE_FAMILY ); int campusId = cpCampusFilter.SelectedCampusId.Value; var qryFamilyMembersForCampus = new GroupMemberService( rockContext ).Queryable().Where( a => a.Group.GroupType.Guid == familyGuid && a.Group.CampusId == campusId ); qry = qry.Where( a => qryFamilyMembersForCampus.Any( f => f.PersonId == a.PersonId ) ); } // Filter by signed documents if ( Signers != null ) { if ( ddlSignedDocument.SelectedValue.AsBooleanOrNull() == true ) { qry = qry.Where( m => Signers.Contains( m.PersonId ) ); } else if ( ddlSignedDocument.SelectedValue.AsBooleanOrNull() == false ) { qry = qry.Where( m => !Signers.Contains( m.PersonId ) ); } } // Filter query by any configured attribute filters if ( AvailableAttributes != null && AvailableAttributes.Any() ) { var attributeValueService = new AttributeValueService( rockContext ); var parameterExpression = attributeValueService.ParameterExpression; foreach ( var attribute in AvailableAttributes ) { var filterControl = phAttributeFilters.FindControl( "filter_" + attribute.Id.ToString() ); if ( filterControl != null ) { var filterValues = attribute.FieldType.Field.GetFilterValues( filterControl, attribute.QualifierValues, Rock.Reporting.FilterMode.SimpleFilter ); var expression = attribute.FieldType.Field.AttributeFilterExpression( attribute.QualifierValues, filterValues, parameterExpression ); if ( expression != null ) { var attributeValues = attributeValueService .Queryable() .Where( v => v.Attribute.Id == attribute.Id ); attributeValues = attributeValues.Where( parameterExpression, expression, null ); qry = qry.Where( w => attributeValues.Select( v => v.EntityId ).Contains( w.Id ) ); } } } } _inactiveStatus = DefinedValueCache.Read( Rock.SystemGuid.DefinedValue.PERSON_RECORD_STATUS_INACTIVE ); SortProperty sortProperty = gGroupMembers.SortProperty; bool hasGroupRequirements = new GroupRequirementService( rockContext ).Queryable().Where( a => a.GroupId == _group.Id ).Any(); // If there are group requirements that that member doesn't meet, show an icon in the grid bool includeWarnings = false; var groupMemberIdsThatLackGroupRequirements = new GroupService( rockContext ).GroupMembersNotMeetingRequirements( _group.Id, includeWarnings ).Select( a => a.Key.Id ); List<GroupMember> groupMembersList = null; if ( sortProperty != null && sortProperty.Property != "FirstAttended" && sortProperty.Property != "LastAttended" ) { groupMembersList = qry.Sort( sortProperty ).ToList(); } else { groupMembersList = qry.OrderBy( a => a.GroupRole.Order ).ThenBy( a => a.Person.LastName ).ThenBy( a => a.Person.FirstName ).ToList(); } // If there is a required signed document that member has not signed, show an icon in the grid var personIdsThatHaventSigned = new List<int>(); if ( Signers != null ) { var memberPersonIds = groupMembersList.Select( m => m.PersonId ).ToList(); personIdsThatHaventSigned = memberPersonIds.Where( i => !Signers.Contains( i ) ).ToList(); } // Since we're not binding to actual group member list, but are using AttributeField columns, // we need to save the group members into the grid's object list gGroupMembers.ObjectList = new Dictionary<string, object>(); groupMembersList.ForEach( m => gGroupMembers.ObjectList.Add( m.Id.ToString(), m ) ); gGroupMembers.EntityTypeId = EntityTypeCache.Read( Rock.SystemGuid.EntityType.GROUP_MEMBER.AsGuid() ).Id; var homePhoneType = DefinedValueCache.Read( Rock.SystemGuid.DefinedValue.PERSON_PHONE_TYPE_HOME ); var cellPhoneType = DefinedValueCache.Read( Rock.SystemGuid.DefinedValue.PERSON_PHONE_TYPE_MOBILE ); // If exporting to Excel, the selectAll option will be true, and home location should be calculated var homeLocations = new Dictionary<int, Location>(); if ( isExporting ) { foreach ( var m in groupMembersList ) { homeLocations.Add( m.Id, m.Person.GetHomeLocation( rockContext ) ); } } var groupMemberIds = groupMembersList.Select( m => m.Id ).ToList(); // Get all the group members with any associated registrations _groupMembersWithRegistrations = new RegistrationRegistrantService( rockContext ) .Queryable().AsNoTracking() .Where( r => r.Registration != null && r.Registration.RegistrationInstance != null && r.GroupMemberId.HasValue && groupMemberIds.Contains( r.GroupMemberId.Value ) ) .ToList() .GroupBy( r => r.GroupMemberId.Value ) .Select( g => new { GroupMemberId = g.Key, Registrations = g.ToList() .Select( r => new { Id = r.Registration.Id, Name = r.Registration.RegistrationInstance.Name } ).Distinct() .ToDictionary( r => r.Id, r => r.Name ) } ) .ToDictionary( r => r.GroupMemberId, r => r.Registrations ); var registrationField = gGroupMembers.ColumnsOfType<RockTemplateFieldUnselected>().FirstOrDefault(); if ( registrationField != null ) { registrationField.Visible = _groupMembersWithRegistrations.Any(); } var connectionStatusField = gGroupMembers.ColumnsOfType<DefinedValueField>().FirstOrDefault( a => a.DataField == "ConnectionStatusValueId" ); if ( connectionStatusField != null ) { connectionStatusField.Visible = _group.GroupType.ShowConnectionStatus; } string photoFormat = "<div class=\"photo-icon photo-round photo-round-xs pull-left margin-r-sm js-person-popover\" personid=\"{0}\" data-original=\"{1}&w=50\" style=\"background-image: url( '{2}' ); background-size: cover; background-repeat: no-repeat;\"></div>"; var attendanceFirstLast = new Dictionary<int, DateRange>(); bool showAttendance = GetAttributeValue( "ShowAttendance" ).AsBoolean() && _group.GroupType.TakesAttendance; gGroupMembers.ColumnsOfType<DateField>().First( a => a.DataField == "FirstAttended" ).Visible = showAttendance; gGroupMembers.ColumnsOfType<DateField>().First( a => a.DataField == "LastAttended" ).Visible = showAttendance; if ( showAttendance ) { foreach ( var attendance in new AttendanceService( rockContext ) .Queryable().AsNoTracking() .Where( a => a.GroupId.HasValue && a.GroupId.Value == _group.Id && a.DidAttend.HasValue && a.DidAttend.Value ) .GroupBy( a => a.PersonAlias.PersonId ) .Select( g => new { PersonId = g.Key, FirstAttended = g.Min( a => a.StartDateTime ), LastAttended = g.Max( a => a.StartDateTime ) } ) .ToList() ) { attendanceFirstLast.Add( attendance.PersonId, new DateRange( attendance.FirstAttended, attendance.LastAttended ) ); } } var dataSource = groupMembersList.Select( m => new { m.Id, m.Guid, m.PersonId, m.Person.NickName, m.Person.LastName, Name = ( isExporting ? m.Person.LastName + ", " + m.Person.NickName : string.Format( photoFormat, m.PersonId, m.Person.PhotoUrl, ResolveUrl( "~/Assets/Images/person-no-photo-male.svg" ) ) + m.Person.NickName + " " + m.Person.LastName + ( ( hasGroupRequirements && groupMemberIdsThatLackGroupRequirements.Contains( m.Id ) ) ? " <i class='fa fa-exclamation-triangle text-warning'></i>" : string.Empty ) + ( !string.IsNullOrEmpty( m.Note ) ? " <i class='fa fa-file-text-o text-info'></i>" : string.Empty ) + ((personIdsThatHaventSigned.Contains( m.PersonId )) ? " <i class='fa fa-pencil-square-o text-danger'></i>" : string.Empty)), m.Person.BirthDate, m.Person.Age, m.Person.ConnectionStatusValueId, m.DateTimeAdded, FirstAttended = attendanceFirstLast.Where( a => a.Key == m.PersonId ).Select( a => a.Value.Start ).FirstOrDefault(), LastAttended = attendanceFirstLast.Where( a => a.Key == m.PersonId ).Select( a => a.Value.End ).FirstOrDefault(), Email = m.Person.Email, HomePhone = isExporting && homePhoneType != null ? m.Person.PhoneNumbers .Where( p => p.NumberTypeValueId.HasValue && p.NumberTypeValueId.Value == homePhoneType.Id ) .Select( p => p.NumberFormatted ) .FirstOrDefault() : string.Empty, CellPhone = isExporting && cellPhoneType != null ? m.Person.PhoneNumbers .Where( p => p.NumberTypeValueId.HasValue && p.NumberTypeValueId.Value == cellPhoneType.Id ) .Select( p => p.NumberFormatted ) .FirstOrDefault() : string.Empty, HomeAddress = homeLocations.ContainsKey( m.Id ) && homeLocations[m.Id] != null ? homeLocations[m.Id].FormattedAddress : string.Empty, Latitude = homeLocations.ContainsKey( m.Id ) && homeLocations[m.Id] != null ? homeLocations[m.Id].Latitude : (double?)null, Longitude = homeLocations.ContainsKey( m.Id ) && homeLocations[m.Id] != null ? homeLocations[m.Id].Longitude : (double?)null, GroupRole = m.GroupRole.Name, m.GroupMemberStatus, RecordStatusValueId = m.Person.RecordStatusValueId, IsDeceased = m.Person.IsDeceased } ).ToList(); if ( sortProperty != null ) { if ( sortProperty.Property == "FirstAttended" ) { if ( sortProperty.Direction == SortDirection.Descending ) { dataSource = dataSource.OrderByDescending( a => a.FirstAttended ?? DateTime.MinValue ).ToList(); } else { dataSource = dataSource.OrderBy( a => a.FirstAttended ?? DateTime.MinValue ).ToList(); } } if ( sortProperty.Property == "LastAttended" ) { if ( sortProperty.Direction == SortDirection.Descending ) { dataSource = dataSource.OrderByDescending( a => a.LastAttended ?? DateTime.MinValue ).ToList(); } else { dataSource = dataSource.OrderBy( a => a.LastAttended ?? DateTime.MinValue ).ToList(); } } } gGroupMembers.DataSource = dataSource; gGroupMembers.DataBind(); } else { nbRoleWarning.Text = string.Format( "{0} cannot be added to this {1} because the '{2}' group type does not have any roles defined.", _group.GroupType.GroupMemberTerm.Pluralize(), _group.GroupType.GroupTerm, _group.GroupType.Name ); nbRoleWarning.Visible = true; rFilter.Visible = false; gGroupMembers.Visible = false; } } else { pnlGroupMembers.Visible = false; } }
/// <summary> /// Handles the Click event of the btnSave control. /// </summary> /// <param name="sender">The source of the event.</param> /// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param> protected void btnSave_Click( object sender, EventArgs e ) { Group group; bool wasSecurityRole = false; bool triggersUpdated = false; RockContext rockContext = new RockContext(); GroupService groupService = new GroupService( rockContext ); GroupLocationService groupLocationService = new GroupLocationService( rockContext ); GroupRequirementService groupRequirementService = new GroupRequirementService( rockContext ); GroupMemberWorkflowTriggerService groupMemberWorkflowTriggerService = new GroupMemberWorkflowTriggerService( rockContext ); ScheduleService scheduleService = new ScheduleService( rockContext ); AttributeService attributeService = new AttributeService( rockContext ); AttributeQualifierService attributeQualifierService = new AttributeQualifierService( rockContext ); CategoryService categoryService = new CategoryService( rockContext ); var roleGroupType = GroupTypeCache.Read( Rock.SystemGuid.GroupType.GROUPTYPE_SECURITY_ROLE.AsGuid() ); int roleGroupTypeId = roleGroupType != null ? roleGroupType.Id : int.MinValue; if ( CurrentGroupTypeId == 0 ) { ddlGroupType.ShowErrorMessage( Rock.Constants.WarningMessage.CannotBeBlank( GroupType.FriendlyTypeName ) ); return; } int groupId = hfGroupId.Value.AsInteger(); if ( groupId == 0 ) { group = new Group(); group.IsSystem = false; group.Name = string.Empty; } else { group = groupService.Queryable( "Schedule,GroupLocations.Schedules" ).Where( g => g.Id == groupId ).FirstOrDefault(); wasSecurityRole = group.IsActive && ( group.IsSecurityRole || group.GroupTypeId == roleGroupTypeId ); // remove any locations that removed in the UI var selectedLocations = GroupLocationsState.Select( l => l.Guid ); foreach ( var groupLocation in group.GroupLocations.Where( l => !selectedLocations.Contains( l.Guid ) ).ToList() ) { group.GroupLocations.Remove( groupLocation ); groupLocationService.Delete( groupLocation ); } // remove any group requirements that removed in the UI var selectedGroupRequirements = GroupRequirementsState.Select( a => a.Guid ); foreach ( var groupRequirement in group.GroupRequirements.Where( a => !selectedGroupRequirements.Contains( a.Guid ) ).ToList() ) { group.GroupRequirements.Remove( groupRequirement ); groupRequirementService.Delete( groupRequirement ); } // Remove any triggers that were removed in the UI var selectedTriggerGuids = MemberWorkflowTriggersState.Select( r => r.Guid ); foreach ( var trigger in group.GroupMemberWorkflowTriggers.Where( r => !selectedTriggerGuids.Contains( r.Guid ) ).ToList() ) { group.GroupMemberWorkflowTriggers.Remove( trigger ); groupMemberWorkflowTriggerService.Delete( trigger ); triggersUpdated = true; } } // add/update any group requirements that were added or changed in the UI (we already removed the ones that were removed above) foreach ( var groupRequirementState in GroupRequirementsState ) { GroupRequirement groupRequirement = group.GroupRequirements.Where( a => a.Guid == groupRequirementState.Guid ).FirstOrDefault(); if ( groupRequirement == null ) { groupRequirement = new GroupRequirement(); group.GroupRequirements.Add( groupRequirement ); } groupRequirement.CopyPropertiesFrom( groupRequirementState ); } // add/update any group locations that were added or changed in the UI (we already removed the ones that were removed above) foreach ( var groupLocationState in GroupLocationsState ) { GroupLocation groupLocation = group.GroupLocations.Where( l => l.Guid == groupLocationState.Guid ).FirstOrDefault(); if ( groupLocation == null ) { groupLocation = new GroupLocation(); group.GroupLocations.Add( groupLocation ); } else { groupLocationState.Id = groupLocation.Id; groupLocationState.Guid = groupLocation.Guid; var selectedSchedules = groupLocationState.Schedules.Select( s => s.Guid ).ToList(); foreach ( var schedule in groupLocation.Schedules.Where( s => !selectedSchedules.Contains( s.Guid ) ).ToList() ) { groupLocation.Schedules.Remove( schedule ); } } groupLocation.CopyPropertiesFrom( groupLocationState ); var existingSchedules = groupLocation.Schedules.Select( s => s.Guid ).ToList(); foreach ( var scheduleState in groupLocationState.Schedules.Where( s => !existingSchedules.Contains( s.Guid ) ).ToList() ) { var schedule = scheduleService.Get( scheduleState.Guid ); if ( schedule != null ) { groupLocation.Schedules.Add( schedule ); } } } foreach ( var triggerState in MemberWorkflowTriggersState ) { GroupMemberWorkflowTrigger trigger = group.GroupMemberWorkflowTriggers.Where( r => r.Guid == triggerState.Guid ).FirstOrDefault(); if ( trigger == null ) { trigger = new GroupMemberWorkflowTrigger(); group.GroupMemberWorkflowTriggers.Add( trigger ); } else { triggerState.Id = trigger.Id; triggerState.Guid = trigger.Guid; } trigger.CopyPropertiesFrom( triggerState ); triggersUpdated = true; } group.Name = tbName.Text; group.Description = tbDescription.Text; group.CampusId = ddlCampus.SelectedValueAsInt(); group.GroupTypeId = CurrentGroupTypeId; group.ParentGroupId = gpParentGroup.SelectedValueAsInt(); group.GroupCapacity = nbGroupCapacity.Text.AsIntegerOrNull(); group.RequiredSignatureDocumentTemplateId = ddlSignatureDocumentTemplate.SelectedValueAsInt(); group.IsSecurityRole = cbIsSecurityRole.Checked; group.IsActive = cbIsActive.Checked; group.IsPublic = cbIsPublic.Checked; group.MustMeetRequirementsToAddMember = cbMembersMustMeetRequirementsOnAdd.Checked; // save sync settings group.SyncDataViewId = dvpSyncDataview.SelectedValue.AsIntegerOrNull(); group.WelcomeSystemEmailId = ddlWelcomeEmail.SelectedValue.AsIntegerOrNull(); group.ExitSystemEmailId = ddlExitEmail.SelectedValue.AsIntegerOrNull(); group.AddUserAccountsDuringSync = rbCreateLoginDuringSync.Checked; string iCalendarContent = string.Empty; // If unique schedule option was selected, but a schedule was not defined, set option to 'None' var scheduleType = rblScheduleSelect.SelectedValueAsEnum<ScheduleType>( ScheduleType.None ); if ( scheduleType == ScheduleType.Custom ) { iCalendarContent = sbSchedule.iCalendarContent; var calEvent = ScheduleICalHelper.GetCalenderEvent( iCalendarContent ); if ( calEvent == null || calEvent.DTStart == null ) { scheduleType = ScheduleType.None; } } if ( scheduleType == ScheduleType.Weekly ) { if ( !dowWeekly.SelectedDayOfWeek.HasValue ) { scheduleType = ScheduleType.None; } } int? oldScheduleId = hfUniqueScheduleId.Value.AsIntegerOrNull(); if ( scheduleType == ScheduleType.Custom || scheduleType == ScheduleType.Weekly ) { if ( !oldScheduleId.HasValue || group.Schedule == null ) { group.Schedule = new Schedule(); } if ( scheduleType == ScheduleType.Custom ) { group.Schedule.iCalendarContent = iCalendarContent; group.Schedule.WeeklyDayOfWeek = null; group.Schedule.WeeklyTimeOfDay = null; } else { group.Schedule.iCalendarContent = null; group.Schedule.WeeklyDayOfWeek = dowWeekly.SelectedDayOfWeek; group.Schedule.WeeklyTimeOfDay = timeWeekly.SelectedTime; } } else { // If group did have a unique schedule, delete that schedule if ( oldScheduleId.HasValue ) { var schedule = scheduleService.Get( oldScheduleId.Value ); if ( schedule != null && string.IsNullOrEmpty( schedule.Name ) ) { // Make sure this is the only group trying to use this schedule. if ( !groupService.Queryable().Where( g => g.ScheduleId == schedule.Id && g.Id != group.Id ).Any() ) { scheduleService.Delete( schedule ); } } } if ( scheduleType == ScheduleType.Named ) { group.ScheduleId = spSchedule.SelectedValueAsId(); } else { group.ScheduleId = null; } } if ( group.ParentGroupId == group.Id ) { gpParentGroup.ShowErrorMessage( "Group cannot be a Parent Group of itself." ); return; } group.LoadAttributes(); Rock.Attribute.Helper.GetEditValues( phGroupAttributes, group ); group.GroupType = new GroupTypeService( rockContext ).Get( group.GroupTypeId ); if ( group.ParentGroupId.HasValue ) { group.ParentGroup = groupService.Get( group.ParentGroupId.Value ); } // Check to see if group type is allowed as a child of new parent group. if ( group.ParentGroup != null ) { var allowedGroupTypeIds = GetAllowedGroupTypes( group.ParentGroup, rockContext ).Select( t => t.Id ).ToList(); if ( !allowedGroupTypeIds.Contains( group.GroupTypeId ) ) { var groupType = CurrentGroupTypeCache; nbInvalidParentGroup.Text = string.Format( "The '{0}' group does not allow child groups with a '{1}' group type.", group.ParentGroup.Name, groupType != null ? groupType.Name : string.Empty ); nbInvalidParentGroup.Visible = true; return; } } // Check to see if user is still allowed to edit with selected group type and parent group if ( !group.IsAuthorized( Authorization.EDIT, CurrentPerson ) ) { nbNotAllowedToEdit.Visible = true; return; } if ( !Page.IsValid ) { return; } // if the groupMember IsValid is false, and the UI controls didn't report any errors, it is probably because the custom rules of GroupMember didn't pass. // So, make sure a message is displayed in the validation summary cvGroup.IsValid = group.IsValid; if ( !cvGroup.IsValid ) { cvGroup.ErrorMessage = group.ValidationResults.Select( a => a.ErrorMessage ).ToList().AsDelimited( "<br />" ); return; } // use WrapTransaction since SaveAttributeValues does it's own RockContext.SaveChanges() rockContext.WrapTransaction( () => { var adding = group.Id.Equals( 0 ); if ( adding ) { groupService.Add( group ); } rockContext.SaveChanges(); if ( adding ) { // add ADMINISTRATE to the person who added the group Rock.Security.Authorization.AllowPerson( group, Authorization.ADMINISTRATE, this.CurrentPerson, rockContext ); } group.SaveAttributeValues( rockContext ); /* Take care of Group Member Attributes */ var entityTypeId = EntityTypeCache.Read( typeof( GroupMember ) ).Id; string qualifierColumn = "GroupId"; string qualifierValue = group.Id.ToString(); // Get the existing attributes for this entity type and qualifier value var attributes = attributeService.Get( entityTypeId, qualifierColumn, qualifierValue ); // Delete any of those attributes that were removed in the UI var selectedAttributeGuids = GroupMemberAttributesState.Select( a => a.Guid ); foreach ( var attr in attributes.Where( a => !selectedAttributeGuids.Contains( a.Guid ) ) ) { Rock.Web.Cache.AttributeCache.Flush( attr.Id ); attributeService.Delete( attr ); } // Update the Attributes that were assigned in the UI foreach ( var attributeState in GroupMemberAttributesState ) { Rock.Attribute.Helper.SaveAttributeEdits( attributeState, entityTypeId, qualifierColumn, qualifierValue, rockContext ); } rockContext.SaveChanges(); if ( group.IsActive == false && cbInactivateChildGroups.Checked ) { var allActiveChildGroupsId = groupService.GetAllDescendents( group.Id ).Where( a => a.IsActive ).Select( a => a.Id ).ToList(); var allActiveChildGroups = groupService.GetByIds( allActiveChildGroupsId ); foreach ( var childGroup in allActiveChildGroups ) { if ( childGroup.IsActive ) { childGroup.IsActive = false; } } rockContext.SaveChanges(); } } ); bool isNowSecurityRole = group.IsActive && ( group.IsSecurityRole || group.GroupTypeId == roleGroupTypeId ); if ( group != null && wasSecurityRole ) { if ( !isNowSecurityRole ) { // if this group was a SecurityRole, but no longer is, flush Rock.Security.Role.Flush( group.Id ); Rock.Security.Authorization.Flush(); } } else { if ( isNowSecurityRole ) { // new security role, flush Rock.Security.Authorization.Flush(); } } AttributeCache.FlushEntityAttributes(); if ( triggersUpdated ) { GroupMemberWorkflowTriggerService.FlushCachedTriggers(); } var qryParams = new Dictionary<string, string>(); qryParams["GroupId"] = group.Id.ToString(); qryParams["ExpandedIds"] = PageParameter( "ExpandedIds" ); NavigateToPage( RockPage.Guid, qryParams ); }
/// <summary> /// Binds the group members grid. /// </summary> protected void BindGroupMembersGrid(bool selectAll = false) { if (_group != null) { pnlGroupMembers.Visible = true; lHeading.Text = string.Format("{0} {1}", _group.GroupType.GroupTerm, _group.GroupType.GroupMemberTerm.Pluralize()); if (_group.GroupType.Roles.Any()) { nbRoleWarning.Visible = false; rFilter.Visible = true; gGroupMembers.Visible = true; var rockContext = new RockContext(); GroupMemberService groupMemberService = new GroupMemberService(rockContext); var qry = groupMemberService.Queryable("Person,GroupRole", true).AsNoTracking() .Where(m => m.GroupId == _group.Id); // Filter by First Name string firstName = tbFirstName.Text; if (!string.IsNullOrWhiteSpace(firstName)) { qry = qry.Where(m => m.Person.FirstName.StartsWith(firstName) || m.Person.NickName.StartsWith(firstName)); } // Filter by Last Name string lastName = tbLastName.Text; if (!string.IsNullOrWhiteSpace(lastName)) { qry = qry.Where(m => m.Person.LastName.StartsWith(lastName)); } // Filter by role var validGroupTypeRoles = _group.GroupType.Roles.Select(r => r.Id).ToList(); var roles = new List <int>(); foreach (var roleId in cblRole.SelectedValues.AsIntegerList()) { if (validGroupTypeRoles.Contains(roleId)) { roles.Add(roleId); } } if (roles.Any()) { qry = qry.Where(m => roles.Contains(m.GroupRoleId)); } // Filter by Group Member Status var statuses = new List <GroupMemberStatus>(); foreach (string status in cblGroupMemberStatus.SelectedValues) { if (!string.IsNullOrWhiteSpace(status)) { statuses.Add(status.ConvertToEnum <GroupMemberStatus>()); } } if (statuses.Any()) { qry = qry.Where(m => statuses.Contains(m.GroupMemberStatus)); } // Filter by Campus if (cpCampusFilter.SelectedCampusId.HasValue) { Guid familyGuid = new Guid(Rock.SystemGuid.GroupType.GROUPTYPE_FAMILY); int campusId = cpCampusFilter.SelectedCampusId.Value; var qryFamilyMembersForCampus = new GroupMemberService(rockContext).Queryable().Where(a => a.Group.GroupType.Guid == familyGuid && a.Group.CampusId == campusId); qry = qry.Where(a => qryFamilyMembersForCampus.Any(f => f.PersonId == a.PersonId)); } // Filter query by any configured attribute filters if (AvailableAttributes != null && AvailableAttributes.Any()) { var attributeValueService = new AttributeValueService(rockContext); var parameterExpression = attributeValueService.ParameterExpression; foreach (var attribute in AvailableAttributes) { var filterControl = phAttributeFilters.FindControl("filter_" + attribute.Id.ToString()); if (filterControl != null) { var filterValues = attribute.FieldType.Field.GetFilterValues(filterControl, attribute.QualifierValues, Rock.Reporting.FilterMode.SimpleFilter); var expression = attribute.FieldType.Field.AttributeFilterExpression(attribute.QualifierValues, filterValues, parameterExpression); if (expression != null) { var attributeValues = attributeValueService .Queryable() .Where(v => v.Attribute.Id == attribute.Id); attributeValues = attributeValues.Where(parameterExpression, expression, null); qry = qry.Where(w => attributeValues.Select(v => v.EntityId).Contains(w.Id)); } } } } _inactiveStatus = DefinedValueCache.Read(Rock.SystemGuid.DefinedValue.PERSON_RECORD_STATUS_INACTIVE); SortProperty sortProperty = gGroupMembers.SortProperty; bool hasGroupRequirements = new GroupRequirementService(rockContext).Queryable().Where(a => a.GroupId == _group.Id).Any(); // If there are group requirements that that member doesn't meet, show an icon in the grid bool includeWarnings = false; var groupMemberIdsThatLackGroupRequirements = new GroupService(rockContext).GroupMembersNotMeetingRequirements(_group.Id, includeWarnings).Select(a => a.Key.Id); List <GroupMember> groupMembersList = null; if (sortProperty != null) { groupMembersList = qry.Sort(sortProperty).ToList(); } else { groupMembersList = qry.OrderBy(a => a.GroupRole.Order).ThenBy(a => a.Person.LastName).ThenBy(a => a.Person.FirstName).ToList(); } // Since we're not binding to actual group member list, but are using AttributeField columns, // we need to save the workflows into the grid's object list gGroupMembers.ObjectList = new Dictionary <string, object>(); groupMembersList.ForEach(m => gGroupMembers.ObjectList.Add(m.Id.ToString(), m)); gGroupMembers.EntityTypeId = EntityTypeCache.Read(Rock.SystemGuid.EntityType.GROUP_MEMBER.AsGuid()).Id; var homePhoneType = DefinedValueCache.Read(Rock.SystemGuid.DefinedValue.PERSON_PHONE_TYPE_HOME); var cellPhoneType = DefinedValueCache.Read(Rock.SystemGuid.DefinedValue.PERSON_PHONE_TYPE_MOBILE); // If exporting to Excel, the selectAll option will be true, and home location should be calculated var homeLocations = new Dictionary <int, Location>(); if (selectAll) { foreach (var m in groupMembersList) { homeLocations.Add(m.Id, m.Person.GetHomeLocation(rockContext)); } } var groupMemberIds = groupMembersList.Select(m => m.Id).ToList(); // Get all the group members with any associated registrations _groupMembersWithRegistrations = new RegistrationRegistrantService(rockContext) .Queryable().AsNoTracking() .Where(r => r.Registration != null && r.Registration.RegistrationInstance != null && r.GroupMemberId.HasValue && groupMemberIds.Contains(r.GroupMemberId.Value)) .ToList() .GroupBy(r => r.GroupMemberId.Value) .Select(g => new { GroupMemberId = g.Key, Registrations = g.ToList() .Select(r => new { Id = r.Registration.Id, Name = r.Registration.RegistrationInstance.Name }) .ToDictionary(r => r.Id, r => r.Name) }) .ToDictionary(r => r.GroupMemberId, r => r.Registrations); var registrationField = gGroupMembers.ColumnsOfType <RockTemplateFieldUnselected>().FirstOrDefault(); if (registrationField != null) { registrationField.Visible = _groupMembersWithRegistrations.Any(); } var connectionStatusField = gGroupMembers.ColumnsOfType <DefinedValueField>().FirstOrDefault(a => a.DataField == "ConnectionStatusValueId"); if (connectionStatusField != null) { connectionStatusField.Visible = _group.GroupType.ShowConnectionStatus; } string photoFormat = "<div class=\"photo-icon photo-round photo-round-xs pull-left margin-r-sm js-person-popover\" personid=\"{0}\" data-original=\"{1}&w=50\" style=\"background-image: url( '{2}' ); background-size: cover; background-repeat: no-repeat;\"></div>"; gGroupMembers.DataSource = groupMembersList.Select(m => new { m.Id, m.Guid, m.PersonId, m.Person.NickName, m.Person.LastName, Name = (selectAll ? m.Person.LastName + ", " + m.Person.NickName : string.Format(photoFormat, m.PersonId, m.Person.PhotoUrl, ResolveUrl("~/Assets/Images/person-no-photo-male.svg")) + m.Person.NickName + " " + m.Person.LastName + (hasGroupRequirements && groupMemberIdsThatLackGroupRequirements.Contains(m.Id) ? " <i class='fa fa-exclamation-triangle text-warning'></i>" : string.Empty) + (!string.IsNullOrEmpty(m.Note) ? " <i class='fa fa-file-text-o text-info'></i>" : string.Empty)), m.Person.ConnectionStatusValueId, Email = m.Person.Email, HomePhone = selectAll && homePhoneType != null ? m.Person.PhoneNumbers .Where(p => p.NumberTypeValueId.HasValue && p.NumberTypeValueId.Value == homePhoneType.Id) .Select(p => p.NumberFormatted) .FirstOrDefault() : string.Empty, CellPhone = selectAll && cellPhoneType != null ? m.Person.PhoneNumbers .Where(p => p.NumberTypeValueId.HasValue && p.NumberTypeValueId.Value == cellPhoneType.Id) .Select(p => p.NumberFormatted) .FirstOrDefault() : string.Empty, HomeAddress = homeLocations.ContainsKey(m.Id) && homeLocations[m.Id] != null ? homeLocations[m.Id].FormattedAddress : string.Empty, Latitude = homeLocations.ContainsKey(m.Id) && homeLocations[m.Id] != null ? homeLocations[m.Id].Latitude : (double?)null, Longitude = homeLocations.ContainsKey(m.Id) && homeLocations[m.Id] != null ? homeLocations[m.Id].Longitude : (double?)null, GroupRole = m.GroupRole.Name, m.GroupMemberStatus, RecordStatusValueId = m.Person.RecordStatusValueId, IsDeceased = m.Person.IsDeceased }).ToList(); gGroupMembers.DataBind(); } else { nbRoleWarning.Text = string.Format( "{0} cannot be added to this {1} because the '{2}' group type does not have any roles defined.", _group.GroupType.GroupMemberTerm.Pluralize(), _group.GroupType.GroupTerm, _group.GroupType.Name); nbRoleWarning.Visible = true; rFilter.Visible = false; gGroupMembers.Visible = false; } } else { pnlGroupMembers.Visible = false; } }
/// <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; }