EnVisageOnline/Main/Source/EnVisage/Models/ScenarioModel.cs

1383 lines
56 KiB
C#

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;
using EnVisage.Code.BLL;
namespace EnVisage.Models
{
public class ScenarioModel : IBaseModel<Scenario>, IValidatableObject
{
#region Enums and subclasses
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; }
/// <summary>
/// Gets or sets an Id of the template.
/// </summary>
[Display(Name = "Template")]
public Guid? TemplateId { get; set; }
/// <summary>
/// Gets or sets an Id of the project.
/// </summary>
public Guid ParentId { get; set; }
public string BackUrl { get; set; }
public string BackName { get; set; }
/// <summary>
/// Gets or sets a color that will be used for scenario in reports.
/// </summary>
[MaxLength(8)]
public string Color { get; set; }
/// <summary>
/// Gets or sets a type of scenario.
/// </summary>
[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<ExpenditureItem> Expenditures { get; set; }
public bool AsDraft { get; set; }
public ScenarioStatus? Status { get; set; }
#endregion
#region Constructors
public ScenarioModel()
{
Expenditures = new List<ExpenditureItem>();
IsActiveScenario = true;
UseActuals = true;
IsRevenueGenerating = true;
}
#endregion
#region Methods
/// <summary>
/// Casts a <see cref="Scenario"/> obect to the object of type <see cref="ScenarioModel"/>.
/// </summary>
/// <param name="obj">A <see cref="Scenario"/> object.</param>
/// <returns>A <see cref="ScenarioModel"/> object filled with data from db.</returns>
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.HasValue ? obj.EndDate.Value.Date : (DateTime?)null,
StartDate = obj.StartDate.HasValue ? obj.StartDate.Value.Date : (DateTime?)null,
StartDateDisabled = obj.StartDate.HasValue ? obj.StartDate.Value.Date : (DateTime?)null,
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<ExpenditureItem>(),
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; }
/// <summary>
/// Copies data from model to DAL object.
/// </summary>
/// <param name="dbObj">A target DAL object.</param>
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.HasValue ? EndDate.Value.Date : (DateTime?)null;
dbObj.StartDate = StartDate.HasValue ? StartDate.Value.Date : (DateTime?)null;
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<ValidationResult> 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
}
/// <summary>
/// An UI representation of scenario template to be displayed as list items
/// </summary>
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<string> 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<string> TemplateGroups { get; set; }
public IList<ScenarioModel.ExpenditureItem> 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<string>();
}
public ScenarioTemplateCreationModel()
{
Id = Guid.NewGuid();
TemplateName = "Template";
Expenditures = new List<ScenarioModel.ExpenditureItem>();
//TemplateGroupNames = new List<string>();
}
}
/// <summary>
/// Represents a <see cref="ScenarioDetail"/> DB record with some fields from related entities, like Expenditure and ExpenditureCategory.
/// </summary>
public class ScenarioDetailWithProxyItemModel
{
public Guid Id { get; set; }
public Guid ScenarioId { get; set; }
public Guid ExpenditureCategoryId { get; set; }
public string ExpenditureCategoryName { get; set; }
public DateTime? WeekEndingDate { get; set; }
/// <summary>
/// Gets or sets a week ending date in milliseconds from 1/1/1970. 0 if ScenarioDetail.WeekEndingDate is null.
/// </summary>
public long WeekEndingDateMs { get; set; }
/// <summary>
/// Gets or sets a week ordinal number in scenario. 0 if ScenarioDetail.WeekOrdinal is null.
/// </summary>
public int WeekOrdinal { get; set; }
/// <summary>
/// Gets or sets a weekly quantity. 0 if ScenarioDetail.Quantity is null.
/// </summary>
public decimal Quantity { get; set; }
/// <summary>
/// Gets or sets a weekly cost. 0 if ScenarioDetail.Cost is null.
/// </summary>
public decimal Cost { get; set; }
/// <summary>
/// Gets or sets an expenditure category type. 0 if ExpenditureCategory.Type is null.
/// </summary>
public int Type { get; set; }
/// <summary>
/// Gets or sets an expenditure category use type. 1 if ExpenditureCategory.UseType is null.
/// </summary>
public int UseType { get; set; }
public string CGEFX { get; set; }
public Guid GLId { get; set; }
public Guid UOMId { get; set; }
public Guid CreditId { get; set; }
public Guid? SystemAttributeOne { get; set; }
public Guid? SystemAttributeTwo { get; set; }
public int? SortOrder { get; set; }
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,
WeekEndingDate = entity.WeekEndingDate,
WeekEndingDateMs = Utils.ConvertToUnixDate(entity.WeekEndingDate ?? Constants.UnixEpochDate),
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<ScenarioDetailsModel.Header>();
Rows = new List<ScenarioDetailsModel.ScenarioCalendarRow>();
AllResources = new List<ScenarioDetailsModel.ScenarioCalendarRowResource>();
}
public List<ScenarioDetailsModel.Header> Headers { get; set; }
public List<ScenarioDetailsModel.ScenarioCalendarRow> Rows { get; set; }
public List<ScenarioDetailsModel.ScenarioCalendarRowResource> 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 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<ScenarioCalendarRowResource> Resources { get; set; }
public ScenarioCalendarRow()
{
CollapsedClass = "fa-plus-square";
Collapsed = true;
ForecastCompletedPercent = 0;
ForecastQtyTotalInActualsRange = 0;
Resources = new List<ScenarioCalendarRowResource>();
}
}
public class Header
{
/// <summary>
/// Gets or sets a number of milliseconds between month end date and 1/1/1970.
/// </summary>
public long Milliseconds { get; set; }
/// <summary>
/// Gets or sets an index of the related month cell.
/// </summary>
public int MonthIndex { get; set; }
/// <summary>
/// Gets or sets a values indicating whether the header cell is collapsed or not.
/// </summary>
public bool Collapsed { get; set; }
/// <summary>
/// Gets or sets a values indicating whether to show header cell.
/// </summary>
public bool Show { get; set; }
/// <summary>
/// Gets or sets a values indicating whether the header cell represents a month or a week.
/// </summary>
public bool IsMonth { get; set; }
/// <summary>
/// Gets or sets a CSS class used for the <i>plus/minus</i> icons in the month header.
/// </summary>
public string CollapsedClass { get; set; }
/// <summary>
/// Gets or sets a text of the header cell.
/// </summary>
public string Title { get; set; }
/// <summary>
/// Gets or sets an array of indexes of children cells. I.e. week cells for month cell.
/// </summary>
public List<int> 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
{
/// <summary>
/// Gets or sets a number of milliseconds between month end date and 1/1/1970.
/// </summary>
public long Milliseconds { get; set; }
/// <summary>
/// Visibility of the header for particular grid view (forecast / actuals)
/// </summary>
public Dictionary<string, bool> Visible { get; set; }
/// <summary>
/// Display header (if not collapsed)
/// </summary>
public bool Show { get; set; }
/// <summary>
/// Cells of the header are editable
/// </summary>
public Dictionary<string, bool> Editable { get; set; }
/// <summary>
/// Gets or sets a text of the header cell.
/// </summary>
public string Title { get; set; }
/// <summary>
/// Gets or sets a values indicating whether the header cell is collapsed or not.
/// </summary>
public bool IsCollapsed { get; set; }
public HeaderBase()
{
IsCollapsed = true;
Visible = new Dictionary<string, bool>();
Visible.Add(CalendarDataViewModes.Forecast, false);
Visible.Add(CalendarDataViewModes.Actuals, false);
Editable = new Dictionary<string, bool>();
Editable.Add(CalendarDataViewModes.Forecast, true);
Editable.Add(CalendarDataViewModes.Actuals, true);
}
}
public class MonthHeader : HeaderBase
{
/// <summary>
/// Gets or sets a CSS class used for the <i>plus/minus</i> icons in the month header.
/// </summary>
public string CollapsedClass { get; set; }
public Dictionary<string, int> ColspanValues { get; set; }
/// <summary>
/// Gets or sets an array of indexes of children cells. I.e. week cells for month cell.
/// </summary>
public List<int> WeekHeaders { get; set; }
/// <summary>
/// The index for the corresponding child totals cell for current month
/// </summary>
public int TotalsHeader { get; set; }
public MonthHeader()
: base()
{
CollapsedClass = "fa-plus-square";
WeekHeaders = new List<int>();
ColspanValues = new Dictionary<string, int>();
ColspanValues.Add(ScenarioDetailsModel.CalendarDataViewModes.Forecast, 1);
ColspanValues.Add(ScenarioDetailsModel.CalendarDataViewModes.Actuals, 1);
}
}
public class WeekHeader : HeaderBase
{
/// <summary>
/// Type of data for the column
/// </summary>
public int DataType { get; set; }
/// <summary>
/// Index of the correcponding parent month header
/// </summary>
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; }
/// <summary>
/// Gets or sets a value indicating whether to display only forecast data or actuals+forecast in the the grid.
/// </summary>
public bool ShowActuals { get; set; }
/// <summary>
/// Indicates whether scenario details grid data is in Hours or in Resources.<br/>
/// null - data in Hours<br/>
/// true - data in Hours<br/>
/// false - data in Resources.
/// </summary>
[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<SelectListItem> AvailableExpenditures { get; set; }
[Display(Name = "Expenditure Categories")]
public List<Guid> SelectedExpCats { get; set; }
public List<ScenarioCalendarRowResource> AllResources { get; set; }
public bool? PreferredTotalsDisplaying { get; set; }
/// <summary>
/// Gets or sets a list of scenario details data for each expenditure category.
/// </summary>
public List<ScenarioCalendarRow> ScenarioCalendar { get; set; }
public decimal? YellowIndicator { get; set; }
public decimal? RedIndicator { get; set; }
public long ActualsStartDateMs { get; set; }
public List<Header> Headers { get; set; }
public ScenarioDetailsModel()
{
AvailableExpenditures = new List<SelectListItem>();
SelectedExpCats = new List<Guid>();
ScenarioCalendar = new List<ScenarioCalendarRow>();
Headers = new List<Header>();
IsTableModeQuantity = true;
}
}
[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<ModelClientValidationRule> 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
{
#region Private
private bool _validateIfEmpty = true;
#endregion
#region Public Properties
public string OtherPropertyName { get; set; }
/// <summary>Gets or sets whether or not to validate date if it is null (in this case min date value will be used)</summary>
public bool ValidateIfEmpty
{
get { return _validateIfEmpty; }
set { _validateIfEmpty = value; }
}
#endregion
public DateGreaterThanOrEqualAttribute(string otherPropertyName, string errorMessage)
: base(errorMessage)
{
OtherPropertyName = otherPropertyName;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var validationResult = ValidationResult.Success;
try
{
// if current value is null and empty value validation is turned off we should return success
if (value == null && !ValidateIfEmpty)
return validationResult;
// Using reflection we can get a reference to the other date property, in this example the project start date
var otherPropertyInfo = validationContext.ObjectType.GetProperty(OtherPropertyName);
// Let's check that otherProperty is of type DateTime as we expect it to be
if (otherPropertyInfo.PropertyType == new DateTime().GetType())
{
var toValidate = value != null ? (DateTime)value : DateTime.MinValue;
var referenceProperty = (DateTime)otherPropertyInfo.GetValue(validationContext.ObjectInstance, null);
if (referenceProperty > toValidate)
{
validationResult = new ValidationResult(ErrorMessageString);
}
}
else if (otherPropertyInfo.PropertyType.Equals(typeof(DateTime?)))
{
var toValidate = value != null ? (DateTime)value : DateTime.MinValue;
var referenceProperty = otherPropertyInfo.GetValue(validationContext.ObjectInstance, null);
var 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<ModelClientValidationRule> 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);
dateGreaterThanOrEqualRule.ValidationParameters.Add("validateifempty", ValidateIfEmpty);
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<ModelClientValidationRule> 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<ModelClientValidationRule> 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 + "_";
var thisFieldLastIndex = depProp.LastIndexOf(thisField);
if (thisFieldLastIndex >= 0)
// strip it off again
depProp = depProp.Replace(thisField, string.Empty);
//if (depProp.StartsWith(thisField))
// // strip it off again
// depProp = depProp.Substring(thisField.Length);
return depProp;
}
}
public class CreateScenarioModel //: IValidatableObject
{
public class GeneralInfoModel
{
public GeneralInfoModel() : base()
{
HideName = false;
}
public decimal EFXSplit { get; set; }
//TODO: consider removing it as it always Guid.Empty
public Guid ScenarioId { get; set; }
[Display(Name = "Project Name")]
public Guid ProjectId { get; set; }
[Display(Name = "Project Name")]
public string ProjectName { get; set; }
[Display(Name = "Part Name")]
public Guid? PartId { get; set; }
[RequiredIf("HideName", false)]
[Display(Name = "Scenario Name")]
[MaxLength(200, ErrorMessage = "Name should not exceed 200 characters")]
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")]
[DateGreaterThanOrEqual("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; }
public bool HideName { 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<ScenarioModel.ExpenditureItem> ScenarioExpenditures { get; set; }
[RequiredIf("IsBottomUp", false)]
[Display(Name = "Expenditure Categories")]
public string SelectedExpenditures { get; set; }
public short LaborSplitPercentage { get; set; }
private bool _statusIsEditable = true;
public bool StatusIsEditable
{
get { return _statusIsEditable; }
set { _statusIsEditable = value; }
}
[Display(Name = "Scenario Type")]
public bool IsBottomUp { get; set; }
/// <summary>
/// True, if parent project has dependencies with other projects
/// </summary>
public bool ProjectHasDependencies { get; set; }
public DateTime? StartDateConstraint { get; set; }
public DateTime? EndDateConstraint { get; set; }
public long? StartDateConstraintMs
{
get
{
return StartDateConstraint.HasValue ? Utils.ConvertToUnixDate(StartDateConstraint.Value.ToUniversalTime()) : (long?)null;
}
}
public long? EndDateConstraintMs
{
get
{
return EndDateConstraint.HasValue ? Utils.ConvertToUnixDate(EndDateConstraint.Value.ToUniversalTime()) : (long?)null;
}
}
[JsonIgnore]
public string SerializedModel
{
get { return JsonConvert.SerializeObject(this); }
}
public SlidersGroupModel Teams { get; set; }
[Display(Name = "WorkFlow Process")]
public string WorkFlowSchemaCode { 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; }
/// <summary>
/// The name of js functions to call after scenario will be saved
/// </summary>
public string SaveCallback { get; set; }
public GeneralInfoModel Step1 { get; set; }
public ScenarioDetailModel Step2 { get; set; }
public FinInfoModel Step3 { get; set; }
private bool GetDefaultScenarioStatus()
{
using (var dbContext = new EnVisageEntities())
{
bool defaultScenarioStatus = false;
var settingsMngr = new SystemSettingsManager(dbContext);
var systemSetting = settingsMngr.GetSystemSettingsValue((int)SystemSettingType.DefaultScenarioStatus, false);
if (!string.IsNullOrEmpty(systemSetting))
bool.TryParse(systemSetting, out defaultScenarioStatus);
return defaultScenarioStatus;
}
}
#region Constructors
public CreateScenarioModel()
{
Step1 = new GeneralInfoModel
{
//CreateAsActive = false
CreateAsActive = GetDefaultScenarioStatus()
};
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 Guid ScenarioId { get; set; }
public DateTime? ScenarioStartDate { get; set; }
public DateTime? ScenarioEndDate { 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 bool? HideName { 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; }
}
}
public class ActualCapacityItemModel
{
public Guid ResourceId { get; set; }
public Guid ExpenditureCategoryId { get; set; }
public Guid TeamId { get; set; }
public DateTime WeekEndingDate { get; set; }
/// <summary>
/// Direct (Unadjusted) quantity in hours
/// </summary>
public decimal QuantityDirect { get; set; }
/// <summary>
/// Direct (Unadjusted) cost
/// </summary>
public decimal CostDirect { get; set; }
/// <summary>
/// Direct (Unadjusted) quantity in resources
/// </summary>
public decimal ResourcesDirect { get; set; }
/// <summary>
/// Adjusted by time off quantity in hours
/// </summary>
public decimal QuantityAdjusted { get; set; }
/// <summary>
/// Adjusted by time cost
/// </summary>
public decimal CostAdjusted { get; set; }
/// <summary>
/// Adjusted quantity in resources
/// </summary>
public decimal ResourcesAdjusted { get; set; }
public decimal TimeOffCoefficient { get; set; }
public static explicit operator ActualCapacityItemModel(VW_ActualCapacityAdjusted entity)
{
if (entity == null)
return null;
return new ActualCapacityItemModel()
{
ResourceId = entity.PeopleResourceId,
ExpenditureCategoryId = entity.ExpenditureCategoryId,
TeamId = entity.TeamId,
WeekEndingDate = entity.WeekEndingDate,
QuantityDirect = entity.QuantityDirect,
CostDirect = entity.CostDirect,
ResourcesDirect = 1,
QuantityAdjusted = entity.QuantityAdjusted,
CostAdjusted = entity.CostAdjusted,
ResourcesAdjusted = entity.AdjustmentKoeff,
TimeOffCoefficient = entity.AdjustmentKoeff
};
}
public static explicit operator ActualCapacityItemModel(VW_ActualCapacityByTeamsAdjusted entity)
{
if (entity == null)
return null;
return new ActualCapacityItemModel()
{
ExpenditureCategoryId = entity.ExpenditureCategoryId,
TeamId = entity.TeamId,
WeekEndingDate = entity.WeekEndingDate,
QuantityDirect = entity.QuantityDirect,
CostDirect = entity.CostDirect,
ResourcesDirect = entity.ResourcesDirect,
QuantityAdjusted = entity.QuantityAdjusted,
CostAdjusted = entity.CostAdjusted,
ResourcesAdjusted = entity.ResourcesAdjusted,
TimeOffCoefficient = 1
};
}
}
}