/// <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) { foreach (var family in checkInState.CheckIn.GetFamilies(true)) { foreach (var person in family.People) { foreach (var kioskGroupType in checkInState.Kiosk.ActiveGroupTypes(checkInState.ConfiguredGroupTypes)) { if (kioskGroupType.KioskGroups.SelectMany(g => g.KioskLocations).Any(l => l.IsCheckInActive && l.Location.IsActive)) { if (!person.GroupTypes.Any(g => g.GroupType.Id == kioskGroupType.GroupType.Id)) { var checkinGroupType = new CheckInGroupType(); checkinGroupType.GroupType = kioskGroupType.GroupType; person.GroupTypes.Add(checkinGroupType); } } } } } return(true); } return(false); }
/// <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 groupIdAttributeKey = string.Empty; var groupIdAttributeGuid = GetAttributeValue(action, "GroupMembershipGroupAttribute").AsGuid(); if (groupIdAttributeGuid != Guid.Empty) { groupIdAttributeKey = AttributeCache.Get(groupIdAttributeGuid).Key; } var family = checkInState.CheckIn.CurrentFamily; if (family != null) { GroupMemberService groupMemberService = new GroupMemberService(rockContext); var remove = GetAttributeValue(action, "Remove").AsBoolean(); foreach (var person in family.People) { if (person.Person.Attributes == null) { person.Person.LoadAttributes(rockContext); } foreach (var groupType in person.GroupTypes.ToList()) { foreach (var group in groupType.Groups.ToList()) { var groupGuid = group.Group.GetAttributeValue(groupIdAttributeKey).AsGuidOrNull(); if (groupGuid != null) { if (!groupMemberService.GetByGroupGuid(groupGuid ?? new Guid()) .Where(gm => gm.PersonId == person.Person.Id && (gm.GroupMemberStatus == GroupMemberStatus.Active || !checkInState.CheckInType.PreventInactivePeople)).Any()) { if (remove) { groupType.Groups.Remove(group); } else { group.ExcludedByFilter = true; } } } } } } } return(true); }
/// <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 wfrockContext, Rock.Model.WorkflowAction wfaction, Object entity, out List <string> wferrorMessages) { errorMessages = new List <string>(); wferrorMessages = errorMessages; action = wfaction; rockContext = wfrockContext; personAliasGuid = GetAttributeValue(action, "Person", true); smsKeyword = GetAttributeValue(action, "SMSKeyword", true); keywordAttribute = GetAttributeValue(action, "KeywordAttribute", true); keywordSearchType = GetAttributeValue(action, "KeywordSearchType", true); var attribute = AttributeCache.Get(keywordAttribute.AsGuid()); if (attribute == null || attribute.EntityTypeId != EntityTypeCache.Get(typeof(Group)).Id) { errorMessages.Add("Attribute must not be null and must for Group"); } AttributeValueService attributeValueService = new AttributeValueService(rockContext); GroupService groupService = new GroupService(rockContext); var groupServiceQueryable = groupService.Queryable(); groups = attributeValueService.Queryable() .Where(av => av.AttributeId == attribute.Id) .Join(groupServiceQueryable, av => av.EntityId, g => g.Id, (av, g) => g ).ToList(); switch (keywordSearchType) { case "0": IgnoreSchedule(); break; case "1": ClosestSchedule(); break; case "2": DuringSchedule(); break; default: break; } return(true); }
/// <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) { bool loadAll = GetAttributeValue(action, "LoadAll").AsBoolean(); foreach (var family in checkInState.CheckIn.GetFamilies(true)) { foreach (var person in family.GetPeople(!loadAll)) { foreach (var groupType in person.GetGroupTypes(!loadAll)) { var kioskGroupType = checkInState.Kiosk.ActiveGroupTypes(checkInState.ConfiguredGroupTypes) .Where(g => g.GroupType.Id == groupType.GroupType.Id) .FirstOrDefault(); if (kioskGroupType != null) { foreach (var kioskGroup in kioskGroupType.KioskGroups) { bool validGroup = true; if (groupType.GroupType.AttendanceRule == AttendanceRule.AlreadyBelongs) { validGroup = new GroupMemberService(rockContext).Queryable() .Any(m => m.GroupId == kioskGroup.Group.Id && m.GroupMemberStatus == GroupMemberStatus.Active && m.PersonId == person.Person.Id); } if (validGroup && !groupType.Groups.Any(g => g.Group.Id == kioskGroup.Group.Id)) { var checkInGroup = new CheckInGroup(); checkInGroup.Group = kioskGroup.Group.Clone(false); checkInGroup.Group.CopyAttributesFrom(kioskGroup.Group); groupType.Groups.Add(checkInGroup); } } } } } } return(true); } errorMessages.Add($"Attempted to run {this.GetType().GetFriendlyTypeName()} in check-in, but the check-in state was null."); return(false); }
/// <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) { bool loadAll = GetAttributeValue(action, "LoadAll").AsBoolean(); foreach (var family in checkInState.CheckIn.GetFamilies(true)) { foreach (var person in family.GetPeople(!loadAll)) { foreach (var groupType in person.GetGroupTypes(!loadAll).ToList()) { var kioskGroupType = checkInState.Kiosk.ActiveGroupTypes(checkInState.ConfiguredGroupTypes) .Where(g => g.GroupType.Id == groupType.GroupType.Id) .FirstOrDefault(); if (kioskGroupType != null) { foreach (var group in groupType.GetGroups(!loadAll)) { foreach (var kioskGroup in kioskGroupType.KioskGroups .Where(g => g.Group.Id == group.Group.Id) .ToList()) { foreach (var kioskLocation in kioskGroup.KioskLocations.Where(l => l.Location.IsActive)) { if (!group.Locations.Any(l => l.Location.Id == kioskLocation.Location.Id)) { var checkInLocation = new CheckInLocation(); checkInLocation.Location = kioskLocation.Location.Clone(false); checkInLocation.Location.CopyAttributesFrom(kioskLocation.Location); checkInLocation.CampusId = kioskLocation.CampusId; group.Locations.Add(checkInLocation); } } } } } } } } return(true); } return(false); }
/// <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 family = checkInState.CheckIn.Families.Where(f => f.Selected).FirstOrDefault(); if (family != null) { var remove = GetAttributeValue(action, "Remove").AsBoolean(); foreach (var person in family.People) { string personsGender = person.Person.Gender.ToString("d"); foreach (var groupType in person.GroupTypes.ToList()) { foreach (var group in groupType.Groups.ToList()) { var groupAttributes = group.Group.GetAttributeValues("Gender"); if (groupAttributes.Any() && !groupAttributes.Contains(personsGender)) { if (remove) { groupType.Groups.Remove(group); } else { group.ExcludedByFilter = true; } } } } } } return(true); }
/// <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); } ObjectCache cache = Rock.Web.Cache.RockMemoryCache.Default; var dataViewAttributeKey = string.Empty; var dataViewAttributeGuid = GetAttributeValue(action, "DataViewGroupAttribute").AsGuid(); if (dataViewAttributeGuid != Guid.Empty) { dataViewAttributeKey = AttributeCache.Read(dataViewAttributeGuid, rockContext).Key; } var dataViewService = new DataViewService(rockContext); var family = checkInState.CheckIn.CurrentFamily; if (family != null) { var remove = GetAttributeValue(action, "Remove").AsBoolean(); foreach (var person in family.People) { foreach (var groupType in person.GroupTypes.ToList()) { foreach (var group in groupType.Groups.ToList()) { if (group.ExcludedByFilter == true) { continue; } var approvedPeopleGuid = group.Group.GetAttributeValue(dataViewAttributeKey); if (string.IsNullOrWhiteSpace(approvedPeopleGuid)) { continue; } //Get approved people dataview from cache or from db var approvedPeopleList = cache[approvedPeopleGuid] as List <int>; if (approvedPeopleList == null) { DataView approvedPeople = dataViewService.Get(approvedPeopleGuid.AsGuid()); if (approvedPeople == null) { continue; } var errors = new List <string>(); var approvedPeopleQry = approvedPeople.GetQuery(null, 30, out errors); if (approvedPeopleQry != null) { approvedPeopleList = approvedPeopleQry.Select(e => e.Id).ToList(); var cachePolicy = new CacheItemPolicy(); cachePolicy.AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(10); cache.Set(approvedPeopleGuid, approvedPeopleList, cachePolicy); } } if (approvedPeopleList != null && !approvedPeopleList.Contains(person.Person.Id)) { if (remove) { groupType.Groups.Remove(group); } else { group.ExcludedByFilter = true; } } } } } } return(true); }
/// <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)).DistinctBy(s => s.Schedule.Id).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; if (numValidLocations == 1) { bestLocation = validLocations.FirstOrDefault(); } else { var filteredLocations = validLocations.Where(l => !l.ExcludedByFilter && !excludedLocations.Contains(l.Location.Name)); // 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(); } if (bestLocation != null && bestLocation.Schedules.Any()) { // finished finding assignment, verify we can select everything var bestSchedule = bestLocation.Schedules.OrderBy(s => s.Schedule.StartTimeOfDay).FirstOrDefault(); if (bestSchedule != null) { 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); }
/// <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); } int peopleWithoutAssignments = 0; bool roomBalance = GetAttributeValue(action, "RoomBalance").AsBoolean(); int balanceOverride = GetAttributeValue(action, "DifferentialOverride").AsIntegerOrNull() ?? 10; int previousMonthsNumber = GetAttributeValue(action, "PreviousMonthsAttendance").AsIntegerOrNull() ?? 3; int maxAssignments = GetAttributeValue(action, "MaxAssignments").AsIntegerOrNull() ?? 5; var cutoffDate = Rock.RockDateTime.Today.AddMonths(previousMonthsNumber * -1); var attendanceService = new AttendanceService(rockContext); var family = checkInState.CheckIn.Families.FirstOrDefault(f => f.Selected); if (family != null) { // get the number of people checking in, including visitors or first-timers peopleWithoutAssignments = family.People.Where(p => p.Selected).Count(); foreach (var previousAttender in family.People.Where(p => p.Selected && !p.FirstTime)) { var personGroupTypeIds = previousAttender.GroupTypes.Select(gt => gt.GroupType.Id); var lastDateAttendances = attendanceService.Queryable() .Where(a => a.PersonAlias.PersonId == previousAttender.Person.Id && personGroupTypeIds.Contains(a.Group.GroupTypeId) && a.StartDateTime >= cutoffDate && a.DidAttend == true) .OrderByDescending(a => a.StartDateTime).Take(maxAssignments) .ToList(); if (lastDateAttendances.Any()) { bool createdMatchingAssignment = false; var isSpecialNeeds = previousAttender.Person.GetAttributeValue("IsSpecialNeeds").AsBoolean(); var lastAttended = lastDateAttendances.Max(a => a.StartDateTime).Date; foreach (var groupAttendance in lastDateAttendances.Where(a => a.StartDateTime >= lastAttended)) { // Start with filtered groups unless they have abnormal age and grade parameters (1%) var groupType = previousAttender.GroupTypes.FirstOrDefault(gt => gt.GroupType.Id == groupAttendance.Group.GroupTypeId && (!gt.ExcludedByFilter || isSpecialNeeds)); if (groupType != null) { CheckInGroup group = null; if (groupType.Groups.Count == 1) { // Only a single group is open group = groupType.Groups.FirstOrDefault(g => !g.ExcludedByFilter || isSpecialNeeds); } else { // Pick the group they last attended group = groupType.Groups.FirstOrDefault(g => g.Group.Id == groupAttendance.GroupId && (!g.ExcludedByFilter || isSpecialNeeds)); if (group != null && roomBalance && !isSpecialNeeds) { var currentAttendance = group.Locations.Select(l => KioskLocationAttendance.Read(l.Location.Id).CurrentCount).Sum(); var lowestAttendedGroup = groupType.Groups.Where(g => !g.ExcludedByFilter) .Select(g => new { Group = g, Attendance = g.Locations.Select(l => KioskLocationAttendance.Read(l.Location.Id).CurrentCount).Sum() }) .OrderBy(g => g.Attendance) .FirstOrDefault(); if (lowestAttendedGroup != null && lowestAttendedGroup.Attendance < (currentAttendance - balanceOverride)) { group = lowestAttendedGroup.Group; } } } if (group != null) { CheckInLocation location = null; if (group.Locations.Count == 1) { // Only a single location is open location = group.Locations.FirstOrDefault(l => !l.ExcludedByFilter || isSpecialNeeds); } else { // Pick the location they last attended location = group.Locations.FirstOrDefault(l => l.Location.Id == groupAttendance.LocationId && (!l.ExcludedByFilter || isSpecialNeeds)); if (location != null && roomBalance && !isSpecialNeeds) { var currentAttendance = KioskLocationAttendance.Read(location.Location.Id).CurrentCount; var lowestAttendedLocation = group.Locations.Where(l => !l.ExcludedByFilter) .Select(l => new { Location = l, Attendance = KioskLocationAttendance.Read(location.Location.Id).CurrentCount }) .OrderBy(l => l.Attendance) .FirstOrDefault(); if (lowestAttendedLocation != null && lowestAttendedLocation.Attendance < (currentAttendance - balanceOverride)) { location = lowestAttendedLocation.Location; } } } if (location != null) { CheckInSchedule schedule = null; if (location.Schedules.Count == 1) { schedule = location.Schedules.FirstOrDefault(s => !s.ExcludedByFilter || isSpecialNeeds); } else if (groupAttendance.ScheduleId != null) { schedule = location.Schedules.FirstOrDefault(s => s.Schedule.Id == groupAttendance.ScheduleId && (!s.ExcludedByFilter || isSpecialNeeds)); } else { // if the schedule doesn't exactly match but everything else does, select it schedule = location.Schedules.FirstOrDefault(s => (!s.ExcludedByFilter && !isSpecialNeeds)); } if (schedule != null) { schedule.Selected = true; schedule.PreSelected = true; schedule.LastCheckIn = groupAttendance.StartDateTime; location.Selected = true; location.PreSelected = true; location.LastCheckIn = groupAttendance.StartDateTime; group.Selected = true; group.PreSelected = true; group.LastCheckIn = groupAttendance.StartDateTime; groupType.Selected = true; groupType.PreSelected = true; group.LastCheckIn = groupAttendance.StartDateTime; groupType.LastCheckIn = groupAttendance.StartDateTime; groupType.Selected = true; groupType.PreSelected = true; previousAttender.PreSelected = true; previousAttender.LastCheckIn = groupAttendance.StartDateTime; createdMatchingAssignment = true; } } } } } if (createdMatchingAssignment) { peopleWithoutAssignments--; } } } } // true condition will continue to the next auto-assignment // false condition will stop processing auto-assignments if (action.Activity.AttributeValues.Any() && action.Activity.AttributeValues.ContainsKey("ContinueAssignments")) { var continueAssignments = peopleWithoutAssignments > 0; action.Activity.AttributeValues["ContinueAssignments"].Value = continueAssignments.ToString(); } return(true); }
/// <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); } int selectFromDaysBack = checkInState.CheckInType.AutoSelectDaysBack; var roomBalanceGroupTypes = GetAttributeValue(action, "RoomBalanceGrouptypes").SplitDelimitedValues().AsGuidList(); int roomBalanceOverride = GetAttributeValue(action, "BalancingOverride").AsIntegerOrNull() ?? 5; int maxAssignments = GetAttributeValue(action, "MaxAssignments").AsIntegerOrNull() ?? 5; var excludedLocations = GetAttributeValue(action, "ExcludedLocations").SplitDelimitedValues(whitespace: false) .Select(s => s.Trim()).ToList(); var personSpecialNeedsKey = string.Empty; var personSpecialNeedsGuid = GetAttributeValue(action, "PersonSpecialNeedsAttribute").AsGuid(); if (personSpecialNeedsGuid != Guid.Empty) { personSpecialNeedsKey = AttributeCache.Read(personSpecialNeedsGuid, rockContext).Key; } // log a warning if the attribute is 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)); } var family = checkInState.CheckIn.Families.FirstOrDefault(f => f.Selected); if (family != null) { var cutoffDate = RockDateTime.Today.AddDays(selectFromDaysBack * -1); var attendanceService = new AttendanceService(rockContext); // only process people who have been here before foreach (var previousAttender in family.People.Where(p => p.Selected && !p.FirstTime)) { // get a list of this person's available grouptypes var availableGroupTypeIds = previousAttender.GroupTypes.Select(gt => gt.GroupType.Id).ToList(); // order by most recent attendance var lastDateAttendances = attendanceService.Queryable().Where(a => a.PersonAlias.PersonId == previousAttender.Person.Id && availableGroupTypeIds.Contains(a.Group.GroupTypeId) && a.StartDateTime >= cutoffDate && a.DidAttend == true) .OrderByDescending(a => a.StartDateTime).Take(maxAssignments) .ToList(); if (lastDateAttendances.Any()) { var assignmentsGiven = 0; // get the most recent day, then create assignments starting with the earliest attendance record var lastAttended = lastDateAttendances.Max(a => a.StartDateTime).Date; var numAttendances = lastDateAttendances.Count(a => a.StartDateTime >= lastAttended); foreach (var groupAttendance in lastDateAttendances.Where(a => a.StartDateTime >= lastAttended).OrderBy(a => a.Schedule.StartTimeOfDay)) { bool currentlyCheckedIn = false; var serviceCutoff = groupAttendance.StartDateTime; if (serviceCutoff > RockDateTime.Now.Date && groupAttendance.Schedule != null) { // calculate the service window to determine if people are still checked in var serviceTime = groupAttendance.StartDateTime.Date + groupAttendance.Schedule.StartTimeOfDay; var serviceStart = serviceTime.AddMinutes((groupAttendance.Schedule.CheckInStartOffsetMinutes ?? 0) * -1.0); serviceCutoff = serviceTime.AddMinutes((groupAttendance.Schedule.CheckInEndOffsetMinutes ?? 0)); currentlyCheckedIn = RockDateTime.Now > serviceStart && RockDateTime.Now < serviceCutoff; } // override exists in case they are currently checked in or have special needs bool useCheckinOverride = currentlyCheckedIn || previousAttender.Person.GetAttributeValue(personSpecialNeedsKey).AsBoolean(); // get a list of room balanced grouptype ID's since CheckInGroup model is a shallow clone var roomBalanceGroupTypeIds = previousAttender.GroupTypes.Where(gt => roomBalanceGroupTypes.Contains(gt.GroupType.Guid)) .Select(gt => gt.GroupType.Id).ToList(); // start with filtered groups unless they have abnormal age and grade parameters (1%) var groupType = previousAttender.GroupTypes.FirstOrDefault(gt => gt.GroupType.Id == groupAttendance.Group.GroupTypeId && (!gt.ExcludedByFilter || useCheckinOverride)); if (groupType != null) { // assigning the right schedule depends on prior attendance & currently available schedules being sorted var orderedSchedules = groupType.Groups.SelectMany(g => g.Locations.SelectMany(l => l.Schedules)) .DistinctBy(s => s.Schedule.Id).OrderBy(s => s.Schedule.StartTimeOfDay) .Select(s => s.Schedule.Id).ToList(); int?currentScheduleId = null; if (orderedSchedules.Count == 1) { currentScheduleId = orderedSchedules.FirstOrDefault(); } else if (currentlyCheckedIn) { // always pick the schedule they're currently checked into currentScheduleId = orderedSchedules.Where(s => s == groupAttendance.ScheduleId).FirstOrDefault(); } else { // sort the earliest schedule for the current grouptype, then skip the number of assignments already given (multiple services) currentScheduleId = groupType.AvailableForSchedule .OrderBy(d => orderedSchedules.IndexOf(d)) .Skip(assignmentsGiven).FirstOrDefault(); } CheckInGroup group = null; if (groupType.Groups.Count == 1) { // only a single group is open group = groupType.Groups.FirstOrDefault(g => !g.ExcludedByFilter || useCheckinOverride); } else { // pick the group they last attended, as long as it's open or what they're currently checked into group = groupType.Groups.FirstOrDefault(g => g.Group.Id == groupAttendance.GroupId && (!g.ExcludedByFilter || useCheckinOverride)); // room balance only on new check-ins and only for the current service if (group != null && currentScheduleId != null && roomBalanceGroupTypeIds.Contains(group.Group.GroupTypeId) && !excludedLocations.Contains(group.Group.Name) && !useCheckinOverride) { // make sure balanced rooms are open for the current service var currentAttendance = group.Locations.Where(l => l.AvailableForSchedule.Contains((int)currentScheduleId)) .Select(l => Helpers.ReadAttendanceBySchedule(l.Location.Id, currentScheduleId)).Sum(); var lowestAttendedGroup = groupType.Groups.Where(g => g.AvailableForSchedule.Contains((int)currentScheduleId)) .Where(g => !g.ExcludedByFilter && !excludedLocations.Contains(g.Group.Name)) .Select(g => new { Group = g, Attendance = g.Locations.Select(l => Helpers.ReadAttendanceBySchedule(l.Location.Id, currentScheduleId)).Sum() }) .OrderBy(g => g.Attendance) .FirstOrDefault(); if (lowestAttendedGroup != null && lowestAttendedGroup.Attendance < (currentAttendance - roomBalanceOverride + 1)) { group = lowestAttendedGroup.Group; } } } if (group != null) { CheckInLocation location = null; if (group.Locations.Count == 1) { // only a single location is open location = group.Locations.FirstOrDefault(l => !l.ExcludedByFilter || useCheckinOverride); } else { // pick the location they last attended, as long as it's open or what they're currently checked into location = group.Locations.FirstOrDefault(l => l.Location.Id == groupAttendance.LocationId && (!l.ExcludedByFilter || useCheckinOverride)); // room balance only on new check-ins and only for the current service if (location != null && currentScheduleId != null && roomBalanceGroupTypeIds.Contains(group.Group.GroupTypeId) && !excludedLocations.Contains(location.Location.Name) && !useCheckinOverride) { var currentAttendance = Helpers.ReadAttendanceBySchedule(location.Location.Id, currentScheduleId); var lowestAttendedLocation = group.Locations.Where(l => l.AvailableForSchedule.Contains((int)currentScheduleId)) .Where(l => !l.ExcludedByFilter && !excludedLocations.Contains(l.Location.Name)) .Select(l => new { Location = l, Attendance = Helpers.ReadAttendanceBySchedule(l.Location.Id, currentScheduleId) }) .OrderBy(l => l.Attendance) .FirstOrDefault(); if (lowestAttendedLocation != null && lowestAttendedLocation.Attendance < (currentAttendance - roomBalanceOverride + 1)) { location = lowestAttendedLocation.Location; } } } if (location != null) { // the current schedule could exist on multiple locations, so pick the one owned by this location // if the current schedule just closed, get the first available schedule at this location CheckInSchedule schedule = location.Schedules.OrderByDescending(s => s.Schedule.Id == currentScheduleId).FirstOrDefault(); if (schedule != null) { // it's impossible to currently be checked in unless these match exactly if (group.Group.Id == groupAttendance.GroupId && location.Location.Id == groupAttendance.LocationId && schedule.Schedule.Id == groupAttendance.ScheduleId) { // checkout feature either removes the attendance or sets the EndDateTime var endOfCheckinWindow = groupAttendance.EndDateTime ?? serviceCutoff; schedule.LastCheckIn = endOfCheckinWindow; location.LastCheckIn = endOfCheckinWindow; group.LastCheckIn = endOfCheckinWindow; groupType.LastCheckIn = endOfCheckinWindow; previousAttender.LastCheckIn = endOfCheckinWindow; } // finished finding assignment, verify everything is selected schedule.Selected = true; schedule.PreSelected = true; location.Selected = true; location.PreSelected = true; group.Selected = true; group.PreSelected = true; groupType.Selected = true; groupType.PreSelected = true; previousAttender.Selected = true; previousAttender.PreSelected = true; assignmentsGiven++; } } } } } } } } return(true); }
/// <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 isMobile = GetAttributeValue(action, "IsMobile").AsBoolean(); var mobileDidAttendId = DefinedValueCache.Get(Constants.DEFINED_VALUE_MOBILE_DID_ATTEND).Id; var mobileNotAttendId = DefinedValueCache.Get(Constants.DEFINED_VALUE_MOBILE_NOT_ATTEND).Id; var checkInState = GetCheckInState(entity, out errorMessages); if (checkInState != null) { KioskService kioskService = new KioskService(rockContext); var kioskTypeId = kioskService.GetByClientName(checkInState.Kiosk.Device.Name).KioskTypeId; var kioskType = KioskTypeCache.Get(kioskTypeId.Value); var campusId = kioskType.CampusId; if (campusId == null) { var compatableKioskType = KioskTypeCache.All().Where(kt => kt.CampusId.HasValue && kt.CheckinTemplateId == kioskType.CheckinTemplateId).FirstOrDefault(); if (compatableKioskType != null) { campusId = compatableKioskType.CampusId; } else { campusId = 0; } } campusId = GetCampusOrFamilyCampusId(campusId, checkInState.CheckIn.CurrentFamily.Group.CampusId); AttendanceCode attendanceCode = null; DateTime startDateTime = Rock.RockDateTime.Now; DateTime today = startDateTime.Date; DateTime tomorrow = startDateTime.AddDays(1); bool reuseCodeForFamily = checkInState.CheckInType != null && checkInState.CheckInType.ReuseSameCode; int securityCodeLength = checkInState.CheckInType != null ? checkInState.CheckInType.SecurityCodeAlphaNumericLength : 3; var attendanceCodeService = new AttendanceCodeService(rockContext); var attendanceService = new AttendanceService(rockContext); var groupMemberService = new GroupMemberService(rockContext); var personAliasService = new PersonAliasService(rockContext); //This list is just for mobile check-in List <Attendance> attendances = new List <Attendance>(); var family = checkInState.CheckIn.CurrentFamily; if (family != null) { foreach (var person in family.GetPeople(true)) { if (reuseCodeForFamily && attendanceCode != null) { person.SecurityCode = attendanceCode.Code; } else { attendanceCode = AttendanceCodeService.GetNew(securityCodeLength); person.SecurityCode = attendanceCode.Code; } foreach (var groupType in person.GetGroupTypes(true)) { foreach (var group in groupType.GetGroups(true)) { if (groupType.GroupType.AttendanceRule == AttendanceRule.AddOnCheckIn && groupType.GroupType.DefaultGroupRoleId.HasValue && !groupMemberService.GetByGroupIdAndPersonId(group.Group.Id, person.Person.Id, true).Any()) { var groupMember = new GroupMember(); groupMember.GroupId = group.Group.Id; groupMember.PersonId = person.Person.Id; groupMember.GroupRoleId = groupType.GroupType.DefaultGroupRoleId.Value; groupMemberService.Add(groupMember); } foreach (var location in group.GetLocations(true)) { foreach (var schedule in location.GetSchedules(true)) { var primaryAlias = personAliasService.GetPrimaryAlias(person.Person.Id); if (primaryAlias != null) { int groupId = ActualGroupId(group.Group); // If a like attendance service exists close it before creating another one. var oldAttendance = attendanceService.Queryable() .Where(a => a.StartDateTime >= today && a.StartDateTime < tomorrow && a.Occurrence.LocationId == location.Location.Id && a.Occurrence.ScheduleId == schedule.Schedule.Id && a.Occurrence.GroupId == groupId && a.PersonAlias.PersonId == person.Person.Id) .FirstOrDefault(); if (oldAttendance != null) { oldAttendance.EndDateTime = Rock.RockDateTime.Now; oldAttendance.DidAttend = false; } var attendance = attendanceService.AddOrUpdate(primaryAlias.Id, startDateTime.Date, groupId, location.Location.Id, schedule.Schedule.Id, campusId ?? location.CampusId, checkInState.Kiosk.Device.Id, checkInState.CheckIn.SearchType.Id, checkInState.CheckIn.SearchValue, family.Group.Id, attendanceCode.Id); attendance.DeviceId = checkInState.Kiosk.Device.Id; attendance.SearchTypeValueId = checkInState.CheckIn.SearchType.Id; attendance.SearchValue = checkInState.CheckIn.SearchValue; attendance.CheckedInByPersonAliasId = checkInState.CheckIn.CheckedInByPersonAliasId; attendance.SearchResultGroupId = family.Group.Id; attendance.AttendanceCodeId = attendanceCode.Id; attendance.CreatedDateTime = startDateTime; attendance.StartDateTime = startDateTime; attendance.EndDateTime = null; attendance.Note = group.Notes; attendance.DidAttend = isMobile ? false : groupType.GroupType.GetAttributeValue("SetDidAttend").AsBoolean(); if (isMobile) { if (groupType.GroupType.GetAttributeValue("SetDidAttend").AsBoolean()) { attendance.QualifierValueId = mobileDidAttendId; } else { attendance.QualifierValueId = mobileNotAttendId; } } ; attendanceService.Add(attendance); attendances.Add(attendance); } } } } } } } if (isMobile) { var alreadyExistingMobileCheckin = MobileCheckinRecordCache.GetActiveByFamilyGroupId(checkInState.CheckIn.CurrentFamily.Group.Id); if (alreadyExistingMobileCheckin != null) { //This should never run, it's just in case. Each family should only have 1 mobile check-in reservation. MobileCheckinRecordCache.CancelReservation(alreadyExistingMobileCheckin, true); } campusId = RollUpToParentCampus(campusId); MobileCheckinRecordService mobileCheckinRecordService = new MobileCheckinRecordService(rockContext); var mobileCheckinRecord = new MobileCheckinRecord { AccessKey = "MCR" + Guid.NewGuid().ToString("N").Substring(0, 12), ReservedUntilDateTime = Rock.RockDateTime.Now.AddMinutes(kioskType.MinutesValid ?? 10), ExpirationDateTime = Rock.RockDateTime.Now.AddMinutes((kioskType.MinutesValid ?? 10) + (kioskType.GraceMinutes ?? 60)), UserName = checkInState.CheckIn.SearchValue, FamilyGroupId = checkInState.CheckIn.CurrentFamily.Group.Id, CampusId = campusId.Value }; foreach (var attendance in attendances) { mobileCheckinRecord.Attendances.Add(attendance); } mobileCheckinRecordService.Add(mobileCheckinRecord); } rockContext.SaveChanges(); foreach (var attendance in attendances) { AttendanceCache.AddOrUpdate(attendance); } return(true); } errorMessages.Add($"Attempted to run {this.GetType().GetFriendlyTypeName()} in check-in, but the check-in state was null."); return(false); }
/// <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); } int selectFromDaysBack = checkInState.CheckInType.AutoSelectDaysBack; var roomBalanceGroupTypes = GetAttributeValue(action, "RoomBalanceGrouptypes").SplitDelimitedValues().AsGuidList(); int roomBalanceOverride = GetAttributeValue(action, "BalancingOverride").AsIntegerOrNull() ?? 5; int maxAssignments = GetAttributeValue(action, "MaxAssignments").AsIntegerOrNull() ?? 5; var excludedLocations = GetAttributeValue(action, "ExcludedLocations").SplitDelimitedValues(whitespace: false) .Select(s => s.Trim()); // get the admin-selected attribute key 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; } // log a warning if the attribute is 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)); } var family = checkInState.CheckIn.Families.FirstOrDefault(f => f.Selected); if (family != null) { var cutoffDate = RockDateTime.Today.AddDays(selectFromDaysBack * -1); var attendanceService = new AttendanceService(rockContext); // only process people who have been here before foreach (var previousAttender in family.People.Where(p => p.Selected && !p.FirstTime)) { // get a list of this person's available grouptypes var availableGroupTypeIds = previousAttender.GroupTypes.Select(gt => gt.GroupType.Id).ToList(); var lastDateAttendances = attendanceService.Queryable().Where(a => a.PersonAlias.PersonId == previousAttender.Person.Id && availableGroupTypeIds.Contains(a.Group.GroupTypeId) && a.StartDateTime >= cutoffDate && a.DidAttend == true) .OrderByDescending(a => a.StartDateTime).Take(maxAssignments) .ToList(); if (lastDateAttendances.Any()) { var lastAttended = lastDateAttendances.Max(a => a.StartDateTime).Date; var numAttendances = lastDateAttendances.Count(a => a.StartDateTime >= lastAttended); foreach (var groupAttendance in lastDateAttendances.Where(a => a.StartDateTime >= lastAttended)) { bool currentlyCheckedIn = false; var serviceCutoff = groupAttendance.StartDateTime; if (serviceCutoff > RockDateTime.Now.Date) { // calculate the service window to determine if people are still checked in var serviceTime = groupAttendance.StartDateTime.Date + groupAttendance.Schedule.NextStartDateTime.Value.TimeOfDay; var serviceStart = serviceTime.AddMinutes((groupAttendance.Schedule.CheckInStartOffsetMinutes ?? 0) * -1.0); serviceCutoff = serviceTime.AddMinutes((groupAttendance.Schedule.CheckInEndOffsetMinutes ?? 0)); currentlyCheckedIn = RockDateTime.Now > serviceStart && RockDateTime.Now < serviceCutoff; } // override exists in case they are currently checked in or have special needs bool useCheckinOverride = currentlyCheckedIn || previousAttender.Person.GetAttributeValue(personSpecialNeedsKey).AsBoolean(); // get a list of room balanced grouptype ID's since CheckInGroup model is a shallow clone var roomBalanceGroupTypeIds = previousAttender.GroupTypes.Where(gt => roomBalanceGroupTypes.Contains(gt.GroupType.Guid)) .Select(gt => gt.GroupType.Id).ToList(); // Start with filtered groups unless they have abnormal age and grade parameters (1%) var groupType = previousAttender.GroupTypes.FirstOrDefault(gt => gt.GroupType.Id == groupAttendance.Group.GroupTypeId && (!gt.ExcludedByFilter || useCheckinOverride)); if (groupType != null) { CheckInGroup group = null; if (groupType.Groups.Count == 1) { // Only a single group is open group = groupType.Groups.FirstOrDefault(g => !g.ExcludedByFilter || useCheckinOverride); } else { // Pick the group they last attended, as long as it's open or what they're currently checked into group = groupType.Groups.FirstOrDefault(g => g.Group.Id == groupAttendance.GroupId && (!g.ExcludedByFilter || useCheckinOverride)); // room balance only on new check-ins if (group != null && roomBalanceGroupTypeIds.Contains(group.Group.GroupTypeId) && !useCheckinOverride) { //TODO: use KioskLocationAttendance and group.AvailableForSchedule to room balance by service time attendance, not the entire day var currentAttendance = group.Locations.Select(l => KioskLocationAttendance.Read(l.Location.Id).CurrentCount).Sum(); var lowestAttendedGroup = groupType.Groups.Where(g => !g.ExcludedByFilter && !excludedLocations.Contains(g.Group.Name)) .Select(g => new { Group = g, Attendance = g.Locations.Select(l => KioskLocationAttendance.Read(l.Location.Id).CurrentCount).Sum() }) .OrderBy(g => g.Attendance) .FirstOrDefault(); if (lowestAttendedGroup != null && lowestAttendedGroup.Attendance < (currentAttendance - roomBalanceOverride + 1)) { group = lowestAttendedGroup.Group; } } } if (group != null) { CheckInLocation location = null; if (group.Locations.Count == 1) { // Only a single location is open location = group.Locations.FirstOrDefault(l => !l.ExcludedByFilter || useCheckinOverride); } else { // Pick the location they last attended, as long as it's open or what they're currently checked into location = group.Locations.FirstOrDefault(l => l.Location.Id == groupAttendance.LocationId && (!l.ExcludedByFilter || useCheckinOverride)); // room balance only on new check-ins if (location != null && roomBalanceGroupTypeIds.Contains(group.Group.GroupTypeId) && !useCheckinOverride) { //TODO: use KioskLocationAttendance and location.AvailableForSchedule to room balance by service time attendance, not the entire day var currentAttendance = KioskLocationAttendance.Read(location.Location.Id).CurrentCount; var lowestAttendedLocation = group.Locations.Where(l => !l.ExcludedByFilter && !excludedLocations.Contains(l.Location.Name)) .Select(l => new { Location = l, Attendance = KioskLocationAttendance.Read(l.Location.Id).CurrentCount }) .OrderBy(l => l.Attendance) .FirstOrDefault(); if (lowestAttendedLocation != null && lowestAttendedLocation.Attendance < (currentAttendance - roomBalanceOverride + 1)) { location = lowestAttendedLocation.Location; } } } if (location != null) { CheckInSchedule schedule = null; if (location.Schedules.Count == 1) { schedule = location.Schedules.FirstOrDefault(s => !s.ExcludedByFilter || useCheckinOverride); } else { // if assigning to multiple services or currently checked in (not SN, otherwise they would get the wrong auto-schedule) if (numAttendances > 1 || currentlyCheckedIn) { // pick what they last attended last schedule = location.Schedules.FirstOrDefault(s => s.Schedule.Id == groupAttendance.ScheduleId && (!s.ExcludedByFilter || useCheckinOverride)); } // otherwise pick the earliest available schedule schedule = schedule ?? location.Schedules.OrderBy(s => s.Schedule.StartTimeOfDay).FirstOrDefault(s => !s.ExcludedByFilter); } if (schedule != null) { // it's impossible to currently be checked in unless these match exactly if (group.Group.Id == groupAttendance.GroupId && location.Location.Id == groupAttendance.LocationId && schedule.Schedule.Id == groupAttendance.ScheduleId) { // Checkout would've removed the attendance or set the EndDateTime var endOfCheckinWindow = groupAttendance.EndDateTime ?? serviceCutoff; schedule.LastCheckIn = endOfCheckinWindow; location.LastCheckIn = endOfCheckinWindow; group.LastCheckIn = endOfCheckinWindow; groupType.LastCheckIn = endOfCheckinWindow; previousAttender.LastCheckIn = endOfCheckinWindow; } // finished finding assignment, verify everything is selected schedule.Selected = true; schedule.PreSelected = true; location.Selected = true; location.PreSelected = true; group.Selected = true; group.PreSelected = true; groupType.Selected = true; groupType.PreSelected = true; groupType.Selected = true; groupType.PreSelected = true; previousAttender.Selected = true; previousAttender.PreSelected = true; } } } } } } } } return(true); }
/// <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); } bool roomBalance = GetAttributeValue(action, "RoomBalance").AsBoolean(); bool useGroupMembership = GetAttributeValue(action, "PrioritizeGroupMembership").AsBoolean(); int balanceOverride = GetAttributeValue(action, "DifferentialOverride").AsIntegerOrNull() ?? 10; var family = checkInState.CheckIn.Families.FirstOrDefault(f => f.Selected); if (family != null) { // don't run for people who already have attendance assignments foreach (var person in family.People.Where(f => f.Selected && !f.GroupTypes.Any(gt => gt.Selected))) { decimal baseVariance = 100; char[] delimiter = { ',' }; // variable must be a string to compare to group attribute value var specialNeedsValue = person.Person.GetAttributeValue("IsSpecialNeeds").ToStringSafe(); var isSpecialNeeds = specialNeedsValue.AsBoolean(); 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("AgeRange") && g.Group.AttributeValues["AgeRange"].Value != null && g.Group.AttributeValues.ContainsKey("IsSpecialNeeds") == isSpecialNeeds ) .ToList() .Select(g => new { Group = g, AgeRange = g.Group.AttributeValues["AgeRange"].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 (isSpecialNeeds) { closestNeedsGroup = closestAgeGroup; } } } } else if (isSpecialNeeds) { // special needs was checked but no 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("GradeRange") && g.Group.AttributeValues["GradeRange"].Value != null) .ToList() .Select(g => new { Group = g, GradeOffsets = g.Group.AttributeValues["GradeRange"].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: Ability, then Grade, then Age, then 1st available bestGroup = closestNeedsGroup ?? closestGradeGroup ?? closestAgeGroup ?? validGroups.FirstOrDefault(g => !g.ExcludedByFilter); // room balance if they fit into multiple groups if (bestGroup != null && roomBalance) { var currentGroupAttendance = bestGroup.Locations.Select(l => KioskLocationAttendance.Read(l.Location.Id).CurrentCount).Sum(); var lowestGroup = validGroups.Where(g => !g.ExcludedByFilter) .Select(g => new { Group = g, Attendance = g.Locations.Select(l => KioskLocationAttendance.Read(l.Location.Id).CurrentCount).Sum() }) .OrderBy(g => g.Attendance) .FirstOrDefault(); if (lowestGroup != null && lowestGroup.Attendance < (currentGroupAttendance - balanceOverride)) { 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 orderedLocations = validLocations.Where(l => !l.ExcludedByFilter && l.Schedules.Any(s => s.Schedule.IsCheckInActive)); if (roomBalance) { orderedLocations = orderedLocations.OrderBy(l => KioskLocationAttendance.Read(l.Location.Id).CurrentCount); } bestLocation = orderedLocations.FirstOrDefault(); validSchedules = bestLocation.Schedules; } // check how many schedules exist without getting the whole list int numValidSchedules = validSchedules.Take(2).Count(); if (numValidSchedules > 0) { var bestSchedule = validSchedules.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.PreSelected = true; } } } } } } } } } return(true); }
/// <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) { errorMessages.Add($"Attempted to run {this.GetType().GetFriendlyTypeName()} in check-in, but the check-in state was null."); return(false); } var dataViewAttributeKey = string.Empty; var dataViewAttributeGuid = GetAttributeValue(action, "DataViewGroupAttribute").AsGuid(); if (dataViewAttributeGuid != Guid.Empty) { dataViewAttributeKey = AttributeCache.Get(dataViewAttributeGuid).Key; } var dataViewService = new DataViewService(new RockContext()); var family = checkInState.CheckIn.CurrentFamily; if (family != null) { var remove = GetAttributeValue(action, "Remove").AsBoolean(); foreach (var person in family.People) { foreach (var groupType in person.GroupTypes.ToList()) { foreach (var group in groupType.Groups.ToList()) { if (group.ExcludedByFilter == true) { continue; } var approvedPeopleGuid = group.Group.GetAttributeValue(dataViewAttributeKey); if (string.IsNullOrWhiteSpace(approvedPeopleGuid)) { continue; } //Get approved people dataview from cache or from db var approvedPeopleList = RockCache.Get(approvedPeopleGuid) as List <int>; if (approvedPeopleList == null) { DataView approvedPeople = dataViewService.Get(approvedPeopleGuid.AsGuid()); if (approvedPeople == null) { continue; } var errors = new List <string>(); var dbContext = approvedPeople.GetDbContext(); var approvedPeopleQry = (IQueryable <Person>)approvedPeople.GetQuery(null, dbContext, 30, out errors).AsNoTracking(); if (approvedPeopleQry != null) { try { approvedPeopleQry = approvedPeopleQry.Skip(0).Take(5000); approvedPeopleList = approvedPeopleQry.Select(e => e.Id).ToList(); RockCache.AddOrUpdate(approvedPeopleGuid, null, approvedPeopleList, RockDateTime.Now.AddMinutes(10), Constants.CACHE_TAG); } catch (Exception ex) { if (remove) { groupType.Groups.Remove(group); } else { group.ExcludedByFilter = true; } ExceptionLogService.LogException(ex); } } } if (approvedPeopleList != null && !approvedPeopleList.Contains(person.Person.Id)) { if (remove) { groupType.Groups.Remove(group); } else { group.ExcludedByFilter = true; } } } } } } return(true); }
/// <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 dataViewAttributeKey = string.Empty; var dataViewAttributeGuid = GetAttributeValue(action, "DataViewGroupAttribute").AsGuid(); if (dataViewAttributeGuid != Guid.Empty) { dataViewAttributeKey = AttributeCache.Get(dataViewAttributeGuid, rockContext).Key; } var dataViewService = new DataViewService(rockContext); var family = checkInState.CheckIn.CurrentFamily; if (family != null) { var remove = GetAttributeValue(action, "Remove").AsBoolean(); foreach (var person in family.People) { foreach (var groupType in person.GroupTypes.ToList()) { foreach (var group in groupType.Groups.ToList()) { if (group.ExcludedByFilter == true) { continue; } var dataviewGuids = group.Group.GetAttributeValue(dataViewAttributeKey); if (string.IsNullOrWhiteSpace(dataviewGuids)) { continue; } foreach (var dataviewGuid in dataviewGuids.SplitDelimitedValues()) { DataView dataview = dataViewService.Get(dataviewGuid.AsGuid()); if (dataview == null) { continue; } if (dataview.PersistedScheduleIntervalMinutes.HasValue && dataview.PersistedLastRefreshDateTime.HasValue) { //Get record from persisted. var persistedValuesQuery = rockContext.DataViewPersistedValues.Where(a => a.DataViewId == dataview.Id); if (!persistedValuesQuery.Any(v => v.EntityId == person.Person.Id)) { if (remove) { groupType.Groups.Remove(group); } else { group.ExcludedByFilter = true; } break; } } else { //Qry dataview var dataViewGetQueryArgs = new DataViewGetQueryArgs { DatabaseTimeoutSeconds = 30 }; var approvedPeopleQry = dataview.GetQuery(dataViewGetQueryArgs); if (approvedPeopleQry != null) { var approvedPeopleList = approvedPeopleQry.Select(e => e.Id).ToList(); if (approvedPeopleList != null && !approvedPeopleList.Contains(person.Person.Id)) { if (remove) { groupType.Groups.Remove(group); } else { group.ExcludedByFilter = true; } break; } } } } } } } } return(true); }
/// <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) { errorMessages.Add($"Attempted to run {this.GetType().GetFriendlyTypeName()} in check-in, but the check-in state was null."); return(false); } var groupIdAttributeKey = string.Empty; var groupIdAttributeGuid = GetAttributeValue(action, "GroupMembershipGroupAttribute").AsGuid(); if (groupIdAttributeGuid != Guid.Empty) { groupIdAttributeKey = AttributeCache.Get(groupIdAttributeGuid).Key; } var family = checkInState.CheckIn.CurrentFamily; if (family != null) { GroupMemberService groupMemberService = new GroupMemberService(rockContext); var remove = GetAttributeValue(action, "Remove").AsBoolean(); foreach (var person in family.People) { if (person.Person.Attributes == null) { person.Person.LoadAttributes(rockContext); } foreach (var groupType in person.GroupTypes.ToList()) { foreach (var group in groupType.Groups.ToList()) { var groupGuid = group.Group.GetAttributeValue(groupIdAttributeKey).AsGuidOrNull(); if (groupGuid.HasValue) { bool allowInactive = false; if (checkInState.CheckInType != null) { allowInactive = !checkInState.CheckInType.PreventInactivePeople; } var groupmembers = groupMemberService.GetByGroupGuid(groupGuid.Value) .Where(gm => gm.PersonId == person.Person.Id && (gm.GroupMemberStatus == GroupMemberStatus.Active || allowInactive)); var state = GetAttributeValue(action, "CheckRequirements"); switch (state) { case "0": break; case "1": groupmembers = groupmembers.Where( gm => !gm.GroupMemberRequirements.Where( r => r.RequirementFailDateTime != null) .Any() ); break; case "2": groupmembers = groupmembers.Where( gm => !gm.GroupMemberRequirements.Where( r => r.RequirementFailDateTime != null || r.RequirementWarningDateTime != null) .Any()); break; } if (!groupmembers.Any()) { if (remove) { groupType.Groups.Remove(group); } else { group.ExcludedByFilter = true; } } } } } } } return(true); }
/// <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) { errorMessages.Add($"Attempted to run {this.GetType().GetFriendlyTypeName()} in check-in, but the check-in state was null."); return(false); } var groupAttributeKey = AttributeCache.Get(Constants.GROUP_ATTRIBUTE_MEMBERSHIP_GROUP).Key; var checkRequirementsKey = AttributeCache.Get(Constants.GROUP_ATTRIBUTE_CHECK_REQUIREMENTS).Key; var memberRoleAttributeKey = AttributeCache.Get(Constants.GROUP_ATTRIBUTE_MEMBER_ROLE).Key; bool allowInactive = false; if (checkInState.CheckInType != null) { allowInactive = !checkInState.CheckInType.PreventInactivePeople; } var family = checkInState.CheckIn.CurrentFamily; if (family != null) { GroupService groupService = new GroupService(rockContext); GroupMemberService groupMemberService = new GroupMemberService(rockContext); GroupTypeRoleService groupTypeRoleService = new GroupTypeRoleService(rockContext); var remove = GetAttributeValue(action, "Remove").AsBoolean(); foreach (var person in family.People) { if (person.Person.Attributes == null) { person.Person.LoadAttributes(rockContext); } foreach (var groupType in person.GroupTypes.ToList()) { var groupsToCheck = groupType.Groups .Where(g => g.Group.AttributeValues.ContainsKey(groupAttributeKey)) .Select(g => g.Group.AttributeValues[groupAttributeKey].Value) .Where(s => s.IsNotNullOrWhiteSpace()) .Select(s => s.AsGuid()); if (!groupsToCheck.Any()) { //There are no groups to search here. Don't remove any and move on continue; } var groupMemberQry = groupMemberService.Queryable().AsNoTracking() .Where(gm => gm.PersonId == person.Person.Id); if (!allowInactive) { groupMemberQry = groupMemberQry.Where(gm => gm.GroupMemberStatus == GroupMemberStatus.Active); } //Check all the groups at once. This turns many little requests into one medium request. var qry = groupService.Queryable().AsNoTracking() .Where(g => groupsToCheck.Contains(g.Guid)) .Join(groupMemberQry, g => g.Id, gm => gm.GroupId, (g, gm) => new { g.Guid, gm.GroupRole.IsLeader }); var validGroups = qry.ToList(); foreach (var group in groupType.Groups.ToList()) { var groupGuid = group.Group.GetAttributeValue(groupAttributeKey).AsGuid(); if (groupGuid == Guid.Empty) { //There is no group to check... do not remove check-in group continue; } var cannotCheckin = false; var role = group.Group.GetAttributeValue(memberRoleAttributeKey); switch (role) { case "0": // Any if (!validGroups.Where(g => g.Guid == groupGuid).Any()) { cannotCheckin = true; } break; case "1": //Leaders Only if (!validGroups.Where(g => g.Guid == groupGuid && g.IsLeader).Any()) { cannotCheckin = true; } break; case "2": //Non Leaders if (!validGroups.Where(g => g.Guid == groupGuid && !g.IsLeader).Any()) { cannotCheckin = true; } break; } if (!cannotCheckin) { //Check the group requirements to see if this person passes var requirement = group.Group.GetAttributeValue(checkRequirementsKey); switch (requirement) { case "0": //No requirement break; case "1": //Required Only cannotCheckin = !groupMemberService.GetByGroupGuid(groupGuid) .Where(gm => gm.PersonId == person.Person.Id && (gm.GroupMemberStatus == GroupMemberStatus.Active || allowInactive)) .Where(gm => !gm.GroupMemberRequirements.Where(r => r.RequirementFailDateTime != null) .Any()) .Any(); break; case "2": //Required And Warning cannotCheckin = !groupMemberService.GetByGroupGuid(groupGuid) .Where(gm => gm.PersonId == person.Person.Id && (gm.GroupMemberStatus == GroupMemberStatus.Active || allowInactive)) .Where(gm => !gm.GroupMemberRequirements.Where(r => r.RequirementFailDateTime != null || r.RequirementWarningDateTime != null) .Any()) .Any(); break; } } if (cannotCheckin) { if (remove) { groupType.Groups.Remove(group); } else { group.ExcludedByFilter = true; } } } } } } return(true); }
/// <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 family = checkInState.CheckIn.CurrentFamily; if (family != null) { var remove = GetAttributeValue(action, "Remove").AsBoolean(); bool gradeRequired = checkInState.CheckInType == null || checkInState.CheckInType.GradeRequired; foreach (var person in family.People) { if (person.Person.GraduationYear == null && !gradeRequired) { continue; } foreach (var groupType in person.GroupTypes.ToList()) { int?personsGradeOffset = person.Person.GraduationYear - CurrentGraduationYear(groupType.GroupType); foreach (var group in groupType.Groups.ToList()) { string gradeOffsetRange = group.Group.GetAttributeValue("GradeRange") ?? string.Empty; var gradeOffsetRangePair = gradeOffsetRange.Split(new char[] { ',' }, StringSplitOptions.None).AsGuidOrNullList().ToArray(); DefinedValueCache minGradeDefinedValue = null; DefinedValueCache maxGradeDefinedValue = null; if (gradeOffsetRangePair.Length == 2) { minGradeDefinedValue = gradeOffsetRangePair[0].HasValue ? DefinedValueCache.Read(gradeOffsetRangePair[0].Value) : null; maxGradeDefinedValue = gradeOffsetRangePair[1].HasValue ? DefinedValueCache.Read(gradeOffsetRangePair[1].Value) : null; } /* * example (assuming defined values are the stock values): * minGrade,maxGrade of between 4th and 6th grade * 4th grade is 8 years until graduation * 6th grade is 6 years until graduation * GradeOffsetRange would be 8 and 6 * if person is in: * 7th grade or older (gradeOffset 5 or smaller), they would be NOT included * 6th grade (gradeOffset 6), they would be included * 5th grade (gradeOffset 7), they would be included * 4th grade (gradeOffset 8), they would be included * 3th grade or younger (gradeOffset 9 or bigger), they would be NOT included * NULL grade, not included */ // if the group type specifies a min grade (max gradeOffset)... if (maxGradeDefinedValue != null) { // NOTE: minGradeOffset is actually based on the MAX Grade since GradeOffset's are Years Until Graduation int?minGradeOffset = maxGradeDefinedValue.Value.AsIntegerOrNull(); if (minGradeOffset.HasValue) { // remove if the person does not have a grade or if their grade offset is more than the max offset (too young) // example person is in 3rd grade (offset 9) and range is 4th to 6th (offset 6 to 8) if (!personsGradeOffset.HasValue || personsGradeOffset < minGradeOffset.Value) { if (remove) { groupType.Groups.Remove(group); } else { group.ExcludedByFilter = true; } continue; } } } // if the group type specifies a max grade (min gradeOffset)... if (minGradeDefinedValue != null) { // NOTE: maxGradeOffset is actually based on the MIN Grade since GradeOffset's are Years Until Graduation int?maxGradeOffset = minGradeDefinedValue.Value.AsIntegerOrNull(); if (maxGradeOffset.HasValue) { // remove if the person does not have a grade or if their grade offset is less than the min offset (too old) // example person is in 7rd grade (offset 5) and range is 4th to 6th (offset 6 to 8) if (!personsGradeOffset.HasValue || personsGradeOffset > maxGradeOffset) { if (remove) { groupType.Groups.Remove(group); } else { group.ExcludedByFilter = true; } continue; } } } } } } } return(true); }
/// <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) { var checkinAttributeKey = ""; var checkinAttributeGuid = GetAttributeValue(action, "CheckinGroupsAttribute"); if (!string.IsNullOrWhiteSpace(checkinAttributeGuid)) { var attributeCache = AttributeCache.Read(checkinAttributeGuid.AsGuid()); if (attributeCache != null) { checkinAttributeKey = attributeCache.Key; } } foreach (var family in checkInState.CheckIn.GetFamilies(true)) { foreach (var person in family.GetPeople(false)) { if (person.Person.Attributes == null) { person.Person.LoadAttributes(); } var groupGuidsString = person.Person.GetAttributeValue(checkinAttributeKey); if (!string.IsNullOrWhiteSpace(groupGuidsString)) { var guids = groupGuidsString .Split(new char[] { '|' }, StringSplitOptions.RemoveEmptyEntries) .Select(g => g.AsGuid()) .ToList(); foreach (var groupType in person.GroupTypes) { var kioskGroup = checkInState.Kiosk.ActiveGroupTypes(checkInState.ConfiguredGroupTypes) .Where(g => g.GroupType.Id == groupType.GroupType.Id) .FirstOrDefault(); if (kioskGroup != null) { var injectGroups = kioskGroup.KioskGroups .Where(g => g.IsCheckInActive && guids.Contains(g.Group.Guid) ); if (!injectGroups.Any()) { continue; } foreach (var injectGroup in injectGroups) { var checkInGroup = new CheckInGroup(); checkInGroup.Group = injectGroup.Group.Clone(false); checkInGroup.Group.CopyAttributesFrom(injectGroup.Group); groupType.Groups.Insert(0, checkInGroup); } groupType.Groups = groupType.Groups.DistinctBy(g => g.Group.Id).ToList(); } } } } } return(true); } return(false); }
/// <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 && checkInState.CheckIn.SearchType != null) { var personService = new PersonService(rockContext); var memberService = new GroupMemberService(rockContext); GroupService groupService = new GroupService(rockContext); PhoneNumberService phoneNumberService = new PhoneNumberService(rockContext); int familyGroupTypeId = GroupTypeCache.Get(Rock.SystemGuid.GroupType.GROUPTYPE_FAMILY.AsGuid()).Id; if (checkInState.CheckIn.SearchType.Guid.Equals(new Guid(Rock.SystemGuid.DefinedValue.CHECKIN_SEARCH_TYPE_PHONE_NUMBER))) { string numericPhone = checkInState.CheckIn.SearchValue.AsNumeric(); var personRecordTypeId = DefinedValueCache.Get(Rock.SystemGuid.DefinedValue.PERSON_RECORD_TYPE_PERSON.AsGuid()).Id; // Find the families with any member who has a phone number that contains selected value var familyQry = phoneNumberService.Queryable().AsNoTracking(); if (checkInState.CheckInType == null || checkInState.CheckInType.PhoneSearchType == PhoneSearchType.EndsWith) { char[] charArray = numericPhone.ToCharArray(); Array.Reverse(charArray); familyQry = familyQry.Where(o => o.NumberReversed.StartsWith(new string( charArray ))); } else { familyQry = familyQry.Where(o => o.Number.Contains(numericPhone)); } var tmpQry = familyQry.Join(personService.Queryable().AsNoTracking(), o => new { PersonId = o.PersonId, IsDeceased = false, RecordTypeValueId = personRecordTypeId }, p => new { PersonId = p.Id, IsDeceased = p.IsDeceased, RecordTypeValueId = p.RecordTypeValueId.Value }, (pn, p) => new { Person = p, PhoneNumber = pn }) .Join(memberService.Queryable().AsNoTracking(), pn => pn.Person.Id, m => m.PersonId, (o, m) => new { PersonNumber = o.PhoneNumber, GroupMember = m }); var familyIdQry = groupService.Queryable().Where(g => tmpQry.Any(o => o.GroupMember.GroupId == g.Id) && g.GroupTypeId == familyGroupTypeId) .Select(g => g.Id) .Distinct(); int maxResults = checkInState.CheckInType != null ? checkInState.CheckInType.MaxSearchResults : 100; if (maxResults > 0) { familyIdQry = familyIdQry.Take(maxResults); } var familyIds = familyIdQry.ToList(); // Load the family members var familyMembers = memberService .Queryable("Group,GroupRole,Person").AsNoTracking() .Where(m => familyIds.Contains(m.GroupId)) .ToList(); // Add each family foreach (int familyId in familyIds) { // Get each of the members for this family var thisFamilyMembers = familyMembers .Where(m => m.GroupId == familyId && m.Person.NickName != null) .ToList(); if (thisFamilyMembers.Any()) { var group = thisFamilyMembers .Select(m => m.Group) .FirstOrDefault(); var firstNames = thisFamilyMembers .OrderBy(m => m.GroupRole.Order) .ThenBy(m => m.Person.BirthYear) .ThenBy(m => m.Person.BirthMonth) .ThenBy(m => m.Person.BirthDay) .ThenBy(m => m.Person.Gender) .Select(m => m.Person.NickName) .ToList(); var family = new CheckInFamily(); family.Group = group.Clone(false); family.Caption = group.ToString(); family.SubCaption = firstNames.AsDelimited(", "); checkInState.CheckIn.Families.Add(family); } } } else if (checkInState.CheckIn.SearchType.Guid.Equals(new Guid(Rock.SystemGuid.DefinedValue.CHECKIN_SEARCH_TYPE_NAME))) { foreach (var person in personService.GetByFullName(checkInState.CheckIn.SearchValue, false).AsNoTracking()) { foreach (var group in person.Members.Where(m => m.Group.GroupTypeId == familyGroupTypeId).Select(m => m.Group).ToList()) { var family = checkInState.CheckIn.Families.Where(f => f.Group.Id == group.Id).FirstOrDefault(); if (family == null) { family = new CheckInFamily(); family.Group = group.Clone(false); family.Group.LoadAttributes(rockContext); family.Caption = group.ToString(); family.SubCaption = memberService.GetFirstNames(group.Id).ToList().AsDelimited(", "); checkInState.CheckIn.Families.Add(family); } } } } else { errorMessages.Add("Invalid Search Type"); return(false); } return(true); } errorMessages.Add("Invalid Check-in State"); return(false); }
/// <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) { AttendanceCode attendanceCode = null; DateTime startDateTime = Rock.RockDateTime.Now; DateTime today = startDateTime.Date; DateTime tomorrow = startDateTime.AddDays(1); bool reuseCodeForFamily = checkInState.CheckInType != null && checkInState.CheckInType.ReuseSameCode; int securityCodeLength = checkInState.CheckInType != null ? checkInState.CheckInType.SecurityCodeAlphaNumericLength : 3; var attendanceCodeService = new AttendanceCodeService(rockContext); var attendanceService = new AttendanceService(rockContext); var groupMemberService = new GroupMemberService(rockContext); var personAliasService = new PersonAliasService(rockContext); var family = checkInState.CheckIn.CurrentFamily; if (family != null) { foreach (var person in family.GetPeople(true)) { if (reuseCodeForFamily && attendanceCode != null) { person.SecurityCode = attendanceCode.Code; } else { attendanceCode = AttendanceCodeService.GetNew(securityCodeLength); person.SecurityCode = attendanceCode.Code; } foreach (var groupType in person.GetGroupTypes(true)) { foreach (var group in groupType.GetGroups(true)) { if (groupType.GroupType.AttendanceRule == AttendanceRule.AddOnCheckIn && groupType.GroupType.DefaultGroupRoleId.HasValue && !groupMemberService.GetByGroupIdAndPersonId(group.Group.Id, person.Person.Id, true).Any()) { var groupMember = new GroupMember(); groupMember.GroupId = group.Group.Id; groupMember.PersonId = person.Person.Id; groupMember.GroupRoleId = groupType.GroupType.DefaultGroupRoleId.Value; groupMemberService.Add(groupMember); } foreach (var location in group.GetLocations(true)) { foreach (var schedule in location.GetSchedules(true)) { var primaryAlias = personAliasService.GetPrimaryAlias(person.Person.Id); if (primaryAlias != null) { // If a like attendance service exists close it before creating another one. var oldAttendance = attendanceService.Queryable() .Where(a => a.StartDateTime >= today && a.StartDateTime < tomorrow && a.Occurrence.LocationId == location.Location.Id && a.Occurrence.ScheduleId == schedule.Schedule.Id && a.Occurrence.GroupId == group.Group.Id && a.PersonAlias.PersonId == person.Person.Id) .FirstOrDefault(); if (oldAttendance != null) { oldAttendance.EndDateTime = Rock.RockDateTime.Now; oldAttendance.DidAttend = false; } var attendance = attendanceService.AddOrUpdate(primaryAlias.Id, startDateTime.Date, group.Group.Id, location.Location.Id, schedule.Schedule.Id, location.CampusId, checkInState.Kiosk.Device.Id, checkInState.CheckIn.SearchType.Id, checkInState.CheckIn.SearchValue, family.Group.Id, attendanceCode.Id); attendance.DeviceId = checkInState.Kiosk.Device.Id; attendance.SearchTypeValueId = checkInState.CheckIn.SearchType.Id; attendance.SearchValue = checkInState.CheckIn.SearchValue; attendance.CheckedInByPersonAliasId = checkInState.CheckIn.CheckedInByPersonAliasId; attendance.SearchResultGroupId = family.Group.Id; attendance.AttendanceCodeId = attendanceCode.Id; attendance.StartDateTime = startDateTime; attendance.EndDateTime = null; attendance.Note = group.Notes; attendance.DidAttend = groupType.GroupType.GetAttributeValue("SetDidAttend").AsBoolean(); attendanceService.Add(attendance); CheckInCountCache.AddAttendance(attendance); } } } } } } } rockContext.SaveChanges(); return(true); } return(false); }