using System; using System.Activities.Expressions; using System.Collections.Generic; using System.Data.Entity; using System.Linq; using System.Threading.Tasks; using EnVisage.Models; using Prevu.Core.Audit.Model; using Resources; using static EnVisage.Code.AuditProxy; namespace EnVisage.Code.BLL { public class RateManager : ManagerBase { public RateManager() : base(null) { } public RateManager(EnVisageEntities dbContext) : base(dbContext) { } protected override Rate InitInstance() { return new Rate { Id = Guid.NewGuid() }; } protected override Rate RetrieveReadOnlyById(Guid key) { return DataTable.AsNoTracking().FirstOrDefault(t => t.Id == key); } public override DbSet DataTable => DbContext.Rates; #region Public Methods public void Save(RateModel model, Guid? userid) { #if DEBUG var watch1 = new System.Diagnostics.Stopwatch(); watch1.Start(); Logger.Debug("Start EditRate Save"); #endif var isNew = false; Rate oldRate = null; if (model == null) throw new ArgumentNullException(nameof(model)); #region Save Rate data Rate dbObj = null; if (model.Id != Guid.Empty) { dbObj = DataTable.Find(model.Id); } if (dbObj == null) { isNew = true; dbObj = InitInstance(); } if (isNew) { model.CopyTo(dbObj); DbContext.Rates.Add(dbObj); } else { oldRate = Load(dbObj.Id); model.CopyTo(dbObj); } #endregion #if DEBUG watch1.Stop(); System.Diagnostics.Debug.WriteLine($"EditRate Save end save has taken {watch1.ElapsedMilliseconds} ms"); Logger.Debug($"EditRate Save end save All {watch1.ElapsedMilliseconds} ms"); #endif #region Update Related Scenarios //var scenarioManager = new ScenarioManager(_dbContext); //scenarioManager.ApplyRateAndRecalculateScenarios(dbObj); //if (model.Type == RateModel.RateType.Global) // scenarioManager.RecalculateCapacityScenariosRates(dbObj); ThreadedProcessing.BackgroundProcessManager bpm = new ThreadedProcessing.BackgroundProcessManager(userid.Value); bpm.RateScenarioUpdatesAsync(model, dbObj.Id); #endregion if (IsContextLocal) { DbContext.SaveChanges(); LogRateEvents(dbObj, oldRate, userid.Value.ToString(), null); } } public List GetRates(Guid expenditureCategoryId, RateModel.RateType? type, Guid? parentId = null, DateTime? scenarioStartDate = null, DateTime? scenarioEndDate = null, Guid? derivedId = null) { var ratesQuery = DbContext.Rates.Where(x => x.ExpenditureCategoryId == expenditureCategoryId); if (type.HasValue) ratesQuery = ratesQuery.Where(x => x.Type == (short)type); if (parentId.HasValue) ratesQuery = ratesQuery.Where(x => x.ParentId == parentId.Value); if (scenarioEndDate.HasValue) ratesQuery = ratesQuery.Where(x => x.StartDate <= scenarioEndDate); if (scenarioStartDate.HasValue) ratesQuery = ratesQuery.Where(x => x.EndDate >= scenarioStartDate); if (derivedId.HasValue) ratesQuery = ratesQuery.Where(x => x.DerivedId == derivedId.Value); return ratesQuery.ToList(); } public Dictionary> GetRates(List expCats, RateModel.RateType? type) { if (expCats == null || expCats.Count <= 0) return new Dictionary>(); var ratesQuery = DbContext.Rates.AsNoTracking().Where(x => expCats.Contains(x.ExpenditureCategoryId)); if (type.HasValue) ratesQuery = ratesQuery.Where(x => x.Type == (short)type); return ratesQuery.ToList().GroupBy(x => x.ExpenditureCategoryId) .ToDictionary(x => x.Key, g => g.ToList()); } public decimal GetRateValue(Dictionary> rates, Guid expenditureCategoryId, DateTime currentDate) { if (expenditureCategoryId == Guid.Empty) throw new ArgumentException(Messages.Rate_Parameter_expenditureCatetoryId_CantBeEmpty); if (rates == null || rates.Count <= 0) return 0; if (!rates.ContainsKey(expenditureCategoryId)) return 0; var rate = rates[expenditureCategoryId].FirstOrDefault(x => currentDate >= x.StartDate && currentDate <= x.EndDate); return rate?.Rate1 ?? 0; } public static decimal GetRateValue(Dictionary> rates, Guid expenditureCategoryId, DateTime currentDate) { if (expenditureCategoryId == Guid.Empty) throw new ArgumentException(Messages.Rate_Parameter_expenditureCatetoryId_CantBeEmpty); if (rates == null || rates.Count <= 0) return 0; if (!rates.ContainsKey(expenditureCategoryId)) return 0; var rate = rates[expenditureCategoryId].FirstOrDefault(x => currentDate >= x.StartDate && currentDate <= x.EndDate); return rate?.Rate1 ?? 0; } public Dictionary> GetRates(Guid? scenarioId) { var localRates = DbContext.Rates.Where(x => x.Type == (short)RateModel.RateType.Derived && x.ParentId == scenarioId).ToList(); var globalRates = DbContext.Rates.Where(x => x.Type == (short)RateModel.RateType.Global).ToList(); return MergeRates(globalRates, localRates); } /// /// Loads both global and local rates (for the specified scenarioIds) and returns local rates as first item in the tupple, global as second item in tuple. /// /// A list of scenario.Id values. /// A of two items, where first item always represents loca rates and second item always represents global rates. public Tuple, IEnumerable> LoadRatesByScenarios(IEnumerable scenarioIds) { var localRates = scenarioIds != null && scenarioIds.Any() ? DbContext.Rates.Where(x => x.Type == (short)RateModel.RateType.Derived && x.ParentId.HasValue && scenarioIds.Contains(x.ParentId.Value)).ToList() : new List(); var globalRates = DbContext.Rates.Where(x => x.Type == (short)RateModel.RateType.Global).ToList(); return new Tuple, IEnumerable>(localRates.Select(t => (RateModel)t), globalRates.Select(t => (RateModel)t)); } /// /// Groups rates specified in argument by scenarioIds and then by ExpenditureCategoryId. /// The lowest level contains a collection of instances describing both local rate and global rates. /// If there are both rates ending at the same EndDate then only local rate is returned. /// /// A list of scenario.Id values. /// A containing both local (for the specified scenarios) and global rates. /// /// /// AK: I'm not sure why we use so strange filter by EndDate, I just refactored existing GetRates method. /// This method supposed to be used together with GetRateValue method to find correct rate for any specified date. /// public Dictionary>> GetRatesDict(IEnumerable scenarioIds, Tuple, IEnumerable> rates) { var resultDict = new Dictionary>>(); //var rates = LoadRatesByScenarios(scenarioIds); if (scenarioIds.Any()) { var localRatesDict = rates.Item1.GroupBy(t => t.ParentId.Value).ToDictionary(gr => gr.Key, elems => elems.AsEnumerable()); var globalRates = rates.Item2; foreach (var scenarioId in scenarioIds) { var scenarioLocalRates = localRatesDict.ContainsKey(scenarioId) ? localRatesDict[scenarioId] : new List(0); resultDict.Add(scenarioId, MergeRateModels(scenarioLocalRates, globalRates)); } } else { resultDict.Add(Guid.Empty, MergeRateModels(rates.Item1, rates.Item2)); } return resultDict; } [Obsolete("Do not use DAL objects, use MergeRateModels method which deals with models")] public Dictionary> MergeRates(List globalRates, List localRates) { if (globalRates == null) globalRates = new List(); if (localRates == null) localRates = new List(); var result = new Dictionary>(); var expCatsWithLocalRates = localRates.Select(x => x.ExpenditureCategoryId).ToList(); var expCatsWithGlobalRates = globalRates.Select(x => x.ExpenditureCategoryId).ToList(); foreach (var expCatId in expCatsWithLocalRates.Union(expCatsWithGlobalRates)) { var rates = new List(); var expCatLocal = localRates.FindAll(x => x.ExpenditureCategoryId == expCatId); var expCatGlobal = globalRates.FindAll(x => x.ExpenditureCategoryId == expCatId); foreach (var rateDate in expCatLocal.Select(x => x.EndDate).Union(expCatGlobal.Select(x => x.EndDate)).OrderBy(x => x)) { var localRate = expCatLocal.FirstOrDefault(x => x.EndDate == rateDate); var globalRate = expCatGlobal.FirstOrDefault(x => x.EndDate == rateDate); rates.Add(localRate ?? globalRate); } result.Add(expCatId, rates); } return result; } public Dictionary> MergeRateModels(IEnumerable globalRates, IEnumerable localRates) { if (globalRates == null) globalRates = new List(); if (localRates == null) localRates = new List(); var result = new Dictionary>(); var localRatesDict = localRates.GroupBy(t => t.ExpenditureCategoryId).ToDictionary(ecGr => ecGr.Key, ecElems => ecElems.GroupBy(dtGr => dtGr.EndDate).ToDictionary(dtGr => dtGr.Key, dtElems => dtElems.FirstOrDefault())); var globalRatesDict = globalRates.GroupBy(t => t.ExpenditureCategoryId).ToDictionary(ecGr => ecGr.Key, ecElems => ecElems.GroupBy(dtGr => dtGr.EndDate).ToDictionary(dtGr => dtGr.Key, dtElems => dtElems.FirstOrDefault())); foreach (var expCatId in localRatesDict.Keys.Union(globalRatesDict.Keys)) { var rates = new List(); var expCatLocal = localRatesDict.ContainsKey(expCatId) ? localRatesDict[expCatId] : new Dictionary(); var expCatGlobal = globalRatesDict.ContainsKey(expCatId) ? globalRatesDict[expCatId] : new Dictionary(); foreach (var rateDate in expCatLocal.Keys.Union(expCatGlobal.Keys).OrderBy(x => x)) { var localRate = expCatLocal.ContainsKey(rateDate) ? expCatLocal[rateDate] : null; var globalRate = expCatGlobal.ContainsKey(rateDate) ? expCatGlobal[rateDate] : null; rates.Add(localRate ?? globalRate); } result.Add(expCatId, rates); } return result; } public Dictionary> Get4Parents(List parents, RateModel.RateType? type) { if (parents == null || parents.Count <= 0) return new Dictionary>(); var query = DbContext.Rates.Where(x => x.ParentId.HasValue && parents.Contains(x.ParentId.Value)); if (type.HasValue) query = query.Where(x => x.Type == (short)type.Value); return query.ToList() .GroupBy(x => x.ParentId.Value) .ToDictionary(x => x.Key, g => g.ToList()); } public void Delete(Rate rate, Guid? userid) { DbContext.Rates.Remove(rate); DbContext.SaveChanges(); LogRateEvents(null, rate, null, null); var bpm = new ThreadedProcessing.BackgroundProcessManager(userid.Value); if (rate.Type == (short)RateModel.RateType.Derived) { var globalRates = DbContext.Rates.Where(q => q.ExpenditureCategoryId == rate.ExpenditureCategoryId && q.Type == (short)RateModel.RateType.Global && (q.StartDate < rate.EndDate || q.EndDate > rate.StartDate)); if (globalRates.Any()) { foreach (var globalRate in globalRates) { bpm.RateScenarioUpdatesAsync((RateModel)globalRate, globalRate.Id); } } } else { var globalRate = rate.Clone(); globalRate.Rate1 = 0; bpm.RateScenarioUpdatesAsync((RateModel)globalRate, globalRate.Id); } } public void DeleteRange(IEnumerable rates) { DbContext.Rates.RemoveRange(rates); DbContext.SaveChanges(); LogRatesDeleteEvent(rates, null, null); } #endregion #region Log Methods private readonly Func _rateValue = r => $"{r.Rate1} - {r.StartDate} and {r.EndDate}"; public void LogRatesDeleteEvent(IEnumerable rates, string userId, string transactionId) { transactionId = Utils.GetOrCreateTransactionId(DbContext, transactionId); foreach (var rate in rates) { LogRateEvents(null, rate, userId, transactionId); } } public void LogRateEvents(Rate rate, Rate oldrate, string userId, string transactionId) { Scenario scenario = null; Guid? scenarioId = null; Guid? projectId = null; var parentid = rate?.ParentId ?? oldrate?.ParentId; if (parentid != Guid.Empty) { using (var scenarioManager = new ScenarioManager(DbContext)) { scenario = scenarioManager.Load(parentid); } } if (scenario != null) { scenarioId = scenario.Id; projectId = scenario.ParentId; } transactionId = Utils.GetOrCreateTransactionId(DbContext, transactionId); if (string.IsNullOrEmpty(userId)) userId = SecurityManager.GetUserPrincipal().ToString(); if (rate != null && oldrate == null) { #region Rate Created LogEvent(transactionId, new EventRequestBaseModel { EntityId = rate.Id, ScenarioId = scenarioId, ProjectId = projectId, ClassificationKey = "RateAdd", NewValue = _rateValue(rate), UserId = userId }); #endregion } if (oldrate != null) { if (rate != null) LogEvent(transactionId, new EventRequestBaseModel { EntityId = rate.Id, ScenarioId = scenarioId, ProjectId = projectId, ClassificationKey = "RateEditOrDerive", UserId = userId, NewValue = _rateValue(rate), OldValue = _rateValue(oldrate) }); else LogEvent(transactionId, new EventRequestBaseModel { EntityId = oldrate.Id, ScenarioId = scenarioId, ProjectId = projectId, ClassificationKey = "RateDelete", UserId = userId, OldValue = _rateValue(oldrate) }); } Task.Run(() => CommitEventChanges(transactionId)); } #endregion } }