using System; using System.Collections.Generic; using System.Data.Entity; using System.Linq; using EnVisage.Models; 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 /// /// 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 = timePoint.Date; 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) { if (!id.HasValue || id.Value == Guid.Empty) return null; var record = _dbContext.FiscalCalendarSettings.Find(id.Value); var 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 = timePoint.Date; 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 = timePoint.Date; 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 FiscalCalendar GetFirstWeek(DateTime? date = null) { var query = _dbContext.FiscalCalendars.AsNoTracking() .Where(x => x.Type == (int)FiscalCalendarModel.FiscalYearType.Week && x.NonWorking == 0); if (date.HasValue) query = query.Where(x => x.EndDate >= date.Value); return query.OrderBy(x => x.StartDate).FirstOrDefault(); } public FiscalCalendar GetLastWeek() { var lastWeek = _dbContext.FiscalCalendars.AsNoTracking() .Where(x => x.Type == (int)FiscalCalendarModel.FiscalYearType.Week && x.NonWorking == 0) .OrderByDescending(x => x.EndDate) .FirstOrDefault(); return lastWeek; } public List GetFiscalCalendar(FiscalCalendarModel.FiscalYearType type, DateTime? startDate, DateTime? endDate, bool onlyWorkingWeeks, bool? adjustingPeriod, bool includePartialWeeks) { return FilterWeeks(type, startDate, endDate, onlyWorkingWeeks, adjustingPeriod, includePartialWeeks).OrderBy(x => x.StartDate).ToList(); } public List GetFiscalCalendar(FiscalCalendarModel.FiscalYearType type, List weeks, bool onlyWorkingWeeks, bool? adjustingPeriod, bool includePartialWeeks) { if (weeks == null || weeks.Count <= 0) return new List(); var fc = FilterWeeks(type, null, null, onlyWorkingWeeks, adjustingPeriod, includePartialWeeks) .Where(x => weeks.Contains(x.EndDate)) .OrderBy(x => x.StartDate) .ToList(); return fc; } public Dictionary>> CastWeekEndingsToTree(List weekEndings) { var weekEndingsHierarchy = weekEndings.Select(x => new { x.Year, x.Month, x.Day }) .GroupBy(x => x.Year) .OrderBy(x => x.Key) .ToDictionary(year => year.Key.ToString(), months => months.GroupBy(month => month.Month) .OrderBy(month => month.Key) .ToDictionary(month => month.Key.ToString(), days => days.Select(day => day.Day).OrderBy(day => day).ToList())); return weekEndingsHierarchy; } private IQueryable FilterWeeks(FiscalCalendarModel.FiscalYearType type, DateTime? startDate, DateTime? endDate, bool onlyWorkingWeeks, bool? adjustingPeriod, bool includePartialWeeks) { var query = _dbContext.FiscalCalendars.AsNoTracking().Where(x => x.Type == (int)type && (!onlyWorkingWeeks || x.NonWorking == 0)); if (startDate.HasValue) query = query.Where(x => x.EndDate >= startDate.Value); if (endDate.HasValue) { // lets suppose endDate = 2017-01-31 if (includePartialWeeks) // in this case 2017-01-28 - 2017-02-03 will be the last week in the result query = query.Where(x => x.StartDate <= endDate.Value); else // in this case 2017-01-21 - 2017-01-27 will be the last week in the result 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; } #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.HolidayGroupId) .Distinct() .Count(); return itemCount; } public IEnumerable GetHolidays(DateTime timePoint) { DateTime validTimePoint = timePoint.Date; var activeItemsEffectiveDates = from item in (from Holiday h in _dbContext.Holidays where h.EffectiveChangeDate <= validTimePoint select new { h.HolidayGroupId, h.EffectiveChangeDate }) group item by item.HolidayGroupId into g select new { HolidayGroupId = 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 { HolidayGroupId = h.HolidayGroupId, EffectiveChangeDate = h.EffectiveChangeDate }) group item by item.HolidayGroupId into g select new { HolidayGroupId = g.Key, EffectiveChangeDate = (from d in g select d.EffectiveChangeDate).Min() }) .ToDictionary(k => k.HolidayGroupId, v => v.EffectiveChangeDate); // Build query for data to display List srcData = (from item in _dbContext.Holidays join dt in activeItemsEffectiveDates on new { item.HolidayGroupId, item.EffectiveChangeDate } equals dt select item).ToList(); List qry = srcData.Select(t => (HolidayDisplayModel)(HolidayModel)t).ToList(); qry.ForEach(t => t.ValidTo = activeItemsValidityDates.Keys.Contains(t.HolidayGroupId) ? activeItemsValidityDates[t.HolidayGroupId].AddDays(-1) : (DateTime?)null); 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 holidayGroupId, DateTime timePoint) { DateTime validTimePoint = timePoint.Date; Holiday rec = (from Holiday item in _dbContext.Holidays orderby item.EffectiveChangeDate descending where item.HolidayGroupId.Equals(holidayGroupId) && (item.EffectiveChangeDate <= validTimePoint) select item) .FirstOrDefault(); var result = (HolidayModel)rec; if (!result.MultipleDaysHoliday) result.EndDate = null; if (result.Id != Guid.Empty) { // 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.HolidayGroupId.Equals(holidayGroupId) && (item.EffectiveChangeDate > validTimePoint) select item) .FirstOrDefault(); if (nextRec != null) result.ValidTo = nextRec.EffectiveChangeDate.AddDays(-1); result.IncludexpenditureCategories = result.ExpenditureCategories != null && result.ExpenditureCategories.Count > 0; } return result; } /// /// Removes all future Holiday records relative to given time point /// protected void RemoveFutureHolidayItems(Guid holidayGroupId, DateTime timePoint, Guid? exceptId = null) { var itemsToRemove = _dbContext.Holidays.Where(t => t.HolidayGroupId.Equals(holidayGroupId) && (t.EffectiveChangeDate > timePoint) && (!exceptId.HasValue || !t.Id.Equals(exceptId.Value))); if (itemsToRemove.Count() > 0) { _dbContext.Holiday2Team.RemoveRange(itemsToRemove.SelectMany(x => x.Holiday2Team)); _dbContext.Holiday2PeopleResource.RemoveRange(itemsToRemove.SelectMany(x => x.Holiday2PeopleResource)); _dbContext.Holiday2ExpenditureCategory.RemoveRange(itemsToRemove.SelectMany(x => x.Holiday2ExpenditureCategory)); _dbContext.Holidays.RemoveRange(itemsToRemove); } } protected void RemoveHolidayItem(Guid id) { var foundItems = _dbContext.Holidays.Where(t => t.Id.Equals(id)); if (foundItems.Count() > 0) { _dbContext.Holiday2Team.RemoveRange(foundItems.SelectMany(x => x.Holiday2Team)); _dbContext.Holiday2PeopleResource.RemoveRange(foundItems.SelectMany(x => x.Holiday2PeopleResource)); _dbContext.Holiday2ExpenditureCategory.RemoveRange(foundItems.SelectMany(x => x.Holiday2ExpenditureCategory)); _dbContext.Holidays.RemoveRange(foundItems); } } public void SaveHoliday(HolidayModel model, DateTime timePoint) { if (model == null) throw new ArgumentNullException("model"); // Create new holiday item (group of item versions) var itemToSave = new Holiday(); model.CopyTo(itemToSave); itemToSave.Id = Guid.NewGuid(); if (model.HolidayGroupId.Equals(Guid.Empty)) itemToSave.HolidayGroupId = Guid.NewGuid(); if (!model.CompanyImpactAllResources) { if (!model.IncludexpenditureCategories) { var holiday2PeopleResourceToAddCollection = new List(); foreach (var x in model.Resources) { var item = new Holiday2PeopleResource { Id = Guid.NewGuid(), HolidayId = itemToSave.Id, ResourceId = x }; holiday2PeopleResourceToAddCollection.Add(item); } _dbContext.Holiday2PeopleResource.AddRange(holiday2PeopleResourceToAddCollection); var holiday2TeamToAddCollection = new List(); foreach (var x in model.Teams) { var item = new Holiday2Team { Id = Guid.NewGuid(), HolidayId = itemToSave.Id, TeamId = x }; holiday2TeamToAddCollection.Add(item); } _dbContext.Holiday2Team.AddRange(holiday2TeamToAddCollection); _dbContext.Holiday2ExpenditureCategory.RemoveRange(itemToSave.Holiday2ExpenditureCategory); } else { var holiday2ExpenditureCategoryToAddCollection = new List(); foreach (var x in model.ExpenditureCategories) { var item = new Holiday2ExpenditureCategory { Id = Guid.NewGuid(), HolidayId = itemToSave.Id, ExpenditureCategoryId = x }; holiday2ExpenditureCategoryToAddCollection.Add(item); } _dbContext.Holiday2ExpenditureCategory.AddRange(holiday2ExpenditureCategoryToAddCollection); _dbContext.Holiday2Team.RemoveRange(itemToSave.Holiday2Team); _dbContext.Holiday2PeopleResource.RemoveRange(itemToSave.Holiday2PeopleResource); } } else { _dbContext.Holiday2Team.RemoveRange(itemToSave.Holiday2Team); _dbContext.Holiday2PeopleResource.RemoveRange(itemToSave.Holiday2PeopleResource); _dbContext.Holiday2ExpenditureCategory.RemoveRange(itemToSave.Holiday2ExpenditureCategory); } var validTimePoint = timePoint.Date; HolidayModel activeItem = null; if (!model.HolidayGroupId.Equals(Guid.Empty)) { activeItem = this.GetActiveHolidayItem(model.HolidayGroupId, validTimePoint); if ((activeItem != null) && !activeItem.Id.Equals(itemToSave.Id)) { if (model.EffectiveChangeDate.HasValue) // Remove holiday allocations this.RemoveHolidayAllocations(model.HolidayGroupId, model.EffectiveChangeDate.Value); } else activeItem = null; } _dbContext.BulkSaveChanges(); // Holiday Allocations generation HolidayModel modelCopy = model.Clone(); modelCopy.Id = itemToSave.Id; modelCopy.HolidayGroupId = itemToSave.HolidayGroupId; this.CreateHolidayAllocations(modelCopy); _dbContext.Holidays.Add(itemToSave); _dbContext.SaveChanges(); if (!model.HolidayGroupId.Equals(Guid.Empty)) { if (activeItem != null) { // If pended to activation calendar setting exist, we should remove them this.RemoveFutureHolidayItems(model.HolidayGroupId, validTimePoint, itemToSave.Id); 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); } } } } /// /// Returns history records for Holiday /// /// Current time point to mark Active, Inactive and Future records /// public HolidayListModel GetHolidayHistoryItems(Guid holidayGroupId, DateTime timePoint) { bool activeItemSet = false; DateTime validTimePoint = timePoint.Date; HolidayListModel result = new HolidayListModel(); List items = (from Holiday rec in _dbContext.Holidays orderby rec.EffectiveChangeDate descending where rec.HolidayGroupId.Equals(holidayGroupId) select rec).Take(10).ToList(); for (int index = 0; index < items.Count; index++) { HolidayModel currentItem = (HolidayModel)items[index]; 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; } } HolidayDisplayModel currentDisplayItem = (HolidayDisplayModel)currentItem; result.Items.Add(currentDisplayItem); } return result; } public void DeleteHoliday(Guid holidayGroupId) { var foundItems = _dbContext.Holidays.Where(t => t.HolidayGroupId.Equals(holidayGroupId)); if (foundItems.Count() < 1) throw new Exception(String.Format("Holiday (holidayGroupId = '{0}') not found", holidayGroupId.ToString())); _dbContext.Holiday2Team.RemoveRange(foundItems.SelectMany(x => x.Holiday2Team)); _dbContext.Holiday2PeopleResource.RemoveRange(foundItems.SelectMany(x => x.Holiday2PeopleResource)); _dbContext.Holiday2ExpenditureCategory.RemoveRange(foundItems.SelectMany(x => x.Holiday2ExpenditureCategory)); _dbContext.Holidays.RemoveRange(foundItems); // Remove all holiday allocations for all its group of records this.RemoveHolidayAllocations(holidayGroupId); } #endregion #region Holiday Allocations /// /// Removes all holiday allocation records for specified Holiday Group /// protected void RemoveHolidayAllocations(Guid holidayGroupId) { var itemsToRemove = _dbContext.HolidayAllocations.Where(x => x.HolidayGroupId.Equals(holidayGroupId)); _dbContext.HolidayAllocations.RemoveRange(itemsToRemove); } /// /// Return date for start team membership (according to fiscal calendar) by specified date point /// /// Source date point /// /// public static DateTime GetDateForTeamMembershipStart(DateTime date, EnVisageEntities dbContext) { var fiscalWeeks = GetFiscalCalendarCurrentAndNextWeeksForDate(date, dbContext); if (fiscalWeeks.Count > 0) { if (fiscalWeeks.First().StartDate == date) return fiscalWeeks.First().StartDate; if (fiscalWeeks.Count > 1) return fiscalWeeks.Last().StartDate; return Constants.FISCAL_CALENDAR_MAX_DATE; } string errMessage = "There are no full fiscal weeks for the specified date point: {0}"; throw new Exception(String.Format(errMessage, date.ToShortDateString())); } /// /// Return date for end team membership (according to fiscal calendar) by specified date point /// /// Source date point /// /// public static DateTime? GetDateForTeamMembershipEnd(DateTime date, EnVisageEntities dbContext) { var fiscalWeeks = GetFiscalCalendarCurrentAndPrevWeeksForDate(date, dbContext); if (fiscalWeeks.Count > 0) { if (fiscalWeeks.First().EndDate == date) return fiscalWeeks.First().EndDate; if (fiscalWeeks.Count > 1) return fiscalWeeks.Last().EndDate; } return null; } /// /// Removes holiday allocations for specified Holiday Group from specified time point and to the future /// protected void RemoveHolidayAllocations(Guid holidayGroupId, DateTime startingPoint) { // Note: // 1. We must find the week, startingPoint belongs to. Let's call it Updatable week. // If Holiday allocations for this week exist, we should update this allocations record, // because this record can share allocations for current holiday item and its previous version. // 2. We must remove all the allocations, next to the Updatable week // (they will be calculated and created againt, when the holiday item saved) DateTime startDateCorrected = startingPoint.Date; DateRange we = GetFiscalCalendarWeekForDate(startingPoint, _dbContext); DateTime updatableWeekWe = we.EndDate; // Get updatable allocations record var updatableAllocRec = _dbContext.HolidayAllocations.FirstOrDefault(x => x.HolidayGroupId.Equals(holidayGroupId) && (x.WeekEndingDate == updatableWeekWe)); if (updatableAllocRec != null) { // Allocation record for update found. Perform update int daysAffected = Convert.ToInt32((updatableWeekWe - startDateCorrected).TotalDays) + 1; for (int index = 0; index < daysAffected; index++) { DateTime currentDate = startDateCorrected.AddDays(index); switch (currentDate.DayOfWeek) { case DayOfWeek.Sunday: updatableAllocRec.Sunday = true; break; case DayOfWeek.Monday: updatableAllocRec.Monday = true; break; case DayOfWeek.Tuesday: updatableAllocRec.Tuesday = true; break; case DayOfWeek.Wednesday: updatableAllocRec.Wednesday = true; break; case DayOfWeek.Thursday: updatableAllocRec.Thursday = true; break; case DayOfWeek.Friday: updatableAllocRec.Friday = true; break; case DayOfWeek.Saturday: updatableAllocRec.Saturday = true; break; } } } // Perform deleting of future allocation records var recsToRemove = _dbContext.HolidayAllocations .Where(x => x.HolidayGroupId.Equals(holidayGroupId) && (x.WeekEndingDate > updatableWeekWe)); _dbContext.HolidayAllocations.RemoveRange(recsToRemove); } /// /// Creates holiday allocations records for holiday /// protected void CreateHolidayAllocations(HolidayModel model) { if (model.WorkingDays) return; // Get holiday days and corresponding weekendings in the Fiscal Calendar List holidayDates = model.GetHolidayDates(); if (holidayDates.Count < 1) return; var weekendingsForDates = GetFiscalCalendarWeekendingsForDates(holidayDates); var newAllocations = new List(); Dictionary allocationsForDates = _dbContext.HolidayAllocations.Where(a => a.HolidayGroupId.Equals(model.HolidayGroupId) && weekendingsForDates.Values.Contains(a.WeekEndingDate)). ToDictionary(k => k.WeekEndingDate, v => v); foreach (DateTime holidayDate in holidayDates) { DateTime? we = weekendingsForDates[holidayDate]; if (we.HasValue) { HolidayAllocation ha = null; if (allocationsForDates.ContainsKey(we.Value)) { ha = allocationsForDates[we.Value]; ha.HolidayId = model.Id; } else { ha = new HolidayAllocation { Id = Guid.NewGuid(), HolidayId = model.Id, HolidayGroupId = model.HolidayGroupId, WeekEndingDate = we.Value, Sunday = true, Monday = true, Tuesday = true, Wednesday = true, Thursday = true, Friday = true, Saturday = true }; newAllocations.Add(ha); allocationsForDates[we.Value] = ha; } switch (holidayDate.DayOfWeek) { case DayOfWeek.Sunday: ha.Sunday = false; break; case DayOfWeek.Monday: ha.Monday = false; break; case DayOfWeek.Tuesday: ha.Tuesday = false; break; case DayOfWeek.Wednesday: ha.Wednesday = false; break; case DayOfWeek.Thursday: ha.Thursday = false; break; case DayOfWeek.Friday: ha.Friday = false; break; case DayOfWeek.Saturday: ha.Saturday = false; break; } } } _dbContext.HolidayAllocations.AddRange(newAllocations); } /// /// Gets weekly holiday allocations grouped by PeopleResourceId and then by weekending date. /// /// A collection of people resource identifiers. /// Start date of period to search. /// End date of period to search. /// A dictionary of dictionaries [PeopleResourceId, [Weekending, AdjustmentKoeff]] public Dictionary> GetHolidayAllocationsByResource(DateTime? startDate, DateTime? endDate, IEnumerable resourceIds = null, IEnumerable teamIds = null) { if (endDate <= startDate) return new Dictionary>(); var result = new Dictionary>(); var list = GetHolidayAllocations(startDate, endDate, resourceIds, teamIds); foreach (var item in list.OrderBy(t => t.WeekEndingDate)) { if (!result.ContainsKey(item.PeopleResourceId.Value)) result.Add(item.PeopleResourceId.Value, new Dictionary()); if (!result[item.PeopleResourceId.Value].ContainsKey(item.WeekEndingDate)) result[item.PeopleResourceId.Value].Add(item.WeekEndingDate, item.AdjustmentKoeff); } return result; } public IEnumerable GetHolidayAllocations(DateTime? startDate, DateTime? endDate, IEnumerable resourceIds = null, IEnumerable teamIds = null) { if (endDate <= startDate) return new List(); var holidaysQuery = _dbContext.VW_HolidayAllocation.AsNoTracking().AsQueryable(); if (resourceIds != null && resourceIds.Any()) holidaysQuery = holidaysQuery.Where(t => t.PeopleResourceId.HasValue && resourceIds.Contains(t.PeopleResourceId.Value)); if (teamIds != null && teamIds.Any()) holidaysQuery = holidaysQuery.Where(t => t.TeamId.HasValue && teamIds.Contains(t.TeamId.Value)); if (startDate.HasValue) holidaysQuery = holidaysQuery.Where(x => x.WeekEndingDate >= startDate); if (endDate.HasValue) holidaysQuery = holidaysQuery.Where(x => x.WeekEndingDate <= endDate); return holidaysQuery.ToList(); } #endregion #region Get Weekendings Data methods /// /// Return StartDate and EndDate for fiscal week containing specified date point /// public static DateRange GetFiscalCalendarWeekForDate(DateTime date, EnVisageEntities dbContext) { if (dbContext == null) throw new ArgumentNullException("dbContext"); string errMessage; DateTime dateCorrected = date.Date; var weekending = dbContext.FiscalCalendars .Where(c => (c.Type == (int)FiscalCalendarModel.FiscalYearType.Week) && (c.StartDate <= dateCorrected) && (c.EndDate >= dateCorrected)).FirstOrDefault(); if (weekending == null) { errMessage = String.Format("No fiscal week, containing specified date found (date point: {0})", date.ToShortDateString()); throw new Exception(errMessage); } return (DateRange)weekending; } /// /// Return StartDate and EndDate for previous fiscal week for the one, containing specified date point /// public static DateRange GetFiscalCalendarPrevWeekForDate(DateTime date, EnVisageEntities dbContext) { if (dbContext == null) throw new ArgumentNullException("dbContext"); string errMessage; DateTime dateCorrected = date.Date; var weekendingsOrdered = dbContext.FiscalCalendars.AsNoTracking() .Where(c => (c.Type == (int)FiscalCalendarModel.FiscalYearType.Week) && (c.StartDate <= dateCorrected)).OrderByDescending(x => x.StartDate) .Take(2).ToList(); if (weekendingsOrdered.Count < 2) { errMessage = String.Format("No previous fiscal week for the one, containing specified date found (date point: {0})", date.ToShortDateString()); throw new Exception(errMessage); } return (DateRange)weekendingsOrdered.Last(); } /// /// Returns current fiscal week (containing date point) and the previous one (1st = current, 2nd = prev) /// public static List GetFiscalCalendarCurrentAndPrevWeeksForDate(DateTime date, EnVisageEntities dbContext) { if (dbContext == null) throw new ArgumentNullException("dbContext"); DateTime dateCorrected = date.Date; var weekendingsOrdered = dbContext.FiscalCalendars.AsNoTracking() .Where(c => (c.Type == (int)FiscalCalendarModel.FiscalYearType.Week) && (c.StartDate <= dateCorrected)).OrderByDescending(x => x.StartDate) .Take(2).ToList(); List result = weekendingsOrdered.Select(x => (DateRange)x).ToList(); return result; } /// /// Returns current fiscal week (containing date point) and the next one (1st = current, 2nd = next) /// public static List GetFiscalCalendarCurrentAndNextWeeksForDate(DateTime date, EnVisageEntities dbContext) { if (dbContext == null) throw new ArgumentNullException("dbContext"); DateTime dateCorrected = date.Date; var weekendingsOrdered = dbContext.FiscalCalendars.AsNoTracking() .Where(c => (c.Type == (int)FiscalCalendarModel.FiscalYearType.Week) && (c.EndDate >= dateCorrected)).OrderBy(x => x.StartDate) .Take(2).ToList(); List result = weekendingsOrdered.Select(x => (DateRange)x).ToList(); return result; } /// /// Returns weekendings for the fiscal weeks containing specified dates. Result is the dictionary, /// where keys are specified dates, and values are their corresponding weekendings /// protected Dictionary GetFiscalCalendarWeekendingsForDates(List dates) { List datesCorrected = dates.Select(d => d.Date).OrderBy(d => d).ToList(); DateTime minDate = datesCorrected.First(); DateTime maxDate = datesCorrected.Last(); DateTime fcBrowseStartDate = minDate.AddMonths(-1); DateTime fcBrowseEndDate = maxDate.AddMonths(1); var weekendings = _dbContext.FiscalCalendars .Where(c => (c.Type == (int)FiscalCalendarModel.FiscalYearType.Week) && (c.StartDate >= fcBrowseStartDate) && (c.EndDate <= fcBrowseEndDate)) .Select(c => new { c.StartDate, c.EndDate }).ToList(); Dictionary couples = datesCorrected.Select(d => new KeyValuePair(d, weekendings .Where(c => (c.StartDate <= d) && (c.EndDate >= d)) .Select(c => c.EndDate).FirstOrDefault()) ).ToDictionary(k => k.Key, v => (v.Value.HasValue && v.Value != DateTime.MinValue) ? v.Value : (DateTime?)null); return couples; } public static List GetWeekendingsByRange(DateTime startDate, DateTime? endDate, EnVisageEntities dbContext) { var weekendings = dbContext.FiscalCalendars.AsNoTracking().Where(x => (x.Type == (int)FiscalCalendarModel.FiscalYearType.Week) && (x.EndDate >= startDate) && (!endDate.HasValue || (x.StartDate <= endDate.Value)) && (x.AdjustingPeriod == false)) .OrderBy(x => x.StartDate) .Select(x => x.EndDate).ToList(); return weekendings; } /// /// Returns Fiscal Calendar Weeks (start & end dates) for specified date range /// public static List GetFiscalCalendarWeeksByRange(DateTime startDate, DateTime? endDate, EnVisageEntities dbContext) { List result = dbContext.FiscalCalendars.AsNoTracking().Where(x => (x.Type == (int)FiscalCalendarModel.FiscalYearType.Week) && (x.EndDate >= startDate) && (!endDate.HasValue || (x.StartDate <= endDate.Value)) && (x.AdjustingPeriod == false)) .OrderBy(x => x.StartDate).ToList() .Select(x => (DateRange)x).ToList(); return result; } /// /// Returns StartDate for the earliest period of _type_ in fiscal calendar /// /// Fiscal Calendar Type /// public static DateTime GetCalendarMinDate(FiscalCalendarModel.FiscalYearType type = FiscalCalendarModel.FiscalYearType.Week) { using (var dbContext = new EnVisageEntities()) { var result = dbContext.FiscalCalendars.Where(x => x.Type == (int)type).Select(x => x.StartDate).Min(); return result; } } /// /// Returns EndDate for the latest period of _type_ in fiscal calendar /// /// Fiscal Calendar Type /// public static DateTime GetCalendarMaxDate(FiscalCalendarModel.FiscalYearType type = FiscalCalendarModel.FiscalYearType.Week) { using (var dbContext = new EnVisageEntities()) { var result = dbContext.FiscalCalendars.Where(x => x.Type == (int)type).Select(x => x.EndDate).Max(); return result; } } #endregion } /// /// Class for representing Date Ranges /// public class DateRange { public DateTime StartDate { get; set; } public DateTime EndDate { get; set; } public static explicit operator DateRange(DateRangeNullable obj) { if (obj == null) return null; if (!obj.StartDate.HasValue || !obj.EndDate.HasValue) throw new InvalidCastException("DateRangeNullable can't be casted to DateRange because of null values"); var result = new DateRange() { StartDate = obj.StartDate.Value, EndDate = obj.EndDate.Value }; return result; } public static explicit operator DateRange(FiscalCalendar obj) { if (obj == null) return null; var result = new DateRange() { StartDate = obj.StartDate, EndDate = obj.EndDate }; return result; } } /// /// Class for representing Date Ranges with nullable bounds /// public class DateRangeNullable { public DateTime? StartDate { get; set; } public DateTime? EndDate { get; set; } public static explicit operator DateRangeNullable(DateRange obj) { if (obj == null) return null; var result = new DateRangeNullable() { StartDate = obj.StartDate, EndDate = obj.EndDate }; return result; } // Returns True, if date fits the range public bool IsInRange(DateTime date) { return (!StartDate.HasValue || (StartDate.HasValue && StartDate.Value <= date)) && (!EndDate.HasValue || (EndDate.HasValue && EndDate.Value >= date)); } } }