using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Web.Mvc; using System.Linq; using EnVisage.Code; using EnVisage.Code.HtmlHelpers; using Newtonsoft.Json; namespace EnVisage.Models { public class ScenarioModel : IBaseModel, IValidatableObject { #region Enums and subclasses //public enum ScenarioStatus //{ // InActive = 0, // Active = 1 //} public enum ScenarioProbability { Low = 1, Expected = 2, High = 3 } public class ExpenditureItem { public Guid Id { get; set; } public string Group { get; set; } public string Name { get; set; } public bool Checked { get; set; } } public class ProjectItem { public Guid Id { get; set; } public string Name { get; set; } public bool Checked { get; set; } public bool IsRevenueGenerating { get; set; } } #endregion #region Properties public Guid Id { get; set; } [Required] [MaxLength(100)] public string Name { get; set; } /// /// Gets or sets an Id of the template. /// [Display(Name = "Template")] public Guid? TemplateId { get; set; } /// /// Gets or sets an Id of the project. /// public Guid ParentId { get; set; } public string BackUrl { get; set; } public string BackName { get; set; } /// /// Gets or sets a color that will be used for scenario in reports. /// [MaxLength(8)] public string Color { get; set; } /// /// Gets or sets a type of scenario. /// [Required] [Display(Name = "Scenario Type")] public ScenarioType Type { get; set; } [Display(Name = "Create as an active scenario")] public bool IsActiveScenario { get; set; } [Display(Name = "Allow prior week values to be adjusted?")] public bool AllowAdjustment { get; set; } [Display(Name = "Use this project's ACTUALS data for weekending dates that apply?")] public bool UseActuals { get; set; } [Display(Name = "Child Scenarios #")] public int ChildScenarios { get; set; } [DataType(DataType.Date), DisplayFormat(DataFormatString = "{0:MM/dd/yy}", ApplyFormatInEditMode = true)] [Display(Name = "Prior Week Cut-off")] public DateTime? PriorWeekCutOff { get; set; } [Required] [DataType(DataType.Date), DisplayFormat(DataFormatString = "{0:MM/dd/yy}", ApplyFormatInEditMode = true)] [Display(Name = "Scenario Start Date")] public DateTime? StartDate { get; set; } public DateTime? StartDateDisabled { get; set; } [Required] [DataType(DataType.Date), DisplayFormat(DataFormatString = "{0:MM/dd/yy}", ApplyFormatInEditMode = true)] [Display(Name = "Scenario End Date")] public DateTime? EndDate { get; set; } [Required] [DataType(DataType.Date), DisplayFormat(DataFormatString = "{0:MM/dd/yy}", ApplyFormatInEditMode = true)] [DateRange(ErrorMessage = "Scenario End Date should be greater than or equal Scenario Start Date and Milestone Start Date should be greater than or equal to Scenario Start Date and less than or equal to Scenario End Date")] [Display(Name = "Milestone Start Date")] public DateTime? MilestoneStartDate { get; set; } [Display(Name = "Growth Scenario?")] public bool GrowthScenario { get; set; } [Display(Name = "Projected Revenue")] [DataType(DataType.Currency)] [Range(0, 100000000000)] public decimal? ProjectedRevenue { get; set; } [Display(Name = "Projected Expense")] [DataType(DataType.Currency)] [Range(0, 100000000000)] public decimal? TDDirectCosts { get; set; } public decimal BUDirectCosts { get; set; } public bool IsRevenueGenerating { get; set; } [Display(Name = "Use L&M Margin?")] public bool UseLMMargin { get; set; } [Display(Name = "Gross Margin")] [Range(0, Int32.MaxValue)] public decimal? GrossMargin { get; set; } [Display(Name = "L&M Margin")] [Range(0, Int32.MaxValue)] public decimal? LMMargin { get; set; } [Display(Name = "Total Milestones")] [Range(1, 2147483647)] public int TotalMilestones { get; set; } [Display(Name = "Labor Split Percentage")] [Range(0, 100)] [UIHint("Slider")] public short LaborSplitPercentage { get; set; } [Display(Name = "Freeze Resource")] public bool FreezeResource { get; set; } public IList Expenditures { get; set; } public bool AsDraft { get; set; } public ScenarioStatus? Status { get; set; } #endregion #region Constructors public ScenarioModel() { Expenditures = new List(); IsActiveScenario = true; UseActuals = true; IsRevenueGenerating = true; } #endregion #region Methods /// /// Casts a obect to the object of type . /// /// A object. /// A object filled with data from db. public static explicit operator ScenarioModel(Scenario obj) { if (obj == null) return null; var model = new ScenarioModel { Id = obj.Id, Name = obj.Name, Color = obj.Color, TemplateId = ScenarioStatus.Draft.GetHashCode().Equals(obj.Status) ? null : obj.TemplateId, ParentId = obj.ParentId ?? Guid.Empty, Type = (ScenarioType)obj.Type, ChildScenarios = obj.ChildScenarios.Count, EndDate = obj.EndDate, StartDate = obj.StartDate, StartDateDisabled = obj.StartDate, GrossMargin = obj.ExpectedGrossMargin.HasValue ? Convert.ToInt64(obj.ExpectedGrossMargin * 100) : (decimal?)null, GrowthScenario = obj.GrowthScenario, Status = (ScenarioStatus?)obj.Status, IsActiveScenario = obj.Status == ScenarioStatus.Active.GetHashCode(), LMMargin = obj.ExpectedGrossMargin_LM.HasValue ? Convert.ToInt64(obj.ExpectedGrossMargin_LM * 100) : (decimal?)null, UseLMMargin = obj.UseLMMargin == 1, LaborSplitPercentage = Convert.ToInt16(obj.CGSplit * 100), MilestoneStartDate = obj.ShotStartDate, ProjectedRevenue = obj.ProjectedRevenue, TotalMilestones = obj.Shots ?? 0, Expenditures = new List(), FreezeResource = obj.FreezeRevenue, IsRevenueGenerating = (obj.Project == null || obj.Project.Type == null || obj.Project.IsRevenueGenerating), TDDirectCosts = obj.TDDirectCosts ?? 0, BUDirectCosts = obj.BUDirectCosts ?? 0 }; model.TrimStringProperties(); return model; } public static explicit operator ScenarioModel(CreateScenarioModel obj) { if (obj == null) return null; var model = new ScenarioModel { Id = obj.ScenarioId, Name = obj.Step1.ScenarioName, Color = null, TemplateId = obj.Step1.TemplateId, ParentId = obj.Step1.PartId ?? obj.Step1.ProjectId, Type = ScenarioType.Portfolio, ChildScenarios = 0, EndDate = obj.Step1.EndDate, StartDate = obj.Step1.StartDate, StartDateDisabled = obj.Step1.StartDate, GrossMargin = obj.Step3.UseLMMargin ? (decimal?)null : obj.Step3.Margin, GrowthScenario = obj.Step1.GrowthScenario, Status = obj.Step1.CreateAsActive ? ScenarioStatus.Active : ScenarioStatus.Inactive, IsActiveScenario = obj.Step1.CreateAsActive, LMMargin = obj.Step3.UseLMMargin ? obj.Step3.Margin : (decimal?)null, UseLMMargin = obj.Step3.UseLMMargin, LaborSplitPercentage = 100, MilestoneStartDate = obj.Step1.StartDate, ProjectedRevenue = obj.Step3.IsRevenueGenerating ? obj.Step3.ProjectedRevenue : (decimal?)null, TotalMilestones = 1, Expenditures = obj.Step1.ScenarioExpenditures, FreezeResource = false, IsRevenueGenerating = obj.Step3.IsRevenueGenerating, AsDraft = obj.SaveAsDraft, TDDirectCosts = obj.Step3.IsRevenueGenerating ? (decimal?)null : obj.Step3.ProjectedExpense, BUDirectCosts = 0 }; if (obj.Step3.CostSavings != null) { model.CostSavings = obj.Step3.CostSavings.CostSavings; model.CostSavingStartDate = obj.Step3.CostSavings.CostSavingStartDate; model.CostSavingEndDate = obj.Step3.CostSavings.CostSavingEndDate; model.CostSavingType = obj.Step3.CostSavings.CostSavingType; model.CostSavingDescription = obj.Step3.CostSavings.CostSavingDescription; } model.TrimStringProperties(); return model; } public bool CostSavingType { get; set; } public DateTime? CostSavingEndDate { get; set; } public DateTime? CostSavingStartDate { get; set; } public decimal? CostSavings { get; set; } [Display(Name = "Cost Saving Description")] public string CostSavingDescription { get; set; } /// /// Copies data from model to DAL object. /// /// A target DAL object. public void CopyTo(Scenario dbObj) { if (dbObj == null) throw new ArgumentNullException(); dbObj.Name = Name; dbObj.Color = (Color ?? string.Empty).Replace("#", ""); dbObj.TemplateId = TemplateId; dbObj.ParentId = ParentId; dbObj.Type = Type.GetHashCode(); dbObj.EndDate = EndDate; dbObj.StartDate = StartDate; dbObj.GrowthScenario = GrowthScenario; dbObj.Status = AsDraft ? (int)ScenarioStatus.Draft : IsActiveScenario ? (int)ScenarioStatus.Active : (int)ScenarioStatus.Inactive; dbObj.ExpectedGrossMargin = GrossMargin.HasValue ? (decimal)(((double)GrossMargin) / 100.00) : (decimal?)null; dbObj.ExpectedGrossMargin_LM = LMMargin.HasValue ? (decimal)(((double)LMMargin) / 100.00) : (decimal?)null; dbObj.UseLMMargin = UseLMMargin ? 1 : 0; dbObj.CGSplit = 1; dbObj.EFXSplit = 0; dbObj.ShotStartDate = MilestoneStartDate; dbObj.ProjectedRevenue = ProjectedRevenue; dbObj.Shots = TotalMilestones; dbObj.FreezeRevenue = FreezeResource; dbObj.TDDirectCosts = TDDirectCosts; //dbObj.TDDirectCosts_LM = TDDirectCosts; dbObj.BUDirectCosts = BUDirectCosts; //dbObj.BUDirectCosts_LM = BUDirectCosts; dbObj.CostSavings = CostSavings; dbObj.CostSavingsStartDate = CostSavingStartDate; dbObj.CostSavingsEndDate = CostSavingEndDate; dbObj.CostSavingsType = Convert.ToInt16(CostSavingType ? 1 : 0); dbObj.CostSavingsDescription = CostSavingDescription; } public IEnumerable Validate(ValidationContext validationContext) { if (!AsDraft && Type != ScenarioType.Scheduling && ProjectedRevenue <= 0) yield return new ValidationResult(string.Format(Constants.ERROR_TEMPLATE_REQUIRED, "Projected Revenue"), new[] { "ProjectedRevenue" }); if (!AsDraft && IsRevenueGenerating && UseLMMargin && LMMargin == null) yield return new ValidationResult(string.Format(Constants.ERROR_TEMPLATE_REQUIRED, "L&M Margin"), new[] { "LMMargin" }); if (!AsDraft && IsRevenueGenerating && !UseLMMargin && GrossMargin == null) yield return new ValidationResult(string.Format(Constants.ERROR_TEMPLATE_REQUIRED, "Gross Margin"), new[] { "GrossMargin" }); if (!Guid.Empty.Equals(Id) && !AsDraft && IsRevenueGenerating && ProjectedRevenue == null) yield return new ValidationResult(string.Format(Constants.ERROR_TEMPLATE_REQUIRED, "Projected Revenue"), new[] { "ProjectedRevenue" }); if (!AsDraft && (Guid.Empty.Equals(Id) || (!Guid.Empty.Equals(Id) && ScenarioStatus.Draft.Equals(Status))) && null == TemplateId) yield return new ValidationResult(string.Format(Constants.ERROR_TEMPLATE_REQUIRED, "Template"), new[] { "TemplateId" }); if (Guid.Empty.Equals(Id) && !AsDraft && IsRevenueGenerating && ProjectedRevenue == null && GrossMargin == 0) yield return new ValidationResult(string.Format(Constants.ERROR_TEMPLATE_REQUIRED2, "Projected Revenue", "Gross Margin"), new[] { "ProjectedRevenue", "GrossMargin" }); } #endregion } /// /// An UI representation of scenario template to be displayed as list items /// public class ScenarioListItemModel { public Guid Id { get; set; } public string Name { get; set; } public DateTime? StartDate { get; set; } public DateTime? EndDate { get; set; } public int? Duration { get; set; } public decimal? CGSplit { get; set; } public decimal? EFXSplit { get; set; } public int ScenariosCount { get; set; } public int? Status { get; set; } public List TemplateGroupNames { get; set; } } public class ScenarioTemplateCreationModel { public Guid Id { get; set; } [Required] [Display(Name = "Template Name")] public string TemplateName { get; set; } [UIHint("TemplateGroup")] [Display(Name = "Template Group")] public string TemplateGroupNames { get; set; } public IEnumerable TemplateGroups { get; set; } public IList Expenditures { get; set; } public ScenarioTemplateCreationModel(ScenarioDetailModel model) { Id = model.Id; TemplateName = model.Name + " Template"; Expenditures = model.Expenditures; foreach (var exp in Expenditures) exp.Checked = true; //TemplateGroupNames = new List(); } public ScenarioTemplateCreationModel() { Id = Guid.NewGuid(); TemplateName = "Template"; Expenditures = new List(); //TemplateGroupNames = new List(); } } // TODO: review to remove public class ScenarioDetailWithProxyItemModel { public Guid Id { get; set; } public Guid ScenarioId { get; set; } public Guid ExpenditureCategoryId { get; set; }//1 public string ExpenditureCategoryName { get; set; }//2 public long WeekEndingDate { get; set; } public long LastUpdate { get; set; } public int WeekOrdinal { get; set; } public decimal Quantity { get; set; } public decimal Cost { get; set; } public int Type { get; set; } //3 // default is 1 public int UseType { get; set; } //4 public string CGEFX { get; set; }//5 public Guid GLId { get; set; }//6 public Guid UOMId { get; set; }//7 public Guid CreditId { get; set; }//8 public Guid? SystemAttributeOne { get; set; }//9 public Guid? SystemAttributeTwo { get; set; }//10 public int? SortOrder { get; set; }//11 public static explicit operator ScenarioDetailWithProxyItemModel(VW_ScenarioAndProxyDetails entity) { if (entity == null) return null; return new ScenarioDetailWithProxyItemModel() { Id = entity.Id, ScenarioId = entity.ParentID ?? Guid.Empty, ExpenditureCategoryId = entity.ExpenditureCategoryId ?? Guid.Empty, ExpenditureCategoryName = entity.ExpCategoryWithCcName, // SA. ENV-839 WeekEndingDate = (long)(entity.WeekEndingDate ?? Constants.UnixEpochDate).Subtract(Constants.UnixEpochDate).TotalMilliseconds, LastUpdate = (long)(entity.LastUpdate ?? Constants.UnixEpochDate).Subtract(Constants.UnixEpochDate).TotalMilliseconds, WeekOrdinal = entity.WeekOrdinal ?? 0, Quantity = entity.Quantity ?? 0, Cost = entity.Cost ?? 0, Type = entity.Type ?? 0, UseType = entity.UseType ?? 1, CGEFX = entity.CGEFX, GLId = entity.GLId, UOMId = entity.UOMId, CreditId = entity.CreditId, SystemAttributeOne = entity.SystemAttributeOne, SystemAttributeTwo = entity.SystemAttributeTwo, SortOrder = entity.SortOrder }; } } public class CalendarModel { public CalendarModel() { Headers = new List(); Rows = new List(); AllResources = new List(); } public List Headers { get; set; } public List Rows { get; set; } public List AllResources { get; set; } } public class ScenarioDetailsModel { public class ScenarioCalendarRowResource { public Guid Id { get; set; } public Guid? ExpedentureCategoryId { get; set; } public string Name { get; set; } public decimal[] QuantityValues { get; set; } public decimal[] CapacityQuantityValues { get; set; } public decimal[] CostValues { get; set; } public decimal GrandTotalCost { get; set; } public decimal GrandTotalQuantity { get; set; } public bool IsActiveEmployee { get; set; } } public class ScenarioCalendarRow { public Guid ExpCatId { get; set; } public Guid? ResourceToAssignId { get; set; } public string ExpCatName { get; set; } public decimal GrandTotalCost { get; set; } public decimal GrandTotalQuantity { get; set; } public ExpenditureCategoryModel.UseTypes UseType { get; set; } public bool Checked { get; set; } public decimal[] QuantityValues { get; set; } public decimal[] CostValues { get; set; } public Guid[] ScenarioDetailIds { get; set; } public bool Collapsed { get; set; } public string CollapsedClass { get; set; } public decimal[] RestQuantity { get; set; } public decimal[] RestCost { get; set; } public decimal[] ActualsQuantities { get; set; } public decimal[] ActualsCosts { get; set; } public decimal ForecastCompletedPercent { get; set; } public decimal ForecastQtyTotalInActualsRange { get; set; } public List Resources { get; set; } public ScenarioCalendarRow() { CollapsedClass = "fa-plus-square"; Collapsed = true; ForecastCompletedPercent = 0; ForecastQtyTotalInActualsRange = 0; Resources = new List(); } } public class Header { /// /// Gets or sets a number of milliseconds between month end date and 1/1/1970. /// public long Milliseconds { get; set; } /// /// Gets or sets an index of the related month cell. /// public int MonthIndex { get; set; } /// /// Gets or sets a values indicating whether the header cell is collapsed or not. /// public bool Collapsed { get; set; } /// /// Gets or sets a values indicating whether to show header cell. /// public bool Show { get; set; } /// /// Gets or sets a values indicating whether the header cell represents a month or a week. /// public bool IsMonth { get; set; } /// /// Gets or sets a CSS class used for the plus/minus icons in the month header. /// public string CollapsedClass { get; set; } /// /// Gets or sets a text of the header cell. /// public string Title { get; set; } /// /// Gets or sets an array of indexes of children cells. I.e. week cells for month cell. /// public List Weeks { get; set; } public Header() { Collapsed = true; Show = false; CollapsedClass = "fa-plus-square"; } } public static class HeaderPeriod { public static string Month = "1"; public static string Week = "2"; }; public static class WeekHeaderType { public static int Ordinal = 1; public static int Totals = 100; } public static class CalendarDataViewModes { public static string Forecast = "F"; public static string Actuals = "A"; } public class HeaderBase { /// /// Gets or sets a number of milliseconds between month end date and 1/1/1970. /// public long Milliseconds { get; set; } /// /// Visibility of the header for particular grid view (forecast / actuals) /// public Dictionary Visible { get; set; } /// /// Display header (if not collapsed) /// public bool Show { get; set; } /// /// Cells of the header are editable /// public Dictionary Editable { get; set; } /// /// Gets or sets a text of the header cell. /// public string Title { get; set; } /// /// Gets or sets a values indicating whether the header cell is collapsed or not. /// public bool IsCollapsed { get; set; } public HeaderBase() { IsCollapsed = true; Visible = new Dictionary(); Visible.Add(CalendarDataViewModes.Forecast, false); Visible.Add(CalendarDataViewModes.Actuals, false); Editable = new Dictionary(); Editable.Add(CalendarDataViewModes.Forecast, true); Editable.Add(CalendarDataViewModes.Actuals, true); } } public class MonthHeader : HeaderBase { /// /// Gets or sets a CSS class used for the plus/minus icons in the month header. /// public string CollapsedClass { get; set; } public Dictionary ColspanValues { get; set; } /// /// Gets or sets an array of indexes of children cells. I.e. week cells for month cell. /// public List WeekHeaders { get; set; } /// /// The index for the corresponding child totals cell for current month /// public int TotalsHeader { get; set; } public MonthHeader() : base() { CollapsedClass = "fa-plus-square"; WeekHeaders = new List(); ColspanValues = new Dictionary(); ColspanValues.Add(ScenarioDetailsModel.CalendarDataViewModes.Forecast, 1); ColspanValues.Add(ScenarioDetailsModel.CalendarDataViewModes.Actuals, 1); } } public class WeekHeader : HeaderBase { /// /// Type of data for the column /// public int DataType { get; set; } /// /// Index of the correcponding parent month header /// public int MonthHeader { get; set; } public WeekHeader() : base() { DataType = WeekHeaderType.Ordinal; } } [Display(Name = "Category Type")] public ExpenditureCategoryModel.CategoryTypes? CategoryType { get; set; } [Display(Name = "Labor / Materials")] public ExpenditureCategoryModel.CgEfx? LaborMaterials { get; set; } [Display(Name = "Income Type")] public Guid? IncomeType { get; set; } [Display(Name = "GL Name")] public Guid? GLAccount { get; set; } [Display(Name = "Cost Center")] public Guid? CreditDepartment { get; set; } [Display(Name = "Quantity Mode")] public bool IsTableModeQuantity { get; set; } /// /// Gets or sets a value indicating whether to display only forecast data or actuals+forecast in the the grid. /// public bool ShowActuals { get; set; } /// /// Indicates whether scenario details grid data is in Hours or in Resources.
/// null - data in Hours
/// true - data in Hours
/// false - data in Resources. ///
[Display(Name = "Resources Mode")] public bool? IsUOMHours { get; set; } public DateTime StartDate { get; set; } public DateTime EndDate { get; set; } public ScenarioType? ScenarioType { get; set; } public Guid ScenarioId { get; set; } public bool GrowthScenario { get; set; } public Guid ParentId { get; set; } public long ActualsEndDateMs { get; set; } public List AvailableExpenditures { get; set; } [Display(Name = "Expenditure Categories")] public List SelectedExpCats { get; set; } public List AllResources { get; set; } public bool? PreferredTotalsDisplaying { get; set; } /// /// Gets or sets a list of scenario details data for each expenditure category. /// public List ScenarioCalendar { get; set; } public decimal? YellowIndicator { get; set; } public decimal? RedIndicator { get; set; } public long ActualsStartDateMs { get; set; } public List
Headers { get; set; } public string UserPageSettings { get; set; } public ScenarioDetailsModel() { AvailableExpenditures = new List(); SelectedExpCats = new List(); ScenarioCalendar = new List(); Headers = new List
(); IsTableModeQuantity = true; } } public class SaveScenarioDetailsChangesModel { public class ChangedRow { public Guid Id { get; set; } public List Values { get; set; } public List Resources { get; set; } public ChangedRow() { Values = new List(); } } public class ResourcesRow { public Guid Id { get; set; } public List Values { get; set; } public bool IsAdded { get; set; } public bool IsRemoved { get; set; } } public class ChangedColumn { public Guid? Id { get; set; } /// /// Number of milliseconds from 1/1/1970 /// public long Milliseconds { get; set; } public decimal Cost { get; set; } public decimal Quantity { get; set; } } public Guid ScenarioId { get; set; } public List ChangedExpCats { get; set; } public ScenarioDetailsModel ScenarioFilters { get; set; } public SaveScenarioDetailsChangesModel() { ChangedExpCats = new List(); } } public class UpdateScenarioModel { public Guid Id { get; set; } [Display(Name = "Allow prior week values to be adjusted?")] public bool AllowAdjustment { get; set; } [DataType(DataType.Date), DisplayFormat(DataFormatString = "{0:MM/dd/yy}", ApplyFormatInEditMode = true)] [Display(Name = "Prior Week Cut-off")] public DateTime? PriorWeekCutOff { get; set; } [DataType(DataType.Date), DisplayFormat(DataFormatString = "{0:MM/dd/yy}", ApplyFormatInEditMode = true)] [Display(Name = "Scenario Start Date")] public DateTime? StartDate { get; set; } [DataType(DataType.Date), DisplayFormat(DataFormatString = "{0:MM/dd/yy}", ApplyFormatInEditMode = true)] [Display(Name = "Scenario End Date")] public DateTime? EndDate { get; set; } [Display(Name = "Projected Revenue")] [DataType(DataType.Currency)] [Range(1, 100000000000)] public decimal? ProjectedRevenue { get; set; } [Display(Name = "Projected Expense")] [DataType(DataType.Currency)] [Range(-100000000000, 100000000000)] public decimal? TDDirectCosts { get; set; } [Display(Name = "Use L&M Margin?")] public bool UseLMMargin { get; set; } [Display(Name = "Gross Margin")] [Range(1, 100)] public decimal? GrossMargin { get; set; } [Display(Name = "L&M Margin")] [Range(1, 100)] public decimal? LMMargin { get; set; } public bool IsRevenueGenerating { get; set; } public decimal BUDirectCosts { get; set; } public IList Expenditures { get; set; } } [Obsolete] public class DateRangeAttribute : ValidationAttribute { public DateTime StartDate { get; set; } public DateTime EndDate { get; set; } public DateRangeAttribute() { StartDate = DateTime.MaxValue; EndDate = DateTime.MaxValue; } protected override ValidationResult IsValid(object value, ValidationContext validationContext) { if (value != null) { DateTime date = DateTime.Parse(value.ToString()); // assuming it's in a parsable string format var propertyStartDate = validationContext.ObjectType.GetProperty("StartDate"); var propertyStartDateDisabled = validationContext.ObjectType.GetProperty("StartDateDisabled"); var propertyEndDate = validationContext.ObjectType.GetProperty("EndDate"); if (propertyStartDate != null && propertyEndDate != null && propertyStartDateDisabled != null) { var startDateValue = propertyStartDate.GetValue(validationContext.ObjectInstance, null); var startDateDisabledValue = propertyStartDateDisabled.GetValue(validationContext.ObjectInstance, null); var endDateValue = propertyEndDate.GetValue(validationContext.ObjectInstance, null); if ((startDateValue != null || startDateDisabledValue != null) && endDateValue != null) { StartDate = DateTime.Parse((startDateValue == null ? startDateDisabledValue : startDateValue).ToString()); EndDate = DateTime.Parse(endDateValue.ToString()); if (StartDate <= EndDate && date >= StartDate && date <= EndDate) return ValidationResult.Success; } propertyStartDate.SetValue(validationContext.ObjectInstance, startDateDisabledValue); } return new ValidationResult(ErrorMessageString); } return ValidationResult.Success; } } [AttributeUsage(AttributeTargets.Property, AllowMultiple = true)] public class DateNotExceedPropValueAttribute : ValidationAttribute, IClientValidatable { string otherPropertyName; public DateNotExceedPropValueAttribute(string otherPropertyName, string errorMessage) : base(errorMessage) { this.otherPropertyName = otherPropertyName; } protected override ValidationResult IsValid(object value, ValidationContext validationContext) { ValidationResult validationResult = ValidationResult.Success; try { var otherPropertyInfo = validationContext.ObjectType.GetProperty(this.otherPropertyName); if (otherPropertyInfo.PropertyType == new DateTime().GetType()) { DateTime toValidate = value != null ? (DateTime)value : DateTime.MinValue; DateTime referenceProperty = (DateTime)otherPropertyInfo.GetValue(validationContext.ObjectInstance, null); if (toValidate > referenceProperty) { validationResult = new ValidationResult(ErrorMessageString); } } else if (otherPropertyInfo.PropertyType.Equals(typeof(DateTime?))) { DateTime toValidate = value != null ? (DateTime)value : DateTime.MinValue; object referenceProperty = otherPropertyInfo.GetValue(validationContext.ObjectInstance, null); if (referenceProperty != null && toValidate > (DateTime)referenceProperty) { validationResult = new ValidationResult(ErrorMessageString); } } else { validationResult = new ValidationResult("An error occurred while validating the property. OtherProperty is not of type DateTime"); } } catch (Exception ex) { throw ex; } return validationResult; } public IEnumerable GetClientValidationRules(ModelMetadata metadata, ControllerContext context) { string errorMessage = ErrorMessageString; ModelClientValidationRule dateNotExceedPropRule = new ModelClientValidationRule(); dateNotExceedPropRule.ErrorMessage = errorMessage; dateNotExceedPropRule.ValidationType = "datenotexceedprop"; // This is the name the jQuery adapter will use //"otherpropertyname" is the name of the jQuery parameter for the adapter, must be LOWERCASE! dateNotExceedPropRule.ValidationParameters.Add("otherpropertyname", otherPropertyName); yield return dateNotExceedPropRule; } } [AttributeUsage(AttributeTargets.Property, AllowMultiple = true)] public class DateGreaterThanOrEqualAttribute : ValidationAttribute, IClientValidatable { string otherPropertyName; public DateGreaterThanOrEqualAttribute(string otherPropertyName, string errorMessage) : base(errorMessage) { this.otherPropertyName = otherPropertyName; } protected override ValidationResult IsValid(object value, ValidationContext validationContext) { ValidationResult validationResult = ValidationResult.Success; try { // Using reflection we can get a reference to the other date property, in this example the project start date var otherPropertyInfo = validationContext.ObjectType.GetProperty(this.otherPropertyName); // Let's check that otherProperty is of type DateTime as we expect it to be if (otherPropertyInfo.PropertyType == new DateTime().GetType()) { DateTime toValidate = value != null ? (DateTime)value : DateTime.MinValue; DateTime referenceProperty = (DateTime)otherPropertyInfo.GetValue(validationContext.ObjectInstance, null); if (referenceProperty > toValidate) { validationResult = new ValidationResult(ErrorMessageString); } } else if (otherPropertyInfo.PropertyType.Equals(typeof(DateTime?))) { DateTime toValidate = value != null ? (DateTime)value : DateTime.MinValue; object referenceProperty = otherPropertyInfo.GetValue(validationContext.ObjectInstance, null); DateTime referenceDT = referenceProperty != null ? (DateTime)referenceProperty : DateTime.MinValue; if (referenceDT > toValidate) { validationResult = new ValidationResult(ErrorMessageString); } } else { validationResult = new ValidationResult("An error occurred while validating the property. OtherProperty is not of type DateTime"); } } catch (Exception ex) { throw ex; } return validationResult; } public IEnumerable GetClientValidationRules(ModelMetadata metadata, ControllerContext context) { string errorMessage = ErrorMessageString; ModelClientValidationRule dateGreaterThanOrEqualRule = new ModelClientValidationRule(); dateGreaterThanOrEqualRule.ErrorMessage = errorMessage; dateGreaterThanOrEqualRule.ValidationType = "dategreaterthanorequal"; // This is the name the jQuery adapter will use //"otherpropertyname" is the name of the jQuery parameter for the adapter, must be LOWERCASE! dateGreaterThanOrEqualRule.ValidationParameters.Add("otherpropertyname", otherPropertyName); yield return dateGreaterThanOrEqualRule; } } [AttributeUsage(AttributeTargets.Property, AllowMultiple = true)] public class EitherOfRequiredAttribute : ValidationAttribute, IClientValidatable { private readonly string _otherPropertyName; private string _singleErorrMessage; private bool _onlyClientSideValidation; public EitherOfRequiredAttribute(string otherPropertyName, string errorMessage, string singleErrorMessage, bool onlyClientSideValidation) : base(errorMessage) { this._otherPropertyName = otherPropertyName; this._singleErorrMessage = singleErrorMessage; this._onlyClientSideValidation = onlyClientSideValidation; } protected override ValidationResult IsValid(object value, ValidationContext validationContext) { ValidationResult validationResult = ValidationResult.Success; if (this._onlyClientSideValidation) return validationResult; try { // Using reflection we can get a reference to the other date property, in this example the project start date var otherPropertyInfo = validationContext.ObjectType.GetProperty(this._otherPropertyName); // Let's check that otherProperty is of type DateTime as we expect it to be if (otherPropertyInfo.PropertyType == (typeof(String))) { string referenceProperty = (string)otherPropertyInfo.GetValue(validationContext.ObjectInstance, null); string toValidate = value != null ? (string)value : string.Empty; if (string.IsNullOrEmpty(referenceProperty) && string.IsNullOrEmpty(toValidate)) { validationResult = new ValidationResult(ErrorMessageString); } } else { object referenceProperty = otherPropertyInfo.GetValue(validationContext.ObjectInstance, null); if (referenceProperty == null && value == null) { validationResult = new ValidationResult(ErrorMessageString); } } } catch (Exception ex) { throw ex; } return validationResult; } public IEnumerable GetClientValidationRules(ModelMetadata metadata, ControllerContext context) { string errorMessage = ErrorMessageString; ModelClientValidationRule eitherOfRequiredRule = new ModelClientValidationRule(); eitherOfRequiredRule.ErrorMessage = errorMessage; eitherOfRequiredRule.ValidationType = "eitherofrequired"; // This is the name the jQuery adapter will use //"otherpropertyname" is the name of the jQuery parameter for the adapter, must be LOWERCASE! eitherOfRequiredRule.ValidationParameters.Add("otherpropertyname", _otherPropertyName); eitherOfRequiredRule.ValidationParameters.Add("singleerrormessage", _singleErorrMessage); yield return eitherOfRequiredRule; } } [AttributeUsage(AttributeTargets.Property, AllowMultiple = true)] public class RequiredIfAttribute : ValidationAttribute, IClientValidatable { private readonly RequiredAttribute _innerAttribute = new RequiredAttribute(); public string DependentProperty { get; set; } public object TargetValue { get; set; } public RequiredIfAttribute(string dependentProperty, object targetValue) { DependentProperty = dependentProperty; TargetValue = targetValue; } protected override ValidationResult IsValid(object value, ValidationContext validationContext) { // get a reference to the property this validation depends upon var containerType = validationContext.ObjectInstance.GetType(); var field = containerType.GetProperty(this.DependentProperty); if (field != null) { // get the value of the dependent property var dependentvalue = field.GetValue(validationContext.ObjectInstance, null); // compare the value against the target value if ((dependentvalue == null && this.TargetValue == null) || (dependentvalue != null && dependentvalue.Equals(this.TargetValue))) { // match => means we should try validating this field if (!_innerAttribute.IsValid(value)) // validation failed - return an error return new ValidationResult(this.ErrorMessage, new[] { validationContext.MemberName }); } } return ValidationResult.Success; } public IEnumerable GetClientValidationRules(ModelMetadata metadata, ControllerContext context) { var rule = new ModelClientValidationRule() { ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()), ValidationType = "requiredif", }; string depProp = BuildDependentPropertyId(metadata, context as ViewContext); // find the value on the control we depend on; // if it's a bool, format it javascript style // (the default is True or False!) string targetValue = (this.TargetValue ?? "").ToString(); if (TargetValue.GetType() == typeof(bool)) targetValue = targetValue.ToLower(); rule.ValidationParameters.Add("dependentproperty", depProp); rule.ValidationParameters.Add("targetvalue", targetValue); yield return rule; } private string BuildDependentPropertyId(ModelMetadata metadata, ViewContext viewContext) { // build the ID of the property string depProp = viewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(this.DependentProperty); // unfortunately this will have the name of the current field appended to the beginning, // because the TemplateInfo's context has had this fieldname appended to it. Instead, we // want to get the context as though it was one level higher (i.e. outside the current property, // which is the containing object (our Person), and hence the same level as the dependent property. //var thisField = metadata.PropertyName + "_"; //if (depProp.StartsWith(thisField)) // // strip it off again // depProp = depProp.Substring(thisField.Length); return depProp; } } public class CreateScenarioModel //: IValidatableObject { public class GeneralInfoModel { public decimal EFXSplit { get; set; } //TODO: consider removing it as it always Guid.Empty public Guid ScenarioId { get; set; } //[UIHint("Project")] [Display(Name = "Project Name")] public Guid ProjectId { get; set; } //[Display(Name = "Part Name")] //public string PartName { get; set; } [Display(Name = "Part Name")] public Guid? PartId { get; set; } [Required] [Display(Name = "Scenario Name")] [MaxLength(200, ErrorMessage = "Name should not exceed 200 characters")] // SA. ENV-906. Added public string ScenarioName { get; set; } [Required] [DataType(DataType.Date), DisplayFormat(DataFormatString = "{0:MM/dd/yy}", ApplyFormatInEditMode = true)] [Display(Name = "Scenario Start Date")] public DateTime? StartDate { get; set; } [Required] [DataType(DataType.Date), DisplayFormat(DataFormatString = "{0:MM/dd/yy}", ApplyFormatInEditMode = true)] [DateNotExceedPropValue("ProjectDeadline", "Scenario End Date should not exceed Project Deadline date")] [DateGreaterThanOrEqualAttribute("StartDate", "Scenario End Date should not be less than Start Date")] [Display(Name = "Scenario End Date")] public DateTime? EndDate { get; set; } [Required] [Display(Name = "Template")] [UIHint("SingleSelect")] public Guid? TemplateId { get; set; } [Display(Name = "Create As Active")] public bool CreateAsActive { get; set; } [Display(Name = "Growth Scenario")] public bool GrowthScenario { get; set; } public DateTime? ProjectDeadline { get; set; } [JsonIgnore] [Display(Name = "Project")] public Project Project { get; set; } public IList ScenarioExpenditures { get; set; } public short LaborSplitPercentage { get; set; } private bool _statusIsEditable = true; public bool StatusIsEditable { get { return _statusIsEditable; } set { _statusIsEditable = value; } } [JsonIgnore] public string SerializedModel { get { return JsonConvert.SerializeObject(this); } } public SlidersGroupModel Teams { get; set; } } public class FinInfoModel { [DataType(DataType.Currency)] [Range(0, 100000000000)] [Display(Name = "Projected Revenue")] [EitherOfRequired("Margin", "Either Projected Revenue or Margin should be entered", "", true)] public decimal? ProjectedRevenue { get; set; } [DataType(DataType.Currency)] [Range(0, 100000000000)] [Display(Name = "Projected Expense")] public decimal? ProjectedExpense { get; set; } [Display(Name = "Margin")] [Range(0, Int32.MaxValue)] public decimal? Margin { get; set; } public bool UseLMMargin { get; set; } public CostSavingModel CostSavings { get; set; } public bool IsRevenueGenerating { get; set; } [JsonIgnore] public string SerializedModel { get { return JsonConvert.SerializeObject(this); } } } public Guid ScenarioId { get; set; } public bool IsRevenueGenerating { get; set; } public string CurrentStep { get; set; } public string Action { get; set; } [Display(Name = "Save as Draft")] public bool SaveAsDraft { get; set; } public bool CanSaveDraft { get; set; } /// /// The name of js functions to call after scenario will be saved /// public string SaveCallback { get; set; } public GeneralInfoModel Step1 { get; set; } public ScenarioDetailModel Step2 { get; set; } public FinInfoModel Step3 { get; set; } #region Constructors public CreateScenarioModel() { Step1 = new GeneralInfoModel { CreateAsActive = true }; Step2 = new ScenarioDetailModel(); Step3 = new FinInfoModel { CostSavings = new CostSavingModel { IsEditable = true } }; } #endregion } public class CostSavingModel { public bool CostSavingsPanelExpanded { get; set; } //[RequiredIf("CostSavingsPanelExpanded", true, ErrorMessage = "Cost Savings required")] [Range(0, int.MaxValue)] [Display(Name = "Cost Savings")] public decimal? CostSavings { get; set; } [RequiredIf("CostSavingsPanelExpanded", true, ErrorMessage = "Cost Saving Start Date required")] [DataType(DataType.Date), DisplayFormat(DataFormatString = "{0:MM/dd/yyyy}", ApplyFormatInEditMode = true)] [Display(Name = "Cost Saving Start Date")] public DateTime? CostSavingStartDate { get; set; } [RequiredIf("CostSavingsPanelExpanded", true, ErrorMessage = "Cost Saving End Date required")] [DataType(DataType.Date), DisplayFormat(DataFormatString = "{0:MM/dd/yyyy}", ApplyFormatInEditMode = true)] [Display(Name = "Cost Saving End Date")] public DateTime? CostSavingEndDate { get; set; } public string CostSavingItems { get; set; } [Display(Name = "Cost Saving Type")] public bool CostSavingType { get; set; } [Display(Name = "Cost Saving Description")] public string CostSavingDescription { get; set; } public bool IsMonthlyMode { get; set; } [Display(Name = "Calculated ROI Date")] public DateTime? ROIDate { get; set; } public bool IsEditable { get; set; } public DateTime? ScenarioStartDate { get; set; } } public class ScenarioCostSavingModel { public int Year { get; set; } public decimal?[] Costs { get; set; } } public class ScenarioLoadModel { public Guid Id { get; set; } public DateTime? StartDate { get; set; } public DateTime? EndDate { get; set; } public Guid? TeamId { get; set; } public string TeamName { get; set; } public string SaveCallback { get; set; } // this should be true by default private bool _canSaveDraft = true; public bool CanSaveDraft { get { return _canSaveDraft; } set { _canSaveDraft = value; } } private bool _statusIsEditable = true; public bool StatusIsEditable { get { return _statusIsEditable; } set { _statusIsEditable = value; } } } }