/// <summary> /// Update the open attempt record if there are changes. /// </summary> /// <param name="openAttempt"></param> /// <param name="achievementTypeCache">The achievement type cache.</param> /// <param name="streak">The streak.</param> protected override void UpdateOpenAttempt(AchievementAttempt openAttempt, AchievementTypeCache achievementTypeCache, Streak streak) { var rockContext = new RockContext(); var streakTypeService = new StreakTypeService(rockContext); var streakTypeCache = GetStreakTypeCache(achievementTypeCache); // Validate the attribute values var numberToAchieve = GetAttributeValue(achievementTypeCache, AttributeKey.NumberToAchieve).AsInteger(); if (numberToAchieve <= 0) { ExceptionLogService.LogException($"StreakAchievement.UpdateOpenAttempt cannot process because the numberToAchieve attribute is less than 1"); return; } var attributeTimespanDays = GetAttributeValue(achievementTypeCache, AttributeKey.TimespanInDays).AsIntegerOrNull(); if (attributeTimespanDays.HasValue && attributeTimespanDays.Value <= 0) { ExceptionLogService.LogException($"StreakAchievement.UpdateOpenAttempt cannot process because the TimespanInDays attribute is less than 1"); return; } // Calculate the date range where the open attempt can be validly fulfilled var attributeMaxDate = GetAttributeValue(achievementTypeCache, AttributeKey.EndDateTime).AsDateTime(); var minDate = openAttempt.AchievementAttemptStartDateTime; var maxDate = CalculateMaxDateForAchievementAttempt(minDate, attributeMaxDate); // Get the max date that streaks can be broken. This is to avoid breaking streaks while people still have time to // engage in that day or week (because it is the current day or week) var maxDateForStreakBreaking = StreakTypeService.GetMaxDateForStreakBreaking(streakTypeCache); // Track the streak var computedStreak = new ComputedStreak(minDate) { EndDate = minDate }; // Define what happens for each bit in the date range bool iterationAction(int currentUnit, DateTime currentDate, bool hasOccurrence, bool hasEngagement, bool hasExclusion) { var iterationCanStop = false; // If there is an engagement, then increment the streak if (hasOccurrence && hasEngagement) { computedStreak.Count++; computedStreak.EndDate = currentDate; // Check for a fulfilled attempt if (computedStreak.Count >= numberToAchieve) { ApplyStreakToAttempt(computedStreak, openAttempt, numberToAchieve, !achievementTypeCache.AllowOverAchievement); iterationCanStop = !achievementTypeCache.AllowOverAchievement; } } else if (hasOccurrence && !hasEngagement && !hasExclusion) { // Break the streak and close the attempt if there is an unexcused absence ApplyStreakToAttempt(computedStreak, openAttempt, numberToAchieve, currentDate <= maxDateForStreakBreaking); iterationCanStop = true; } // If there is a timespan and this streak is too old, then the attempt is closed if (attributeTimespanDays.HasValue) { var inclusiveAge = (currentDate - computedStreak.StartDate).Days + 1; if (inclusiveAge >= attributeTimespanDays.Value) { ApplyStreakToAttempt(computedStreak, openAttempt, numberToAchieve, currentDate <= maxDateForStreakBreaking); iterationCanStop = true; } } return(iterationCanStop); } // Iterate through the streak date for the date range specified streakTypeService.IterateStreakMap(streakTypeCache, streak.PersonAliasId, minDate, maxDate, iterationAction, out var errorMessage); if (!errorMessage.IsNullOrWhiteSpace()) { ExceptionLogService.LogException($"StreakAchievement.UpdateOpenAttempt got an error calling StreakTypeService.IterateStreakMap: {errorMessage}"); return; } // If the attempt wasn't closed in the iteration, then it will remain open if (!openAttempt.IsClosed) { var progress = CalculateProgress(computedStreak.Count, numberToAchieve); openAttempt.Progress = progress; openAttempt.IsSuccessful = progress >= 1m; } }
/// <summary> /// Create new attempt records and return them in a list. All new attempts should be after the most recent successful attempt. /// </summary> /// <param name="achievementTypeCache">The achievement type cache.</param> /// <param name="streak">The streak.</param> /// <param name="mostRecentClosedAttempt">The most recent closed attempt.</param> /// <returns></returns> protected override List <AchievementAttempt> CreateNewAttempts(AchievementTypeCache achievementTypeCache, Streak streak, AchievementAttempt mostRecentClosedAttempt) { var rockContext = new RockContext(); var streakTypeService = new StreakTypeService(rockContext); var streakTypeCache = StreakTypeCache.Get(streak.StreakTypeId); // Validate the attribute values var numberToAchieve = GetAttributeValue(achievementTypeCache, AttributeKey.NumberToAchieve).AsInteger(); if (numberToAchieve <= 0) { ExceptionLogService.LogException($"StreakAchievement.CreateNewAttempts cannot process because the NumberToAchieve attribute is less than 1"); return(null); } var attributeTimespanDays = GetAttributeValue(achievementTypeCache, AttributeKey.TimespanInDays).AsIntegerOrNull(); if (attributeTimespanDays.HasValue && attributeTimespanDays.Value <= 0) { ExceptionLogService.LogException($"StreakAchievement.CreateNewAttempts cannot process because the TimespanInDays attribute is less than 1"); return(null); } // Calculate the date range where new achievements can be validly found var attributeMinDate = GetAttributeValue(achievementTypeCache, AttributeKey.StartDateTime).AsDateTime(); var attributeMaxDate = GetAttributeValue(achievementTypeCache, AttributeKey.EndDateTime).AsDateTime(); var minDate = CalculateMinDateForAchievementAttempt(streak.EnrollmentDate, mostRecentClosedAttempt, attributeMinDate, numberToAchieve); var maxDate = CalculateMaxDateForAchievementAttempt(minDate, attributeMaxDate); // Get the max date that streaks can be broken. This is to avoid breaking streaks while people still have time to // engage in that day or week (because it is the current day or week) var maxDateForStreakBreaking = StreakTypeService.GetMaxDateForStreakBreaking(streakTypeCache); // Track the attempts in a list that will be returned. The int is the streak count for that attempt var attempts = new List <AchievementAttempt>(); var streaks = new List <ComputedStreak>(); // Define what happens for each bit in the date range bool iterationAction(int currentUnit, DateTime currentDate, bool hasOccurrence, bool hasEngagement, bool hasExclusion) { // If there is an engagement and a timespan, then this is a possible attempt. If there is no timespan then only one // attempt needs to be tracked at a time if (hasOccurrence && hasEngagement && (attributeTimespanDays.HasValue || !streaks.Any())) { streaks.Add(new ComputedStreak(currentDate)); } else if (hasOccurrence && !hasEngagement && !hasExclusion && streaks.Any()) { // Break the streaks and close an attempt if there is an unexcused absence var longestStreak = streaks.First(); attempts.Add(GetAttemptFromStreak(longestStreak, numberToAchieve, currentDate <= maxDateForStreakBreaking)); streaks.Clear(); return(false); } for (var i = streaks.Count - 1; i >= 0; i--) { var computedStreak = streaks[i]; if (hasOccurrence && hasEngagement) { // Increment the streak computedStreak.Count++; computedStreak.EndDate = currentDate; // Check for a fulfilled attempt if (computedStreak.Count >= numberToAchieve) { streaks.Clear(); if (achievementTypeCache.AllowOverAchievement) { streaks.Add(computedStreak); i = 0; } else { attempts.Add(GetAttemptFromStreak(computedStreak, numberToAchieve, !achievementTypeCache.AllowOverAchievement)); break; } } } // If there is a timespan and this streak is too old, then the attempt is closed if (attributeTimespanDays.HasValue) { var inclusiveAge = (currentDate - computedStreak.StartDate).Days + 1; if (inclusiveAge >= attributeTimespanDays.Value) { var timedOutAttempt = GetAttemptFromStreak(computedStreak, numberToAchieve, true); attempts.Add(timedOutAttempt); streaks.RemoveAt(i); // Remove more recently started streaks that started before the next valid start date (based // on the deficiency of this timed out attempt) var nextValidStartDate = CalculateMinDateForAchievementAttempt(streak.EnrollmentDate, timedOutAttempt, attributeMinDate, numberToAchieve); for (var j = streaks.Count - 1; j >= i; j--) { var moreRecentStreak = streaks[j]; if (moreRecentStreak.StartDate < nextValidStartDate) { streaks.RemoveAt(j); } } } } } return(false); } // Iterate through the streak date for the date range specified streakTypeService.IterateStreakMap(streakTypeCache, streak.PersonAliasId, minDate, maxDate, iterationAction, out var errorMessage); if (!errorMessage.IsNullOrWhiteSpace()) { ExceptionLogService.LogException($"StreakAchievement.CreateNewAttempts got an error calling StreakTypeService.IterateStreakMap: {errorMessage}"); return(null); } // The longest leftover streak is an open attempt if (streaks.Any()) { var longestStreak = streaks.First(); attempts.Add(GetAttemptFromStreak(longestStreak, numberToAchieve, false)); } return(attempts); }
/// <summary> /// Update the open attempt record if there are changes. /// </summary> /// <param name="openAttempt"></param> /// <param name="streakTypeAchievementTypeCache">The streak type achievement type cache.</param> /// <param name="streak">The streak.</param> protected override void UpdateOpenAttempt(StreakAchievementAttempt openAttempt, StreakTypeAchievementTypeCache streakTypeAchievementTypeCache, Streak streak) { var rockContext = new RockContext(); var streakTypeService = new StreakTypeService(rockContext); var streakTypeCache = streakTypeAchievementTypeCache.StreakTypeCache; // Validate the attribute values var numberToAccumulate = GetAttributeValue(streakTypeAchievementTypeCache, AttributeKey.NumberToAccumulate).AsInteger(); if (numberToAccumulate <= 0) { ExceptionLogService.LogException($"AccumulativeAchievement.UpdateOpenAttempt cannot process because the NumberToAccumulate attribute is less than 1"); return; } var attributeTimespanDays = GetAttributeValue(streakTypeAchievementTypeCache, AttributeKey.TimespanInDays).AsIntegerOrNull(); if (attributeTimespanDays.HasValue && attributeTimespanDays.Value <= 0) { ExceptionLogService.LogException($"AccumulativeAchievement.UpdateOpenAttempt cannot process because the TimespanInDays attribute is less than 1"); return; } // Calculate the date range where the open attempt can be validly fulfilled var attributeMaxDate = GetAttributeValue(streakTypeAchievementTypeCache, AttributeKey.EndDateTime).AsDateTime(); var minDate = openAttempt.AchievementAttemptStartDateTime; var maxDate = CalculateMaxDateForAchievementAttempt(minDate, attributeMaxDate); // Track the accumulation var accumulation = new ComputedStreak(minDate) { EndDate = minDate }; // Define what happens for each bit in the date range bool iterationAction(int currentUnit, DateTime currentDate, bool hasOccurrence, bool hasEngagement, bool hasExclusion) { // If there is an engagement, then increment the accumulation if (hasOccurrence && hasEngagement) { accumulation.Count++; accumulation.EndDate = currentDate; // Check for a fulfilled attempt if (accumulation.Count >= numberToAccumulate) { var progress = CalculateProgress(accumulation.Count, numberToAccumulate); openAttempt.AchievementAttemptEndDateTime = accumulation.EndDate; openAttempt.Progress = progress; openAttempt.IsClosed = !streakTypeAchievementTypeCache.AllowOverAchievement; openAttempt.IsSuccessful = progress >= 1m; } } // If there is a timespan and this accumulation is too old, then the attempt is closed if (attributeTimespanDays.HasValue) { var inclusiveAge = (currentDate - accumulation.StartDate).Days + 1; if (inclusiveAge >= attributeTimespanDays.Value) { var progress = CalculateProgress(accumulation.Count, numberToAccumulate); openAttempt.AchievementAttemptEndDateTime = accumulation.EndDate; openAttempt.Progress = progress; openAttempt.IsClosed = true; openAttempt.IsSuccessful = progress >= 1m; } } return(openAttempt.IsClosed); } // Iterate through the streak date for the date range specified streakTypeService.IterateStreakMap(streakTypeCache, streak.PersonAliasId, minDate, maxDate, iterationAction, out var errorMessage); if (!errorMessage.IsNullOrWhiteSpace()) { ExceptionLogService.LogException($"AccumulativeAchievement.UpdateOpenAttempt got an error calling StreakTypeService.IterateStreakMap: {errorMessage}"); return; } // If the attempt wasn't closed in the iteration, then it will remain open if (!openAttempt.IsClosed) { var progress = CalculateProgress(accumulation.Count, numberToAccumulate); openAttempt.Progress = progress; openAttempt.IsSuccessful = progress >= 1m; } }