/// <summary>
        /// Processes attempts for the specified streak type achievement type identifier. This adds new attempts and updates existing attempts.
        /// </summary>
        /// <param name="streakTypeAchievementTypeId">The streak type achievement type identifier.</param>
        public static void Process(int streakTypeAchievementTypeId)
        {
            var achievementTypeCache = StreakTypeAchievementTypeCache.Get(streakTypeAchievementTypeId);

            if (achievementTypeCache == null)
            {
                throw new ArgumentException($"The StreakTypeAchievementTypeCache did not resolve for record id {streakTypeAchievementTypeId}");
            }

            var achievementComponent = achievementTypeCache.AchievementComponent;

            if (achievementComponent == null)
            {
                throw new ArgumentException($"The AchievementComponent did not resolve for record id {streakTypeAchievementTypeId}");
            }

            var streakTypeId  = achievementTypeCache.StreakTypeId;
            var streakService = new StreakService(new RockContext());
            var streaks       = streakService.Queryable().AsNoTracking()
                                .Where(s => s.StreakTypeId == streakTypeId);

            foreach (var streak in streaks)
            {
                // Process each streak in it's own data context to avoid the data context changes getting too big and slow
                var rockContext = new RockContext();
                achievementComponent.Process(rockContext, achievementTypeCache, streak);
                rockContext.SaveChanges();
            }
        }
        /// <summary>
        /// Gets the progress statement for the person for this achievement. Flat means that the unmet prerequisites are not computed.
        /// </summary>
        /// <param name="streakTypeAchievementTypeCache">The streak type achievement type cache.</param>
        /// <param name="personId">The person identifier.</param>
        /// <returns></returns>
        private ProgressStatement GetFlatProgressStatement(StreakTypeAchievementTypeCache streakTypeAchievementTypeCache, int personId)
        {
            var rockContext    = Context as RockContext;
            var attemptService = new StreakAchievementAttemptService(rockContext);

            var attempts = attemptService.Queryable()
                           .AsNoTracking()
                           .Where(saa =>
                                  saa.StreakTypeAchievementTypeId == streakTypeAchievementTypeCache.Id &&
                                  saa.Streak.PersonAlias.PersonId == personId)
                           .OrderByDescending(saa => saa.AchievementAttemptStartDateTime)
                           .ToList();

            var progressStatement = new ProgressStatement(streakTypeAchievementTypeCache);

            // If there are no attempts, no other information can be derived
            if (!attempts.Any())
            {
                return(progressStatement);
            }

            var mostRecentAttempt = attempts.First();
            var bestAttempt       = attempts.OrderByDescending(saa => saa.Progress).First();

            progressStatement.SuccessCount      = attempts.Count(saa => saa.IsSuccessful);
            progressStatement.AttemptCount      = attempts.Count();
            progressStatement.BestAttempt       = bestAttempt;
            progressStatement.MostRecentAttempt = mostRecentAttempt;

            return(progressStatement);
        }
        /// <summary>
        /// Gets the progress statements for the person for all active achievements.
        /// </summary>
        /// <param name="personId">The person identifier.</param>
        /// <returns></returns>
        public List <ProgressStatement> GetProgressStatements(int personId)
        {
            var achievementTypes             = StreakTypeAchievementTypeCache.All().Where(stat => stat.IsActive).ToList();
            var orderedAchievementTypes      = SortAccordingToPrerequisites(achievementTypes);
            var progressStatementsDictionary = new Dictionary <int, ProgressStatement>();
            var progressStatements           = new List <ProgressStatement>();

            foreach (var achievementType in orderedAchievementTypes)
            {
                var progressStatement = GetFlatProgressStatement(achievementType, personId);
                progressStatementsDictionary[achievementType.Id] = progressStatement;
                progressStatements.Add(progressStatement);

                foreach (var prerequisite in achievementType.PrerequisiteAchievementTypes)
                {
                    var prerequisiteProgressStatement = progressStatementsDictionary[prerequisite.Id];

                    if (prerequisiteProgressStatement.SuccessCount == 0)
                    {
                        progressStatement.UnmetPrerequisites.Add(prerequisiteProgressStatement);
                    }
                }
            }

            return(progressStatements);
        }
Exemple #4
0
        /// <summary>
        /// Loads the drop down items.
        /// </summary>
        /// <param name="picker">The picker.</param>
        /// <param name="includeEmptyOption">if set to <c>true</c> [include empty option].</param>
        public static void LoadDropDownItems(IStreakTypeAchievementTypePicker picker, bool includeEmptyOption)
        {
            var selectedItems = picker.Items.Cast <ListItem>()
                                .Where(i => i.Selected)
                                .Select(i => i.Value).AsIntegerList();

            picker.Items.Clear();

            if (includeEmptyOption)
            {
                // add Empty option first
                picker.Items.Add(new ListItem());
            }

            var achievementTypes = StreakTypeAchievementTypeCache.All()
                                   .Where(stat => stat.IsActive)
                                   .OrderBy(stat => stat.Name)
                                   .ToList();

            foreach (var achievementType in achievementTypes)
            {
                var li = new ListItem(achievementType.Name, achievementType.Id.ToString());
                li.Selected = selectedItems.Contains(achievementType.Id);
                picker.Items.Add(li);
            }
        }
 /// <summary>
 /// Returns a collection of Achievement Types that can be selected as prerequisites for a new Achievement Type.
 /// </summary>
 /// <param name="streakTypeCache">The streak type cache.</param>
 /// <returns></returns>
 public static List <StreakTypeAchievementTypeCache> GetEligiblePrerequisiteAchievementTypeCaches(StreakTypeCache streakTypeCache)
 {
     return(StreakTypeAchievementTypeCache.All()
            .Where(stat =>
                   stat.IsActive &&
                   stat.StreakTypeId == streakTypeCache.Id)
            .ToList());
 }
Exemple #6
0
 /// <summary>
 /// Initializes a new instance of the <see cref="ProgressStatement" /> class.
 /// </summary>
 /// <param name="streakTypeAchievementTypeCache">The streak type achievement type cache.</param>
 public ProgressStatement(StreakTypeAchievementTypeCache streakTypeAchievementTypeCache)
 {
     StreakTypeAchievementTypeId          = streakTypeAchievementTypeCache.Id;
     StreakTypeAchievementTypeName        = streakTypeAchievementTypeCache.Name;
     StreakTypeAchievementTypeDescription = streakTypeAchievementTypeCache.Description;
     Attributes = streakTypeAchievementTypeCache.AttributeValues?
                  .Where(kvp => kvp.Key != "Active" && kvp.Key != "Order")
                  .ToDictionary(kvp => kvp.Key, kvp => kvp.Value.Value);
 }
 /// <summary>
 /// Returns a collection of Achievement Types that can be selected as prerequisites of the specified Achievement Type.
 /// An Achievement Type cannot be a prerequisite of itself, or of any Achievement Type that has it as a prerequisite.
 /// </summary>
 /// <param name="streakTypeAchievementTypeCache">The Achievement Type for which prerequisites are required.</param>
 /// <returns></returns>
 public static List <StreakTypeAchievementTypeCache> GetEligiblePrerequisiteAchievementTypeCaches(StreakTypeAchievementTypeCache streakTypeAchievementTypeCache)
 {
     // Get achievement types of which the specified achievement type is not already a prerequisite.
     return(StreakTypeAchievementTypeCache.All()
            .Where(stat =>
                   stat.IsActive &&
                   stat.Id != streakTypeAchievementTypeCache.Id &&
                   stat.StreakTypeId == streakTypeAchievementTypeCache.StreakTypeId &&
                   !stat.Prerequisites.Any(p => p.PrerequisiteStreakTypeAchievementTypeId == streakTypeAchievementTypeCache.Id))
            .ToList());
 }
        /// <summary>
        /// Gets the progress statement for the person for this achievement.
        /// </summary>
        /// <param name="streakTypeAchievementTypeCache">The streak type achievement type cache.</param>
        /// <param name="personId">The person identifier.</param>
        /// <returns></returns>
        public ProgressStatement GetProgressStatement(StreakTypeAchievementTypeCache streakTypeAchievementTypeCache, int personId)
        {
            var progressStatement = GetFlatProgressStatement(streakTypeAchievementTypeCache, personId);

            foreach (var prerequisite in streakTypeAchievementTypeCache.PrerequisiteAchievementTypes)
            {
                var prerequisiteProgressStatement = GetFlatProgressStatement(prerequisite, personId);
                progressStatement.UnmetPrerequisites.Add(prerequisiteProgressStatement);
            }

            return(progressStatement);
        }
        /// <summary>
        /// Gets the unmet prerequisites.
        /// </summary>
        /// <param name="achievementTypeId">The achievement type identifier.</param>
        /// <param name="personId">The person identifier.</param>
        /// <returns></returns>
        public List <ProgressStatement> GetUnmetPrerequisites(int achievementTypeId, int personId)
        {
            var achievementType = StreakTypeAchievementTypeCache.Get(achievementTypeId);

            if (achievementType == null || !achievementType.Prerequisites.Any())
            {
                return(new List <ProgressStatement>());
            }

            return(achievementType.Prerequisites
                   .Select(stat => GetFlatProgressStatement(stat.PrerequisiteStreakTypeAchievementType, personId))
                   .Where(ps => ps.SuccessCount == 0)
                   .ToList());
        }
        /// <summary>
        /// Visit for sort. Part of the <see cref="SortAccordingToPrerequisites(List{StreakTypeAchievementTypeCache})" /> algorithm
        /// </summary>
        /// <param name="currentAchievementType">The current achievement type being visited.</param>
        /// <param name="visitedIds">The IDs of achievement types that have been visited already.</param>
        /// <param name="sorted">The sorted achievement types thus far.</param>
        /// <exception cref="System.Exception">Attempting to sort achievement types according to prerequisites and encountered a cyclic dependency on id: {item.Id}</exception>
        private static void VisitForSort(StreakTypeAchievementTypeCache currentAchievementType, HashSet <int> visitedIds, List <StreakTypeAchievementTypeCache> sorted)
        {
            if (!visitedIds.Contains(currentAchievementType.Id))
            {
                visitedIds.Add(currentAchievementType.Id);

                foreach (var prerequisite in currentAchievementType.Prerequisites)
                {
                    VisitForSort(prerequisite.PrerequisiteStreakTypeAchievementType, visitedIds, sorted);
                }

                sorted.Add(currentAchievementType);
            }
            else if (!sorted.Contains(currentAchievementType))
            {
                throw new Exception($"Attempting to sort achievement types according to prerequisites and encountered a cyclic dependency on id: {currentAchievementType.Id}");
            }
        }
Exemple #11
0
        /// <summary>
        /// Executes this instance.
        /// </summary>
        /// <exception cref="System.NotImplementedException"></exception>
        public void Execute()
        {
            var achievementTypeCache = StreakTypeAchievementTypeCache.Get(StreakTypeAchievementTypeId);

            if (achievementTypeCache == null || !achievementTypeCache.IsActive)
            {
                return;
            }

            if (IsNowStarting && achievementTypeCache.AchievementStartWorkflowTypeId.HasValue)
            {
                LaunchWorkflow(achievementTypeCache.AchievementStartWorkflowTypeId.Value);
            }

            if (IsNowEnding && achievementTypeCache.AchievementFailureWorkflowTypeId.HasValue)
            {
                LaunchWorkflow(achievementTypeCache.AchievementFailureWorkflowTypeId.Value);
            }

            if (IsNowSuccessful && achievementTypeCache.AchievementSuccessWorkflowTypeId.HasValue)
            {
                LaunchWorkflow(achievementTypeCache.AchievementSuccessWorkflowTypeId.Value);
            }

            if (IsNowSuccessful &&
                achievementTypeCache.AchievementStepStatusId.HasValue &&
                achievementTypeCache.AchievementStepTypeId.HasValue)
            {
                var rockContext   = new RockContext();
                var streakService = new StreakService(rockContext);
                var personAliasId = streakService.Queryable().AsNoTracking()
                                    .Where(s => s.Id == StreakId)
                                    .Select(s => s.PersonAliasId)
                                    .FirstOrDefault();

                if (personAliasId != default)
                {
                    AddStep(achievementTypeCache.AchievementStepTypeId.Value,
                            achievementTypeCache.AchievementStepStatusId.Value, personAliasId);
                }
            }
        }
Exemple #12
0
        /// <summary>
        /// Gets the type of the achievement.
        /// </summary>
        /// <returns></returns>
        private StreakTypeAchievementTypeCache GetAchievementTypeCache()
        {
            if (_streakTypeAchievementTypeCache != null)
            {
                return(_streakTypeAchievementTypeCache);
            }

            var attempt           = GetAttempt();
            var achievementTypeId = PageParameter(PageParameterKey.StreakTypeAchievementTypeId).AsIntegerOrNull();

            if (attempt != null)
            {
                achievementTypeId = attempt.StreakTypeAchievementTypeId;
            }

            if (achievementTypeId.HasValue && achievementTypeId.Value > 0)
            {
                _streakTypeAchievementTypeCache = StreakTypeAchievementTypeCache.Get(achievementTypeId.Value);
            }

            return(_streakTypeAchievementTypeCache);
        }
Exemple #13
0
        /// <summary>
        /// Rs the filter_ display filter value.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The e.</param>
        protected void rFilter_DisplayFilterValue(object sender, GridFilter.DisplayFilterValueArgs e)
        {
            switch (e.Key)
            {
            case FilterKey.AchievementType:
                var achievementTypeCache = StreakTypeAchievementTypeCache.Get(e.Value.AsInteger());
                e.Value = achievementTypeCache != null ? achievementTypeCache.Name : string.Empty;
                break;

            case FilterKey.Status:
            case FilterKey.FirstName:
            case FilterKey.LastName:
                break;

            case FilterKey.AttemptStartDateRange:
                e.Value = DateRangePicker.FormatDelimitedValues(e.Value);
                break;

            default:
                e.Value = string.Empty;
                break;
            }
        }
Exemple #14
0
        public virtual List <ProgressStatement> GetProgressForPerson([FromUri] int personId = default)
        {
            var rockContext = Service.Context as RockContext;

            // If not specified, use the current person id
            if (personId == default)
            {
                personId = GetPerson(rockContext)?.Id ?? default;

                if (personId == default)
                {
                    var errorResponse = ControllerContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, "The personId for the current user did not resolve");
                    throw new HttpResponseException(errorResponse);
                }
            }

            var achievementTypeService = Service as StreakTypeAchievementTypeService;
            var progressStatements     = StreakTypeAchievementTypeCache.All()
                                         .Where(stat => stat.IsActive)
                                         .Select(stat => achievementTypeService.GetProgressStatement(stat, personId))
                                         .ToList();

            return(progressStatements);
        }
Exemple #15
0
 /// <summary>
 /// Updates any Cache Objects that are associated with this entity
 /// </summary>
 /// <param name="entityState">State of the entity.</param>
 /// <param name="dbContext">The database context.</param>
 public void UpdateCache(EntityState entityState, Rock.Data.DbContext dbContext)
 {
     StreakTypeAchievementTypeCache.UpdateCachedEntity(Id, entityState);
 }
Exemple #16
0
 /// <summary>
 /// Gets the cache object associated with this Entity
 /// </summary>
 /// <returns></returns>
 public IEntityCache GetCacheObject()
 {
     return(StreakTypeAchievementTypeCache.Get(this.Id));
 }
        /// <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 );

            // 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 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 )
            {
                var iterationCanStop = false;

                // 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;
                        iterationCanStop = !streakTypeAchievementTypeCache.AllowOverAchievement;
                    }
                }

                // 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 = currentDate <= maxDateForStreakBreaking;
                        openAttempt.IsSuccessful = progress >= 1m;
                        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( $"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;
            }
        }
Exemple #18
0
        /// <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 numberToAchieve = GetAttributeValue(streakTypeAchievementTypeCache, AttributeKey.NumberToAchieve).AsInteger();

            if (numberToAchieve <= 0)
            {
                ExceptionLogService.LogException($"StreakAchievement.UpdateOpenAttempt cannot process because the numberToAchieve attribute is less than 1");
                return;
            }

            var attributeTimespanDays = GetAttributeValue(streakTypeAchievementTypeCache, 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(streakTypeAchievementTypeCache, AttributeKey.EndDateTime).AsDateTime();
            var minDate          = openAttempt.AchievementAttemptStartDateTime;
            var maxDate          = CalculateMaxDateForAchievementAttempt(minDate, attributeMaxDate);

            // 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)
            {
                // 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, !streakTypeAchievementTypeCache.AllowOverAchievement);
                    }
                }
                else if (hasOccurrence && !hasEngagement && !hasExclusion)
                {
                    // Break the streak and close the attempt if there is an unexcused absence
                    ApplyStreakToAttempt(computedStreak, openAttempt, numberToAchieve, 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, true);
                    }
                }

                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($"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;
            }
        }
Exemple #19
0
        /// <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="streakTypeAchievementTypeCache">The streak type achievement type cache.</param>
        /// <param name="streak">The streak.</param>
        /// <param name="mostRecentClosedAttempt">The most recent closed attempt.</param>
        /// <returns></returns>
        protected override List <StreakAchievementAttempt> CreateNewAttempts(StreakTypeAchievementTypeCache streakTypeAchievementTypeCache, Streak streak, StreakAchievementAttempt mostRecentClosedAttempt)
        {
            var rockContext       = new RockContext();
            var streakTypeService = new StreakTypeService(rockContext);
            var streakTypeCache   = streakTypeAchievementTypeCache.StreakTypeCache;

            // Validate the attribute values
            var numberToAchieve = GetAttributeValue(streakTypeAchievementTypeCache, 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(streakTypeAchievementTypeCache, 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(streakTypeAchievementTypeCache, AttributeKey.StartDateTime).AsDateTime();
            var attributeMaxDate = GetAttributeValue(streakTypeAchievementTypeCache, AttributeKey.EndDateTime).AsDateTime();
            var minDate          = CalculateMinDateForAchievementAttempt(streak.EnrollmentDate, mostRecentClosedAttempt, attributeMinDate, numberToAchieve);
            var maxDate          = CalculateMaxDateForAchievementAttempt(minDate, attributeMaxDate);

            // Track the attempts in a list that will be returned. The int is the streak count for that attempt
            var attempts = new List <StreakAchievementAttempt>();
            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, true));
                    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 (streakTypeAchievementTypeCache.AllowOverAchievement)
                            {
                                streaks.Add(computedStreak);
                                i = 0;
                            }
                            else
                            {
                                attempts.Add(GetAttemptFromStreak(computedStreak, numberToAchieve, !streakTypeAchievementTypeCache.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);
        }
Exemple #20
0
 /// <summary>
 /// Gets the achievement type cache.
 /// </summary>
 /// <returns></returns>
 private StreakTypeAchievementTypeCache GetAchievementTypeCache()
 {
     return(StreakTypeAchievementTypeCache.Get(PageParameter(PageParameterKey.StreakTypeAchievementTypeId).AsInteger()));
 }