/// <summary> /// Gets the attempt from the streak. /// </summary> /// <param name="computedStreak">The computed streak.</param> /// <param name="targetCount">The target count.</param> /// <param name="isClosed">if set to <c>true</c> [is closed].</param> /// <returns></returns> private static AchievementAttempt GetAttemptFromStreak(ComputedStreak computedStreak, int targetCount, bool isClosed) { var attempt = new AchievementAttempt(); ApplyStreakToAttempt(computedStreak, attempt, targetCount, isClosed); return(attempt); }
/// <summary> /// Gets the attempt from the streak /// </summary> /// <param name="computedStreak">The computed streak.</param> /// <param name="attempt">The attempt.</param> /// <param name="targetCount">The target count.</param> /// <param name="isClosed">if set to <c>true</c> [is closed].</param> private static void ApplyStreakToAttempt(ComputedStreak computedStreak, AchievementAttempt attempt, int targetCount, bool isClosed) { var progress = CalculateProgress(computedStreak.Count, targetCount); attempt.AchievementAttemptStartDateTime = computedStreak.StartDate; attempt.AchievementAttemptEndDateTime = computedStreak.EndDate; attempt.Progress = progress; attempt.IsClosed = isClosed; attempt.IsSuccessful = progress >= 1m; }
/// <summary> /// Gets the attempt from the accumulation /// </summary> /// <param name="accumulation">The accumulation.</param> /// <param name="targetCount">The target count.</param> /// <param name="isClosed">if set to <c>true</c> [is closed].</param> /// <returns></returns> private static AchievementAttempt GetAttempt(ComputedStreak accumulation, int targetCount, bool isClosed) { var progress = CalculateProgress(accumulation.Count, targetCount); return(new AchievementAttempt { AchievementAttemptStartDateTime = accumulation.StartDate, AchievementAttemptEndDateTime = accumulation.EndDate, Progress = progress, IsClosed = isClosed, IsSuccessful = progress >= 1m }); }
/// <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="transaction">The financial transaction.</param> /// <param name="mostRecentSuccess">The most recent successful attempt.</param> /// <returns></returns> private List <AchievementAttempt> CreateNewAttempts(AchievementTypeCache achievementTypeCache, FinancialTransaction transaction, AchievementAttempt mostRecentSuccess) { // Validate the attribute values var numberToAccumulate = GetAttributeValue(achievementTypeCache, AttributeKey.NumberToAccumulate).AsInteger(); if (numberToAccumulate <= 0) { ExceptionLogService.LogException($"{GetType().Name}. CreateNewAttempts cannot process because the NumberToAccumulate 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(DateTime.MinValue, mostRecentSuccess, attributeMinDate, numberToAccumulate); var maxDate = CalculateMaxDateForAchievementAttempt(minDate, attributeMaxDate); // Track the attempts in a list that will be returned var attempts = new List <AchievementAttempt>(); ComputedStreak accumulation = null; // Get the transaction dates and begin calculating attempts var transactionDates = GetOrderedFinancialTransactionDatesByPerson(achievementTypeCache, transaction.AuthorizedPersonAlias.PersonId, minDate, maxDate); foreach (var transactionDate in transactionDates) { if (!transactionDate.HasValue) { // Nothing we can do without a date continue; } if (accumulation == null) { accumulation = new ComputedStreak(transactionDate.Value); } // Increment the accumulation accumulation.Count++; accumulation.EndDate = transactionDate; // Check for a fulfilled attempt if (accumulation.Count >= numberToAccumulate) { attempts.Add(GetAttempt(accumulation, numberToAccumulate, true)); if (!achievementTypeCache.AllowOverAchievement) { accumulation = null; } } } // The leftover accumulation is an open attempt if (accumulation != null) { var openAttempt = GetAttempt(accumulation, numberToAccumulate, false); var lastAttempt = attempts.LastOrDefault(); if (null == lastAttempt || openAttempt.Progress != lastAttempt.Progress || openAttempt.AchievementAttemptStartDateTime != lastAttempt.AchievementAttemptStartDateTime || openAttempt.AchievementAttemptEndDateTime != lastAttempt.AchievementAttemptEndDateTime) { attempts.Add(openAttempt); } } 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; } }