/// <summary> /// Toggles on or off one check-in GroupLocationSchedule (it will deselct others) /// </summary> /// <param name="chGroup">Checkin Group</param> /// <param name="chLocation">Checkin Location</param> /// <param name="chSchedule">Checkin Schedule</param> /// <param name="alreadySelected">Is the Group Location Schedule already selected</param> private void ToggleClass(CheckInGroup chGroup, CheckInLocation chLocation, CheckInSchedule chSchedule, bool alreadySelected) { foreach (var groupType in CurrentCheckInState.CheckIn.CurrentPerson.GroupTypes) { groupType.Selected = true; foreach (var group in groupType.Groups) { group.Selected = false; foreach (var location in group.Locations) { location.Selected = false; foreach (var schedule in location.Schedules) { schedule.Selected = false; } } } } if (!alreadySelected) { chGroup.Selected = true; chLocation.Selected = true; chSchedule.Selected = true; btnCheckin.Visible = true; } SaveState(); ShowPersonCheckin(); }
/// <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); RockPage.AddScriptLink("~/Scripts/iscroll.js"); RockPage.AddScriptLink("~/Scripts/CheckinClient/checkin-core.js"); if (CurrentWorkflow == null || CurrentCheckInState == null) { NavigateToHomePage(); } else { if (!Page.IsPostBack) { ClearSelection(); CheckInPerson person = null; CheckInGroup group = null; person = CurrentCheckInState.CheckIn.Families.Where(f => f.Selected) .SelectMany(f => f.People.Where(p => p.Selected)) .FirstOrDefault(); if (person != null) { group = person.GroupTypes.Where(t => t.Selected) .SelectMany(t => t.Groups.Where(g => g.Selected)) .FirstOrDefault(); } if (group == null) { GoBack(); } lTitle.Text = person.ToString(); lSubTitle.Text = group.ToString(); var availLocations = group.Locations.Where(l => !l.ExcludedByFilter).ToList(); if (availLocations.Count == 1) { if (UserBackedUp) { GoBack(); } else { availLocations.FirstOrDefault().Selected = true; ProcessSelection(); } } else { rSelection.DataSource = availLocations; rSelection.DataBind(); } } } }
/// <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, 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)) { var memberGroupIds = new GroupMemberService(rockContext) .Queryable() .AsNoTracking() .Where(m => m.GroupMemberStatus == GroupMemberStatus.Active && m.PersonId == person.Person.Id) .Select(m => m.GroupId) .ToList(); 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.Where(g => g.IsCheckInActive)) { bool validGroup = true; var configuredKioskGroup = checkInState.ConfiguredGroups; if (configuredKioskGroup.Any()) { validGroup = configuredKioskGroup.Contains(kioskGroup.Group.Id); } if (validGroup && groupType.GroupType.AttendanceRule == AttendanceRule.AlreadyBelongs) { validGroup = memberGroupIds.Contains(kioskGroup.Group.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); } return(false); }
private void SelectLocation(CheckInPerson person, CheckInGroupType gt, CheckInGroup g, CheckInLocation l) { l.Selected = !l.Selected; if (l.Selected) { person.Selected = true; gt.Selected = true; g.Selected = true; foreach (var s in l.Schedules) { s.Selected = true; } } else { foreach (var s in l.Schedules) { s.Selected = false; } if (!g.Locations.Where(_l => _l.Selected).Any()) { g.Selected = false; } if (!gt.Groups.Where(_g => _g.Selected).Any()) { gt.Selected = false; } if (!person.GroupTypes.Where(_gt => _gt.Selected).Any()) { person.Selected = false; } } SaveState(); BuildMemberCards(); }
/// <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, WorkflowAction action, Object entity, out List <string> errorMessages) { var checkInState = GetCheckInState(entity, out errorMessages); if (checkInState != null) { var groupService = new GroupService(rockContext); foreach (var family in checkInState.CheckIn.Families.Where(f => f.Selected).ToList()) { foreach (var person in family.People) { var checkinGroupTypes = new List <CheckInGroupType>(); foreach (var id in checkInState.ConfiguredGroupTypes) { var cgt = new CheckInGroupType(); cgt.GroupType = GroupTypeCache.Get(id); checkinGroupTypes.Add(cgt); var groups = groupService.Queryable().Where(g => g.GroupTypeId == id); List <CheckInGroup> checkinGroups = new List <CheckInGroup>(); foreach (var group in groups) { var cg = new CheckInGroup(); cg.Group = group; group.LoadAttributes(); checkinGroups.Add(cg); var groupLocations = group.GroupLocations; List <CheckInLocation> checkinLocations = new List <CheckInLocation>(); foreach (var groupLocation in groupLocations) { var cl = new CheckInLocation(); cl.Location = groupLocation.Location; checkinLocations.Add(cl); var schedules = new List <CheckInSchedule>(); foreach (var schedule in groupLocation.Schedules.ToList()) { if (!schedule.WasCheckInActive(RockDateTime.Now)) { continue; } var cs = new CheckInSchedule(); cs.Schedule = schedule; schedules.Add(cs); } cl.Schedules = schedules; } cg.Locations = checkinLocations; } cgt.Groups = checkinGroups; } person.GroupTypes = checkinGroupTypes; } } 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, WorkflowAction action, Object entity, out List <string> errorMessages) { var checkInState = GetCheckInState(entity, out errorMessages); if (checkInState != null) { var groupService = new GroupService(rockContext); foreach (var family in checkInState.CheckIn.Families.Where(f => f.Selected).ToList()) { foreach (var person in family.People) { var checkinGroupTypes = new List <CheckInGroupType>(); foreach (var id in checkInState.ConfiguredGroupTypes) { var cgt = new CheckInGroupType(); cgt.GroupType = GroupTypeCache.Get(id); checkinGroupTypes.Add(cgt); var groups = groupService.Queryable().Where(g => g.GroupTypeId == id); List <CheckInGroup> checkinGroups = new List <CheckInGroup>(); foreach (var group in groups.Where(g => g.IsActive)) { var cg = new CheckInGroup(); cg.Group = group; group.LoadAttributes(); checkinGroups.Add(cg); var groupLocations = group.GroupLocations; List <CheckInLocation> checkinLocations = new List <CheckInLocation>(); foreach (var groupLocation in groupLocations) { var cl = new CheckInLocation(); cl.Location = groupLocation.Location; cl.CampusId = cl.Location.CampusId; checkinLocations.Add(cl); var schedules = new List <CheckInSchedule>(); foreach (var schedule in groupLocation.Schedules) { var cs = new CheckInSchedule(); cs.Schedule = schedule; schedules.Add(cs); } cl.Schedules = schedules; } cg.Locations = checkinLocations; } cgt.Groups = checkinGroups; } person.GroupTypes = checkinGroupTypes; } } return(true); } return(false); }
private void FilterLocationSchedules(CheckInGroup checkinGroup, List <CheckInLocation> locationList, List <CheckInSchedule> selectedSchedules, bool remove) { // Check the locations in the sorted or for the first one that has all the schedules available and use it if it exists. var locationForSchedules = locationList.Where(l => l.Schedules.Select(s => s.Schedule.Id).Intersect(selectedSchedules.Select(ss => ss.Schedule.Id)).Count() == selectedSchedules.Count).FirstOrDefault(); if (locationForSchedules != null) { locationList.Remove(locationForSchedules); foreach (var location in locationList) { if (remove) { checkinGroup.Locations.Remove(location); } else { location.ExcludedByFilter = true; } } return; } // There is no location that has all of the selected schedules for this person so we need to choose each schedule location in the sorted list order. // As a practical matter this will probably not be more than two. The choosing will be done by removing the schedules from the locations that are not needed. foreach (var selectedSchedule in selectedSchedules) { var foundFirstMatch = false; foreach (var location in locationList) { if (location.Schedules.Contains(selectedSchedule)) { if (foundFirstMatch) { foreach (var checkinLocation in checkinGroup.Locations) { if (remove) { checkinLocation.Schedules.Remove(selectedSchedule); } else { selectedSchedule.ExcludedByFilter = true; } } } else { foundFirstMatch = 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, Model.WorkflowAction action, Object entity, out List <string> errorMessages) { var checkInState = GetCheckInState(entity, out errorMessages); if (checkInState != null) { bool loadAll = false; if (bool.TryParse(GetAttributeValue(action, "LoadAll"), out loadAll) && loadAll) { loadAll = true; } foreach (var family in checkInState.CheckIn.Families.Where(f => f.Selected).ToList()) { foreach (var person in family.People.Where(p => p.Selected || loadAll).ToList()) { foreach (var groupType in person.GroupTypes.Where(g => g.Selected || loadAll).ToList()) { var kioskGroupType = checkInState.Kiosk.FilteredGroupTypes(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.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); } return(false); }
/// <summary> /// Handles the Delete event of the gPersonList control. /// </summary> /// <param name="sender">The source of the event.</param> /// <param name="e">The <see cref="RowEventArgs" /> instance containing the event data.</param> protected void gPersonList_Delete(object sender, RowEventArgs e) { var dataKeyValues = gPersonList.DataKeys[e.RowIndex].Values; var personId = Convert.ToInt32(dataKeyValues["PersonId"]); var locationId = Convert.ToInt32(dataKeyValues["LocationId"]); var scheduleId = Convert.ToInt32(dataKeyValues["ScheduleId"]); var selectedPerson = CurrentCheckInState.CheckIn.Families.Where(f => f.Selected).FirstOrDefault() .People.Where(p => p.Person.Id == personId).FirstOrDefault(); var selectedGroups = selectedPerson.GroupTypes.Where(gt => gt.Selected) .SelectMany(gt => gt.Groups.Where(g => g.Selected)); CheckInGroup selectedGroup = selectedGroups.Where(g => g.Selected && g.Locations.Any(l => l.Location.Id == locationId && l.Schedules.Any(s => s.Schedule.Id == scheduleId))).FirstOrDefault(); CheckInLocation selectedLocation = selectedGroup.Locations.Where(l => l.Selected && l.Location.Id == locationId && l.Schedules.Any(s => s.Schedule.Id == scheduleId)).FirstOrDefault(); CheckInSchedule selectedSchedule = selectedLocation.Schedules.Where(s => s.Selected && s.Schedule.Id == scheduleId).FirstOrDefault(); selectedSchedule.Selected = false; selectedSchedule.PreSelected = false; // clear checkin rows without anything selected if (!selectedLocation.Schedules.Any(s => s.Selected)) { selectedLocation.Selected = false; selectedLocation.PreSelected = false; } if (!selectedGroup.Locations.Any(l => l.Selected)) { selectedGroup.Selected = false; selectedGroup.PreSelected = false; } if (!selectedGroups.Any()) { selectedPerson.Selected = false; selectedPerson.PreSelected = false; } BindGrid(); }
/// <summary> /// Executes the specified workflow. /// </summary> /// <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(Model.WorkflowAction action, Object entity, out List <string> errorMessages) { var checkInState = GetCheckInState(entity, out errorMessages); if (checkInState != null) { bool loadAll = false; if (bool.TryParse(GetAttributeValue(action, "LoadAll"), out loadAll) && loadAll) { loadAll = true; } foreach (var family in checkInState.CheckIn.Families.Where(f => f.Selected).ToList()) { foreach (var person in family.People.Where(p => p.Selected || loadAll).ToList()) { foreach (var groupType in person.GroupTypes.Where(g => g.Selected || loadAll).ToList()) { var kioskGroupType = checkInState.Kiosk.FilteredGroupTypes(checkInState.ConfiguredGroupTypes).Where(g => g.GroupType.Id == groupType.GroupType.Id).FirstOrDefault(); if (kioskGroupType != null) { foreach (var kioskGroup in kioskGroupType.KioskGroups) { if (!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); } return(false); }
private void FilterLocations(CheckInGroup checkinGroup, List <CheckInLocation> locationList, bool remove) { var foundFirstMatch = false; foreach (var location in locationList) { if (foundFirstMatch) { if (remove) { checkinGroup.Locations.Remove(location); } else { location.ExcludedByFilter = true; } } else { foundFirstMatch = true; } } }
/// <summary> /// Executes the specified workflow. /// </summary> /// <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(Model.WorkflowAction action, Object entity, out List <string> errorMessages) { var checkInState = GetCheckInState(entity, out errorMessages); if (checkInState != null) { var family = checkInState.CheckIn.Families.Where(f => f.Selected).FirstOrDefault(); if (family != null) { foreach (var person in family.People.Where(f => f.Selected)) { char[] delimiter = { ',' }; if (person.GroupTypes.Any()) { CheckInGroupType groupType; if (person.GroupTypes.Count > 1) { // check grouptypes for a grade range var gradeFilter = person.GroupTypes.Where(gt => gt.GroupType.Attributes.ContainsKey("GradeRange")).Select(g => new { GroupType = g, GradeRange = g.GroupType.GetAttributeValue("GradeRange").Split(delimiter, StringSplitOptions.None) .Select(av => av.AsType <int?>()) }).ToList(); // #TODO: Test the upper value of grade and age ranges var groupTypeMatchGrade = gradeFilter.Aggregate((x, y) => Math.Abs(Convert.ToDouble(x.GradeRange.First() - person.Person.Grade)) < Math.Abs(Convert.ToDouble(y.GradeRange.First() - person.Person.Grade)) ? x : y) .GroupType; // check grouptypes for an age range var ageFilter = person.GroupTypes.Where(g => g.GroupType.Attributes.ContainsKey("AgeRange")).Select(g => new { GroupType = g, AgeRange = g.GroupType.GetAttributeValue("AgeRange").Split(delimiter, StringSplitOptions.None) .Select(av => av.AsType <double?>()) }).ToList(); var groupTypeMatchAge = ageFilter.Aggregate((x, y) => Math.Abs(Convert.ToDouble(x.AgeRange.First() - person.Person.Age)) < Math.Abs(Convert.ToDouble(y.AgeRange.First() - person.Person.Age)) ? x : y) .GroupType; groupType = groupTypeMatchGrade ?? groupTypeMatchAge; } else { // only one grouptype is available groupType = person.GroupTypes.FirstOrDefault(); } if (groupType != null && groupType.Groups.Any()) { groupType.PreSelected = true; groupType.Selected = true; var group = groupType.Groups.Where(g => g.Selected).FirstOrDefault(); if (group == null && groupType.Groups.Any()) { // check groups by grade var gradeGroups = groupType.Groups.Where(g => g.Group.Attributes.ContainsKey("GradeRange")).Select(g => new { Group = g, GradeRange = g.Group.GetAttributeValue("GradeRange").Split(delimiter, StringSplitOptions.None) .Select(av => av.AsType <int?>()) }).ToList(); CheckInGroup groupMatchGrade = null; if (gradeGroups.Count > 0) { groupMatchGrade = gradeGroups.Aggregate((x, y) => Math.Abs(Convert.ToDouble(x.GradeRange.First() - person.Person.Grade)) < Math.Abs(Convert.ToDouble(y.GradeRange.First() - person.Person.Grade)) ? x : y) .Group; } // check groups by age var ageGroups = groupType.Groups.Where(g => g.Group.Attributes.ContainsKey("AgeRange")).Select(g => new { Group = g, AgeRange = g.Group.GetAttributeValue("AgeRange").Split(delimiter, StringSplitOptions.None) .Select(av => av.AsType <double?>()) }).ToList(); CheckInGroup groupMatchAge = null; if (ageGroups.Count > 0) { groupMatchAge = ageGroups.Aggregate((x, y) => Math.Abs(Convert.ToDouble(x.AgeRange.First() - person.Person.Age)) < Math.Abs(Convert.ToDouble(y.AgeRange.First() - person.Person.Age)) ? x : y) .Group; } group = groupMatchGrade ?? groupMatchAge ?? groupType.Groups.FirstOrDefault(); } if (group != null && group.Locations.Any()) { group.PreSelected = true; group.Selected = true; var location = group.Locations.Where(l => l.Selected).FirstOrDefault(); if (location == null) { // this works when a group is only meeting at one location per campus int primaryGroupLocationId = new GroupLocationService().Queryable().Where(gl => gl.GroupId == group.Group.Id) .Select(gl => gl.LocationId).ToList().FirstOrDefault(); location = group.Locations.Where(l => l.Location.Id == primaryGroupLocationId).FirstOrDefault(); } if (location != null && location.Schedules.Any()) { location.PreSelected = true; location.Selected = true; var schedule = location.Schedules.Where(s => s.Selected).FirstOrDefault(); if (schedule == null) { schedule = location.Schedules.FirstOrDefault(); schedule.PreSelected = true; schedule.Selected = true; } } } } } } } return(true); } return(false); }
/// <summary> /// Enforces the strict location threshold by removing attendances that would have ended up going into full location+schedules. /// Note: The is also checked earlier in the check-in process, so this catches ones that might have just gotten full in the last few seconds. /// </summary> /// <param name="action">The action.</param> /// <param name="checkInState">State of the check in.</param> /// <param name="attendanceService">The attendance service.</param> /// <param name="currentOccurrences">The current occurrences.</param> /// <param name="person">The person.</param> /// <param name="group">The group.</param> /// <param name="location">The location.</param> /// <param name="schedule">The schedule.</param> /// <param name="startDateTime">The start date time.</param> private void EnforceStrictLocationThreshold(WorkflowAction action, CheckInState checkInState, AttendanceService attendanceService, List <OccurrenceRecord> currentOccurrences, CheckInPerson person, CheckInGroup group, CheckInLocation location, CheckInSchedule schedule, DateTime startDateTime) { var thresHold = location.Location.SoftRoomThreshold.Value; if (checkInState.ManagerLoggedIn && location.Location.FirmRoomThreshold.HasValue && location.Location.FirmRoomThreshold.Value > location.Location.SoftRoomThreshold.Value) { thresHold = location.Location.FirmRoomThreshold.Value; } var currentOccurrence = GetCurrentOccurrence(currentOccurrences, location, schedule, startDateTime.Date); // The totalAttended is the number of people still checked in (not people who have been checked-out) // not counting the current person who may already be checked in, // + the number of people we have checked in so far (but haven't been saved yet). var attendanceQry = attendanceService.GetByDateOnLocationAndSchedule(startDateTime.Date, location.Location.Id, schedule.Schedule.Id) .AsNoTracking() .Where(a => a.EndDateTime == null); // Only process if the current person is NOT already checked-in to this location and schedule if (!attendanceQry.Where(a => a.PersonAlias.PersonId == person.Person.Id).Any()) { var totalAttended = attendanceQry.Count() + (currentOccurrence == null ? 0 : currentOccurrence.Count); // If over capacity, remove the schedule and add a warning message. if (totalAttended >= thresHold) { // Remove the schedule since the location was full for this schedule. location.Schedules.Remove(schedule); var message = new CheckInMessage() { MessageType = MessageType.Warning }; var mergeFields = Lava.LavaHelper.GetCommonMergeFields(null); mergeFields.Add(MergeFieldKey.Person, person.Person); mergeFields.Add(MergeFieldKey.Group, group.Group); mergeFields.Add(MergeFieldKey.Location, location.Location); mergeFields.Add(MergeFieldKey.Schedule, schedule.Schedule); message.MessageText = GetAttributeValue(action, AttributeKey.NotCheckedInMessageFormat).ResolveMergeFields(mergeFields); // Now add it to the check-in state message list for others to see. checkInState.Messages.Add(message); return; } else { // Keep track of anyone who was checked in so far. if (currentOccurrence == null) { currentOccurrence = new OccurrenceRecord() { Date = startDateTime.Date, LocationId = location.Location.Id, ScheduleId = schedule.Schedule.Id }; currentOccurrences.Add(currentOccurrence); } currentOccurrence.Count += 1; } } }
protected override void OnLoad(EventArgs e) { base.OnLoad(e); RockPage.AddScriptLink("~/Scripts/CheckinClient/checkin-core.js"); if (CurrentWorkflow == null || CurrentCheckInState == null) { NavigateToHomePage(); } else { if (!Page.IsPostBack) { ClearSelection(); var personSchedules = new List <CheckInSchedule>(); var distinctSchedules = new List <CheckInSchedule>(); if (CurrentCheckInType != null && CurrentCheckInType.TypeOfCheckin == TypeOfCheckin.Family) { CheckInFamily family = CurrentCheckInState.CheckIn.CurrentFamily; if (family != null) { foreach (var schedule in family.GetPeople(true).SelectMany(p => p.PossibleSchedules).ToList()) { personSchedules.Add(schedule); if (!distinctSchedules.Any(s => s.Schedule.Id == schedule.Schedule.Id)) { distinctSchedules.Add(schedule); } } } else { GoBack(); } lTitle.Text = GetTitleText(); lbSelect.Text = "Next"; lbSelect.Attributes.Add("data-loading-text", "Loading..."); } else { CheckInPerson person = CurrentCheckInState.CheckIn.Families.Where(f => f.Selected).SelectMany(f => f.People.Where(p => p.Selected)).FirstOrDefault(); CheckInGroup group = null; CheckInLocation location = null; if (person != null) { group = person.GroupTypes.Where(t => t.Selected).SelectMany(t => t.Groups.Where(g => g.Selected)).FirstOrDefault(); if (group != null) { location = group.Locations.Where(l => l.Selected).FirstOrDefault(); } } if (location == null) { GoBack(); } lTitle.Text = GetTitleText(); lbSelect.Text = "Check In"; lbSelect.Attributes.Add("data-loading-text", "Printing..."); personSchedules = location.Schedules.Where(s => !s.ExcludedByFilter).ToList(); distinctSchedules = personSchedules; } lCaption.Text = GetAttributeValue(AttributeKey.Caption); if (distinctSchedules.Count == 1) { personSchedules.ForEach(s => s.Selected = true); ProcessSelection(maWarning); } else { string script = string.Format(@" <script> function GetTimeSelection() {{ var ids = ''; $('div.checkin-timelist button.active').each( function() {{ ids += $(this).attr('schedule-id') + ','; }}); if (ids == '') {{ bootbox.alert('Please select at least one time'); return false; }} else {{ $('#{0}').button('loading') $('#{1}').val(ids); return true; }} }} </script> ", lbSelect.ClientID, hfTimes.ClientID); Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "SelectTime", script); rSelection.DataSource = distinctSchedules .OrderBy(s => s.StartTime.Value.TimeOfDay) .ThenBy(s => s.Schedule.Name) .ToList(); rSelection.DataBind(); } } } }
/// <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); } 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 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 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) { 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); }
private CheckInStatus DehydrateStatus(CheckInStatus status) { var checkinstatus = new CheckInStatus { SearchType = status.SearchType, SearchValue = status.SearchValue, Families = new List <CheckInFamily>() }; foreach (var family in status.Families) { var checkinFamily = new CheckInFamily() { Selected = family.Selected, Caption = family.Caption, SubCaption = family.SubCaption, AttendanceIds = family.AttendanceIds, FirstNames = family.FirstNames, Group = new Group() { Id = family.Group.Id }, People = new List <CheckInPerson>() }; checkinstatus.Families.Add(checkinFamily); foreach (var person in family.People) { var checkInPerson = new CheckInPerson { GroupTypes = new List <CheckInGroupType>(), FirstTime = person.FirstTime, FamilyMember = person.FamilyMember, Person = new Person { Id = person.Person.Id }, PreSelected = person.PreSelected, SecurityCode = person.SecurityCode, ExcludedByFilter = person.ExcludedByFilter, Selected = person.Selected }; checkinFamily.People.Add(checkInPerson); foreach (var grouptype in person.GroupTypes) { var checkinGroupType = new CheckInGroupType { GroupType = grouptype.GroupType, Selected = grouptype.Selected, PreSelected = grouptype.PreSelected, ExcludedByFilter = grouptype.ExcludedByFilter, Groups = new List <CheckInGroup>() }; checkInPerson.GroupTypes.Add(checkinGroupType); foreach (var group in grouptype.Groups) { var checkinGroup = new CheckInGroup { Group = new Group { Id = group.Group.Id }, Selected = group.Selected, PreSelected = group.PreSelected, ExcludedByFilter = group.ExcludedByFilter, Locations = new List <CheckInLocation>() }; checkinGroupType.Groups.Add(group); foreach (var location in group.Locations) { var checkinLocation = new CheckInLocation { Location = new Location { Id = location.Location.Id }, Selected = location.Selected, PreSelected = location.PreSelected, ExcludedByFilter = location.ExcludedByFilter, CampusId = location.CampusId, Schedules = new List <CheckInSchedule>() }; checkinGroup.Locations.Add(checkinLocation); foreach (var schedule in location.Schedules) { var checkinSchedule = new CheckInSchedule { Schedule = new Schedule { Id = schedule.Schedule.Id }, Selected = schedule.Selected, PreSelected = schedule.PreSelected, ExcludedByFilter = schedule.ExcludedByFilter, CampusId = schedule.CampusId, StartTime = schedule.StartTime }; checkinLocation.Schedules.Add(checkinSchedule); } } } } } } return(checkinstatus); }
protected override void OnLoad(EventArgs e) { base.OnLoad(e); RockPage.AddScriptLink("~/Scripts/iscroll.js"); RockPage.AddScriptLink("~/Scripts/CheckinClient/checkin-core.js"); if (CurrentWorkflow == null || CurrentCheckInState == null) { NavigateToHomePage(); } else { if (!Page.IsPostBack) { ClearSelection(); CheckInPerson person = null; CheckInGroup group = null; CheckInLocation location = null; person = CurrentCheckInState.CheckIn.Families.Where(f => f.Selected) .SelectMany(f => f.People.Where(p => p.Selected)) .FirstOrDefault(); if (person != null) { group = person.GroupTypes.Where(t => t.Selected) .SelectMany(t => t.Groups.Where(g => g.Selected)) .FirstOrDefault(); if (group != null) { location = group.Locations.Where(l => l.Selected) .FirstOrDefault(); } } if (location == null) { GoBack(); } lTitle.Text = person.ToString(); lSubTitle.Text = string.Format("{0} - {1}", group.ToString(), location.ToString()); var availSchedules = location.Schedules.Where(s => !s.ExcludedByFilter).ToList(); if (availSchedules.Count == 1) { availSchedules.FirstOrDefault().Selected = true; ProcessSelection(maWarning); } else { string script = string.Format(@" <script> function GetTimeSelection() {{ var ids = ''; $('div.checkin-timelist button.active').each( function() {{ ids += $(this).attr('schedule-id') + ','; }}); if (ids == '') {{ bootbox.alert('Please select at least one time'); return false; }} else {{ $('#{0}').button('loading') $('#{1}').val(ids); return true; }} }} </script> ", lbSelect.ClientID, hfTimes.ClientID); Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "SelectTime", script); rSelection.DataSource = availSchedules .OrderBy(s => s.StartTime.Value.TimeOfDay) .ThenBy(s => s.Schedule.Name) .ToList(); rSelection.DataBind(); } } } }
private void addLabel(CheckInLabel checkInLabel, CheckInState checkInState, CheckInGroupType groupType, CheckInGroup group, RockContext rockContext) { var PrinterIPs = new Dictionary <int, string>(); if (checkInLabel.PrintTo == PrintTo.Default) { checkInLabel.PrintTo = groupType.GroupType.AttendancePrintTo; } else if (checkInLabel.PrintTo == PrintTo.Location && group.Locations.Any()) { var deviceId = group.Locations.FirstOrDefault().Location.PrinterDeviceId; if (deviceId != null) { checkInLabel.PrinterDeviceId = deviceId; } } else { var device = checkInState.Kiosk.Device; if (device != null) { checkInLabel.PrinterDeviceId = device.PrinterDeviceId; } } if (checkInLabel.PrinterDeviceId.HasValue) { if (PrinterIPs.ContainsKey(checkInLabel.PrinterDeviceId.Value)) { checkInLabel.PrinterAddress = PrinterIPs[checkInLabel.PrinterDeviceId.Value]; } else { var printerDevice = new DeviceService(rockContext).Get(checkInLabel.PrinterDeviceId.Value); if (printerDevice != null) { PrinterIPs.Add(printerDevice.Id, printerDevice.IPAddress); checkInLabel.PrinterAddress = printerDevice.IPAddress; } } } groupType.Labels.Insert(0, checkInLabel); }
/// <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); }