private static void CalculatePlotTimes(AsyncDashboardViewModel.DatesSectionViewModel datesTab)
        {
            var plotTimes = new List<AsyncDashboardViewModel.DatesSectionViewModel.PlotTime>();

            var dtCurrent = DateTime.Now;

            // 0. Start time
            if (datesTab.StartDate.HasValue) plotTimes.Add(new AsyncDashboardViewModel.DatesSectionViewModel.PlotTime("Start Date", datesTab.StartDate.Value));

            // 1. Current time
            plotTimes.Add(new AsyncDashboardViewModel.DatesSectionViewModel.PlotTime("Now", dtCurrent));

            if (datesTab.StartDate.HasValue)
            {
                datesTab.MinimumTime = TimeUtils.Min(dtCurrent, datesTab.StartDate.Value);
            }

            // 2. Code freeze time
            var codeFreeze = datesTab.CodeFreeze;
            if (codeFreeze.HasValue) plotTimes.Add(new AsyncDashboardViewModel.DatesSectionViewModel.PlotTime("Code Freeze", codeFreeze.Value));

            datesTab.MinimumTime = (datesTab.StartDate.HasValue
                                        ? TimeUtils.Min(dtCurrent, datesTab.StartDate.Value)
                                        : (codeFreeze.HasValue
                                               ? TimeUtils.Min(dtCurrent, codeFreeze.Value)
                                               : dtCurrent)).AddDays(-7);

            // 3. Release date
            if (datesTab.Rollout != null)
            {
                plotTimes.Add(new AsyncDashboardViewModel.DatesSectionViewModel.PlotTime("Release", datesTab.Rollout.Value));

                datesTab.MaximumTime = TimeUtils.Max(dtCurrent, datesTab.Rollout.Value).AddDays(7);

                datesTab.PlotTimes = plotTimes.OrderBy(plotTime => plotTime.DateTime).ToArray();
            }
        }
        public ActionResult AsyncDashboard(int projectId, int milestoneId, int? subProjectParentCaseId = null)
        {
            var today = DateTime.Today;

            var model = new AsyncDashboardViewModel();
            model.Setup(projectId, milestoneId, subProjectParentCaseId);

            string cacheKey = MsCacheKey.GenCaseSetKey(projectId, milestoneId, subProjectParentCaseId);
            model.MsCache.CreatedAt = MsCache.GetObjectExpirationTime(cacheKey);

            UpdateViewBagForProjectLayout(model);

            IList<Case> cases = new List<Case>();

            Case projectParentCase = null;
            if (subProjectParentCaseId.HasValue)
            {
                var client = FogBugzGateway.GetClientForParallel();
                projectParentCase = client.GetCase(subProjectParentCaseId.Value);
            }

            foreach (var casesWithMilestone in model.CaseSet.Cases.Where(casesWithMilestone => casesWithMilestone.IndexFixFor == milestoneId))
            {
                cases.Add(casesWithMilestone);
            }

            var fbSchedule = FogBugzGateway.GetFbSchedule();

            if (model.CaseSet.Milestone != null)
            {
                model.DatesSection.StartDate = model.CaseSet.Milestone.DateStart;
                if (model.DatesSection.StartDate.HasValue)
                {
                    model.DatesSection.StartDateDaysRemaining = DateUtils.Difference(today, model.DatesSection.StartDate.Value);
                }

                model.DatesSection.CodeFreeze = (projectParentCase != null && projectParentCase.DateDue != null) ? projectParentCase.DateDue : MilestoneUtils.CodeFreezeCalculate(projectId, model.Milestone, model.SubProjectParentCaseId, model.CaseSet.Milestone.Name, CodeFreezeSubstringRegex);

                if (model.DatesSection.CodeFreeze.HasValue)
                {
                    model.DatesSection.CodeFreezeDaysRemaining = DateUtils.Difference(today, model.DatesSection.CodeFreeze.Value);
                    //model.DatesSection.CodeFreezeHolidaysBefore = HolidaysUtils.GetHolidaysInRange(today, model.DatesSection.CodeFreeze.Value);

                    model.DatesSection.CodeFreezeHolidaysBefore = fbSchedule.GetSiteScheduledDays(today, model.DatesSection.CodeFreeze.Value).TimeOffRanges;
                }

                model.DatesSection.Rollout = projectParentCase != null ? (model.CaseSet.Milestone.Name.Contains("CF") ? DateUtils.GetCodeFreezeFromName(model.CaseSet.Milestone.Name, CodeFreezeSubstringRegex) : null) : model.CaseSet.Milestone.DateRelease;
                if (model.DatesSection.Rollout.HasValue)
                {
                    model.DatesSection.RolloutDaysRemaining = DateUtils.Difference(today, model.DatesSection.Rollout.Value);
                    //model.DatesSection.RolloutHolidaysBefore = HolidaysUtils.GetHolidaysInRange(today, model.DatesSection.Rollout.Value);
                    model.DatesSection.RolloutHolidaysBefore = fbSchedule.GetSiteScheduledDays(today, model.DatesSection.Rollout.Value).TimeOffRanges;
                }

                CalculatePlotTimes(model.DatesSection);

                model.DatesSection.IsActiveProject = today >= model.DatesSection.StartDate && today <= model.DatesSection.Rollout;
            }

            var persons = FogBugzGateway.GetPersons();
            var isLoadedByAdmin = FogBugzGateway.GetIsScheduleLoadedByAdmin();

            var projectStatusCalculator = new ProjectStatusCalculator(model.CaseSet, persons.ToList(), isLoadedByAdmin, fbSchedule);
            model.ProjectStatus = projectStatusCalculator.GetProjectStatus();

            return PartialView("_AsyncDashboard", model);
        }