using System; using System.Collections; using System.Collections.Generic; using System.Data.Entity; using System.Globalization; using System.Linq; using EnVisage.Models; using EnVisage.Properties; using System.Web.Mvc; namespace EnVisage.Code.BLL { /// /// Provides ability to manage fiscal calendar. /// public class FiscalCalendarManager : IDisposable { #region Constructors private readonly EnVisageEntities _dbContext; private readonly bool _isContexLocal = false; public FiscalCalendarManager(EnVisageEntities dbContext = null) { if (dbContext == null) { _dbContext = new EnVisageEntities(); _isContexLocal = true; } else { _dbContext = dbContext; } } public void Dispose() { if (_isContexLocal) _dbContext.Dispose(); } #endregion #region Fiscal Calendar Settings private DateTime GetValidTimePoint(DateTime srcDate) { return new DateTime(srcDate.Year, srcDate.Month, srcDate.Day); } /// /// Returns Fiscal Calendar Settings records /// /// Current time point to mark Active, Inactive and Future records /// public FiscalCalendarListModel GetCalendarSettingsItems(DateTime timePoint) { bool activeItemSet = false; DateTime validTimePoint = GetValidTimePoint(timePoint); FiscalCalendarListModel result = new FiscalCalendarListModel(); List items = (from FiscalCalendarSetting rec in _dbContext.FiscalCalendarSettings orderby rec.EffectiveChangeDate descending where true select rec).Take(10).ToList(); for (int index = 0; index < items.Count; index++) { FiscalCalendarModel currentItem = (FiscalCalendarModel)items[index]; result.Items.Add(currentItem); if (currentItem.EffectiveChangeDate.HasValue && (currentItem.EffectiveChangeDate.Value > validTimePoint)) { currentItem.Status = FiscalCalendarModel.FiscalCalendarSettingStatus.Future; } else { if (!activeItemSet) { currentItem.Status = FiscalCalendarModel.FiscalCalendarSettingStatus.Active; activeItemSet = true; } else { currentItem.Status = FiscalCalendarModel.FiscalCalendarSettingStatus.Inactive; } } } return result; } public FiscalCalendarModel GetCalendarSettingsItem(Guid? id) { FiscalCalendarModel result = new FiscalCalendarModel(); if (id.HasValue && (id.Value != Guid.Empty)) { var record = _dbContext.FiscalCalendarSettings.Find(id.Value); if (record != null) result = (FiscalCalendarModel)record; } return result; } public static IEnumerable GetCalendarTypes() { return new List { new SelectListItem { Text = FiscalCalendarModel.FiscalCalendarType.CalendarYear.ToDisplayValue(), Value = FiscalCalendarModel.FiscalCalendarType.CalendarYear.ToString() }, new SelectListItem { Text = FiscalCalendarModel.FiscalCalendarType.Calendar445.ToDisplayValue(), Value = FiscalCalendarModel.FiscalCalendarType.Calendar445.ToString() }, new SelectListItem { Text = FiscalCalendarModel.FiscalCalendarType.Calendar454.ToDisplayValue(), Value = FiscalCalendarModel.FiscalCalendarType.Calendar454.ToString() }, new SelectListItem { Text = FiscalCalendarModel.FiscalCalendarType.Calendar544.ToDisplayValue(), Value = FiscalCalendarModel.FiscalCalendarType.Calendar544.ToString() }, }; } public static IEnumerable GetWeekendings() { return new List { new SelectListItem { Text = DayOfWeek.Sunday.ToDisplayValue(), Value = DayOfWeek.Sunday.ToString() }, new SelectListItem { Text = DayOfWeek.Monday.ToDisplayValue(), Value = DayOfWeek.Monday.ToString() }, new SelectListItem { Text = DayOfWeek.Tuesday.ToDisplayValue(), Value = DayOfWeek.Tuesday.ToString() }, new SelectListItem { Text = DayOfWeek.Wednesday.ToDisplayValue(), Value = DayOfWeek.Wednesday.ToString() }, new SelectListItem { Text = DayOfWeek.Thursday.ToDisplayValue(), Value = DayOfWeek.Thursday.ToString() }, new SelectListItem { Text = DayOfWeek.Friday.ToDisplayValue(), Value = DayOfWeek.Friday.ToString() }, new SelectListItem { Text = DayOfWeek.Saturday.ToDisplayValue(), Value = DayOfWeek.Saturday.ToString() }, }; } public static IEnumerable GetYearTypes() { return new List { new SelectListItem { Text = FiscalCalendarModel.CalendarYearType.StandardYear.ToDisplayValue(), Value = FiscalCalendarModel.CalendarYearType.StandardYear.ToString() }, new SelectListItem { Text = FiscalCalendarModel.CalendarYearType.Only52Weeks.ToDisplayValue(), Value = FiscalCalendarModel.CalendarYearType.Only52Weeks.ToString() }, new SelectListItem { Text = FiscalCalendarModel.CalendarYearType.LastWeekDay.ToDisplayValue(), Value = FiscalCalendarModel.CalendarYearType.LastWeekDay.ToString() }, }; } public void SaveFiscalCalendarSettings(FiscalCalendarModel model, DateTime timePoint) { if (model == null) throw new ArgumentNullException("model"); DateTime validTimePoint = GetValidTimePoint(timePoint); FiscalCalendarSetting itemToSave = new FiscalCalendarSetting(); FiscalCalendarModel activeItem = this.GetActiveCalendarSettingsItem(validTimePoint); if (activeItem != null) { // If pending to activation calendar setting exist, we should remove them this.RemoveFutureCalendarSettingsItems(validTimePoint); if (activeItem.EffectiveChangeDate.HasValue && model.EffectiveChangeDate.HasValue && (activeItem.EffectiveChangeDate.Value == model.EffectiveChangeDate.Value)) { // Remove active item, because new item has the same EffStartDate as Active one this.RemoveCalendarSettingsItem(activeItem.Id); } } // Create new calendar settings model.CopyTo(itemToSave); itemToSave.Id = Guid.NewGuid(); _dbContext.FiscalCalendarSettings.Add(itemToSave); } /// /// Returns active Fiscal Calendar record for given time point /// /// /// public FiscalCalendarModel GetActiveCalendarSettingsItem(DateTime timePoint) { DateTime validTimePoint = this.GetValidTimePoint(timePoint); FiscalCalendarSetting rec = (from FiscalCalendarSetting item in _dbContext.FiscalCalendarSettings orderby item.EffectiveChangeDate descending where item.EffectiveChangeDate <= validTimePoint select item) .FirstOrDefault(); FiscalCalendarModel result = (FiscalCalendarModel)rec; if (result != null) { // Set status for record. We get active record for the timePoint, so status should be Active result.Status = FiscalCalendarModel.FiscalCalendarSettingStatus.Active; FiscalCalendarSetting nextRec = (from FiscalCalendarSetting item in _dbContext.FiscalCalendarSettings orderby item.EffectiveChangeDate ascending where item.EffectiveChangeDate > validTimePoint select item) .FirstOrDefault(); if (nextRec != null) result.ValidTo = nextRec.EffectiveChangeDate.AddDays(-1); } return result; } /// /// Removes all future Fiscal Calendar records relative to given time point /// /// protected void RemoveFutureCalendarSettingsItems(DateTime timePoint) { var itemsToRemove = _dbContext.FiscalCalendarSettings.Where(t => t.EffectiveChangeDate > timePoint); if (itemsToRemove.Count() > 0) _dbContext.FiscalCalendarSettings.RemoveRange(itemsToRemove); } protected void RemoveCalendarSettingsItem(Guid id) { var foundItems = _dbContext.FiscalCalendarSettings.Where(t => t.Id.Equals(id)); if (foundItems.Count() > 0) _dbContext.FiscalCalendarSettings.RemoveRange(foundItems); } #endregion #region Pended to remove (after implementation of in-memory calendar) //[Obsolete] //public void UpdateFiscalCalendars(FiscalCalendarModel model) //{ // switch (model.Type) // { // case FiscalCalendarModel.FiscalCalendarType.Calendar445: // case FiscalCalendarModel.FiscalCalendarType.Calendar454: // case FiscalCalendarModel.FiscalCalendarType.Calendar544: // Apply445Year(model); // break; // default: // ApplyCalendarYear(model); // break; // } //} ///// ///// Generate fiscal calendar periods by the Calendar Year algo. 52 weeks in the year. Last 1-2 days will be be adjusted. ///// ///// //private void ApplyCalendarYear(FiscalCalendarModel model) //{ // var startDate = model.CurrentYearStartDate ?? DateTime.Today; // var periodEndDate = startDate.AddYears(Settings.Default.GenerateCalendarYears); // var weekEndDate = startDate.AddDays(6); // var weeksList = new List(periodEndDate.Subtract(startDate).Days / 7); // approx number of weeks // var monthList = new List(periodEndDate.Subtract(startDate).Days / 30); // approx number of months // var quarterList = new List(periodEndDate.Subtract(startDate).Days / 91); // approx number of quarters // var yearList = new List(Settings.Default.GenerateCalendarYears); // FiscalCalendar prevWeek = null; // //FiscalCalendar prevMonth = null; // SA. Commented because are unused. Compliller show warnings // //FiscalCalendar prevQuarter = null; // //FiscalCalendar prevYear = null; // #region Load one latest record of each period type before startDate // #endregion // #region Create adjustment between last enddate from loaded record and startDate // #endregion // #region Generate records from startDate to periodEndDate // int weekPeriodInt = 1, monthWeekNum = 1, monthNum = 1, quarterNum = 1; // while (weekEndDate < periodEndDate) // { // #region Create a new year if new week is in the new calendar year // var year = yearList.LastOrDefault(); // if (year == null || year.EndDate < weekEndDate) // { // year = new FiscalCalendar // { // Id = Guid.NewGuid(), // Name = Constants.FISCAL_YEAR_NAME_TEMPLATE, // Type = (int)FiscalCalendarModel.FiscalYearType.Year, // YearInt = weekEndDate.Year, // QuarterInt = 4, // PeriodInt = 1, // StartDate = new DateTime(weekEndDate.Year, 1, 1), // EndDate = new DateTime(weekEndDate.Year, 12, 31), // SystemName = string.Format(Constants.FISCAL_YEAR_SYSTEMNAME_TEMPLATE, (weekEndDate.Year % 100).ToString("00")), // AdjustingPeriod = false, // NonWorking = 0 // }; // yearList.Add(year); // quarterNum = 1; // reset quarter of the year number // monthNum = 1; // reset month of the year number; // weekPeriodInt = 1; // reset week of the year number; // } // #endregion // #region Create a new quarter if new week is in the new quarter // var quarter = quarterList.LastOrDefault(); // if (quarter == null || quarter.EndDate < weekEndDate) // { // quarter = new FiscalCalendar // { // Id = Guid.NewGuid(), // Name = string.Format(Constants.FISCAL_QUARTER_NAME_TEMPLATE, quarterNum), // Type = (int)FiscalCalendarModel.FiscalYearType.Quarter, // YearInt = year.YearInt, // QuarterInt = quarterNum, // PeriodInt = quarterNum, // StartDate = (prevWeek == null) ? startDate : prevWeek.EndDate.AddDays(1), // EndDate = ((prevWeek == null) ? startDate : prevWeek.EndDate.AddDays(1)).AddDays(90),// add 7 days * 13 weeks in quarter = 91 days // SystemName = string.Format(Constants.FISCAL_QUARTER_SYSTEMNAME_TEMPLATE, quarterNum, (weekEndDate.Year % 100).ToString("00")), // AdjustingPeriod = false, // NonWorking = 0 // }; // if (quarterNum == 4) // quarter.EndDate = new DateTime(quarter.EndDate.Year, 12, 31); // quarterList.Add(quarter); // quarterNum++; // } // #endregion // #region Create a new month if new week is in the new month // var month = monthList.LastOrDefault(); // if (month == null || month.EndDate < weekEndDate) // { // month = new FiscalCalendar // { // Id = Guid.NewGuid(), // Name = string.Format(Constants.FISCAL_MONTH_NAME_TEMPLATE, weekEndDate.ToString("MMM").ToUpper()), // Type = (int)FiscalCalendarModel.FiscalYearType.Month, // YearInt = year.YearInt, // QuarterInt = quarterNum - 1, // PeriodInt = monthNum, // StartDate = (prevWeek == null) ? startDate : prevWeek.EndDate.AddDays(1), // EndDate = ((prevWeek == null) ? startDate : prevWeek.EndDate.AddDays(1)).AddDays(27),// add 7 days * 4 weeks in month = 28 days // SystemName = string.Format(Constants.FISCAL_MONTH_SYSTEMNAME_TEMPLATE, weekEndDate.ToString("MMM").ToUpper(), (weekEndDate.Year % 100).ToString("00")), // AdjustingPeriod = false, // NonWorking = 0 // }; // // add 5th week to the 1st month of the quarter // if (monthNum % 3 == 0) // { // month.EndDate = month.EndDate.AddDays(7); // } // // if we can place a 14th quarter week before 1st of January of next year then place it into the last quarter // if (quarter.QuarterInt == 4 && month.PeriodInt == 12) // month.EndDate = new DateTime(month.EndDate.Year, 12, 31); // monthList.Add(month); // monthNum++; // monthWeekNum = 1; // reset week of the month number // } // #endregion // // create a new week record // var week = new FiscalCalendar // { // Id = Guid.NewGuid(), // Name = string.Format(Constants.FISCAL_WEEK_NAME_TEMPLATE, month.Name.ToUpper(), monthWeekNum), // Type = (int)FiscalCalendarModel.FiscalYearType.Week, // YearInt = year.YearInt, // QuarterInt = quarterNum - 1, // PeriodInt = weekPeriodInt, // StartDate = (prevWeek == null) ? startDate : prevWeek.EndDate.AddDays(1), // EndDate = weekEndDate, // SystemName = string.Format(Constants.FISCAL_WEEK_SYSTEMNAME_TEMPLATE, month.Name.ToUpper(), monthWeekNum, (weekEndDate.Year % 100).ToString("00")), // AdjustingPeriod = false, // NonWorking = 0 // }; // weeksList.Add(week); // // adjust remaining 1-2 days at the end of the year // if (week.PeriodInt == 52) // { // monthWeekNum++; // weekPeriodInt++; // week = new FiscalCalendar() // { // Id = Guid.NewGuid(), // Name = string.Format(Constants.FISCAL_WEEK_NAME_TEMPLATE, month.Name.ToUpper(), monthWeekNum), // Type = (int)FiscalCalendarModel.FiscalYearType.Week, // YearInt = year.YearInt, // QuarterInt = quarterNum - 1, // PeriodInt = weekPeriodInt, // StartDate = week.EndDate.AddDays(1), // EndDate = new DateTime(week.EndDate.Year, 12, 31), // SystemName = string.Format(Constants.FISCAL_WEEK_SYSTEMNAME_TEMPLATE, month.Name.ToUpper(), monthWeekNum, (weekEndDate.Year % 100).ToString("00")), // AdjustingPeriod = false, // NonWorking = 1 // }; // weeksList.Add(week); // } // monthWeekNum++; // weekPeriodInt++; // prevWeek = week; // weekEndDate = week.EndDate.AddDays(7); // } // #endregion // #region Save generated fiscal periods to the DB // foreach (var FiscalCalendar in yearList) // { // _dbContext.FiscalCalendars.Add(FiscalCalendar); // } // foreach (var obj in quarterList) // { // _dbContext.FiscalCalendars.Add(obj); // } // foreach (var obj in monthList) // { // _dbContext.FiscalCalendars.Add(obj); // } // foreach (var obj in weeksList) // { // _dbContext.FiscalCalendars.Add(obj); // } // #endregion //} ///// ///// Generate fiscal calendar periods by the 5-4-4 algo. 52/53 weeks in the year. 53rd weeks added only when it become a 7 day gap at the end of the year. ///// ///// //private void Apply445Year(FiscalCalendarModel model) //{ // var startDate = model.CurrentYearStartDate ?? DateTime.Today; // var periodEndDate = startDate.AddYears(Settings.Default.GenerateCalendarYears); // var weekEndDate = startDate.AddDays(6); // var weeksList = new List(periodEndDate.Subtract(startDate).Days / 7); // approx number of weeks // var monthList = new List(periodEndDate.Subtract(startDate).Days / 30); // approx number of months // var quarterList = new List(periodEndDate.Subtract(startDate).Days / 91); // approx number of quarters // var yearList = new List(Settings.Default.GenerateCalendarYears); // FiscalCalendar prevWeek = null; // //FiscalCalendar prevMonth = null; // SA. Commented because are unused. Compiller shows warnings // //FiscalCalendar prevQuarter = null; // //FiscalCalendar prevYear = null; // #region Load one latest record of each period type before startDate // #endregion // #region Create adjustment between last enddate from loaded record and startDate // #endregion // #region Generate records from startDate to periodEndDate // //TODO: initial version, maybe required some refactoring // int weekPeriodInt = 1, monthWeekNum = 1, monthNum = 1, quarterNum = 1; // var isAddExtraWeek = false; // while (weekEndDate < periodEndDate) // { // #region Create a new year if new week is in the new calendar year // var year = yearList.LastOrDefault(); // if (year == null || year.EndDate < weekEndDate) // { // year = new FiscalCalendar // { // Id = Guid.NewGuid(), // Name = Constants.FISCAL_YEAR_NAME_TEMPLATE, // Type = (int)FiscalCalendarModel.FiscalYearType.Year, // YearInt = weekEndDate.Year, // QuarterInt = 4, // PeriodInt = 1, // StartDate = (prevWeek == null) ? startDate : prevWeek.EndDate.AddDays(1), // EndDate = ((prevWeek == null) ? startDate : prevWeek.EndDate.AddDays(1)).AddDays(363),// add 7 days * 52 weeks in year = 364 days // SystemName = string.Format(Constants.FISCAL_YEAR_SYSTEMNAME_TEMPLATE, (weekEndDate.Year % 100).ToString("00")), // AdjustingPeriod = false, // NonWorking = 0 // }; // // if we can place a 53rd week before 1st of January of next year then do it // isAddExtraWeek = year.EndDate.AddDays(7) < new DateTime(weekEndDate.Year + 1, startDate.Month, startDate.Day); // if (isAddExtraWeek) // year.EndDate = year.EndDate.AddDays(7); // yearList.Add(year); // quarterNum = 1; // reset quarter of the year number // monthNum = 1; // reset month of the year number; // weekPeriodInt = 1; // reset week of the year number; // } // #endregion // #region Create a new quarter if new week is in the new quarter // var quarter = quarterList.LastOrDefault(); // if (quarter == null || quarter.EndDate < weekEndDate) // { // quarter = new FiscalCalendar // { // Id = Guid.NewGuid(), // Name = string.Format(Constants.FISCAL_QUARTER_NAME_TEMPLATE, quarterNum), // Type = (int)FiscalCalendarModel.FiscalYearType.Quarter, // YearInt = year.YearInt, // QuarterInt = quarterNum, // PeriodInt = quarterNum, // StartDate = (prevWeek == null) ? startDate : prevWeek.EndDate.AddDays(1), // EndDate = ((prevWeek == null) ? startDate : prevWeek.EndDate.AddDays(1)).AddDays(90),// add 7 days * 13 weeks in quarter = 91 days // SystemName = string.Format(Constants.FISCAL_QUARTER_SYSTEMNAME_TEMPLATE, quarterNum, (weekEndDate.Year % 100).ToString("00")), // AdjustingPeriod = false, // NonWorking = 0 // }; // // if we can place a 14th week before 1st of January of next year then place it into the last quarter // if (isAddExtraWeek && quarterNum == 4) // quarter.EndDate = quarter.EndDate.AddDays(7); // quarterList.Add(quarter); // quarterNum++; // } // #endregion // #region Create a new month if new week is in the new month // var month = monthList.LastOrDefault(); // if (month == null || month.EndDate < weekEndDate) // { // month = new FiscalCalendar // { // Id = Guid.NewGuid(), // Name = string.Format(Constants.FISCAL_MONTH_NAME_TEMPLATE, weekEndDate.ToString("MMM").ToUpper()), // Type = (int)FiscalCalendarModel.FiscalYearType.Month, // YearInt = year.YearInt, // QuarterInt = quarterNum - 1, // PeriodInt = monthNum, // StartDate = (prevWeek == null) ? startDate : prevWeek.EndDate.AddDays(1), // EndDate = ((prevWeek == null) ? startDate : prevWeek.EndDate.AddDays(1)).AddDays(27),// add 7 days * 4 weeks in month = 28 days // SystemName = string.Format(Constants.FISCAL_MONTH_SYSTEMNAME_TEMPLATE, weekEndDate.ToString("MMM").ToUpper(), (weekEndDate.Year % 100).ToString("00")), // AdjustingPeriod = false, // NonWorking = 0 // }; // // add 5th week to the 1st month of the quarter // if (monthNum % 3 == 1) // { // month.EndDate = month.EndDate.AddDays(7); // } // // if we can place a 14th quarter week before 1st of January of next year then place it into the last quarter // if (isAddExtraWeek && quarter.QuarterInt == 4 && month.PeriodInt == 12) // month.EndDate = month.EndDate.AddDays(7); // monthList.Add(month); // monthNum++; // monthWeekNum = 1; // reset week of the month number // } // #endregion // // create a new week record // var week = new FiscalCalendar // { // Id = Guid.NewGuid(), // Name = string.Format(Constants.FISCAL_WEEK_NAME_TEMPLATE, weekEndDate.ToString("MMM").ToUpper(), monthWeekNum), // Type = (int)FiscalCalendarModel.FiscalYearType.Week, // YearInt = year.YearInt, // QuarterInt = quarterNum - 1, // PeriodInt = weekPeriodInt, // StartDate = (prevWeek == null) ? startDate : prevWeek.EndDate.AddDays(1), // EndDate = weekEndDate, // SystemName = string.Format(Constants.FISCAL_WEEK_SYSTEMNAME_TEMPLATE, weekEndDate.ToString("MMM").ToUpper(), monthWeekNum, (weekEndDate.Year % 100).ToString("00")), // AdjustingPeriod = false, // NonWorking = 0 // }; // weeksList.Add(week); // monthWeekNum++; // weekPeriodInt++; // prevWeek = week; // weekEndDate = weekEndDate.AddDays(7); // } // #endregion // #region Save generated fiscal periods to the DB // foreach (var FiscalCalendar in yearList) // { // _dbContext.FiscalCalendars.Add(FiscalCalendar); // } // foreach (var obj in quarterList) // { // _dbContext.FiscalCalendars.Add(obj); // } // foreach (var obj in monthList) // { // _dbContext.FiscalCalendars.Add(obj); // } // foreach (var obj in weeksList) // { // _dbContext.FiscalCalendars.Add(obj); // } // #endregion //} #endregion #region Calendar methods for RMO (pended for refactoring) public List GetFiscalCalendar(FiscalCalendarModel.FiscalYearType type, DateTime? startDate, DateTime? endDate, bool onlyWorkingWeeks, bool? adjustingPeriod) { return FilterWeeks(type, startDate, endDate, onlyWorkingWeeks, adjustingPeriod).OrderBy(x => x.StartDate).ToList(); } private IQueryable FilterWeeks(FiscalCalendarModel.FiscalYearType type, DateTime? startDate, DateTime? endDate, bool onlyWorkingWeeks, bool? adjustingPeriod) { var query = _dbContext.FiscalCalendars.Where(x => x.Type == (int)type && (!onlyWorkingWeeks || x.NonWorking == 0)); if (startDate.HasValue) query = query.Where(x => x.StartDate >= startDate.Value); if (endDate.HasValue) query = query.Where(x => x.EndDate <= endDate.Value); if (onlyWorkingWeeks) query = query.Where(x => x.NonWorking == 0); if (adjustingPeriod.HasValue) query = query.Where(x => x.AdjustingPeriod == adjustingPeriod); return query; } //// SA. ??? Check usage of the method //public List GetWeekEndings(FiscalCalendarModel.FiscalYearType type, DateTime startDate, DateTime endDate, bool onlyWorkingWeeks) //{ // return _dbContext.FiscalCalendars.Where(x => x.Type == (int)type && // x.EndDate >= startDate && // x.EndDate <= endDate && // (!onlyWorkingWeeks || x.NonWorking == 0)) // .Select(x => x.EndDate).ToList(); //} #endregion #region Holidays /// /// Return total count for holidays (not versions, but groups count) /// /// public int GetHolidaysCount() { int itemCount = (from Holiday item in _dbContext.Holidays select item.HolidayId) .Distinct() .Count(); return itemCount; } public IEnumerable GetHolidays(DateTime timePoint) { DateTime validTimePoint = this.GetValidTimePoint(timePoint); var activeItemsEffectiveDates = (from item in (from Holiday h in _dbContext.Holidays where h.EffectiveChangeDate <= validTimePoint select new { h.HolidayId, h.EffectiveChangeDate }) group item by item.HolidayId into g select new { HolidayId = g.Key, EffectiveChangeDate = (from d in g select d.EffectiveChangeDate).Max() }); // Get EffectiveChangeDate for the nearest future record in every Holiday Dictionary activeItemsValidityDates = (from item in (from Holiday h in _dbContext.Holidays where h.EffectiveChangeDate > validTimePoint select new { HolidayId = h.HolidayId, EffectiveChangeDate = h.EffectiveChangeDate }) group item by item.HolidayId into g select new { HolidayId = g.Key, EffectiveChangeDate = (from d in g select d.EffectiveChangeDate).Min() }) .ToDictionary(k => k.HolidayId, v => v.EffectiveChangeDate); // Build query for data to display List srcData = (from item in _dbContext.Holidays join dt in activeItemsEffectiveDates on new { item.HolidayId, item.EffectiveChangeDate } equals dt select new HolidayModel { Id = item.Id, HolidayId = item.HolidayId, Name = item.Name, OccurrenceType = (HolidayModel.HolidayOccurrence)item.OccurrenceType, OccurrenceMonth = item.OccurrenceMonth, OccurrenceMonthDay = item.OccurrenceMonthDay, OccurrenceWeekDay = (DayOfWeek)item.OccurrenceWeekDay, NonWorkingWeek = item.NonWorkingWeek, WorkingDay = item.WorkingDay, EffectiveChangeDate = item.EffectiveChangeDate, }).ToList(); List qry = srcData.Select(t => new HolidayDisplayModel { Id = t.Id, HolidayId = t.HolidayId, Name = t.Name, Date = HolidayModel.GetHolidayDate(t.OccurrenceType.Value, (short)t.OccurrenceMonth.Value, (short)t.OccurrenceMonthDay.Value, (short)t.OccurrenceWeekDay.Value), NonWorkingWeek = t.NonWorkingWeek, WorkingDay = t.WorkingDay, EffectiveChangeDate = t.EffectiveChangeDate.Value, EffectiveChangeDateAsText = t.EffectiveChangeDate.Value.ToShortDateString(), ValidTo = (activeItemsValidityDates.Keys.Contains(t.HolidayId) ? activeItemsValidityDates[t.HolidayId].AddDays(-1) : (DateTime?)null), }).ToList(); qry.ForEach(t => t.DateText = t.Date.ToShortDateString()); qry.ForEach(t => t.ValidToAsText = t.ValidTo.HasValue ? t.ValidTo.Value.ToShortDateString() : String.Empty); return qry; } /// /// Returns active Holiday record for given time point /// /// public HolidayModel GetActiveHolidayItem(Guid holidayId, DateTime timePoint) { DateTime validTimePoint = this.GetValidTimePoint(timePoint); Holiday rec = (from Holiday item in _dbContext.Holidays orderby item.EffectiveChangeDate descending where item.HolidayId.Equals(holidayId) && (item.EffectiveChangeDate <= validTimePoint) select item) .FirstOrDefault(); HolidayModel result = (HolidayModel)rec; if (result != null) { // Set status for record. We get active record for the timePoint, so status should be Active result.Status = HolidayModel.HolidayStatus.Active; Holiday nextRec = (from Holiday item in _dbContext.Holidays orderby item.EffectiveChangeDate ascending where item.HolidayId.Equals(holidayId) && (item.EffectiveChangeDate > validTimePoint) select item) .FirstOrDefault(); if (nextRec != null) result.ValidTo = nextRec.EffectiveChangeDate.AddDays(-1); } return result; } /// /// Removes all future Holiday records relative to given time point /// protected void RemoveFutureHolidayItems(Guid holidayId, DateTime timePoint) { var itemsToRemove = _dbContext.Holidays.Where(t => t.HolidayId.Equals(holidayId) && (t.EffectiveChangeDate > timePoint)); if (itemsToRemove.Count() > 0) _dbContext.Holidays.RemoveRange(itemsToRemove); } protected void RemoveHolidayItem(Guid id) { var foundItems = _dbContext.Holidays.Where(t => t.Id.Equals(id)); if (foundItems.Count() > 0) _dbContext.Holidays.RemoveRange(foundItems); } public void SaveHoliday(HolidayModel model, DateTime timePoint) { if (model == null) throw new ArgumentNullException("model"); DateTime validTimePoint = GetValidTimePoint(timePoint); Holiday itemToSave = new Holiday(); if (!model.HolidayId.Equals(Guid.Empty)) { HolidayModel activeItem = this.GetActiveHolidayItem(model.HolidayId, validTimePoint); if (activeItem != null) { // If pended to activation calendar setting exist, we should remove them this.RemoveFutureHolidayItems(model.HolidayId, validTimePoint); if (activeItem.EffectiveChangeDate.HasValue && model.EffectiveChangeDate.HasValue && (activeItem.EffectiveChangeDate.Value == model.EffectiveChangeDate.Value)) { // Remove active item, because new item has the same EffStartDate as Active one this.RemoveHolidayItem(activeItem.Id); } } } // Create new holiday item (group of item versions) model.CopyTo(itemToSave); itemToSave.Id = Guid.NewGuid(); if (model.HolidayId.Equals(Guid.Empty)) itemToSave.HolidayId = Guid.NewGuid(); _dbContext.Holidays.Add(itemToSave); } /// /// Returns history records for Holiday /// /// Current time point to mark Active, Inactive and Future records /// public HolidayListModel GetHolidayHistoryItems(Guid holidayId, DateTime timePoint) { bool activeItemSet = false; DateTime validTimePoint = GetValidTimePoint(timePoint); HolidayListModel result = new HolidayListModel(); List items = (from Holiday rec in _dbContext.Holidays orderby rec.EffectiveChangeDate descending where rec.HolidayId.Equals(holidayId) select rec).Take(10).ToList(); for (int index = 0; index < items.Count; index++) { HolidayModel currentItem = (HolidayModel)items[index]; result.Items.Add(currentItem); if (currentItem.EffectiveChangeDate.HasValue && (currentItem.EffectiveChangeDate.Value > validTimePoint)) { currentItem.Status = HolidayModel.HolidayStatus.Future; } else { if (!activeItemSet) { currentItem.Status = HolidayModel.HolidayStatus.Active; activeItemSet = true; } else { currentItem.Status = HolidayModel.HolidayStatus.Inactive; } } } return result; } public void DeleteHoliday(Guid holidayId) { var foundItems = _dbContext.Holidays.Where(t => t.HolidayId.Equals(holidayId)); if (foundItems.Count() < 1) throw new Exception(String.Format("Holiday (holidayId = '{0}') not found", holidayId.ToString())); _dbContext.Holidays.RemoveRange(foundItems); } #endregion } }