EnVisageOnline/Main/Source/EnVisage/Code/BLL/RateManager.cs

419 lines
17 KiB
C#

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<Rate, RateModel>
{
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<Rate> 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<Rate> 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<Guid, List<Rate>> GetRates(List<Guid> expCats, RateModel.RateType? type)
{
if (expCats == null || expCats.Count <= 0)
return new Dictionary<Guid, List<Rate>>();
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<Guid, List<Rate>> 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<Guid, IEnumerable<RateModel>> 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<Guid, List<Rate>> 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);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="scenarioIds">A list of scenario.Id values.</param>
/// <returns>A <see cref="Tuple"/> of two items, where first item always represents loca rates and second item always represents global rates.</returns>
public Tuple<IEnumerable<RateModel>, IEnumerable<RateModel>> LoadRatesByScenarios(IEnumerable<Guid> 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<Rate>();
var globalRates = DbContext.Rates.Where(x => x.Type == (short)RateModel.RateType.Global).ToList();
return new Tuple<IEnumerable<RateModel>, IEnumerable<RateModel>>(localRates.Select(t => (RateModel)t), globalRates.Select(t => (RateModel)t));
}
/// <summary>
/// Groups rates specified in <paramref name="rates"/> argument by scenarioIds and then by ExpenditureCategoryId.
/// The lowest level contains a collection of <see cref="RateModel"/> instances describing both local rate and global rates.
/// If there are both rates ending at the same EndDate then only local rate is returned.
/// </summary>
/// <param name="scenarioIds">A list of scenario.Id values.</param>
/// <param name="rates">A <see cref="Tuple"/> containing both local (for the specified scenarios) and global rates.</param>
/// <returns></returns>
/// <remarks>
/// 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.
/// </remarks>
public Dictionary<Guid, Dictionary<Guid, IEnumerable<RateModel>>> GetRatesDict(IEnumerable<Guid> scenarioIds, Tuple<IEnumerable<RateModel>, IEnumerable<RateModel>> rates)
{
var resultDict = new Dictionary<Guid, Dictionary<Guid, IEnumerable<RateModel>>>();
//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<RateModel>(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<Guid, List<Rate>> MergeRates(List<Rate> globalRates, List<Rate> localRates)
{
if (globalRates == null)
globalRates = new List<Rate>();
if (localRates == null)
localRates = new List<Rate>();
var result = new Dictionary<Guid, List<Rate>>();
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<Rate>();
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<Guid, IEnumerable<RateModel>> MergeRateModels(IEnumerable<RateModel> globalRates, IEnumerable<RateModel> localRates)
{
if (globalRates == null)
globalRates = new List<RateModel>();
if (localRates == null)
localRates = new List<RateModel>();
var result = new Dictionary<Guid, IEnumerable<RateModel>>();
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<RateModel>();
var expCatLocal = localRatesDict.ContainsKey(expCatId) ? localRatesDict[expCatId] : new Dictionary<DateTime, RateModel>();
var expCatGlobal = globalRatesDict.ContainsKey(expCatId) ? globalRatesDict[expCatId] : new Dictionary<DateTime, RateModel>();
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<Guid, List<Rate>> Get4Parents(List<Guid> parents, RateModel.RateType? type)
{
if (parents == null || parents.Count <= 0)
return new Dictionary<Guid, List<Rate>>();
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<Rate> rates)
{
DbContext.Rates.RemoveRange(rates);
DbContext.SaveChanges();
LogRatesDeleteEvent(rates, null, null);
}
#endregion
#region Log Methods
private readonly Func<Rate, string> _rateValue = r => $"{r.Rate1} - {r.StartDate} and {r.EndDate}";
public void LogRatesDeleteEvent(IEnumerable<Rate> 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
}
}