private void FilterLocationList(CheckInGroupType checkInGroupType, bool remove, CheckinConfigurationHelper.LocationSelectionStrategy attributeLocationSelectionStrategy, List <CheckInSchedule> selectedSchedules) { // Order the list var checkinGroups = checkInGroupType.Groups.OrderBy(g => g.Group.Order).ToList(); foreach (var checkinGroup in checkinGroups) { // Get a list of locations that have not reached their threshold. var locationListQuery = checkinGroup.Locations .Where(l => l.Location.SoftRoomThreshold == null || KioskLocationAttendance.Get(l.Location.Id).CurrentCount <= l.Location.SoftRoomThreshold.Value); List <CheckInLocation> locationList = new List <CheckInLocation>(); if (attributeLocationSelectionStrategy == CheckinConfigurationHelper.LocationSelectionStrategy.Balance) { locationList = locationListQuery.OrderBy(l => KioskLocationAttendance.Get(l.Location.Id).CurrentCount).ToList(); } else if (attributeLocationSelectionStrategy == CheckinConfigurationHelper.LocationSelectionStrategy.FillInOrder) { locationList = locationListQuery.OrderBy(l => l.Order).ToList(); } if (selectedSchedules.Count() == 1) { // If we only have one schedule then we can just remove the other locations and not care about the schedules. FilterLocations(checkinGroup, locationList, remove); } else { // Now we have to care about schedules. FilterLocationSchedules(checkinGroup, locationList, selectedSchedules, remove); } } }
/// <summary> /// Handles the ItemDataBound event of the rLocations control. /// </summary> /// <param name="sender">The source of the event.</param> /// <param name="e">The <see cref="System.Web.UI.WebControls.RepeaterItemEventArgs"/> instance containing the event data.</param> protected void rLocations_ItemDataBound(object sender, System.Web.UI.WebControls.RepeaterItemEventArgs e) { object locationDataItem = e.Item.DataItem; if (locationDataItem != null) { var lbOpen = e.Item.FindControl("lbOpen") as LinkButton; var lbClose = e.Item.FindControl("lbClose") as LinkButton; var isActive = ( bool )locationDataItem.GetPropertyValue("IsActive"); if (isActive) { lbClose.RemoveCssClass("btn-danger"); lbClose.RemoveCssClass("active"); lbOpen.AddCssClass("btn-success"); lbOpen.AddCssClass("active"); } else { lbOpen.RemoveCssClass("btn-success"); lbOpen.RemoveCssClass("active"); lbClose.AddCssClass("btn-danger"); lbClose.AddCssClass("active"); } var lLocationName = e.Item.FindControl("lLocationName") as Literal; lLocationName.Text = locationDataItem.GetPropertyValue("Name") as string; var lLocationCount = e.Item.FindControl("lLocationCount") as Literal; lLocationCount.Text = KioskLocationAttendance.Get(( int )locationDataItem.GetPropertyValue("LocationId")).CurrentCount.ToString(); } }
/// <summary> /// Formats the count. /// </summary> /// <param name="locationId">The location id.</param> /// <returns></returns> protected string FormatCount(int locationId) { string currentCountFormat = GetAttributeValue("CurrentCountFormat"); if (!string.IsNullOrWhiteSpace(currentCountFormat) && currentCountFormat.Contains("{0}")) { return(string.Format(" <span class='checkin-sub-title'>{0}</span>", string.Format(currentCountFormat, KioskLocationAttendance.Read(locationId).CurrentCount))); } return(string.Empty); }
/// <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) { 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) { foreach (var group in groupType.Groups) { foreach (var location in group.Locations.ToList()) { if (location.Location.SoftRoomThreshold.HasValue) { var locAttendance = KioskLocationAttendance.Get(location.Location.Id); if (locAttendance != null && !locAttendance.DistinctPersonIds.Contains(person.Person.Id) && location.Location.SoftRoomThreshold.Value <= locAttendance.CurrentCount) { if (remove) { group.Locations.Remove(location); } else { location.ExcludedByFilter = true; } } } } } } } } return(true); } return(false); }
/// <summary> /// Get the option text. /// </summary> private string GetOptionText(CheckInPersonSummary option) { var format = GetAttributeValue(AttributeKey.OptionFormat); var mergeFields = new Dictionary <string, object> { { "GroupType", option.GroupType }, { "Group", option.Group }, { "Location", option.Location }, { "Schedule", option.Schedule }, { "DisplayLocationCount", CurrentCheckInState.CheckInType.DisplayLocationCount }, { "LocationCount", KioskLocationAttendance.Get(option.Location.Location.Id).CurrentCount } }; var optionText = format.ResolveMergeFields(mergeFields); return(optionText); }
/// <summary> /// Reads the attendance cache by schedule. /// </summary> /// <param name="locationId">The location identifier.</param> /// <param name="scheduleId">The schedule identifier.</param> /// <returns></returns> public static int ReadAttendanceBySchedule(int locationId, int?scheduleId) { var attendanceCount = 0; var attendanceCache = KioskLocationAttendance.Get(locationId); if (attendanceCache != null) { if (scheduleId != null) { attendanceCount += attendanceCache.Groups.SelectMany(g => g.Schedules).Where(s => s.ScheduleId == (int)scheduleId).Sum(s => s.CurrentCount); } else { attendanceCount = attendanceCache.CurrentCount; } } return(attendanceCount); }
/// <summary> /// Refreshes the view. /// </summary> private void RefreshView() { hfRefreshTimerSeconds.Value = "10"; ManagerLoggedIn = false; pnlActive.Visible = false; pnlManagerLogin.Visible = false; pnlManager.Visible = false; btnManager.Visible = true; lblActiveWhen.Text = string.Empty; if (CurrentKioskId == null || CurrentLocationId == null) { NavigateToParentPage(); return; } pnlActive.Visible = true; var currentPeople = KioskLocationAttendance.Read(CurrentLocationId.Value); var mergeFields = Rock.Lava.LavaHelper.GetCommonMergeFields(this.RockPage, this.CurrentPerson); // add link to detail page Dictionary <string, object> linkedPages = new Dictionary <string, object>(); mergeFields.Add("PersonIds", currentPeople.DistinctPersonIds); mergeFields.Add("LocationCount", currentPeople.CurrentCount); mergeFields.Add("CurrentKioskId", CurrentKioskId); mergeFields.Add("CurrentLocationId", CurrentLocationId); lOutput.Text = GetAttributeValue("LavaTemplate").ResolveMergeFields(mergeFields); // show debug info if (GetAttributeValue("EnableDebug").AsBoolean() && IsUserAuthorized(Authorization.EDIT)) { lDebug.Visible = true; lDebug.Text = mergeFields.lavaDebugInfo(); } }
/// <summary> /// Handles the ItemDataBound event of the rLocations control. /// </summary> /// <param name="sender">The source of the event.</param> /// <param name="e">The <see cref="System.Web.UI.WebControls.RepeaterItemEventArgs"/> instance containing the event data.</param> protected void rLocations_ItemDataBound(object sender, System.Web.UI.WebControls.RepeaterItemEventArgs e) { var locationGridItem = e.Item.DataItem as LocationGridItem; if (locationGridItem != null) { if (hfAllowOpenClose.Value.AsBoolean()) { var lbOpen = e.Item.FindControl("lbOpen") as LinkButton; var lbClose = e.Item.FindControl("lbClose") as LinkButton; var isActive = locationGridItem.IsActive; if (isActive) { lbClose.RemoveCssClass("btn-danger"); lbClose.RemoveCssClass("active"); lbOpen.AddCssClass("btn-success"); lbOpen.AddCssClass("active"); } else { lbOpen.RemoveCssClass("btn-success"); lbOpen.RemoveCssClass("active"); lbClose.AddCssClass("btn-danger"); lbClose.AddCssClass("active"); } } else { var divLocationToggle = e.Item.FindControl("divLocationToggle") as HtmlGenericControl; divLocationToggle.Visible = false; } var lLocationName = e.Item.FindControl("lLocationName") as Literal; lLocationName.Text = locationGridItem.Name; var lLocationCount = e.Item.FindControl("lLocationCount") as Literal; lLocationCount.Text = KioskLocationAttendance.Get(locationGridItem.LocationId).CurrentCount.ToString(); } }
/// <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) { Guid checkinGroupAttributeGuid = GetAttributeValue(action, "CheckinGroupAttribute").AsGuid(); if (checkinGroupAttributeGuid == Guid.Empty) { throw new Exception("CheckInGroupAttribute not set. Set attribute to continue."); } string checkinGroupAttributeKey = AttributeCache.Read(checkinGroupAttributeGuid, rockContext).Key; string sessionAttributeKey = GetAttributeValue(action, "SessionAttributeKey"); AttendanceCode attendanceCode = null; DateTime startDateTime = RockDateTime.Now; bool reuseCodeForFamily = checkInState.CheckInType != null && checkInState.CheckInType.ReuseSameCode; int securityCodeLength = checkInState.CheckInType != null ? checkInState.CheckInType.SecurityCodeAlphaNumericLength : 3; AttendanceCodeService attendanceCodeService = new AttendanceCodeService(rockContext); AttendanceService attendanceService = new AttendanceService(rockContext); GroupMemberService groupMemberService = new GroupMemberService(rockContext); PersonAliasService personAliasService = new PersonAliasService(rockContext); GroupService groupService = new GroupService(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)) { var referenceGroupGuid = group.Group.GetAttributeValue(checkinGroupAttributeKey).AsGuid(); var referenceGroup = groupService.Get(referenceGroupGuid); if (referenceGroup == null) { group.Selected = false; continue; } GroupMember groupMember = groupMemberService.GetByGroupIdAndPersonId(referenceGroup.Id, person.Person.Id).FirstOrDefault(); if (groupMember == null) { group.Selected = false; continue; } groupMember.LoadAttributes(); int sessions = groupMember.GetAttributeValue(sessionAttributeKey).AsInteger(); foreach (var location in group.GetLocations(true)) { foreach (var schedule in location.GetSchedules(true)) { if (sessions == 0) { continue; } // Only create one attendance record per day for each person/schedule/group/location var attendance = attendanceService.Get(startDateTime, location.Location.Id, schedule.Schedule.Id, group.Group.Id, person.Person.Id); if (attendance == null) { var primaryAlias = personAliasService.GetPrimaryAlias(person.Person.Id); if (primaryAlias != null) { attendance = rockContext.Attendances.Create(); attendance.LocationId = location.Location.Id; attendance.CampusId = location.CampusId; attendance.ScheduleId = schedule.Schedule.Id; attendance.GroupId = group.Group.Id; attendance.PersonAlias = primaryAlias; attendance.PersonAliasId = primaryAlias.Id; attendance.DeviceId = checkInState.Kiosk.Device.Id; attendance.SearchTypeValueId = checkInState.CheckIn.SearchType.Id; attendanceService.Add(attendance); } //decrement sessions and save sessions--; groupMember.SetAttributeValue(sessionAttributeKey, sessions); groupMember.SaveAttributeValues(); } else { foreach (var cPerson in checkInState.CheckIn.Families.SelectMany(f => f.People)) { cPerson.Selected = false; cPerson.GroupTypes.ForEach(gt => gt.Selected = false); } return(true); } attendance.AttendanceCodeId = attendanceCode.Id; attendance.StartDateTime = startDateTime; attendance.EndDateTime = null; attendance.DidAttend = true; KioskLocationAttendance.AddAttendance(attendance); } } } } } } rockContext.SaveChanges(); 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, 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)) { var closedGroupLocationIds = new AttendanceOccurrenceService(rockContext) .Queryable() .AsNoTracking() .Where(o => o.GroupId == group.Group.Id && o.OccurrenceDate == RockDateTime.Today) .WhereAttributeValue(rockContext, "rocks.kfs.OccurrenceClosed", "True") .Select(l => l.LocationId) .ToList(); var loadBalance = group.Group.GetAttributeValue("rocks.kfs.LoadBalanceLocations").AsBoolean(); if (loadBalance && loadAll) { group.Locations.Clear(); } var locationAttendance = new Dictionary <CheckInLocation, int>(); foreach (var kioskGroup in kioskGroupType.KioskGroups .Where(g => g.Group.Id == group.Group.Id && g.IsCheckInActive) .ToList()) { foreach (var kioskLocation in kioskGroup.KioskLocations.Where(l => l.IsCheckInActive && l.IsActiveAndNotFull && !closedGroupLocationIds.Contains(l.Location.Id))) { 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; checkInLocation.Order = kioskLocation.Order; locationAttendance.Add(checkInLocation, KioskLocationAttendance.Get(checkInLocation.Location.Id).CurrentCount); } } } if (loadBalance) { var sortedLocationAttendance = locationAttendance.ToList(); sortedLocationAttendance.Sort((x, y) => x.Key.Location.Name.CompareTo(y.Key.Location.Name)); sortedLocationAttendance.Sort((x, y) => x.Value.CompareTo(y.Value)); var order = 0; foreach (var checkInLocationPair in sortedLocationAttendance) { var checkInLocation = checkInLocationPair.Key; checkInLocation.Order = order; group.Locations.Add(checkInLocation); order++; } } else { group.Locations.AddRange(locationAttendance.Select(l => l.Key).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, Model.WorkflowAction action, Object entity, out List <string> errorMessages) { var checkInState = GetCheckInState(entity, out errorMessages); if (checkInState != null) { AttendanceCode attendanceCode = null; DateTime startDateTime = RockDateTime.Now; bool reuseCodeForFamily = GetAttributeValue(action, "ReuseCodeForFamily").AsBoolean(); int securityCodeLength = 3; if (!int.TryParse(GetAttributeValue(action, "SecurityCodeLength"), out securityCodeLength)) { securityCodeLength = 3; } var attendanceCodeService = new AttendanceCodeService(rockContext); var attendanceService = new AttendanceService(rockContext); var groupMemberService = new GroupMemberService(rockContext); var personAliasService = new PersonAliasService(rockContext); foreach (var family in checkInState.CheckIn.Families.Where(f => f.Selected)) { foreach (var person in family.People.Where(p => p.Selected)) { if (reuseCodeForFamily && attendanceCode != null) { person.SecurityCode = attendanceCode.Code; } else { attendanceCode = AttendanceCodeService.GetNew(securityCodeLength); person.SecurityCode = attendanceCode.Code; } foreach (var groupType in person.GroupTypes.Where(g => g.Selected)) { foreach (var group in groupType.Groups.Where(g => g.Selected)) { foreach (var location in group.Locations.Where(l => l.Selected)) { 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 schedule in location.Schedules.Where(s => s.Selected)) { // Only create one attendance record per day for each person/schedule/group/location var attendance = attendanceService.Get(startDateTime, location.Location.Id, schedule.Schedule.Id, group.Group.Id, person.Person.Id); if (attendance == null) { var primaryAlias = personAliasService.GetPrimaryAlias(person.Person.Id); if (primaryAlias != null) { attendance = rockContext.Attendances.Create(); attendance.Occurrence.LocationId = location.Location.Id; attendance.CampusId = location.CampusId; attendance.Occurrence.ScheduleId = schedule.Schedule.Id; attendance.Occurrence.GroupId = group.Group.Id; attendance.PersonAlias = primaryAlias; attendance.PersonAliasId = primaryAlias.Id; attendance.DeviceId = checkInState.Kiosk.Device.Id; attendance.SearchTypeValueId = checkInState.CheckIn.SearchType.Id; attendanceService.Add(attendance); } } attendance.AttendanceCodeId = attendanceCode.Id; attendance.StartDateTime = startDateTime; attendance.EndDateTime = null; attendance.DidAttend = false; KioskLocationAttendance.AddAttendance(attendance); } } } } } } rockContext.SaveChanges(); 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> public override bool Execute(RockContext rockContext, Model.WorkflowAction action, object entity, out List <string> errorMessages) { var checkInState = GetCheckInState(entity, out errorMessages); if (checkInState == null) { return(false); } AttendanceCode attendanceCode = null; bool reuseCodeForFamily = checkInState.CheckInType != null && checkInState.CheckInType.ReuseSameCode; int securityCodeAlphaNumericLength = checkInState.CheckInType != null ? checkInState.CheckInType.SecurityCodeAlphaNumericLength : 3; int securityCodeAlphaLength = checkInState.CheckInType != null ? checkInState.CheckInType.SecurityCodeAlphaLength : 0; int securityCodeNumericLength = checkInState.CheckInType != null ? checkInState.CheckInType.SecurityCodeNumericLength : 0; bool securityCodeNumericRandom = checkInState.CheckInType != null ? checkInState.CheckInType.SecurityCodeNumericRandom : true; bool enablePresence = checkInState.CheckInType != null && checkInState.CheckInType.EnablePresence; var attendanceCodeService = new AttendanceCodeService(rockContext); var attendanceService = new AttendanceService(rockContext); var groupMemberService = new GroupMemberService(rockContext); var personAliasService = new PersonAliasService(rockContext); var attendanceRecords = new List <Attendance>(); AttendanceCheckInSession attendanceCheckInSession = new AttendanceCheckInSession() { DeviceId = checkInState.DeviceId, ClientIpAddress = RockPage.GetClientIpAddress() }; checkInState.Messages.Clear(); var family = checkInState.CheckIn.CurrentFamily; if (family != null) { var currentOccurrences = new List <OccurrenceRecord>(); foreach (var person in family.GetPeople(true)) { if (reuseCodeForFamily && attendanceCode != null) { person.SecurityCode = attendanceCode.Code; } else { attendanceCode = AttendanceCodeService.GetNew(securityCodeAlphaNumericLength, securityCodeAlphaLength, securityCodeNumericLength, securityCodeNumericRandom); 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)) { bool isCheckedIntoLocation = false; foreach (var schedule in location.GetSchedules(true)) { var startDateTime = schedule.CampusCurrentDateTime; // If we're enforcing strict location thresholds, then before we create an attendance record // we need to check the location-schedule's current count. if (GetAttributeValue(action, AttributeKey.EnforceStrictLocationThreshold).AsBoolean() && location.Location.SoftRoomThreshold.HasValue) { EnforceStrictLocationThreshold(action, checkInState, attendanceService, currentOccurrences, person, group, location, schedule, startDateTime); } // Only create one attendance record per day for each person/schedule/group/location var attendance = attendanceService.Get(startDateTime, location.Location.Id, schedule.Schedule.Id, group.Group.Id, person.Person.Id); if (attendance == null) { var primaryAlias = personAliasService.GetPrimaryAlias(person.Person.Id); if (primaryAlias != null) { 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.PersonAlias = primaryAlias; } } attendance.AttendanceCheckInSession = attendanceCheckInSession; 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.CheckedOutByPersonAliasId = null; attendance.DidAttend = true; attendance.Note = group.Notes; attendance.IsFirstTime = person.FirstTime; /* * 7/16/2020 - JH * If EnablePresence is true for this Check-in configuration, it will be the responsibility of the room * attendants to mark a given Person as present, so do not set the 'Present..' property values below. * Otherwise, set the values to match those of the Check-in values: the Person checking them in will * have simultaneously marked them as present. * * Also, note that we sometimes reuse Attendance records (i.e. the Person was already checked into this * schedule/group/location, might have already been checked out, and also might have been previously * marked as present). In this case, the same 'Present..' rules apply, but we might need to go so far * as to null-out the previously set 'Present..' property values, hence the conditional operators below. */ attendance.PresentDateTime = enablePresence ? ( DateTime? )null : startDateTime; attendance.PresentByPersonAliasId = enablePresence ? null : checkInState.CheckIn.CheckedInByPersonAliasId; KioskLocationAttendance.AddAttendance(attendance); isCheckedIntoLocation = true; // Keep track of attendance (Ids) for use by other actions later in the workflow pipeline attendanceRecords.Add(attendance); } // If the person was NOT checked into the location for any schedule then remove the location if (!isCheckedIntoLocation) { group.Locations.Remove(location); } } } } } } if (checkInState.CheckInType.AchievementTypes.Any()) { // Get any achievements that were in-progress *prior* to adding these attendance records var configuredAchievementTypeIds = checkInState.CheckInType.AchievementTypes.Select(a => a.Id).ToList(); var attendanceRecordsPersonAliasIds = attendanceRecords.Where(a => a.PersonAliasId.HasValue).Select(a => a.PersonAliasId.Value).ToArray(); var successfullyCompletedAchievementsPriorToSaveChanges = GetSuccessfullyCompletedAchievementAttempts(rockContext, attendanceRecordsPersonAliasIds, configuredAchievementTypeIds); rockContext.SaveChanges(); AchievementAttemptService.AchievementAttemptWithPersonAlias[] achievementAttemptsAfterSaveChanges = GetAchievementAttemptsWithPersonAliasQuery(rockContext, attendanceRecordsPersonAliasIds, configuredAchievementTypeIds).AsNoTracking().ToArray(); checkInState.CheckIn.SuccessfullyCompletedAchievementsPriorToCheckin = successfullyCompletedAchievementsPriorToSaveChanges; checkInState.CheckIn.AchievementsStateAfterCheckin = achievementAttemptsAfterSaveChanges; } else { rockContext.SaveChanges(); } // Now that the records are persisted, take the Ids and save them to the temp CheckInFamliy object family.AttendanceIds = attendanceRecords.Select(a => a.Id).ToList(); family.AttendanceCheckinSessionGuid = attendanceCheckInSession.Guid; attendanceRecords = null; 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); } 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> /// Handles the Click event of the btnManager control. /// </summary> /// <param name="sender">The source of the event.</param> /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param> protected void btnManager_Click(object sender, EventArgs e) { ManagerLoggedIn = false; pnlNotActive.Visible = false; pnlNotActiveYet.Visible = false; pnlClosed.Visible = false; pnlActive.Visible = false; pnlManager.Visible = false; btnManager.Visible = false; tbPIN.Text = string.Empty; // Get room counts List <int> locations = new List <int>(); foreach (var groupType in CurrentCheckInState.Kiosk.FilteredGroupTypes(CurrentCheckInState.ConfiguredGroupTypes)) { var lUl = new HtmlGenericControl("ul"); lUl.AddCssClass("kioskmanager-count-locations"); phCounts.Controls.Add(lUl); foreach (var location in groupType.KioskGroups.SelectMany(g => g.KioskLocations).OrderBy(l => l.Location.Name).Distinct()) { if (!locations.Contains(location.Location.Id)) { locations.Add(location.Location.Id); var locationAttendance = KioskLocationAttendance.Get(location.Location.Id); if (locationAttendance != null) { var lLi = new HtmlGenericControl("li"); lUl.Controls.Add(lLi); lLi.InnerHtml = string.Format("<strong>{0}</strong>: {1}", locationAttendance.LocationName, locationAttendance.CurrentCount); var gUl = new HtmlGenericControl("ul"); gUl.AddCssClass("kioskmanager-count-groups"); lLi.Controls.Add(gUl); foreach (var groupAttendance in locationAttendance.Groups) { var gLi = new HtmlGenericControl("li"); gUl.Controls.Add(gLi); gLi.InnerHtml = string.Format("<strong>{0}</strong>: {1}", groupAttendance.GroupName, groupAttendance.CurrentCount); var sUl = new HtmlGenericControl("ul"); sUl.AddCssClass("kioskmanager-count-schedules"); gLi.Controls.Add(sUl); foreach (var scheduleAttendance in groupAttendance.Schedules.Where(s => s.IsActive)) { var sLi = new HtmlGenericControl("li"); sUl.Controls.Add(sLi); sLi.InnerHtml = string.Format("<strong>{0}</strong>: {1}", scheduleAttendance.ScheduleName, scheduleAttendance.CurrentCount); } } } } } } pnlManagerLogin.Visible = true; // set manager timer to 10 minutes hfRefreshTimerSeconds.Value = "600"; }
/// <summary> /// Formats the count. /// </summary> /// <param name="locationId">The location id.</param> /// <returns></returns> protected string FormatCount(int locationId) { if (CurrentCheckInType != null && CurrentCheckInType.DisplayLocationCount) { return(string.Format(" <span class='checkin-sub-title'> Count: {0}</span>", KioskLocationAttendance.Read(locationId).CurrentCount)); } return(string.Empty); }
private void RefreshView() { pnlNotActive.Visible = false; pnlNotActiveYet.Visible = false; pnlClosed.Visible = false; pnlActive.Visible = false; lblActiveWhen.Text = string.Empty; if (CurrentCheckInState == null || IsMobileAndExpiredDevice()) { NavigateToPreviousPage(); return; } if (CurrentCheckInState.Kiosk.FilteredGroupTypes(CurrentCheckInState.ConfiguredGroupTypes).Count == 0) { pnlNotActive.Visible = true; } else if (!CurrentCheckInState.Kiosk.HasLocations(CurrentCheckInState.ConfiguredGroupTypes)) { DateTimeOffset activeAt = CurrentCheckInState.Kiosk.FilteredGroupTypes(CurrentCheckInState.ConfiguredGroupTypes).Select(g => g.NextActiveTime).Min(); lblActiveWhen.Text = activeAt.ToString(); pnlNotActiveYet.Visible = true; } else if (!CurrentCheckInState.Kiosk.HasActiveLocations(CurrentCheckInState.ConfiguredGroupTypes)) { pnlClosed.Visible = true; } else { pnlActive.Visible = true; } List <int> locations = new List <int>(); foreach (var groupType in CurrentCheckInState.Kiosk.FilteredGroupTypes(CurrentCheckInState.ConfiguredGroupTypes)) { foreach (var location in groupType.KioskGroups.SelectMany(g => g.KioskLocations).Distinct()) { if (!locations.Contains(location.Location.Id)) { locations.Add(location.Location.Id); var locationAttendance = KioskLocationAttendance.Read(location.Location.Id); if (locationAttendance != null) { var lUl = new HtmlGenericControl("ul"); lUl.AddCssClass("checkin-count-locations"); phCounts.Controls.Add(lUl); var lLi = new HtmlGenericControl("li"); lUl.Controls.Add(lLi); lLi.InnerHtml = string.Format("{0}: <strong>{1}</strong>", locationAttendance.LocationName, locationAttendance.CurrentCount); foreach (var groupAttendance in locationAttendance.Groups) { var gUl = new HtmlGenericControl("ul"); gUl.AddCssClass("checkin-count-groups"); lLi.Controls.Add(gUl); var gLi = new HtmlGenericControl("li"); gUl.Controls.Add(gLi); gLi.InnerHtml = string.Format("{0}: <strong>{1}</strong>", groupAttendance.GroupName, groupAttendance.CurrentCount); foreach (var scheduleAttendance in groupAttendance.Schedules) { var sUl = new HtmlGenericControl("ul"); sUl.AddCssClass("checkin-count-schedules"); gLi.Controls.Add(sUl); var sLi = new HtmlGenericControl("li"); sUl.Controls.Add(sLi); sLi.InnerHtml = string.Format("{0}: <strong>{1}</strong>", scheduleAttendance.ScheduleName, scheduleAttendance.CurrentCount); } } } } } } }
/// <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) { AttendanceCode attendanceCode = null; bool reuseCodeForFamily = checkInState.CheckInType != null && checkInState.CheckInType.ReuseSameCode; int securityCodeAlphaNumericLength = checkInState.CheckInType != null ? checkInState.CheckInType.SecurityCodeAlphaNumericLength : 3; int securityCodeAlphaLength = checkInState.CheckInType != null ? checkInState.CheckInType.SecurityCodeAlphaLength : 0; int securityCodeNumericLength = checkInState.CheckInType != null ? checkInState.CheckInType.SecurityCodeNumericLength : 0; bool securityCodeNumericRandom = checkInState.CheckInType != null ? checkInState.CheckInType.SecurityCodeNumericRandom : true; var attendanceCodeService = new AttendanceCodeService(rockContext); var attendanceService = new AttendanceService(rockContext); var groupMemberService = new GroupMemberService(rockContext); var personAliasService = new PersonAliasService(rockContext); var attendanceRecords = new List <Attendance>(); checkInState.Messages.Clear(); var family = checkInState.CheckIn.CurrentFamily; if (family != null) { var currentOccurences = new List <OccurenceRecord>(); foreach (var person in family.GetPeople(true)) { if (reuseCodeForFamily && attendanceCode != null) { person.SecurityCode = attendanceCode.Code; } else { attendanceCode = AttendanceCodeService.GetNew(securityCodeAlphaNumericLength, securityCodeAlphaLength, securityCodeNumericLength, securityCodeNumericRandom); 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)) { bool isCheckedIntoLocation = false; foreach (var schedule in location.GetSchedules(true)) { var startDateTime = schedule.CampusCurrentDateTime; // If we're enforcing strict location thresholds, then before we create an attendance record // we need to check the location-schedule's current count. if (GetAttributeValue(action, "EnforceStrictLocationThreshold").AsBoolean() && location.Location.SoftRoomThreshold.HasValue) { 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 currentOccurence = GetCurrentOccurence(currentOccurences, 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() + (currentOccurence == null ? 0 : currentOccurence.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("Person", person.Person); mergeFields.Add("Group", group.Group); mergeFields.Add("Location", location.Location); mergeFields.Add("Schedule", schedule.Schedule); message.MessageText = GetAttributeValue(action, "NotChecked-InMessageFormat").ResolveMergeFields(mergeFields); // Now add it to the check-in state message list for others to see. checkInState.Messages.Add(message); continue; } else { // Keep track of anyone who was checked in so far. if (currentOccurence == null) { currentOccurence = new OccurenceRecord() { Date = startDateTime.Date, LocationId = location.Location.Id, ScheduleId = schedule.Schedule.Id }; currentOccurences.Add(currentOccurence); } currentOccurence.Count += 1; } } } // Only create one attendance record per day for each person/schedule/group/location var attendance = attendanceService.Get(startDateTime, location.Location.Id, schedule.Schedule.Id, group.Group.Id, person.Person.Id); if (attendance == null) { var primaryAlias = personAliasService.GetPrimaryAlias(person.Person.Id); if (primaryAlias != null) { 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.PersonAlias = primaryAlias; } } 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.DidAttend = true; attendance.Note = group.Notes; KioskLocationAttendance.AddAttendance(attendance); isCheckedIntoLocation = true; // Keep track of attendance (Ids) for use by other actions later in the workflow pipeline attendanceRecords.Add(attendance); } // If the person was NOT checked into the location for any schedule then remove the location if (!isCheckedIntoLocation) { group.Locations.Remove(location); } } } } } } rockContext.SaveChanges(); // Now that the records are persisted, take the Ids and save them to the temp CheckInFamliy object family.AttendanceIds = attendanceRecords.Select(a => a.Id).ToList(); attendanceRecords = null; 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); } 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, Model.WorkflowAction action, Object entity, out List <string> errorMessages) { var checkInState = GetCheckInState(entity, out errorMessages); if (checkInState != null) { AttendanceCode attendanceCode = null; bool reuseCodeForFamily = checkInState.CheckInType != null && checkInState.CheckInType.ReuseSameCode; int securityCodeAlphaNumericLength = checkInState.CheckInType != null ? checkInState.CheckInType.SecurityCodeAlphaNumericLength : 3; int securityCodeAlphaLength = checkInState.CheckInType != null ? checkInState.CheckInType.SecurityCodeAlphaLength : 0; int securityCodeNumericLength = checkInState.CheckInType != null ? checkInState.CheckInType.SecurityCodeNumericLength : 0; bool securityCodeNumericRandom = checkInState.CheckInType != null ? checkInState.CheckInType.SecurityCodeNumericRandom : true; 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(securityCodeAlphaNumericLength, securityCodeAlphaLength, securityCodeNumericLength, securityCodeNumericRandom); 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 startDateTime = schedule.CampusCurrentDateTime; // Only create one attendance record per day for each person/schedule/group/location var attendance = attendanceService.Get(startDateTime, location.Location.Id, schedule.Schedule.Id, group.Group.Id, person.Person.Id); if (attendance == null) { var primaryAlias = personAliasService.GetPrimaryAlias(person.Person.Id); if (primaryAlias != null) { 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.PersonAlias = primaryAlias; } } 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.DidAttend = true; attendance.Note = group.Notes; KioskLocationAttendance.AddAttendance(attendance); } } } } } } rockContext.SaveChanges(); return(true); } return(false); }
/// <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) { DateTime startDateTime = DateTime.Now; int securityCodeLength = 3; if (!int.TryParse(GetAttributeValue(action, "SecurityCodeLength"), out securityCodeLength)) { securityCodeLength = 3; } using (var uow = new Rock.Data.UnitOfWorkScope()) { var attendanceCodeService = new AttendanceCodeService(); var attendanceService = new AttendanceService(); var groupMemberService = new GroupMemberService(); foreach (var family in checkInState.CheckIn.Families.Where(f => f.Selected)) { foreach (var person in family.People.Where(p => p.Selected)) { var attendanceCode = attendanceCodeService.GetNew(securityCodeLength); person.SecurityCode = attendanceCode.Code; foreach (var groupType in person.GroupTypes.Where(g => g.Selected)) { foreach (var group in groupType.Groups.Where(g => g.Selected)) { foreach (var location in group.Locations.Where(l => l.Selected)) { 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, null); groupMemberService.Save(groupMember, null); } foreach (var schedule in location.Schedules.Where(s => s.Selected)) { // Only create one attendance record per day for each person/schedule/group/location var attendance = attendanceService.Get(startDateTime, location.Location.Id, schedule.Schedule.Id, group.Group.Id, person.Person.Id); if (attendance == null) { attendance = ((Rock.Data.RockContext)uow.DbContext).Attendances.Create(); attendance.LocationId = location.Location.Id; attendance.ScheduleId = schedule.Schedule.Id; attendance.GroupId = group.Group.Id; attendance.PersonId = person.Person.Id; attendance.DeviceId = checkInState.Kiosk.Device.Id; attendance.SearchTypeValueId = checkInState.CheckIn.SearchType.Id; attendanceService.Add(attendance, null); } attendance.AttendanceCodeId = attendanceCode.Id; attendance.StartDateTime = startDateTime; attendance.EndDateTime = null; attendance.DidAttend = true; attendanceService.Save(attendance, null); KioskLocationAttendance.AddAttendance(attendance); } } } } } } } 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 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)) { var currentGroupAttendance = bestGroup.Locations.Select(l => KioskLocationAttendance.Read(l.Location.Id).CurrentCount).Sum(); var lowestGroup = validGroups.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 (lowestGroup != null && lowestGroup.Attendance < (currentGroupAttendance - roomBalanceOverride)) { bestGroup = lowestGroup.Group; } } } validLocations = bestGroup.Locations; } // check how many locations exist without getting the whole list int numValidLocations = validLocations.Take(2).Count(); if (numValidLocations > 0) { CheckInLocation bestLocation = null; IEnumerable <CheckInSchedule> validSchedules; if (numValidLocations == 1) { bestLocation = validLocations.FirstOrDefault(); validSchedules = bestLocation.Schedules; } else { var filteredLocations = validLocations.Where(l => !l.ExcludedByFilter && !excludedLocations.Contains(l.Location.Name) && l.Schedules.Any(s => s.Schedule.IsCheckInActive)); // room balance if they fit into multiple locations if (roomBalanceGroupTypeIds.Contains(bestGroup.Group.GroupTypeId)) { filteredLocations = filteredLocations.OrderBy(l => KioskLocationAttendance.Read(l.Location.Id).CurrentCount); } bestLocation = filteredLocations.FirstOrDefault(); validSchedules = bestLocation.Schedules; } // check how many schedules exist without getting the whole list int numValidSchedules = validSchedules.Take(2).Count(); if (numValidSchedules > 0) { // finished finding assignment, verify everything is selected var bestSchedule = validSchedules.OrderBy(s => s.Schedule.StartTimeOfDay).FirstOrDefault(); bestSchedule.Selected = true; bestSchedule.PreSelected = true; if (bestLocation != null) { bestLocation.PreSelected = true; bestLocation.Selected = true; if (bestGroup != null) { bestGroup.PreSelected = true; bestGroup.Selected = true; bestGroupType = person.GroupTypes.FirstOrDefault(gt => gt.GroupType.Id == bestGroup.Group.GroupTypeId); if (bestGroupType != null) { bestGroupType.Selected = true; bestGroupType.PreSelected = true; person.Selected = true; person.PreSelected = true; } } } } } } } } } return(true); }
/// <summary> /// Handles the Click event of the btnManager control. /// </summary> /// <param name="sender">The source of the event.</param> /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param> protected void btnManager_Click(object sender, EventArgs e) { var rockContext = new RockContext(); pnlActive.Visible = false; pnlManager.Visible = false; ManagerLoggedIn = false; tbPIN.Text = string.Empty; // Get room counts List <int> locations = new List <int>(); var lUl = new HtmlGenericControl("ul"); lUl.AddCssClass("kioskmanager-count-locations"); phCounts.Controls.Add(lUl); var device = new DeviceService(rockContext).Get(CurrentKioskId.Value); if (device != null) { var deviceLocations = new List <Location>(); var locationService = new LocationService(rockContext); foreach (var location in device.Locations) { deviceLocations.Add(location); deviceLocations.AddRange(locationService.GetAllDescendents(location.Id)); } deviceLocations = deviceLocations .Where(l => l.LocationTypeValue == null || l.LocationTypeValue.Guid == Rock.SystemGuid.DefinedValue.LOCATION_TYPE_ROOM.AsGuid()) .Distinct() .ToList(); foreach (var location in deviceLocations) { if (!locations.Contains(location.Id)) { locations.Add(location.Id); var locationAttendance = KioskLocationAttendance.Read(location.Id); if (locationAttendance != null) { var lLi = new HtmlGenericControl("li"); lUl.Controls.Add(lLi); lLi.InnerHtml = string.Format("<strong>{0}</strong>: {1}", locationAttendance.LocationName, locationAttendance.CurrentCount); var gUl = new HtmlGenericControl("ul"); gUl.AddCssClass("kioskmanager-count-groups"); lLi.Controls.Add(gUl); // ARRAN: If we only want to show the count of people in the room, and not further split it into groups and schedules, we need to remove this. // I've tested both with and without it, so removing it is ok. foreach (var groupAttendance in locationAttendance.Groups) { var gLi = new HtmlGenericControl("li"); gUl.Controls.Add(gLi); gLi.InnerHtml = string.Format("<strong>{0}</strong>: {1}", groupAttendance.GroupName, groupAttendance.CurrentCount); var sUl = new HtmlGenericControl("ul"); sUl.AddCssClass("kioskmanager-count-schedules"); gLi.Controls.Add(sUl); foreach (var scheduleAttendance in groupAttendance.Schedules.Where(s => s.IsActive)) { var sLi = new HtmlGenericControl("li"); sUl.Controls.Add(sLi); sLi.InnerHtml = string.Format("<strong>{0}</strong>: {1}", scheduleAttendance.ScheduleName, scheduleAttendance.CurrentCount); } } // Remove up to here } } } } pnlManagerLogin.Visible = true; // set manager timer to 10 minutes hfRefreshTimerSeconds.Value = "600"; }