/// <summary> /// Old DDay.iCal does it WRONG, because doesn't respect Timezones DST rules or offsets, /// just append the TZ to them but does no calculations, so all DST changes and offsets comes /// from the local machine. /// </summary> /// <returns></returns> static string DDayRecurrentExample(string tz) { // This time created is being used as the local time on the time zone later specified, so // is NOT the local machine time and is a different UTC value depending on the TZ var now = new DateTime(2016, 1, 1, 12, 0, 0); var later = now.AddHours(1); var rrule = new DDay.iCal.RecurrencePattern(DDay.iCal.FrequencyType.Monthly, 1) { Count = 12 }; var e = new DDay.iCal.Event { DTStart = new DDay.iCal.iCalDateTime(now, tz), DTEnd = new DDay.iCal.iCalDateTime(later, tz) }; e.RecurrenceRules.Add(rrule); var calendar = new DDay.iCal.iCalendar(); calendar.Events.Add(e); //var serializer = new CalendarSerializer(new SerializationContext()); //return serializer.SerializeToString(calendar); var end = new DateTime(2017, 1, 1, 12, 0, 0); return(e.GetOccurrences(now, end).Aggregate("", (ret, a) => { return ret + a.Period.StartTime.UTC.ToString() + "\n"; })); }
/// <summary> /// Executes the specified context. /// </summary> /// <param name="context">The context.</param> public virtual void Execute(IJobExecutionContext context) { JobDataMap dataMap = context.JobDetail.JobDataMap; var groupType = GroupTypeCache.Read(dataMap.GetString("GroupType").AsGuid()); int attendanceRemindersSent = 0; if (groupType.TakesAttendance && groupType.SendAttendanceReminder) { // Get the occurrence dates that apply var dates = new List <DateTime>(); dates.Add(RockDateTime.Today); try { string[] reminderDays = dataMap.GetString("SendReminders").Split(','); foreach (string reminderDay in reminderDays) { if (reminderDay.Trim() != string.Empty) { var reminderDate = RockDateTime.Today.AddDays(0 - Convert.ToInt32(reminderDay)); if (!dates.Contains(reminderDate)) { dates.Add(reminderDate); } } } } catch { } var rockContext = new RockContext(); var groupService = new GroupService(rockContext); var groupMemberService = new GroupMemberService(rockContext); var scheduleService = new ScheduleService(rockContext); var attendanceService = new AttendanceService(rockContext); var startDate = dates.Min(); var endDate = dates.Max().AddDays(1); // Find all 'occurrences' for the groups that occur on the affected dates var occurrences = new Dictionary <int, List <DateTime> >(); foreach (var group in groupService .Queryable("Schedule").AsNoTracking() .Where(g => g.GroupTypeId == groupType.Id && g.IsActive && g.Schedule != null && g.Members.Any(m => m.GroupMemberStatus == GroupMemberStatus.Active && m.GroupRole.IsLeader && m.Person.Email != null && m.Person.Email != ""))) { // Add the group occurrences.Add(group.Id, new List <DateTime>()); // Check for a iCal schedule DDay.iCal.Event calEvent = group.Schedule.GetCalenderEvent(); if (calEvent != null) { // If schedule has an iCal schedule, get occurrences between first and last dates foreach (var occurrence in calEvent.GetOccurrences(startDate, endDate)) { var startTime = occurrence.Period.StartTime.Value; if (dates.Contains(startTime.Date)) { occurrences[group.Id].Add(startTime); } } } else { // if schedule does not have an iCal, then check for weekly schedule and calculate occurrences starting with first attendance or current week if (group.Schedule.WeeklyDayOfWeek.HasValue) { foreach (var date in dates) { if (date.DayOfWeek == group.Schedule.WeeklyDayOfWeek.Value) { var startTime = date; if (group.Schedule.WeeklyTimeOfDay.HasValue) { startTime = startTime.Add(group.Schedule.WeeklyTimeOfDay.Value); } occurrences[group.Id].Add(startTime); } } } } } // Remove any occurrences during group type exclusion date ranges foreach (var exclusion in groupType.GroupScheduleExclusions) { if (exclusion.Start.HasValue && exclusion.End.HasValue) { foreach (var keyVal in occurrences) { foreach (var occurrenceDate in keyVal.Value.ToList()) { if (occurrenceDate >= exclusion.Start.Value && occurrenceDate < exclusion.End.Value.AddDays(1)) { keyVal.Value.Remove(occurrenceDate); } } } } } // Remove any 'occurrenes' that already have attendance data entered foreach (var occurrence in attendanceService .Queryable().AsNoTracking() .Where(a => a.StartDateTime >= startDate && a.StartDateTime < endDate && occurrences.Keys.Contains(a.GroupId.Value) && a.ScheduleId.HasValue) .Select(a => new { GroupId = a.GroupId.Value, a.StartDateTime }) .Distinct() .ToList()) { occurrences[occurrence.GroupId].RemoveAll(d => d.Date == occurrence.StartDateTime.Date); } // Get the groups that have occurrences var groupIds = occurrences.Where(o => o.Value.Any()).Select(o => o.Key).ToList(); // Get the leaders of those groups var leaders = groupMemberService .Queryable("Group,Person").AsNoTracking() .Where(m => groupIds.Contains(m.GroupId) && m.GroupMemberStatus == GroupMemberStatus.Active && m.GroupRole.IsLeader && m.Person.Email != null && m.Person.Email != "") .ToList(); // Loop through the leaders foreach (var leader in leaders) { foreach (var group in occurrences.Where(o => o.Key == leader.GroupId)) { var mergeObjects = Rock.Lava.LavaHelper.GetCommonMergeFields(null, leader.Person); mergeObjects.Add("Person", leader.Person); mergeObjects.Add("Group", leader.Group); mergeObjects.Add("Occurrence", group.Value.Max()); var recipients = new List <RecipientData>(); recipients.Add(new RecipientData(leader.Person.Email, mergeObjects)); Email.Send(dataMap.GetString("SystemEmail").AsGuid(), recipients); attendanceRemindersSent++; } } } context.Result = string.Format("{0} attendance reminders sent", attendanceRemindersSent); }
/// <summary> /// Gets occurrence data for the selected group /// </summary> /// <param name="group">The group.</param> /// <param name="fromDateTime">From date time.</param> /// <param name="toDateTime">To date time.</param> /// <param name="locationIds">The location ids.</param> /// <param name="scheduleIds">The schedule ids.</param> /// <param name="loadSummaryData">if set to <c>true</c> [load summary data].</param> /// <param name="campusId">The campus identifier.</param> /// <returns></returns> public List <ScheduleOccurrence> GetGroupOccurrences(Group group, DateTime?fromDateTime, DateTime?toDateTime, List <int> locationIds, List <int> scheduleIds, bool loadSummaryData, int?campusId) { var occurrences = new List <ScheduleOccurrence>(); if (group != null) { var rockContext = (RockContext)this.Context; var attendanceService = new AttendanceService(rockContext); var scheduleService = new ScheduleService(rockContext); var locationService = new LocationService(rockContext); using (new Rock.Data.QueryHintScope(rockContext, QueryHintType.RECOMPILE)) { // Set up an 'occurrences' query for the group var qry = attendanceService .Queryable().AsNoTracking() .Where(a => a.GroupId == group.Id); // Filter by date range if (fromDateTime.HasValue) { var fromDate = fromDateTime.Value.Date; qry = qry.Where(a => DbFunctions.TruncateTime(a.StartDateTime) >= (fromDate)); } if (toDateTime.HasValue) { var toDate = toDateTime.Value.Date; qry = qry.Where(a => DbFunctions.TruncateTime(a.StartDateTime) < (toDate)); } // Location Filter if (locationIds.Any()) { qry = qry.Where(a => locationIds.Contains(a.LocationId ?? 0)); } // Schedule Filter if (scheduleIds.Any()) { qry = qry.Where(a => scheduleIds.Contains(a.ScheduleId ?? 0)); } // Get the unique combination of location/schedule/date for the selected group var occurrenceDates = qry .Select(a => new { a.LocationId, a.ScheduleId, Date = DbFunctions.TruncateTime(a.StartDateTime) }) .Distinct() .ToList(); // Get the locations for each unique location id var selectedlocationIds = occurrenceDates.Select(o => o.LocationId).Distinct().ToList(); var locations = locationService .Queryable().AsNoTracking() .Where(l => selectedlocationIds.Contains(l.Id)) .Select(l => new { l.Id, l.ParentLocationId, l.Name }) .ToList(); var locationNames = new Dictionary <int, string>(); locations.ForEach(l => locationNames.Add(l.Id, l.Name)); // Get the parent location path for each unique location var parentlocationPaths = new Dictionary <int, string>(); locations .Where(l => l.ParentLocationId.HasValue) .Select(l => l.ParentLocationId.Value) .Distinct() .ToList() .ForEach(l => parentlocationPaths.Add(l, locationService.GetPath(l))); var locationPaths = new Dictionary <int, string>(); locations .Where(l => l.ParentLocationId.HasValue) .ToList() .ForEach(l => locationPaths.Add(l.Id, parentlocationPaths[l.ParentLocationId.Value])); // Get the schedules for each unique schedule id var selectedScheduleIds = occurrenceDates.Select(o => o.ScheduleId).Distinct().ToList(); var schedules = scheduleService .Queryable().AsNoTracking() .Where(s => selectedScheduleIds.Contains(s.Id)) .ToList(); var scheduleNames = new Dictionary <int, string>(); var scheduleStartTimes = new Dictionary <int, TimeSpan>(); schedules .ForEach(s => { scheduleNames.Add(s.Id, s.Name); scheduleStartTimes.Add(s.Id, s.StartTimeOfDay); }); foreach (var occurrence in occurrenceDates.Where(o => o.Date.HasValue)) { occurrences.Add( new ScheduleOccurrence( occurrence.Date.Value, occurrence.ScheduleId.HasValue && scheduleStartTimes.ContainsKey(occurrence.ScheduleId.Value) ? scheduleStartTimes[occurrence.ScheduleId.Value] : new TimeSpan(), occurrence.ScheduleId, occurrence.ScheduleId.HasValue && scheduleNames.ContainsKey(occurrence.ScheduleId.Value) ? scheduleNames[occurrence.ScheduleId.Value] : string.Empty, occurrence.LocationId, occurrence.LocationId.HasValue && locationNames.ContainsKey(occurrence.LocationId.Value) ? locationNames[occurrence.LocationId.Value] : string.Empty, occurrence.LocationId.HasValue && locationPaths.ContainsKey(occurrence.LocationId.Value) ? locationPaths[occurrence.LocationId.Value] : string.Empty )); } } // Load the attendance data for each occurrence if (loadSummaryData && occurrences.Any()) { var minDate = occurrences.Min(o => o.Date); var maxDate = occurrences.Max(o => o.Date).AddDays(1); var attendanceQry = attendanceService .Queryable().AsNoTracking() .Where(a => a.GroupId.HasValue && a.GroupId == group.Id && a.StartDateTime >= minDate && a.StartDateTime < maxDate && a.PersonAlias != null && a.PersonAliasId.HasValue) .Select(a => new { a.LocationId, a.ScheduleId, a.StartDateTime, a.DidAttend, a.DidNotOccur, a.PersonAliasId, PersonId = a.PersonAlias.PersonId }); if (campusId.HasValue) { var familyGroupType = GroupTypeCache.Read(Rock.SystemGuid.GroupType.GROUPTYPE_FAMILY.AsGuid()); var campusQry = new GroupMemberService(rockContext) .Queryable() .Where(g => g.Group != null && g.Group.GroupTypeId == familyGroupType.Id && g.Group.CampusId.HasValue && g.Group.CampusId.Value == campusId.Value ) .Select(m => m.PersonId); attendanceQry = attendanceQry .Where(s => campusQry.Contains(s.PersonId)); } var attendances = attendanceQry.ToList(); foreach (var summary in attendances .GroupBy(a => new { a.LocationId, a.ScheduleId, Date = a.StartDateTime.Date }) .Select(a => new { a.Key.LocationId, a.Key.ScheduleId, a.Key.Date, DidAttendCount = a .Where(t => t.DidAttend.HasValue && t.DidAttend.Value) .Select(t => t.PersonAliasId.Value) .Distinct() .Count(), DidNotOccurCount = a .Where(t => t.DidNotOccur.HasValue && t.DidNotOccur.Value) .Select(t => t.PersonAliasId.Value) .Distinct() .Count(), TotalCount = a .Select(t => t.PersonAliasId) .Distinct() .Count() })) { var occurrence = occurrences .Where(o => o.ScheduleId.Equals(summary.ScheduleId) && o.LocationId.Equals(summary.LocationId) && o.Date.Equals(summary.Date)) .FirstOrDefault(); if (occurrence != null) { occurrence.DidAttendCount = summary.DidAttendCount; occurrence.DidNotOccurCount = summary.DidNotOccurCount; occurrence.TotalCount = summary.TotalCount; } } } // Create any missing occurrences from the group's schedule (not location schedules) Schedule groupSchedule = null; if (group.ScheduleId.HasValue) { groupSchedule = group.Schedule; if (groupSchedule == null) { groupSchedule = new ScheduleService(rockContext).Get(group.ScheduleId.Value); } } if (groupSchedule != null) { var newOccurrences = new List <ScheduleOccurrence>(); var existingDates = occurrences .Where(o => o.ScheduleId.Equals(groupSchedule.Id)) .Select(o => o.Date) .Distinct() .ToList(); var startDate = fromDateTime.HasValue ? fromDateTime.Value : RockDateTime.Today.AddMonths(-2); var endDate = toDateTime.HasValue ? toDateTime.Value : RockDateTime.Today.AddDays(1); DDay.iCal.Event calEvent = groupSchedule.GetCalenderEvent(); if (calEvent != null) { // If schedule has an iCal schedule, get all the past occurrences foreach (var occurrence in calEvent.GetOccurrences(startDate, endDate)) { var scheduleOccurrence = new ScheduleOccurrence( occurrence.Period.StartTime.Date, occurrence.Period.StartTime.TimeOfDay, groupSchedule.Id, groupSchedule.Name); if (!existingDates.Contains(scheduleOccurrence.Date)) { newOccurrences.Add(scheduleOccurrence); } } } else { // if schedule does not have an iCal, then check for weekly schedule and calculate occurrences starting with first attendance or current week if (groupSchedule.WeeklyDayOfWeek.HasValue) { // default to start with date 2 months earlier startDate = fromDateTime.HasValue ? fromDateTime.Value : RockDateTime.Today.AddMonths(-2); if (existingDates.Any(d => d < startDate)) { startDate = existingDates.Min(); } // Back up start time to the correct day of week while (startDate.DayOfWeek != groupSchedule.WeeklyDayOfWeek.Value) { startDate = startDate.AddDays(-1); } // Add the start time if (groupSchedule.WeeklyTimeOfDay.HasValue) { startDate = startDate.Add(groupSchedule.WeeklyTimeOfDay.Value); } // Create occurrences up to current time while (startDate < endDate) { if (!existingDates.Contains(startDate.Date)) { var scheduleOccurrence = new ScheduleOccurrence(startDate.Date, startDate.TimeOfDay, groupSchedule.Id, groupSchedule.Name); newOccurrences.Add(scheduleOccurrence); } startDate = startDate.AddDays(7); } } } if (newOccurrences.Any()) { // Filter Exclusions var groupType = GroupTypeCache.Read(group.GroupTypeId); foreach (var exclusion in groupType.GroupScheduleExclusions) { if (exclusion.Start.HasValue && exclusion.End.HasValue) { foreach (var occurrence in newOccurrences.ToList()) { if (occurrence.Date >= exclusion.Start.Value && occurrence.Date < exclusion.End.Value.AddDays(1)) { newOccurrences.Remove(occurrence); } } } } } foreach (var occurrence in newOccurrences) { occurrences.Add(occurrence); } } } return(occurrences); }
/// <summary> /// Gets occurrence data for the selected group /// </summary> /// <param name="group">The group.</param> /// <param name="loadAttendanceData">if set to <c>true</c> [load attendance data].</param> /// <returns></returns> public List <ScheduleOccurrence> GetGroupOccurrences(Group group, bool loadAttendanceData = true) { var occurrences = new List <ScheduleOccurrence>(); if (group != null) { var rockContext = (RockContext)this.Context; var attendanceService = new AttendanceService(rockContext); var uniqueStartDates = attendanceService .Queryable().AsNoTracking() .Where(a => a.GroupId == group.Id) .Select(a => a.StartDateTime) .Distinct() .ToList() .Select(dt => dt.Date) .Distinct() .ToList(); Schedule schedule = null; if (group.ScheduleId.HasValue) { schedule = group.Schedule; if (schedule == null) { schedule = new ScheduleService(rockContext).Get(group.ScheduleId.Value); } } if (schedule != null) { var endDate = RockDateTime.Today.AddDays(1); DDay.iCal.Event calEvent = schedule.GetCalenderEvent(); if (calEvent != null) { // If schedule has an iCal schedule, get all the past occurrences foreach (var occurrence in calEvent.GetOccurrences(DateTime.MinValue, endDate)) { occurrences.Add(new ScheduleOccurrence(occurrence, schedule.Id)); } } else { // if schedule does not have an iCal, then check for weekly schedule and calculate occurrences starting with first attendance or current week if (schedule.WeeklyDayOfWeek.HasValue) { // default to start with date 2 months earlier DateTime startDateTime = RockDateTime.Today.AddMonths(-2); if (uniqueStartDates.Any(d => d < startDateTime)) { startDateTime = uniqueStartDates.Min(); } // Back up start time to the correct day of week while (startDateTime.DayOfWeek != schedule.WeeklyDayOfWeek.Value) { startDateTime = startDateTime.AddDays(-1); } // Add the start time if (schedule.WeeklyTimeOfDay.HasValue) { startDateTime = startDateTime.Add(schedule.WeeklyTimeOfDay.Value); } // Create occurrences up to current time while (startDateTime < endDate) { occurrences.Add(new ScheduleOccurrence(startDateTime, startDateTime.AddDays(1), schedule.Id)); startDateTime = startDateTime.AddDays(7); } } } } // Add occurrences for any attendance dates that do not fall into a logical occurrence based on the schedule var occurrenceDates = occurrences.Select(o => o.StartDateTime.Date).ToList(); foreach (var date in uniqueStartDates.Where(d => !occurrenceDates.Contains(d))) { occurrences.Add(new ScheduleOccurrence(date, date.AddDays(1))); } if (occurrences.Any()) { // Filter Exclusions var groupType = GroupTypeCache.Read(group.GroupTypeId); foreach (var exclusion in groupType.GroupScheduleExclusions) { if (exclusion.Start.HasValue && exclusion.End.HasValue) { foreach (var occurrence in occurrences.ToList()) { if (occurrence.StartDateTime >= exclusion.Start.Value && occurrence.StartDateTime < exclusion.End.Value.AddDays(1)) { occurrences.Remove(occurrence); } } } } if (loadAttendanceData) { var minStartValue = occurrences.Min(o => o.StartDateTime); var maxEndValue = occurrences.Max(o => o.EndDateTime); var attendanceQry = attendanceService .Queryable("PersonAlias").AsNoTracking() .Where(a => a.GroupId == group.Id && a.StartDateTime >= minStartValue && a.StartDateTime < maxEndValue); var attendanceData = attendanceQry.ToList(); foreach (var occurrence in occurrences) { occurrence.Attendance = attendanceData .Where(a => a.StartDateTime >= occurrence.StartDateTime && a.StartDateTime < occurrence.EndDateTime) .ToList(); } } } } return(occurrences); }