/// <summary> /// Gets the group historical summary for the specified Person during the specified timeframe /// </summary> /// <param name="personId">The person identifier.</param> /// <param name="startDateTime">The start date time.</param> /// <param name="stopDateTime">The stop date time.</param> /// <param name="groupTypeIds">The group type ids.</param> /// <returns></returns> public List <GroupHistoricalSummary> GetGroupHistoricalSummary(int personId, DateTime?startDateTime, DateTime?stopDateTime, List <int> groupTypeIds) { var rockContext = this.Context as RockContext; var personGroupMemberIdQuery = new GroupMemberService(rockContext).AsNoFilter().Where(a => a.PersonId == personId).Select(a => a.Id); // get all GroupMemberHistorical records for the Person var groupMemberHistoricalQuery = this.AsNoFilter().Where(a => personGroupMemberIdQuery.Contains(a.GroupMemberId)); if (startDateTime.HasValue) { groupMemberHistoricalQuery = groupMemberHistoricalQuery.Where(a => a.EffectiveDateTime >= startDateTime.Value); } if (stopDateTime.HasValue) { groupMemberHistoricalQuery = groupMemberHistoricalQuery.Where(a => a.EffectiveDateTime < stopDateTime.Value); } if (groupTypeIds?.Any() == true) { groupMemberHistoricalQuery = groupMemberHistoricalQuery.Where(a => groupTypeIds.Contains(a.Group.GroupTypeId)); } return(this.GetGroupHistoricalSummary(groupMemberHistoricalQuery)); }
public IQueryable<GuestFamily> GetGuestsForFamily( int groupId ) { Guid knownRelationshipGuid = new Guid( Rock.SystemGuid.GroupType.GROUPTYPE_KNOWN_RELATIONSHIPS ); Guid knownRelationshipOwner = new Guid( Rock.SystemGuid.GroupRole.GROUPROLE_KNOWN_RELATIONSHIPS_OWNER ); Guid knownRelationshipCanCheckin = new Guid( Rock.SystemGuid.GroupRole.GROUPROLE_KNOWN_RELATIONSHIPS_CAN_CHECK_IN ); RockContext rockContext = new RockContext(); GroupMemberService groupMemberService = new GroupMemberService( rockContext ); PersonService personService = new PersonService( rockContext ); var familyMembers = groupMemberService.Queryable() .Where( f => f.GroupId == groupId ) .Select( f => f.PersonId ); var familyMembersKnownRelationshipGroups = new GroupMemberService( rockContext ).Queryable() .Where( g => g.Group.GroupType.Guid == knownRelationshipGuid && g.GroupRole.Guid == knownRelationshipOwner && familyMembers.Contains( g.PersonId ) ) .Select( m => m.GroupId ); rockContext.Database.Log = s => System.Diagnostics.Debug.WriteLine( s ); var guests = groupMemberService.Queryable() .Where( g => g.GroupRole.Guid == knownRelationshipCanCheckin && familyMembersKnownRelationshipGroups.Contains( g.GroupId ) ) .Select( g => g.PersonId ) .Distinct().ToList(); var guestFamilies = new List<GuestFamily>(); rockContext.Database.Log = null; foreach ( var guestPersonId in guests ) { var families = personService.GetFamilies( guestPersonId ); foreach ( var family in families ) { if ( !guestFamilies.Select( f => f.Id ).Contains( family.Id ) ) { GuestFamily guestFamily = new GuestFamily(); guestFamily.Id = family.Id; guestFamily.Guid = family.Guid; guestFamily.Name = family.Name; guestFamily.FamilyMembers = new List<GuestFamilyMember>(); foreach ( var familyMember in family.Members ) { GuestFamilyMember guestFamilyMember = new GuestFamilyMember(); guestFamilyMember.Id = familyMember.PersonId; guestFamilyMember.PersonAliasId = familyMember.Person.PrimaryAliasId.Value; guestFamilyMember.Guid = familyMember.Person.Guid; guestFamilyMember.FirstName = familyMember.Person.NickName; guestFamilyMember.LastName = familyMember.Person.LastName; guestFamilyMember.PhotoUrl = familyMember.Person.PhotoUrl; guestFamilyMember.CanCheckin = guests.Contains( familyMember.PersonId ); guestFamilyMember.Role = familyMember.GroupRole.Name; guestFamilyMember.Age = familyMember.Person.Age; guestFamilyMember.Gender = familyMember.Person.Gender; guestFamily.FamilyMembers.Add( guestFamilyMember ); } guestFamilies.Add( guestFamily ); } } } return guestFamilies.AsQueryable(); }
/// <summary> /// Gets occurrence data for the selected group /// </summary> /// <param name="group">The group.</param> /// <param name="fromDateTime">From date time.</param> /// <param name="toDateTime">To date time.</param> /// <param name="locationIds">The location ids.</param> /// <param name="scheduleIds">The schedule ids.</param> /// <param name="loadSummaryData">if set to <c>true</c> [load summary data].</param> /// <param name="campusId">The campus identifier.</param> /// <returns></returns> public List <ScheduleOccurrence> GetGroupOccurrences(Group group, DateTime?fromDateTime, DateTime?toDateTime, List <int> locationIds, List <int> scheduleIds, bool loadSummaryData, int?campusId) { var occurrences = new List <ScheduleOccurrence>(); if (group != null) { var rockContext = (RockContext)this.Context; var attendanceService = new AttendanceService(rockContext); var scheduleService = new ScheduleService(rockContext); var locationService = new LocationService(rockContext); using (new Rock.Data.QueryHintScope(rockContext, QueryHintType.RECOMPILE)) { // Set up an 'occurrences' query for the group var qry = attendanceService .Queryable().AsNoTracking() .Where(a => a.GroupId == group.Id); // Filter by date range if (fromDateTime.HasValue) { var fromDate = fromDateTime.Value.Date; qry = qry.Where(a => DbFunctions.TruncateTime(a.StartDateTime) >= (fromDate)); } if (toDateTime.HasValue) { var toDate = toDateTime.Value.Date; qry = qry.Where(a => DbFunctions.TruncateTime(a.StartDateTime) < (toDate)); } // Location Filter if (locationIds.Any()) { qry = qry.Where(a => locationIds.Contains(a.LocationId ?? 0)); } // Schedule Filter if (scheduleIds.Any()) { qry = qry.Where(a => scheduleIds.Contains(a.ScheduleId ?? 0)); } // Get the unique combination of location/schedule/date for the selected group var occurrenceDates = qry .Select(a => new { a.LocationId, a.ScheduleId, Date = DbFunctions.TruncateTime(a.StartDateTime) }) .Distinct() .ToList(); // Get the locations for each unique location id var selectedlocationIds = occurrenceDates.Select(o => o.LocationId).Distinct().ToList(); var locations = locationService .Queryable().AsNoTracking() .Where(l => selectedlocationIds.Contains(l.Id)) .Select(l => new { l.Id, l.ParentLocationId, l.Name }) .ToList(); var locationNames = new Dictionary <int, string>(); locations.ForEach(l => locationNames.Add(l.Id, l.Name)); // Get the parent location path for each unique location var parentlocationPaths = new Dictionary <int, string>(); locations .Where(l => l.ParentLocationId.HasValue) .Select(l => l.ParentLocationId.Value) .Distinct() .ToList() .ForEach(l => parentlocationPaths.Add(l, locationService.GetPath(l))); var locationPaths = new Dictionary <int, string>(); locations .Where(l => l.ParentLocationId.HasValue) .ToList() .ForEach(l => locationPaths.Add(l.Id, parentlocationPaths[l.ParentLocationId.Value])); // Get the schedules for each unique schedule id var selectedScheduleIds = occurrenceDates.Select(o => o.ScheduleId).Distinct().ToList(); var schedules = scheduleService .Queryable().AsNoTracking() .Where(s => selectedScheduleIds.Contains(s.Id)) .ToList(); var scheduleNames = new Dictionary <int, string>(); var scheduleStartTimes = new Dictionary <int, TimeSpan>(); schedules .ForEach(s => { scheduleNames.Add(s.Id, s.Name); scheduleStartTimes.Add(s.Id, s.StartTimeOfDay); }); foreach (var occurrence in occurrenceDates.Where(o => o.Date.HasValue)) { occurrences.Add( new ScheduleOccurrence( occurrence.Date.Value, occurrence.ScheduleId.HasValue && scheduleStartTimes.ContainsKey(occurrence.ScheduleId.Value) ? scheduleStartTimes[occurrence.ScheduleId.Value] : new TimeSpan(), occurrence.ScheduleId, occurrence.ScheduleId.HasValue && scheduleNames.ContainsKey(occurrence.ScheduleId.Value) ? scheduleNames[occurrence.ScheduleId.Value] : string.Empty, occurrence.LocationId, occurrence.LocationId.HasValue && locationNames.ContainsKey(occurrence.LocationId.Value) ? locationNames[occurrence.LocationId.Value] : string.Empty, occurrence.LocationId.HasValue && locationPaths.ContainsKey(occurrence.LocationId.Value) ? locationPaths[occurrence.LocationId.Value] : string.Empty )); } } // Load the attendance data for each occurrence if (loadSummaryData && occurrences.Any()) { var minDate = occurrences.Min(o => o.Date); var maxDate = occurrences.Max(o => o.Date).AddDays(1); var attendanceQry = attendanceService .Queryable().AsNoTracking() .Where(a => a.GroupId.HasValue && a.GroupId == group.Id && a.StartDateTime >= minDate && a.StartDateTime < maxDate && a.PersonAlias != null && a.PersonAliasId.HasValue) .Select(a => new { a.LocationId, a.ScheduleId, a.StartDateTime, a.DidAttend, a.DidNotOccur, a.PersonAliasId, PersonId = a.PersonAlias.PersonId }); if (campusId.HasValue) { var familyGroupType = GroupTypeCache.Read(Rock.SystemGuid.GroupType.GROUPTYPE_FAMILY.AsGuid()); var campusQry = new GroupMemberService(rockContext) .Queryable() .Where(g => g.Group != null && g.Group.GroupTypeId == familyGroupType.Id && g.Group.CampusId.HasValue && g.Group.CampusId.Value == campusId.Value ) .Select(m => m.PersonId); attendanceQry = attendanceQry .Where(s => campusQry.Contains(s.PersonId)); } var attendances = attendanceQry.ToList(); foreach (var summary in attendances .GroupBy(a => new { a.LocationId, a.ScheduleId, Date = a.StartDateTime.Date }) .Select(a => new { a.Key.LocationId, a.Key.ScheduleId, a.Key.Date, DidAttendCount = a .Where(t => t.DidAttend.HasValue && t.DidAttend.Value) .Select(t => t.PersonAliasId.Value) .Distinct() .Count(), DidNotOccurCount = a .Where(t => t.DidNotOccur.HasValue && t.DidNotOccur.Value) .Select(t => t.PersonAliasId.Value) .Distinct() .Count(), TotalCount = a .Select(t => t.PersonAliasId) .Distinct() .Count() })) { var occurrence = occurrences .Where(o => o.ScheduleId.Equals(summary.ScheduleId) && o.LocationId.Equals(summary.LocationId) && o.Date.Equals(summary.Date)) .FirstOrDefault(); if (occurrence != null) { occurrence.DidAttendCount = summary.DidAttendCount; occurrence.DidNotOccurCount = summary.DidNotOccurCount; occurrence.TotalCount = summary.TotalCount; } } } // Create any missing occurrences from the group's schedule (not location schedules) Schedule groupSchedule = null; if (group.ScheduleId.HasValue) { groupSchedule = group.Schedule; if (groupSchedule == null) { groupSchedule = new ScheduleService(rockContext).Get(group.ScheduleId.Value); } } if (groupSchedule != null) { var newOccurrences = new List <ScheduleOccurrence>(); var existingDates = occurrences .Where(o => o.ScheduleId.Equals(groupSchedule.Id)) .Select(o => o.Date) .Distinct() .ToList(); var startDate = fromDateTime.HasValue ? fromDateTime.Value : RockDateTime.Today.AddMonths(-2); var endDate = toDateTime.HasValue ? toDateTime.Value : RockDateTime.Today.AddDays(1); DDay.iCal.Event calEvent = groupSchedule.GetCalenderEvent(); if (calEvent != null) { // If schedule has an iCal schedule, get all the past occurrences foreach (var occurrence in calEvent.GetOccurrences(startDate, endDate)) { var scheduleOccurrence = new ScheduleOccurrence( occurrence.Period.StartTime.Date, occurrence.Period.StartTime.TimeOfDay, groupSchedule.Id, groupSchedule.Name); if (!existingDates.Contains(scheduleOccurrence.Date)) { newOccurrences.Add(scheduleOccurrence); } } } else { // if schedule does not have an iCal, then check for weekly schedule and calculate occurrences starting with first attendance or current week if (groupSchedule.WeeklyDayOfWeek.HasValue) { // default to start with date 2 months earlier startDate = fromDateTime.HasValue ? fromDateTime.Value : RockDateTime.Today.AddMonths(-2); if (existingDates.Any(d => d < startDate)) { startDate = existingDates.Min(); } // Back up start time to the correct day of week while (startDate.DayOfWeek != groupSchedule.WeeklyDayOfWeek.Value) { startDate = startDate.AddDays(-1); } // Add the start time if (groupSchedule.WeeklyTimeOfDay.HasValue) { startDate = startDate.Add(groupSchedule.WeeklyTimeOfDay.Value); } // Create occurrences up to current time while (startDate < endDate) { if (!existingDates.Contains(startDate.Date)) { var scheduleOccurrence = new ScheduleOccurrence(startDate.Date, startDate.TimeOfDay, groupSchedule.Id, groupSchedule.Name); newOccurrences.Add(scheduleOccurrence); } startDate = startDate.AddDays(7); } } } if (newOccurrences.Any()) { // Filter Exclusions var groupType = GroupTypeCache.Read(group.GroupTypeId); foreach (var exclusion in groupType.GroupScheduleExclusions) { if (exclusion.Start.HasValue && exclusion.End.HasValue) { foreach (var occurrence in newOccurrences.ToList()) { if (occurrence.Date >= exclusion.Start.Value && occurrence.Date < exclusion.End.Value.AddDays(1)) { newOccurrences.Remove(occurrence); } } } } } foreach (var occurrence in newOccurrences) { occurrences.Add(occurrence); } } } return(occurrences); }
/// <summary> /// Binds the group placement grid. /// </summary> /// <param name="isExporting">if set to <c>true</c> [is exporting].</param> private void BindGroupPlacementGrid( bool isExporting = false ) { int? groupId = gpGroupPlacementParentGroup.SelectedValueAsInt(); int? instanceId = hfRegistrationInstanceId.Value.AsIntegerOrNull(); if ( instanceId.HasValue ) { using ( var rockContext = new RockContext() ) { // Start query for registrants var qry = new RegistrationRegistrantService( rockContext ) .Queryable( "PersonAlias.Person.PhoneNumbers.NumberTypeValue,Fees.RegistrationTemplateFee,GroupMember.Group" ).AsNoTracking() .Where( r => r.Registration.RegistrationInstanceId == instanceId.Value && r.PersonAlias != null && r.PersonAlias.Person != null ); if ( groupId.HasValue ) { var validGroupIds = new GroupService( rockContext ).GetAllDescendents( groupId.Value ) .Select( g => g.Id ) .ToList(); var existingPeopleInGroups = new GroupMemberService( rockContext ) .Queryable().AsNoTracking() .Where( m => validGroupIds.Contains( m.GroupId ) ) .Select( m => m.PersonId ) .ToList(); qry = qry.Where( r => !existingPeopleInGroups.Contains( r.PersonAlias.PersonId ) ); } bool preloadCampusValues = false; var registrantAttributeIds = new List<int>(); var personAttributesIds = new List<int>(); var groupMemberAttributesIds = new List<int>(); if ( RegistrantFields != null ) { // Check if campus is used preloadCampusValues = RegistrantFields .Any( f => f.FieldSource == RegistrationFieldSource.PersonField && f.PersonFieldType.HasValue && f.PersonFieldType.Value == RegistrationPersonFieldType.Campus ); // Get all the registrant attributes selected var registrantAttributes = RegistrantFields .Where( f => f.Attribute != null && f.FieldSource == RegistrationFieldSource.RegistrationAttribute ) .Select( f => f.Attribute ) .ToList(); registrantAttributeIds = registrantAttributes.Select( a => a.Id ).Distinct().ToList(); // Get all the person attributes selected var personAttributes = RegistrantFields .Where( f => f.Attribute != null && f.FieldSource == RegistrationFieldSource.PersonAttribute ) .Select( f => f.Attribute ) .ToList(); personAttributesIds = personAttributes.Select( a => a.Id ).Distinct().ToList(); // Get all the group member attributes selected to be on grid var groupMemberAttributes = RegistrantFields .Where( f => f.Attribute != null && f.FieldSource == RegistrationFieldSource.GroupMemberAttribute ) .Select( f => f.Attribute ) .ToList(); groupMemberAttributesIds = groupMemberAttributes.Select( a => a.Id ).Distinct().ToList(); } // Sort the query IOrderedQueryable<RegistrationRegistrant> orderedQry = null; SortProperty sortProperty = gGroupPlacements.SortProperty; if ( sortProperty != null ) { orderedQry = qry.Sort( sortProperty ); } else { orderedQry = qry .OrderBy( r => r.PersonAlias.Person.LastName ) .ThenBy( r => r.PersonAlias.Person.NickName ); } // Set the grids LinqDataSource which will run query and set results for current page gGroupPlacements.SetLinqDataSource<RegistrationRegistrant>( orderedQry ); if ( RegistrantFields != null ) { // Get the query results for the current page var currentPageRegistrants = gGroupPlacements.DataSource as List<RegistrationRegistrant>; if ( currentPageRegistrants != null ) { // Get all the registrant ids in current page of query results var registrantIds = currentPageRegistrants .Select( r => r.Id ) .Distinct() .ToList(); // Get all the person ids in current page of query results var personIds = currentPageRegistrants .Select( r => r.PersonAlias.PersonId ) .Distinct() .ToList(); // Get all the group member ids and the group id in current page of query results var groupMemberIds = new List<int>(); GroupLinks = new Dictionary<int, string>(); foreach ( var groupMember in currentPageRegistrants .Where( m => m.GroupMember != null && m.GroupMember.Group != null ) .Select( m => m.GroupMember ) ) { groupMemberIds.Add( groupMember.Id ); GroupLinks.AddOrIgnore( groupMember.GroupId, isExporting ? groupMember.Group.Name : string.Format( "<a href='{0}'>{1}</a>", LinkedPageUrl( "GroupDetailPage", new Dictionary<string, string> { { "GroupId", groupMember.GroupId.ToString() } } ), groupMember.Group.Name ) ); } // If the campus column was selected to be displayed on grid, preload all the people's // campuses so that the databind does not need to query each row if ( preloadCampusValues ) { PersonCampusIds = new Dictionary<int, List<int>>(); Guid familyGroupTypeGuid = Rock.SystemGuid.GroupType.GROUPTYPE_FAMILY.AsGuid(); foreach ( var personCampusList in new GroupMemberService( rockContext ) .Queryable().AsNoTracking() .Where( m => m.Group.GroupType.Guid == familyGroupTypeGuid && personIds.Contains( m.PersonId ) ) .GroupBy( m => m.PersonId ) .Select( m => new { PersonId = m.Key, CampusIds = m .Where( g => g.Group.CampusId.HasValue ) .Select( g => g.Group.CampusId.Value ) .ToList() } ) ) { PersonCampusIds.Add( personCampusList.PersonId, personCampusList.CampusIds ); } } // If there are any attributes that were selected to be displayed, we're going // to try and read all attribute values in one query and then put them into a // custom grid ObjectList property so that the AttributeField columns don't need // to do the LoadAttributes and querying of values for each row/column if ( personAttributesIds.Any() || groupMemberAttributesIds.Any() || registrantAttributeIds.Any() ) { // Query the attribute values for all rows and attributes var attributeValues = new AttributeValueService( rockContext ) .Queryable( "Attribute" ).AsNoTracking() .Where( v => v.EntityId.HasValue && ( ( personAttributesIds.Contains( v.AttributeId ) && personIds.Contains( v.EntityId.Value ) ) || ( groupMemberAttributesIds.Contains( v.AttributeId ) && groupMemberIds.Contains( v.EntityId.Value ) ) || ( registrantAttributeIds.Contains( v.AttributeId ) && registrantIds.Contains( v.EntityId.Value ) ) ) ) .ToList(); // Get the attributes to add to each row's object var attributes = new Dictionary<string, AttributeCache>(); RegistrantFields .Where( f => f.Attribute != null ) .Select( f => f.Attribute ) .ToList() .ForEach( a => attributes .Add( a.Id.ToString() + a.Key, a ) ); // Initialize the grid's object list gGroupPlacements.ObjectList = new Dictionary<string, object>(); // Loop through each of the current page's registrants and build an attribute // field object for storing attributes and the values for each of the registrants foreach ( var registrant in currentPageRegistrants ) { // Create a row attribute object var attributeFieldObject = new AttributeFieldObject(); // Add the attributes to the attribute object attributeFieldObject.Attributes = attributes; // Add any person attribute values to object attributeValues .Where( v => personAttributesIds.Contains( v.AttributeId ) && v.EntityId.Value == registrant.PersonAlias.PersonId ) .ToList() .ForEach( v => attributeFieldObject.AttributeValues .Add( v.AttributeId.ToString() + v.Attribute.Key, new AttributeValueCache( v ) ) ); // Add any group member attribute values to object if ( registrant.GroupMemberId.HasValue ) { attributeValues .Where( v => groupMemberAttributesIds.Contains( v.AttributeId ) && v.EntityId.Value == registrant.GroupMemberId.Value ) .ToList() .ForEach( v => attributeFieldObject.AttributeValues .Add( v.AttributeId.ToString() + v.Attribute.Key, new AttributeValueCache( v ) ) ); } // Add any registrant attribute values to object attributeValues .Where( v => registrantAttributeIds.Contains( v.AttributeId ) && v.EntityId.Value == registrant.Id ) .ToList() .ForEach( v => attributeFieldObject.AttributeValues .Add( v.AttributeId.ToString() + v.Attribute.Key, new AttributeValueCache( v ) ) ); // Add row attribute object to grid's object list gGroupPlacements.ObjectList.Add( registrant.Id.ToString(), attributeFieldObject ); } } } } gGroupPlacements.DataBind(); } } }
/// <summary> /// Executes the specified context. /// </summary> /// <param name="context">The context.</param> public void Execute( IJobExecutionContext context ) { JobDataMap dataMap = context.JobDetail.JobDataMap; Guid? groupGuid = dataMap.GetString( "EligibleFollowers" ).AsGuidOrNull(); Guid? systemEmailGuid = dataMap.GetString( "EmailTemplate" ).AsGuidOrNull(); int followingEventsSent = 0; if ( groupGuid.HasValue && systemEmailGuid.HasValue ) { var exceptionMsgs = new List<string>(); using ( var rockContext = new RockContext() ) { var followingService = new FollowingService( rockContext ); var followingEventTypeService = new FollowingEventTypeService( rockContext ); var followingEventNotificationService = new FollowingEventNotificationService( rockContext ); // Get all the active event types var eventTypes = followingEventTypeService .Queryable().AsNoTracking() .Where( e => e.EntityTypeId.HasValue && e.IsActive ) .OrderBy( e => e.Order ) .ToList(); // Get the required event types var requiredEventTypes = eventTypes .Where( e => e.IsNoticeRequired ) .ToList(); // The people who are eligible to get following event notices based on the group type setting for this job var eligiblePersonIds = new GroupMemberService( rockContext ) .Queryable().AsNoTracking() .Where( m => m.Group != null && m.Group.Guid.Equals( groupGuid.Value ) && m.GroupMemberStatus == GroupMemberStatus.Active && m.Person != null && m.Person.Email != null && m.Person.Email != "" ) .Select( m => m.PersonId ) .Distinct() .ToList(); // Get all the subscriptions for the eligible people var eventSubscriptions = new FollowingEventSubscriptionService( rockContext ) .Queryable( "PersonAlias" ).AsNoTracking() .Where( f => eligiblePersonIds.Contains( f.PersonAlias.PersonId ) ) .ToList(); // Dictionaries used to store information that will be used to create notification var personSubscriptions = new Dictionary<int, List<int>>(); // Key: personId, Value: list of event type ids that person subscribes to var personFollowings = new Dictionary<int, List<int>>(); // Key: personId, Value: list of following ids that person follows var eventsThatHappened = new Dictionary<int, Dictionary<int, string>>(); // Key: event type id Value: Dictionary of entity id and formatted event notice for the entity //Get the subscriptions for each person foreach ( int personId in eligiblePersonIds ) { var personEventTypes = eventSubscriptions .Where( s => s.PersonAlias.PersonId == personId ) .Select( s => s.EventType ) .ToList(); personEventTypes.AddRange( requiredEventTypes ); if ( personEventTypes.Any() ) { personSubscriptions.AddOrIgnore( personId, personEventTypes .OrderBy( e => e.Order ) .ThenBy( e => e.Name ) .Select( e => e.Id ) .Distinct() .ToList() ); } } // Get a distinct list of each entitytype/entity that is being followed by anyone that subscribes to events var followings = followingService .Queryable( "PersonAlias" ).AsNoTracking() .Where( f => personSubscriptions.Keys.Contains( f.PersonAlias.PersonId ) ) .ToList(); // group the followings by their type var followedEntityIds = new Dictionary<int, List<int>>(); foreach ( var followedEntity in followings .Select( f => new { f.EntityTypeId, f.EntityId } ) .Distinct() ) { followedEntityIds.AddOrIgnore( followedEntity.EntityTypeId, new List<int>() ); followedEntityIds[followedEntity.EntityTypeId].Add( followedEntity.EntityId ); } // group the followings by the follower foreach ( int personId in personSubscriptions.Select( s => s.Key ) ) { var personFollowing = followings .Where( f => f.PersonAlias.PersonId == personId ) .Select( f => f.Id ) .ToList(); personFollowings.Add( personId, personFollowing ); } var timestamp = RockDateTime.Now; // foreach followed entitytype foreach ( var keyVal in followedEntityIds ) { // Get the entitytype EntityTypeCache itemEntityType = EntityTypeCache.Read( keyVal.Key ); if ( itemEntityType.AssemblyName != null ) { // get the actual type of what is being followed Type entityType = itemEntityType.GetEntityType(); if ( entityType != null ) { var dbContext = Reflection.GetDbContextForEntityType( entityType ); if ( dbContext != null ) { var serviceInstance = Reflection.GetServiceForEntityType( entityType, dbContext ); if ( serviceInstance != null ) { MethodInfo qryMethod = serviceInstance.GetType().GetMethod( "Queryable", new Type[] { } ); var entityQry = qryMethod.Invoke( serviceInstance, new object[] { } ) as IQueryable<IEntity>; // If looking at person alias following, make sure to exclude deceased people if ( entityType == typeof( Rock.Model.PersonAlias ) ) { var personAliasQry = entityQry as IQueryable<PersonAlias>; if ( personAliasQry != null ) { entityQry = personAliasQry.Where( p => !p.Person.IsDeceased ); } } var entityList = entityQry.Where( q => keyVal.Value.Contains( q.Id ) ).ToList(); // If there are any followed entities of this type if ( entityList.Any() ) { // Get the active event types for this entity type foreach ( var eventType in eventTypes.Where( e => e.FollowedEntityTypeId == keyVal.Key ) ) { try { // Get the component var eventComponent = eventType.GetEventComponent(); if ( eventComponent != null ) { // Get the previous notificatoins for this event type var previousNotifications = followingEventNotificationService .Queryable() .Where( n => n.FollowingEventTypeId == eventType.Id ) .ToList(); // check each entity that is followed (by anyone) foreach ( IEntity entity in entityList ) { var previousNotification = previousNotifications .Where( n => n.EntityId == entity.Id ) .FirstOrDefault(); DateTime? lastNotification = previousNotification != null ? previousNotification.LastNotified : (DateTime?)null; // if the event happened if ( eventComponent.HasEventHappened( eventType, entity, lastNotification ) ) { // Store the event type id and the entity for later processing of notifications eventsThatHappened.AddOrIgnore( eventType.Id, new Dictionary<int, string>() ); eventsThatHappened[eventType.Id].Add( entity.Id, eventComponent.FormatEntityNotification( eventType, entity ) ); if ( previousNotification == null ) { previousNotification = new FollowingEventNotification(); previousNotification.FollowingEventTypeId = eventType.Id; previousNotification.EntityId = entity.Id; followingEventNotificationService.Add( previousNotification ); } previousNotification.LastNotified = timestamp; } } rockContext.SaveChanges(); } eventType.LastCheckDateTime = RockDateTime.Now; } catch ( Exception ex ) { exceptionMsgs.Add( string.Format( "An exception occurred calculating events for the '{0}' suggestion type:{1} {2}", eventType.Name, Environment.NewLine, ex.Messages().AsDelimited( Environment.NewLine + " " ) ) ); ExceptionLogService.LogException( ex, System.Web.HttpContext.Current ); } } } } } } } } // send notificatons var appRoot = Rock.Web.Cache.GlobalAttributesCache.Read( rockContext ).GetValue( "ExternalApplicationRoot" ); var possibleRecipients = new PersonService( rockContext ) .Queryable().AsNoTracking() .Where( p => personSubscriptions.Keys.Contains( p.Id ) ) .ToList(); // Loop through the possible recipients that actually subscribe to events foreach ( var personSubscription in personSubscriptions ) { // Get the recipient person int personId = personSubscription.Key; var person = possibleRecipients.Where( p => p.Id == personId ).FirstOrDefault(); if ( person != null ) { try { // Make sure person is actually following anything if ( personFollowings.ContainsKey( personId ) ) { // Dictionary to store the entities that had an event for each event type var personEventTypeNotices = new List<FollowingEventTypeNotices>(); // Get the event types that person subscribes to foreach ( var eventType in eventsThatHappened.Where( e => personSubscription.Value.Contains( e.Key ) ) ) { // Get the EntityTypeId for this event type int entityTypeId = eventTypes .Where( e => e.Id == eventType.Key ) .Select( e => e.FollowedEntityTypeId.Value ) .FirstOrDefault(); // Find all the entities with this event type that the person follows var personFollowedEntityIds = followings .Where( f => personFollowings[personId].Contains( f.Id ) && f.EntityTypeId == entityTypeId ) .Select( f => f.EntityId ) .ToList(); // Get any of those entities that had an event happen var personFollowedEntities = eventType.Value .Where( e => personFollowedEntityIds.Contains( e.Key ) ) .ToList(); // If any were found if ( personFollowedEntities.Any() ) { // Add the entry var eventTypeObj = eventTypes.Where( e => e.Id == eventType.Key ).FirstOrDefault(); if ( eventTypeObj != null ) { personEventTypeNotices.Add( new FollowingEventTypeNotices( eventTypeObj, personFollowedEntities.Select( e => e.Value ).ToList() ) ); } } } // If there are any events for any of the entities that this person follows, send a notification if ( personEventTypeNotices.Any() ) { // Send the notice var recipients = new List<RecipientData>(); var mergeFields = new Dictionary<string, object>(); mergeFields.Add( "Person", person ); mergeFields.Add( "EventTypes", personEventTypeNotices.OrderBy( e => e.EventType.Order ).ToList() ); recipients.Add( new RecipientData( person.Email, mergeFields ) ); Email.Send( systemEmailGuid.Value, recipients, appRoot ); followingEventsSent++; } } } catch ( Exception ex ) { exceptionMsgs.Add( string.Format( "An exception occurred sending event notice to '{0}':{1} {2}", person.FullName, Environment.NewLine, ex.Messages().AsDelimited( Environment.NewLine + " " ) ) ); ExceptionLogService.LogException( ex, System.Web.HttpContext.Current ); } } } } context.Result = string.Format( "{0} following events emails sent", followingEventsSent ); if ( exceptionMsgs.Any() ) { throw new Exception( "One or more exceptions occurred calculating following events..." + Environment.NewLine + exceptionMsgs.AsDelimited( Environment.NewLine ) ); } } }
/// <summary> /// Raises the <see cref="E:System.Web.UI.Control.Load" /> event. /// </summary> /// <param name="e">The <see cref="T:System.EventArgs" /> object that contains the event data.</param> protected override void OnLoad( EventArgs e ) { base.OnLoad( e ); if ( Page.IsPostBack ) { foreach ( var item in lvMembers.Items ) { var rblRole = item.FindControl( "rblRole" ) as RadioButtonList; if ( rblRole != null ) { int? roleId = rblRole.SelectedValueAsInt(); if ( roleId.HasValue ) { var role = _groupType.Roles.Where( r => r.Id == roleId.Value ).FirstOrDefault(); if ( role != null ) { int index = (int)lvMembers.DataKeys[item.DataItemIndex]["Index"]; if ( GroupMembers != null ) { var groupMember = GroupMembers.Where( m => m.Index == index ).FirstOrDefault(); if ( groupMember != null ) { groupMember.RoleGuid = role.Guid; groupMember.RoleName = role.Name; groupMember.IsLeader = role.IsLeader; } } } } } } if ( !string.IsNullOrWhiteSpace( hfActiveTab.Value ) ) { SetActiveTab(); modalAddPerson.Show(); } BuildAttributes( false ); } else { if ( _group != null ) { tbGroupName.Text = _group.Name; // add banner text if ( _isFamilyGroupType && !_group.Name.ToLower().EndsWith( " family" ) ) { lBanner.Text = ( _group.Name + " Family" ).FormatAsHtmlTitle(); } else { lBanner.Text = _group.Name.FormatAsHtmlTitle(); } cpCampus.SelectedCampusId = _group.CampusId; if ( _isFamilyGroupType ) { // If all group members have the same record status, display that value if ( _group.Members.Select( m => m.Person.RecordStatusValueId ).Distinct().Count() == 1 ) { ddlRecordStatus.SetValue( _group.Members.Select( m => m.Person.RecordStatusValueId ).FirstOrDefault() ); } else { ddlRecordStatus.Warning = String.Format( "{0} members have different record statuses", _groupType.Name ); } // If all group members have the same inactive reason, set that value if ( _group.Members.Select( m => m.Person.RecordStatusReasonValueId ).Distinct().Count() == 1 ) { ddlReason.SetValue( _group.Members.Select( m => m.Person.RecordStatusReasonValueId ).FirstOrDefault() ); } else { if ( String.IsNullOrWhiteSpace( ddlRecordStatus.Warning ) ) { ddlRecordStatus.Warning = String.Format( "{0} members have different record status reasons", _groupType.Name ); } else { ddlRecordStatus.Warning += " and record status reasons"; } } } // Get all the group members GroupMembers = new List<GroupMemberInfo>(); foreach ( var groupMember in _group.Members ) { GroupMembers.Add( new GroupMemberInfo( groupMember, true ) ); } // Figure out which ones are in another group var groupMemberPersonIds = GroupMembers.Select( m => m.Id ).ToList(); var otherGroupPersonIds = new GroupMemberService( new RockContext() ).Queryable() .Where( m => groupMemberPersonIds.Contains( m.PersonId ) && m.Group.GroupTypeId == _groupType.Id && m.GroupId != _group.Id ) .Select( m => m.PersonId ) .Distinct(); GroupMembers .Where( m => otherGroupPersonIds.Contains( m.Id ) ) .ToList() .ForEach( m => m.IsInOtherGroups = true ); BindMembers(); GroupAddresses = new List<GroupAddressInfo>(); foreach ( var groupLocation in _group.GroupLocations .Where( l => l.GroupLocationTypeValue != null ) .OrderBy( l => l.GroupLocationTypeValue.Order ) ) { GroupAddresses.Add( new GroupAddressInfo( groupLocation ) ); } foreach ( var groupLocation in _group.GroupLocations .Where( l => l.GroupLocationTypeValue == null ) ) { GroupAddresses.Add( new GroupAddressInfo( groupLocation ) ); } BindLocations(); BuildAttributes( true ); } } }
/// <summary> /// Executes the specified context. /// </summary> /// <param name="context">The context.</param> public void Execute( IJobExecutionContext context ) { var exceptionMsgs = new List<string>(); JobDataMap dataMap = context.JobDetail.JobDataMap; Guid? groupGuid = dataMap.GetString( "EligibleFollowers" ).AsGuidOrNull(); Guid? systemEmailGuid = dataMap.GetString( "EmailTemplate" ).AsGuidOrNull(); int followingSuggestionsEmailsSent = 0; int followingSuggestionsSuggestionsTotal = 0; if ( groupGuid.HasValue && systemEmailGuid.HasValue ) { using ( var rockContext = new RockContext() ) { var followingService = new FollowingService( rockContext ); // The people who are eligible to get following suggestions based on the group type setting for this job var eligiblePersonIds = new GroupMemberService( rockContext ) .Queryable().AsNoTracking() .Where( m => m.Group != null && m.Group.Guid.Equals( groupGuid.Value ) && m.GroupMemberStatus == GroupMemberStatus.Active && m.Person != null && m.Person.Email != null && m.Person.Email != "" ) .Select( m => m.PersonId ) .Distinct(); // check to see if there are any event types that require notification var followerPersonIds = new List<int>(); if ( new FollowingEventTypeService( rockContext ) .Queryable().AsNoTracking() .Any( e => e.IsNoticeRequired ) ) { // if so, include all eligible people followerPersonIds = eligiblePersonIds.ToList(); } else { // if not, filter the list of eligible people down to only those that actually have subscribed to one or more following events followerPersonIds = new FollowingEventSubscriptionService( rockContext ) .Queryable().AsNoTracking() .Where( f => eligiblePersonIds.Contains( f.PersonAlias.PersonId ) ) .Select( f => f.PersonAlias.PersonId ) .Distinct() .ToList(); } if ( followerPersonIds.Any() ) { // Get the primary person alias id for each of the followers var primaryAliasIds = new Dictionary<int, int>(); new PersonAliasService( rockContext ) .Queryable().AsNoTracking() .Where( a => followerPersonIds.Contains( a.PersonId ) && a.PersonId == a.AliasPersonId ) .ToList() .ForEach( a => primaryAliasIds.AddOrIgnore( a.PersonId, a.Id ) ); // Get current date/time. var timestamp = RockDateTime.Now; var suggestionTypes = new FollowingSuggestionTypeService( rockContext ) .Queryable().AsNoTracking() .Where( s => s.IsActive ) .OrderBy( s => s.Name ) .ToList(); var components = new Dictionary<int, SuggestionComponent>(); var suggestedEntities = new Dictionary<int, Dictionary<int, IEntity>>(); foreach ( var suggestionType in suggestionTypes ) { try { // Get the suggestion type component var suggestionComponent = suggestionType.GetSuggestionComponent(); if ( suggestionComponent != null ) { components.Add( suggestionType.Id, suggestionComponent ); // Get the entitytype for this suggestion type var suggestionEntityType = EntityTypeCache.Read( suggestionComponent.FollowedType ); if ( suggestionEntityType != null ) { var entityIds = new List<int>(); // Call the components method to return all of it's suggestions var personEntitySuggestions = suggestionComponent.GetSuggestions( suggestionType, followerPersonIds ); // If any suggestions were returned by the component if ( personEntitySuggestions.Any() ) { int entityTypeId = suggestionEntityType.Id; string reasonNote = suggestionType.ReasonNote; // Get the existing followings for any of the followers var existingFollowings = new Dictionary<int, List<int>>(); foreach( var following in followingService.Queryable( "PersonAlias" ).AsNoTracking() .Where( f => f.EntityTypeId == entityTypeId && followerPersonIds.Contains( f.PersonAlias.PersonId ) ) ) { existingFollowings.AddOrIgnore( following.PersonAlias.PersonId, new List<int>() ); existingFollowings[ following.PersonAlias.PersonId].Add( following.EntityId ); } // Loop through each follower foreach ( var followerPersonId in personEntitySuggestions .Select( s => s.PersonId ) .Distinct() ) { using ( var suggestionContext = new RockContext() ) { var followingSuggestedService = new FollowingSuggestedService( suggestionContext ); // Read all the existing suggestions for this type and the returned followers var existingSuggestions = followingSuggestedService .Queryable( "PersonAlias" ) .Where( s => s.SuggestionTypeId == suggestionType.Id && s.PersonAlias.PersonId == followerPersonId ) .ToList(); // Look through the returned suggestions foreach ( var followedEntityId in personEntitySuggestions .Where( s => s.PersonId == followerPersonId ) .Select( s => s.EntityId ) ) { // Make sure person isn't already following this entity if ( !existingFollowings.ContainsKey( followerPersonId ) || !existingFollowings[followerPersonId].Contains( followedEntityId ) ) { // If this person had a primary alias id if ( primaryAliasIds.ContainsKey( followerPersonId ) ) { entityIds.Add( followedEntityId ); // Look for existing suggestion for this person and entity var suggestion = existingSuggestions .Where( s => s.EntityId == followedEntityId ) .OrderByDescending( s => s.StatusChangedDateTime ) .FirstOrDefault(); // If not found, add one if ( suggestion == null ) { suggestion = new FollowingSuggested(); suggestion.EntityTypeId = entityTypeId; suggestion.EntityId = followedEntityId; suggestion.PersonAliasId = primaryAliasIds[followerPersonId]; suggestion.SuggestionTypeId = suggestionType.Id; suggestion.Status = FollowingSuggestedStatus.PendingNotification; suggestion.StatusChangedDateTime = timestamp; followingSuggestedService.Add( suggestion ); } else { // If found, and it has not been ignored, and it's time to promote again, update the promote date if ( suggestion.Status != FollowingSuggestedStatus.Ignored && ( !suggestionType.ReminderDays.HasValue || !suggestion.LastPromotedDateTime.HasValue || suggestion.LastPromotedDateTime.Value.AddDays( suggestionType.ReminderDays.Value ) <= timestamp ) ) { if ( suggestion.Status != FollowingSuggestedStatus.PendingNotification ) { suggestion.StatusChangedDateTime = timestamp; suggestion.Status = FollowingSuggestedStatus.PendingNotification; } } } } } } // Save the suggestions for this type suggestionContext.SaveChanges(); } } } // If any entities are being suggested for this type, query database for them and save to dictionary if ( entityIds.Any() ) { if ( suggestionEntityType.AssemblyName != null ) { // get the actual type of what is being followed Type entityType = suggestionEntityType.GetEntityType(); if ( entityType != null ) { // Get generic queryable method and query all the entities that are being followed Type[] modelType = { entityType }; Type genericServiceType = typeof( Rock.Data.Service<> ); Type modelServiceType = genericServiceType.MakeGenericType( modelType ); Rock.Data.IService serviceInstance = Activator.CreateInstance( modelServiceType, new object[] { rockContext } ) as IService; MethodInfo qryMethod = serviceInstance.GetType().GetMethod( "Queryable", new Type[] { } ); var entityQry = qryMethod.Invoke( serviceInstance, new object[] { } ) as IQueryable<IEntity>; var entityList = entityQry.AsNoTracking().Where( q => entityIds.Contains( q.Id ) ).ToList(); if ( entityList != null && entityList.Any() ) { var entities = new Dictionary<int, IEntity>(); entityList.ForEach( e => entities.Add( e.Id, e ) ); suggestedEntities.Add( suggestionType.Id, entities ); } } } } } } } catch ( Exception ex ) { exceptionMsgs.Add( string.Format( "An exception occurred calculating suggestions for the '{0}' suggestion type:{1} {2}", suggestionType.Name, Environment.NewLine, ex.Messages().AsDelimited( Environment.NewLine + " " ) ) ); ExceptionLogService.LogException( ex, System.Web.HttpContext.Current ); } } var allSuggestions = new FollowingSuggestedService( rockContext ) .Queryable( "PersonAlias" ) .Where( s => s.Status == FollowingSuggestedStatus.PendingNotification ) .ToList(); var suggestionPersonIds = allSuggestions .Where( s => followerPersonIds.Contains( s.PersonAlias.PersonId ) ) .Select( s => s.PersonAlias.PersonId ) .Distinct() .ToList(); var appRoot = Rock.Web.Cache.GlobalAttributesCache.Read( rockContext ).GetValue( "PublicApplicationRoot" ); foreach ( var person in new PersonService( rockContext ) .Queryable().AsNoTracking() .Where( p => suggestionPersonIds.Contains( p.Id ) ) .ToList() ) { try { var personSuggestionNotices = new List<FollowingSuggestionNotices>(); foreach ( var suggestionType in suggestionTypes ) { var component = components.ContainsKey( suggestionType.Id ) ? components[suggestionType.Id] : null; if ( component != null && suggestedEntities.ContainsKey( suggestionType.Id ) ) { var entities = new List<IEntity>(); foreach ( var suggestion in allSuggestions .Where( s => s.PersonAlias.PersonId == person.Id && s.SuggestionTypeId == suggestionType.Id ) .ToList() ) { if ( suggestedEntities[suggestionType.Id].ContainsKey( suggestion.EntityId ) ) { entities.Add( suggestedEntities[suggestionType.Id][suggestion.EntityId] ); suggestion.LastPromotedDateTime = timestamp; suggestion.Status = FollowingSuggestedStatus.Suggested; } } var notices = new List<string>(); foreach ( var entity in component.SortEntities( entities ) ) { notices.Add( component.FormatEntityNotification( suggestionType, entity ) ); } if ( notices.Any() ) { personSuggestionNotices.Add( new FollowingSuggestionNotices( suggestionType, notices ) ); } } } if ( personSuggestionNotices.Any() ) { // Send the notice var recipients = new List<RecipientData>(); var mergeFields = new Dictionary<string, object>(); mergeFields.Add( "Person", person ); mergeFields.Add( "Suggestions", personSuggestionNotices.OrderBy( s => s.SuggestionType.Order ).ToList() ); recipients.Add( new RecipientData( person.Email, mergeFields ) ); Email.Send( systemEmailGuid.Value, recipients, appRoot ); followingSuggestionsEmailsSent += recipients.Count(); followingSuggestionsSuggestionsTotal += personSuggestionNotices.Count(); } rockContext.SaveChanges(); } catch ( Exception ex ) { exceptionMsgs.Add( string.Format( "An exception occurred sending suggestions to '{0}':{1} {2}", person.FullName, Environment.NewLine, ex.Messages().AsDelimited( Environment.NewLine + " " ) ) ); ExceptionLogService.LogException( ex, System.Web.HttpContext.Current ); } } } } } context.Result = string.Format( "A total of {0} following suggestions sent to {1} people", followingSuggestionsSuggestionsTotal, followingSuggestionsEmailsSent ); if ( exceptionMsgs.Any() ) { throw new Exception( "One or more exceptions occurred calculating suggestions..." + Environment.NewLine + exceptionMsgs.AsDelimited( Environment.NewLine ) ); } }
/// <summary> /// Gets occurrence data for the selected group /// </summary> /// <param name="group">The group.</param> /// <param name="fromDateTime">From date time.</param> /// <param name="toDateTime">To date time.</param> /// <param name="locationIds">The location ids.</param> /// <param name="scheduleIds">The schedule ids.</param> /// <param name="loadSummaryData">if set to <c>true</c> [load summary data].</param> /// <param name="campusId">The campus identifier.</param> /// <returns></returns> public List<ScheduleOccurrence> GetGroupOccurrences( Group group, DateTime? fromDateTime, DateTime? toDateTime, List<int> locationIds, List<int> scheduleIds, bool loadSummaryData, int? campusId ) { var occurrences = new List<ScheduleOccurrence>(); if ( group != null ) { var rockContext = (RockContext)this.Context; var attendanceService = new AttendanceService( rockContext ); var scheduleService = new ScheduleService( rockContext ); var locationService = new LocationService( rockContext ); using ( new Rock.Data.QueryHintScope( rockContext, QueryHintType.RECOMPILE ) ) { // Set up an 'occurrences' query for the group var qry = attendanceService .Queryable().AsNoTracking() .Where( a => a.GroupId == group.Id ); // Filter by date range if ( fromDateTime.HasValue ) { var fromDate = fromDateTime.Value.Date; qry = qry.Where( a => DbFunctions.TruncateTime( a.StartDateTime ) >= ( fromDate ) ); } if ( toDateTime.HasValue ) { var toDate = toDateTime.Value.Date; qry = qry.Where( a => DbFunctions.TruncateTime( a.StartDateTime ) < ( toDate ) ); } // Location Filter if ( locationIds.Any() ) { qry = qry.Where( a => locationIds.Contains( a.LocationId ?? 0 ) ); } // Schedule Filter if ( scheduleIds.Any() ) { qry = qry.Where( a => scheduleIds.Contains( a.ScheduleId ?? 0 ) ); } // Get the unique combination of location/schedule/date for the selected group var occurrenceDates = qry .Select( a => new { a.LocationId, a.ScheduleId, Date = DbFunctions.TruncateTime( a.StartDateTime ) } ) .Distinct() .ToList(); // Get the locations for each unique location id var selectedlocationIds = occurrenceDates.Select( o => o.LocationId ).Distinct().ToList(); var locations = locationService .Queryable().AsNoTracking() .Where( l => selectedlocationIds.Contains( l.Id ) ) .Select( l => new { l.Id, l.ParentLocationId, l.Name } ) .ToList(); var locationNames = new Dictionary<int, string>(); locations.ForEach( l => locationNames.Add( l.Id, l.Name ) ); // Get the parent location path for each unique location var parentlocationPaths = new Dictionary<int, string>(); locations .Where( l => l.ParentLocationId.HasValue ) .Select( l => l.ParentLocationId.Value ) .Distinct() .ToList() .ForEach( l => parentlocationPaths.Add( l, locationService.GetPath( l ) ) ); var locationPaths = new Dictionary<int, string>(); locations .Where( l => l.ParentLocationId.HasValue ) .ToList() .ForEach( l => locationPaths.Add( l.Id, parentlocationPaths[l.ParentLocationId.Value] ) ); // Get the schedules for each unique schedule id var selectedScheduleIds = occurrenceDates.Select( o => o.ScheduleId ).Distinct().ToList(); var schedules = scheduleService .Queryable().AsNoTracking() .Where( s => selectedScheduleIds.Contains( s.Id ) ) .ToList(); var scheduleNames = new Dictionary<int, string>(); var scheduleStartTimes = new Dictionary<int, TimeSpan>(); schedules .ForEach( s => { scheduleNames.Add( s.Id, s.Name ); scheduleStartTimes.Add( s.Id, s.StartTimeOfDay ); } ); foreach ( var occurrence in occurrenceDates.Where( o => o.Date.HasValue ) ) { occurrences.Add( new ScheduleOccurrence( occurrence.Date.Value, occurrence.ScheduleId.HasValue && scheduleStartTimes.ContainsKey( occurrence.ScheduleId.Value ) ? scheduleStartTimes[occurrence.ScheduleId.Value] : new TimeSpan(), occurrence.ScheduleId, occurrence.ScheduleId.HasValue && scheduleNames.ContainsKey( occurrence.ScheduleId.Value ) ? scheduleNames[occurrence.ScheduleId.Value] : string.Empty, occurrence.LocationId, occurrence.LocationId.HasValue && locationNames.ContainsKey( occurrence.LocationId.Value ) ? locationNames[occurrence.LocationId.Value] : string.Empty, occurrence.LocationId.HasValue && locationPaths.ContainsKey( occurrence.LocationId.Value ) ? locationPaths[occurrence.LocationId.Value] : string.Empty ) ); } } // Load the attendance data for each occurrence if ( loadSummaryData && occurrences.Any()) { var minDate = occurrences.Min( o => o.Date ); var maxDate = occurrences.Max( o => o.Date ).AddDays( 1 ); var attendanceQry = attendanceService .Queryable().AsNoTracking() .Where( a => a.GroupId.HasValue && a.GroupId == group.Id && a.StartDateTime >= minDate && a.StartDateTime < maxDate && a.PersonAlias != null && a.PersonAliasId.HasValue ) .Select( a => new { a.LocationId, a.ScheduleId, a.StartDateTime, a.DidAttend, a.DidNotOccur, a.PersonAliasId, PersonId = a.PersonAlias.PersonId } ); if ( campusId.HasValue ) { var familyGroupType = GroupTypeCache.Read( Rock.SystemGuid.GroupType.GROUPTYPE_FAMILY.AsGuid() ); var campusQry = new GroupMemberService( rockContext ) .Queryable() .Where( g => g.Group != null && g.Group.GroupTypeId == familyGroupType.Id && g.Group.CampusId.HasValue && g.Group.CampusId.Value == campusId.Value ) .Select( m => m.PersonId ); attendanceQry = attendanceQry .Where( s => campusQry.Contains( s.PersonId ) ); } var attendances = attendanceQry.ToList(); foreach ( var summary in attendances .GroupBy( a => new { a.LocationId, a.ScheduleId, Date = a.StartDateTime.Date } ) .Select( a => new { a.Key.LocationId, a.Key.ScheduleId, a.Key.Date, DidAttendCount = a .Where( t => t.DidAttend.HasValue && t.DidAttend.Value ) .Select( t => t.PersonAliasId.Value ) .Distinct() .Count(), DidNotOccurCount = a .Where( t => t.DidNotOccur.HasValue && t.DidNotOccur.Value ) .Select( t => t.PersonAliasId.Value ) .Distinct() .Count(), TotalCount = a .Select( t => t.PersonAliasId ) .Distinct() .Count() } ) ) { var occurrence = occurrences .Where( o => o.ScheduleId.Equals( summary.ScheduleId ) && o.LocationId.Equals( summary.LocationId ) && o.Date.Equals( summary.Date ) ) .FirstOrDefault(); if ( occurrence != null ) { occurrence.DidAttendCount = summary.DidAttendCount; occurrence.DidNotOccurCount = summary.DidNotOccurCount; occurrence.TotalCount = summary.TotalCount; } } } // Create any missing occurrences from the group's schedule (not location schedules) Schedule groupSchedule = null; if ( group.ScheduleId.HasValue ) { groupSchedule = group.Schedule; if ( groupSchedule == null ) { groupSchedule = new ScheduleService( rockContext ).Get( group.ScheduleId.Value ); } } if ( groupSchedule != null ) { var newOccurrences = new List<ScheduleOccurrence>(); var existingDates = occurrences .Where( o => o.ScheduleId.Equals( groupSchedule.Id ) ) .Select( o => o.Date ) .Distinct() .ToList(); var startDate = fromDateTime.HasValue ? fromDateTime.Value : RockDateTime.Today.AddMonths( -2 ); var endDate = toDateTime.HasValue ? toDateTime.Value : RockDateTime.Today.AddDays( 1 ); if ( !string.IsNullOrWhiteSpace( groupSchedule.iCalendarContent ) ) { // If schedule has an iCal schedule, get all the past occurrences foreach ( var occurrence in groupSchedule.GetOccurrences( startDate, endDate ) ) { var scheduleOccurrence = new ScheduleOccurrence( occurrence.Period.StartTime.Date, occurrence.Period.StartTime.TimeOfDay, groupSchedule.Id, groupSchedule.Name ); if ( !existingDates.Contains( scheduleOccurrence.Date ) ) { newOccurrences.Add( scheduleOccurrence ); } } } else { // if schedule does not have an iCal, then check for weekly schedule and calculate occurrences starting with first attendance or current week if ( groupSchedule.WeeklyDayOfWeek.HasValue ) { // default to start with date 2 months earlier startDate = fromDateTime.HasValue ? fromDateTime.Value : RockDateTime.Today.AddMonths( -2 ); if ( existingDates.Any( d => d < startDate ) ) { startDate = existingDates.Min(); } // Back up start time to the correct day of week while ( startDate.DayOfWeek != groupSchedule.WeeklyDayOfWeek.Value ) { startDate = startDate.AddDays( -1 ); } // Add the start time if ( groupSchedule.WeeklyTimeOfDay.HasValue ) { startDate = startDate.Add( groupSchedule.WeeklyTimeOfDay.Value ); } // Create occurrences up to current time while ( startDate < endDate ) { if ( !existingDates.Contains( startDate.Date ) ) { var scheduleOccurrence = new ScheduleOccurrence( startDate.Date, startDate.TimeOfDay, groupSchedule.Id, groupSchedule.Name ); newOccurrences.Add( scheduleOccurrence ); } startDate = startDate.AddDays( 7 ); } } } if ( newOccurrences.Any() ) { // Filter Exclusions var groupType = GroupTypeCache.Read( group.GroupTypeId ); foreach ( var exclusion in groupType.GroupScheduleExclusions ) { if ( exclusion.Start.HasValue && exclusion.End.HasValue ) { foreach ( var occurrence in newOccurrences.ToList() ) { if ( occurrence.Date >= exclusion.Start.Value && occurrence.Date < exclusion.End.Value.AddDays( 1 ) ) { newOccurrences.Remove( occurrence ); } } } } } foreach( var occurrence in newOccurrences ) { occurrences.Add( occurrence ); } } } return occurrences; }
/// <summary> /// Executes the specified workflow. /// </summary> /// <param name="rockContext">The rock context.</param> /// <param name="action">The workflow action.</param> /// <param name="entity">The entity.</param> /// <param name="errorMessages">The error messages.</param> /// <returns></returns> /// <exception cref="System.NotImplementedException"></exception> public override bool Execute( RockContext rockContext, Rock.Model.WorkflowAction action, Object entity, out List<string> errorMessages ) { var checkInState = GetCheckInState( entity, out errorMessages ); if ( checkInState == null ) { return false; } var roomBalanceGroupTypes = GetAttributeValue( action, "RoomBalanceGrouptypes" ).SplitDelimitedValues().AsGuidList(); bool useGroupMembership = GetAttributeValue( action, "PrioritizeGroupMembership" ).AsBoolean(); int roomBalanceOverride = GetAttributeValue( action, "BalancingOverride" ).AsIntegerOrNull() ?? 5; var excludedLocations = GetAttributeValue( action, "ExcludedLocations" ).SplitDelimitedValues( false ) .Select( s => s.Trim() ); // get admin-selected attribute keys instead of using a hardcoded key var personSpecialNeedsKey = string.Empty; var personSpecialNeedsGuid = GetAttributeValue( action, "PersonSpecialNeedsAttribute" ).AsGuid(); if ( personSpecialNeedsGuid != Guid.Empty ) { personSpecialNeedsKey = AttributeCache.Read( personSpecialNeedsGuid, rockContext ).Key; } var groupSpecialNeedsKey = string.Empty; var groupSpecialNeedsGuid = GetAttributeValue( action, "GroupSpecialNeedsAttribute" ).AsGuid(); if ( personSpecialNeedsGuid != Guid.Empty ) { groupSpecialNeedsKey = AttributeCache.Read( groupSpecialNeedsGuid, rockContext ).Key; } var groupAgeRangeKey = string.Empty; var groupAgeRangeGuid = GetAttributeValue( action, "GroupAgeRangeAttribute" ).AsGuid(); if ( personSpecialNeedsGuid != Guid.Empty ) { groupAgeRangeKey = AttributeCache.Read( groupAgeRangeGuid, rockContext ).Key; } var groupGradeRangeKey = string.Empty; var groupGradeRangeGuid = GetAttributeValue( action, "GroupGradeRangeAttribute" ).AsGuid(); if ( personSpecialNeedsGuid != Guid.Empty ) { groupGradeRangeKey = AttributeCache.Read( groupGradeRangeGuid, rockContext ).Key; } // log a warning if any of the attributes are missing or invalid if ( string.IsNullOrWhiteSpace( personSpecialNeedsKey ) ) { action.AddLogEntry( string.Format( "The Person Special Needs attribute is not selected or invalid for '{0}'.", action.ActionType.Name ) ); } if ( string.IsNullOrWhiteSpace( groupSpecialNeedsKey ) ) { action.AddLogEntry( string.Format( "The Group Special Needs attribute is not selected or invalid for '{0}'.", action.ActionType.Name ) ); } if ( string.IsNullOrWhiteSpace( groupAgeRangeKey ) ) { action.AddLogEntry( string.Format( "The Group Age Range attribute is not selected or invalid for '{0}'.", action.ActionType.Name ) ); } if ( string.IsNullOrWhiteSpace( groupGradeRangeKey ) ) { action.AddLogEntry( string.Format( "The Group Grade Range attribute is not selected or invalid for '{0}'.", action.ActionType.Name ) ); } var family = checkInState.CheckIn.Families.FirstOrDefault( f => f.Selected ); if ( family != null ) { // don't process people who already have assignments foreach ( var person in family.People.Where( f => f.Selected && !f.GroupTypes.Any( gt => gt.Selected ) ) ) { decimal baseVariance = 100; char[] delimiter = { ',' }; // check if this person has special needs var hasSpecialNeeds = person.Person.GetAttributeValue( personSpecialNeedsKey ).AsBoolean(); // get a list of room balanced grouptype ID's since CheckInGroup model is a shallow clone var roomBalanceGroupTypeIds = person.GroupTypes.Where( gt => roomBalanceGroupTypes.Contains( gt.GroupType.Guid ) ) .Select( gt => gt.GroupType.Id ).ToList(); if ( person.GroupTypes.Count > 0 ) { CheckInGroupType bestGroupType = null; IEnumerable<CheckInGroup> validGroups; if ( person.GroupTypes.Count == 1 ) { bestGroupType = person.GroupTypes.FirstOrDefault(); validGroups = bestGroupType.Groups; } else { // Start with unfiltered groups since one criteria may not match exactly ( SN > Grade > Age ) validGroups = person.GroupTypes.SelectMany( gt => gt.Groups ); } // check how many groups exist without getting the whole list int numValidGroups = validGroups.Take( 2 ).Count(); if ( numValidGroups > 0 ) { CheckInGroup bestGroup = null; IEnumerable<CheckInLocation> validLocations; if ( numValidGroups == 1 ) { bestGroup = validGroups.FirstOrDefault(); validLocations = bestGroup.Locations; } else { // Select by group assignment first if ( useGroupMembership ) { var personAssignments = new GroupMemberService( rockContext ).GetByPersonId( person.Person.Id ) .Select( gm => gm.Group.Id ).ToList(); if ( personAssignments.Count > 0 ) { bestGroup = validGroups.FirstOrDefault( g => personAssignments.Contains( g.Group.Id ) ); } } // Select group by best fit if ( bestGroup == null ) { // Check age and special needs CheckInGroup closestAgeGroup = null; CheckInGroup closestNeedsGroup = null; var ageGroups = validGroups.Where( g => g.Group.AttributeValues.ContainsKey( groupAgeRangeKey ) && g.Group.AttributeValues[groupAgeRangeKey].Value != null && g.Group.AttributeValues.ContainsKey( personSpecialNeedsKey ) == hasSpecialNeeds ) .ToList() .Select( g => new { Group = g, AgeRange = g.Group.AttributeValues[groupAgeRangeKey].Value .Split( delimiter, StringSplitOptions.None ) .Where( av => !string.IsNullOrEmpty( av ) ) .Select( av => av.AsType<decimal>() ) } ) .ToList(); if ( ageGroups.Count > 0 ) { if ( person.Person.Age != null ) { baseVariance = 100; decimal personAge = (decimal)person.Person.AgePrecise; foreach ( var ageGroup in ageGroups.Where( g => g.AgeRange.Any() ) ) { var minAge = ageGroup.AgeRange.First(); var maxAge = ageGroup.AgeRange.Last(); var ageVariance = maxAge - minAge; if ( maxAge >= personAge && minAge <= personAge && ageVariance < baseVariance ) { closestAgeGroup = ageGroup.Group; baseVariance = ageVariance; if ( hasSpecialNeeds ) { closestNeedsGroup = closestAgeGroup; } } } } else if ( hasSpecialNeeds ) { // person has special needs but not an age, assign to first special needs group closestNeedsGroup = ageGroups.FirstOrDefault().Group; } } // Check grade CheckInGroup closestGradeGroup = null; if ( person.Person.GradeOffset != null ) { var gradeValues = DefinedTypeCache.Read( new Guid( Rock.SystemGuid.DefinedType.SCHOOL_GRADES ) ).DefinedValues; var gradeGroups = validGroups.Where( g => g.Group.AttributeValues.ContainsKey( groupGradeRangeKey ) && g.Group.AttributeValues[groupGradeRangeKey].Value != null ) .ToList() .Select( g => new { Group = g, GradeOffsets = g.Group.AttributeValues[groupGradeRangeKey].Value .Split( delimiter, StringSplitOptions.None ) .Where( av => !string.IsNullOrEmpty( av ) ) .Select( av => gradeValues.FirstOrDefault( v => v.Guid == new Guid( av ) ) ) .Select( av => av.Value.AsDecimal() ) } ) .ToList(); // Only check groups that have valid grade offsets if ( person.Person.GradeOffset != null && gradeGroups.Count > 0 ) { baseVariance = 100; decimal gradeOffset = (decimal)person.Person.GradeOffset.Value; foreach ( var gradeGroup in gradeGroups.Where( g => g.GradeOffsets.Any() ) ) { var minGradeOffset = gradeGroup.GradeOffsets.First(); var maxGradeOffset = gradeGroup.GradeOffsets.Last(); var gradeVariance = minGradeOffset - maxGradeOffset; if ( minGradeOffset >= gradeOffset && maxGradeOffset <= gradeOffset && gradeVariance < baseVariance ) { closestGradeGroup = gradeGroup.Group; baseVariance = gradeVariance; } } /* ======================================================== * optional scenario: find the next closest grade group * ========================================================= * if (grade > max) grade - max else if (grade < min) min - grade else 0; // add a tiny variance to offset larger groups: result += ((max - min)/100) * ========================================================= */ } } // Assignment priority: Group Membership, then Ability, then Grade, then Age, then the first non-excluded group // NOTE: if group member is prioritized (and membership exists) this section is skipped entirely bestGroup = closestNeedsGroup ?? closestGradeGroup ?? closestAgeGroup ?? validGroups.FirstOrDefault( g => !g.ExcludedByFilter ); // room balance if they fit into multiple groups if ( bestGroup != null && roomBalanceGroupTypeIds.Contains( bestGroup.Group.GroupTypeId ) ) { int? bestScheduleId = null; var availableSchedules = validGroups.SelectMany( g => g.Locations.SelectMany( l => l.Schedules ) ).ToList(); if ( availableSchedules.Any() ) { bestScheduleId = availableSchedules.OrderBy( s => s.StartTime ).Select( s => s.Schedule.Id ).FirstOrDefault(); } if ( bestScheduleId != null ) { validGroups = validGroups.Where( g => g.AvailableForSchedule.Contains( (int)bestScheduleId ) ); } var currentGroupAttendance = bestGroup.Locations.Select( l => Helpers.ReadAttendanceBySchedule( l.Location.Id, bestScheduleId ) ).Sum(); var lowestGroup = validGroups.Where( g => !g.ExcludedByFilter && !excludedLocations.Contains( g.Group.Name ) ) .Select( g => new { Group = g, Attendance = g.Locations.Select( l => Helpers.ReadAttendanceBySchedule( l.Location.Id, bestScheduleId ) ).Sum() } ) .OrderBy( g => g.Attendance ) .FirstOrDefault(); if ( lowestGroup != null && lowestGroup.Attendance < ( currentGroupAttendance - roomBalanceOverride ) ) { bestGroup = lowestGroup.Group; } } } validLocations = bestGroup.Locations; } // check how many locations exist without getting the whole list int numValidLocations = validLocations.Take( 2 ).Count(); if ( numValidLocations > 0 ) { CheckInLocation bestLocation = null; IEnumerable<CheckInSchedule> validSchedules; if ( numValidLocations == 1 ) { bestLocation = validLocations.FirstOrDefault(); validSchedules = bestLocation.Schedules; } else { var filteredLocations = validLocations.Where( l => !l.ExcludedByFilter && !excludedLocations.Contains( l.Location.Name ) && l.Schedules.Any( s => s.Schedule.IsCheckInActive ) ); // room balance if they fit into multiple locations if ( roomBalanceGroupTypeIds.Contains( bestGroup.Group.GroupTypeId ) ) { int? bestScheduleId = null; var availableSchedules = filteredLocations.SelectMany( l => l.Schedules ) .DistinctBy( s => s.Schedule.Id ).ToList(); if ( availableSchedules.Any() ) { bestScheduleId = availableSchedules.OrderBy( s => s.StartTime ).Select( s => s.Schedule.Id ).FirstOrDefault(); } if ( bestScheduleId != null ) { filteredLocations = filteredLocations.Where( l => l.AvailableForSchedule.Contains( (int)bestScheduleId ) ); } filteredLocations = filteredLocations.OrderBy( l => Helpers.ReadAttendanceBySchedule( l.Location.Id, bestScheduleId ) ); } bestLocation = filteredLocations.FirstOrDefault(); validSchedules = bestLocation.Schedules; } // check how many schedules exist without getting the whole list int numValidSchedules = validSchedules.Take( 2 ).Count(); if ( numValidSchedules > 0 ) { // finished finding assignment, verify everything is selected var bestSchedule = validSchedules.OrderBy( s => s.Schedule.StartTimeOfDay ).FirstOrDefault(); bestSchedule.Selected = true; bestSchedule.PreSelected = true; if ( bestLocation != null ) { bestLocation.PreSelected = true; bestLocation.Selected = true; if ( bestGroup != null ) { bestGroup.PreSelected = true; bestGroup.Selected = true; bestGroupType = person.GroupTypes.FirstOrDefault( gt => gt.GroupType.Id == bestGroup.Group.GroupTypeId ); if ( bestGroupType != null ) { bestGroupType.Selected = true; bestGroupType.PreSelected = true; person.Selected = true; person.PreSelected = true; } } } } } } } } } return true; }
private void BindData() { using ( var rockContext = new RockContext() ) { var dataView = new DataViewService( rockContext ).Get( _dataViewGuid ?? Guid.Empty ); if ( dataView != null ) { var personService = new PersonService( rockContext ); // Filter people by dataview var errorMessages = new List<string>(); var paramExpression = personService.ParameterExpression; var whereExpression = dataView.GetExpression( personService, paramExpression, out errorMessages ); var personQry = personService .Queryable( false, false ).AsNoTracking() .Where( paramExpression, whereExpression, null ); var dvPersonIdQry = personQry.Select( p => p.Id ); bool filteredQry = false; // Filter by first name string firstName = tbFirstName.Text.Trim(); if ( !string.IsNullOrWhiteSpace( firstName ) ) { personQry = personQry.Where( p => p.FirstName.StartsWith( firstName ) || p.NickName.StartsWith( firstName ) ); filteredQry = true; } // Filter by last name string lastName = tbLastName.Text.Trim(); if ( !string.IsNullOrWhiteSpace( lastName ) ) { personQry = personQry.Where( p => p.LastName.StartsWith( lastName ) ); filteredQry = true; } if ( filteredQry || _showAllPeople ) { SetColumnWidths(); if ( _optOutGroupGuid.HasValue ) { var optOutPersonIdQry = new GroupMemberService( rockContext ) .Queryable().AsNoTracking() .Where( g => g.Group.Guid.Equals( _optOutGroupGuid.Value ) ) .Select( g => g.PersonId ); personQry = personQry.Where( p => !optOutPersonIdQry.Contains( p.Id ) ); } if ( _showFamily ) { BindFamilies( rockContext, personQry, dvPersonIdQry ); } else { BindPeople( rockContext, personQry ); } } else { rptPeople.Visible = false; rptFamilies.Visible = false; } } else { rptPeople.Visible = false; rptFamilies.Visible = false; ShowMessages( new List<string> { "This block requires a valid Data View setting." } ); } if ( CurrentPerson != null && _optOutGroupGuid.HasValue ) { bool optedOut = new GroupMemberService( rockContext ) .Queryable().AsNoTracking() .Any( m => m.PersonId == CurrentPerson.Id && m.Group.Guid.Equals( _optOutGroupGuid.Value ) ); lbOptInOut.Text = optedOut ? "Opt in to the Directory" : "Opt Out of the Directory"; } } }