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

1526 lines
81 KiB
C#

using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using EnVisage.Code.Extensions;
using EnVisage.Models;
namespace EnVisage.Code.BLL
{
public class NonProjectTimeManager : ManagerBase<NonProjectTime, NonProjectTimeModel>
{
public NonProjectTimeManager(EnVisageEntities dbContext)
: base(dbContext)
{
}
public void DeleteNPT(Guid deleteNPTimeId, Guid? ResourceId)
{
var npTime = this.Load(deleteNPTimeId);
var nptRas = this.DbContext.NonProjectTime2Resource.Where(x => x.NonProjectTimeId == npTime.Id &&
(!ResourceId.HasValue || x.PeopleResourceId == ResourceId.Value)).SelectMany(y => y.NonProjectTimeResourceAllocations).ToList();
this.DbContext.NonProjectTimeResourceAllocations.RemoveRange(nptRas);
var nptTas = this.DbContext.NonProjectTime2Team.Where(x => x.NonProjectTimeId == deleteNPTimeId).SelectMany(u => u.NonProjectTimeTeamAllocations).ToList();
this.DbContext.NonProjectTimeTeamAllocations.RemoveRange(nptTas);
this.DbContext.NonProjectTime2Resource.RemoveRange(this.DbContext.NonProjectTime2Resource.Where(x => x.NonProjectTimeId == deleteNPTimeId && (!ResourceId.HasValue || x.PeopleResourceId == ResourceId.Value)).ToList());
this.DbContext.NonProjectTime2Team.RemoveRange(this.DbContext.NonProjectTime2Team.Where(x => x.NonProjectTimeId == deleteNPTimeId).ToList());
if (!ResourceId.HasValue)
this.DbContext.NonProjectTimes.RemoveRange(this.DbContext.NonProjectTimes.Where(x => x.Id == deleteNPTimeId).ToList());
if (this.IsContextLocal)
DbContext.BulkSaveChanges();
}
protected override NonProjectTime InitInstance()
{
return new NonProjectTime { Id = Guid.NewGuid() };
}
protected override NonProjectTime RetrieveReadOnlyById(Guid key)
{
return DataTable.AsNoTracking().FirstOrDefault(t => t.Id == key);
}
public override DbSet<NonProjectTime> DataTable
{
get
{
return DbContext.NonProjectTimes;
}
}
public override NonProjectTime Save(NonProjectTimeModel model)
{
if (model == null)
throw new ArgumentNullException();
if (model.IsTeamAssignmentMode && ((model.Teams == null) || (model.Teams.Count < 1)))
throw new Exception("At least one team must be specified for Non-Project Time item");
if (!model.IsTeamAssignmentMode && ((model.Resources == null) || (model.Resources.Count < 1)))
throw new Exception("At least one resource must be specified for Non-Project Time item");
if ((model.NonProjectTimeAllocations == null) || (model.NonProjectTimeAllocations.Count < 1))
throw new Exception("At least one allocation item must be specified for Non-Project Time");
#region Add Allocation Type
if (model.NonProjectTimeCategoryId == Guid.Empty)
{
if (!SecurityManager.CheckSecurityObjectPermission(Areas.RD_ResourceNPTAllocationCategory, AccessLevel.Write))
{
throw new InvalidOperationException("You haven't permissions to create a new Allocation Category");
}
var npTimeCategory = new NonProjectTimeCategory
{
Id = Guid.NewGuid(),
Name = model.NonProjectTimeCategoryName
};
DbContext.NonProjectTimeCategories.Add(npTimeCategory);
model.NonProjectTimeCategoryId = npTimeCategory.Id;
}
var npTime = base.Save(model);
#endregion
#region Delete old records
var npTime2Resources = new List<NonProjectTime2Resource>();
var npTime2Teams = new List<NonProjectTime2Team>();
var npTimeResourceAllocations = new List<NonProjectTimeResourceAllocation>();
var npTimeTeamAllocations = new List<NonProjectTimeTeamAllocation>();
var fcManager = new FiscalCalendarManager(DbContext);
if (!Guid.Empty.Equals(model.Id))
{
var nextWeek = model.NonProjectTimeEndDate.HasValue ? fcManager.GetFirstWeek(model.NonProjectTimeEndDate) : null;
npTime2Resources = DbContext.NonProjectTime2Resource.Where(t => t.NonProjectTimeId == model.Id).ToList();
npTime2Teams = DbContext.NonProjectTime2Team.Where(t => t.NonProjectTimeId == model.Id).ToList();
var npTime2ResourcesIds = npTime2Resources.Select(x => x.Id).ToArray();
var npTime2TeamsIds = npTime2Teams.Select(x => x.Id).ToArray();
npTimeResourceAllocations = DbContext.NonProjectTimeResourceAllocations.Where(t => npTime2ResourcesIds.Contains(t.NonProjectTime2ResourceId)).ToList();
npTimeTeamAllocations = DbContext.NonProjectTimeTeamAllocations.Where(t => npTime2TeamsIds.Contains(t.NonProjectTime2TeamId)).ToList();
var npt2ResItemsToDelete = npTime2Resources.Where(x => (model.Resources == null) || model.Resources.All(s => s != x.PeopleResourceId)).ToArray();
var npt2TeamItemsToDelete = npTime2Teams.Where(x => (model.Teams == null) || model.Teams.All(s => s != x.TeamId)).ToArray();
var nptResAllocItemsToDelete =
npTimeResourceAllocations.Where(x => x.WeekEndingDate < model.NonProjectTimeStartDate ||
((nextWeek != null) && (x.WeekEndingDate > nextWeek.EndDate)) ||
npt2ResItemsToDelete.Any(s => x.NonProjectTime2ResourceId == s.Id)).ToArray();
var nptTeamAllocItemsToDelete =
npTimeTeamAllocations.Where(x => x.WeekEndingDate < model.NonProjectTimeStartDate ||
((nextWeek != null) && (x.WeekEndingDate > nextWeek.EndDate)) ||
npt2TeamItemsToDelete.Any(s => x.NonProjectTime2TeamId == s.Id)).ToArray();
DbContext.NonProjectTimeResourceAllocations.RemoveRange(nptResAllocItemsToDelete);
DbContext.NonProjectTimeTeamAllocations.RemoveRange(nptTeamAllocItemsToDelete);
DbContext.NonProjectTime2Resource.RemoveRange(npt2ResItemsToDelete);
DbContext.NonProjectTime2Team.RemoveRange(npt2TeamItemsToDelete);
}
#endregion
#region Prepare Allocations
var allocations = new List<NonProjectTimeAllocationModel>();
if (!model.Permanent)
{
allocations = model.NonProjectTimeAllocations;
}
else
{
var allocation = model.NonProjectTimeAllocations.First();
var weeks = fcManager.GetFiscalCalendar(FiscalCalendarModel.FiscalYearType.Week, (model.EffectiveDateOfChange ?? model.NonProjectTimeStartDate), null, true, null, false);
allocations = weeks.Select(x => new NonProjectTimeAllocationModel()
{
WeekStartDate = x.StartDate,
WeekEndingDate = x.EndDate,
HoursOff = allocation.HoursOff
}).ToList();
}
#endregion
if (model.IsTeamAssignmentMode)
{
// Save NPT for teams
#region Save Links to Resources
var npt2TeamItemsNew = new Dictionary<string, Guid>();
var nonProjectTime2TeamToAddCollection = new List<NonProjectTime2Team>();
for (var index = 0; index < model.Teams.Count; index++)
{
var npTime2Team = npTime2Teams.FirstOrDefault(x => x.TeamId == model.Teams[index]);
if (npTime2Team == null)
{
npTime2Team = new NonProjectTime2Team()
{
Id = Guid.NewGuid(),
NonProjectTimeId = npTime.Id,
TeamId = model.Teams[index],
Position = index + 1
};
nonProjectTime2TeamToAddCollection.Add(npTime2Team);
}
else
{
npTime2Team.Position = index + 1;
}
npt2TeamItemsNew.Add(npTime2Team.TeamId.ToString(), npTime2Team.Id);
}
DbContext.NonProjectTime2Team.AddRange(nonProjectTime2TeamToAddCollection);
#endregion
#region Save NonProjectTimeAllocations
var prMngr = new PeopleResourcesManager(this.DbContext);
var defaultUom = prMngr.GetDafaultUOM();
var teamsUomValues = (new TeamManager(DbContext)).GetTeamsUOM(model.Teams, model.NonProjectTimeStartDate);
decimal currentUom;
var nonProjectTimeTeamAllocationToAddCollection = new List<NonProjectTimeTeamAllocation>();
foreach (var npTimeAllocation in allocations)
{
foreach (var team in model.Teams)
{
if (!teamsUomValues.ContainsKey(team))
{
// UOM for team on current date point not found, probably because the team doesn't has
// existing resources on the date point. Get default UOM
currentUom = defaultUom;
//throw new InvalidOperationException(string.Format("UoM was not found for the team {0}", team));
}
else
currentUom = teamsUomValues[team];
var hoursOff = Convert.ToInt32(Math.Min(npTimeAllocation.HoursOff, currentUom));
var npt2TeamItemId = npt2TeamItemsNew[team.ToString()];
var allocation = npTimeTeamAllocations.FirstOrDefault(x => x.NonProjectTime2TeamId == npt2TeamItemId && x.WeekEndingDate == npTimeAllocation.WeekEndingDate);
if (allocation == null)
{
allocation = new NonProjectTimeTeamAllocation
{
Id = Guid.NewGuid(),
NonProjectTime2TeamId = npt2TeamItemId,
WeekEndingDate = npTimeAllocation.WeekEndingDate,
HoursOff = hoursOff
};
nonProjectTimeTeamAllocationToAddCollection.Add(allocation);
}
else
{
if (allocation.HoursOff != hoursOff)
{
allocation.HoursOff = hoursOff;
}
}
}
}
DbContext.NonProjectTimeTeamAllocations.AddRange(nonProjectTimeTeamAllocationToAddCollection);
#endregion
}
else
{
// Save NPT for resources
#region Save Links to Resources
var npt2ResItemsNew = new Dictionary<string, Guid>();
var nonProjectTime2ResourceToAddCollection = new List<NonProjectTime2Resource>();
for (var index = 0; index < model.Resources.Count; index++)
{
var npTime2Resource = npTime2Resources.FirstOrDefault(x => x.PeopleResourceId == model.Resources[index]);
if (npTime2Resource == null)
{
npTime2Resource = new NonProjectTime2Resource()
{
Id = Guid.NewGuid(),
NonProjectTimeId = npTime.Id,
PeopleResourceId = model.Resources[index],
Position = index + 1
};
nonProjectTime2ResourceToAddCollection.Add(npTime2Resource);
}
else
{
npTime2Resource.Position = index + 1;
}
npt2ResItemsNew.Add(npTime2Resource.PeopleResourceId.ToString(), npTime2Resource.Id);
}
DbContext.NonProjectTime2Resource.AddRange(nonProjectTime2ResourceToAddCollection);
#endregion
#region Save NonProjectTimeAllocations
var resourcesUomValues = (new PeopleResourcesManager(DbContext)).GetResourcesUOM(model.Resources);
var nonProjectTimeResourceAllocations = new List<NonProjectTimeResourceAllocation>();
foreach (var npTimeAllocation in allocations)
{
foreach (var res in model.Resources)
{
if (!resourcesUomValues.ContainsKey(res))
throw new InvalidOperationException($"UoM was not found for the resource {res}");
var hoursOff = Convert.ToInt32(Math.Min(npTimeAllocation.HoursOff, resourcesUomValues[res]));
var npt2ResItemId = npt2ResItemsNew[res.ToString()];
var allocation = npTimeResourceAllocations.FirstOrDefault(x => x.NonProjectTime2ResourceId == npt2ResItemId && x.WeekEndingDate == npTimeAllocation.WeekEndingDate);
if (allocation == null)
{
allocation = new NonProjectTimeResourceAllocation
{
Id = Guid.NewGuid(),
NonProjectTime2ResourceId = npt2ResItemId,
WeekEndingDate = npTimeAllocation.WeekEndingDate,
HoursOff = hoursOff
};
nonProjectTimeResourceAllocations.Add(allocation);
}
else
{
if (allocation.HoursOff != hoursOff)
{
allocation.HoursOff = hoursOff;
}
}
}
}
DbContext.NonProjectTimeResourceAllocations.AddRange(nonProjectTimeResourceAllocations);
#endregion
}
return npTime;
}
/// <summary>
/// Returns Non-Project Time item by it's ID. Teams and Resources are filtered by userId permissions
/// </summary>
public NonProjectTimeModel GetNonProjectTime(Guid npTimeId, string userId)
{
if (npTimeId.Equals(Guid.Empty))
return null;
var dbRecord = DbContext.NonProjectTimes.Include(x => x.NonProjectTimeCategory).AsNoTracking().FirstOrDefault(x => x.Id.Equals(npTimeId));
if (dbRecord == null)
throw new Exception($"Non-project Time item not found (Id: {npTimeId})");
var result = (NonProjectTimeModel)dbRecord;
if (result.IsTeamAssignmentMode)
result.Teams = this.GetTeams(npTimeId);
else
result.Resources = this.GetResources(npTimeId);
// Get NPT allocations
var fiscalWeeks = FiscalCalendarManager.GetFiscalCalendarWeeksByRange(result.NonProjectTimeStartDate, result.NonProjectTimeEndDate, this.DbContext)
.ToDictionary(k => k.EndDate, v => v.StartDate);
IEnumerable<NonProjectTimeAllocationModel> allocationsAsRaw;
if (result.IsTeamAssignmentMode)
{
// Allocations to teams
allocationsAsRaw =
DbContext.NonProjectTimeTeamAllocations.Include(x => x.NonProjectTime2Team).AsNoTracking()
.Where(x => result.Teams.Contains(x.NonProjectTime2Team.TeamId) &&
x.NonProjectTime2Team.NonProjectTimeId.Equals(npTimeId))
.OrderBy(x => x.WeekEndingDate).ToList()
.Select(x => new NonProjectTimeAllocationModel()
{
WeekStartDate = fiscalWeeks[x.WeekEndingDate],
WeekEndingDate = x.WeekEndingDate,
HoursOff = x.HoursOff
});
}
else
{
// Allocations to resources
allocationsAsRaw =
DbContext.NonProjectTimeResourceAllocations.Include(x => x.NonProjectTime2Resource).AsNoTracking()
.Where(x => result.Resources.Contains(x.NonProjectTime2Resource.PeopleResourceId) &&
x.NonProjectTime2Resource.NonProjectTimeId.Equals(npTimeId))
.OrderBy(x => x.WeekEndingDate).ToList()
.Select(x => new NonProjectTimeAllocationModel()
{
WeekStartDate = fiscalWeeks[x.WeekEndingDate],
WeekEndingDate = x.WeekEndingDate,
HoursOff = x.HoursOff
});
}
if (allocationsAsRaw != null)
{
var allocationsGrouped =
allocationsAsRaw.GroupBy(x => new { x.WeekStartDate, x.WeekEndingDate },
(key, grp) => new NonProjectTimeAllocationModel()
{
WeekStartDate = key.WeekStartDate,
WeekEndingDate = key.WeekEndingDate,
HoursOff = grp.Select(k => k.HoursOff).Min()
}).ToList();
// Leave single allocation record for Permanent NPT
var allocationsFiltered = result.Permanent ?
allocationsGrouped.Where(x => x.WeekEndingDate >= DateTime.UtcNow.Date).Take(1).ToList() :
allocationsGrouped;
result.NonProjectTimeAllocations = allocationsFiltered;
result.IsCurveConstant = result.NonProjectTimeAllocations.Select(x => x.HoursOff).Distinct().Count() == 1;
}
return result;
}
// Returns Non-Project time list as Dictionary[NptId, NptDataAsModel]
public Dictionary<Guid, NonProjectTimeModel> GetNonProjectTimes(List<Guid> npTimeIds, bool withTeamsAndResources)
{
if (npTimeIds == null)
throw new ArgumentNullException("npTimeIds");
if (npTimeIds.Count < 1)
return new Dictionary<Guid, NonProjectTimeModel>();
var qry = DbContext.NonProjectTimes.AsNoTracking().Where(x => npTimeIds.Contains(x.Id));
if (withTeamsAndResources)
qry = qry.Include(x => x.NonProjectTime2Team).Include(x => x.NonProjectTime2Resource);
var result = qry.ToList().Select(x => (NonProjectTimeModel)x)
.GroupBy(x => x.Id).ToDictionary(k => k.Key, npt => npt.FirstOrDefault());
return result;
}
public List<NonProjectTimeListItemModel> GetNonProjectTimeList(Guid resourceId, bool permanent, bool ishistory, bool includeInvalid)
{
return GetNonProjectTimeList(new List<Guid>() { resourceId }, permanent, ishistory, includeInvalid);
}
public List<NonProjectTimeListItemModel> GetNonProjectTimeList(List<Guid> resources, bool permanent, bool ishistory, bool includeInvalid)
{
if (resources == null || resources.Count <= 0)
return new List<NonProjectTimeListItemModel>();
var uoms = (new PeopleResourcesManager(DbContext)).GetResourcesUOM(resources);
var allocations = GetAllocations(resources);
var weekEndings = allocations.SelectMany(x => x.Value).Select(x => x.WeekEndingDate).Distinct().ToList();
var fcWeekEndings = (new FiscalCalendarManager(DbContext)).GetFiscalCalendar(FiscalCalendarModel.FiscalYearType.Week, weekEndings, true, null, false)
.ToDictionary(x => x.EndDate);
var query = NonProjectTimeListItemModelBaseQuery.Where(x => allocations.Keys.Contains(x.Id) && x.Permanent == permanent);
if (ishistory)
query = query.Where(x => x.EndDate < DateTime.Today);
else if (!permanent)
query = query.Where(x => x.EndDate >= DateTime.Today);
var npTimes = query.ToList();
foreach (var npTime in npTimes)
{
if (!allocations.ContainsKey(npTime.Id))
continue;
var calculatedPercentsOff = new List<decimal>();
foreach (var allocation in allocations[npTime.Id])
{
if (!uoms.ContainsKey(allocation.PeopleResourceId))
continue;
if (!fcWeekEndings.ContainsKey(allocation.WeekEndingDate))
continue;
var distribution = CalculateWeekDistribution(fcWeekEndings[allocation.WeekEndingDate], npTime.StartDate, npTime.EndDate, uoms[allocation.PeopleResourceId]);
if (distribution == null)
continue;
npTime.TotalUOMHours += distribution.HoursOff;
npTime.TotalHours += allocation.HoursOff;
if (uoms[allocation.PeopleResourceId] > 0)
{
var currentPercents = allocation.HoursOff / uoms[allocation.PeopleResourceId] * 100;
if (npTime.TotalPercents == 0)
{
npTime.MinPercents = currentPercents;
npTime.MaxPercents = npTime.MinPercents;
}
else
{
// Set flag, if final percents are
npTime.ApproximatePercents = npTime.ApproximatePercents || (Math.Abs(npTime.TotalPercents - currentPercents) >= 1);
if (npTime.MinPercents > currentPercents)
npTime.MinPercents = currentPercents;
if (npTime.MaxPercents < currentPercents)
npTime.MaxPercents = currentPercents;
}
calculatedPercentsOff.Add(currentPercents);
}
npTime.TotalPercents = calculatedPercentsOff.Count > 0 ? calculatedPercentsOff.Average() : 0;
}
// for permanent resources we should take only one entri that is actual for current date
var actualValue4Today = allocations[npTime.Id].FirstOrDefault(x => x.WeekEndingDate >= DateTime.UtcNow.Date);
if (actualValue4Today != null)
{
npTime.ActualHours4Today = actualValue4Today.HoursOff;
npTime.ActualPercents4Today = Convert.ToInt32(actualValue4Today.HoursOff / uoms[actualValue4Today.PeopleResourceId] * 100);
}
npTime.TotalPercents = Math.Round(npTime.TotalPercents);
npTime.MinPercents = Math.Round(npTime.MinPercents > 0 ? (npTime.MinPercents < 100 ? npTime.MinPercents : 100) : 0);
npTime.MaxPercents = Math.Round(npTime.MaxPercents > 0 ? (npTime.MaxPercents < 100 ? npTime.MaxPercents : 100) : 0);
npTime.Details = npTime.Details.TruncateWithContinuation(60);
}
if (includeInvalid)
{
var alreadyExtractedNptItems = npTimes.Select(x => x.Id).ToList();
var invalidNptItemsQry = DbContext.VW_NonProjectTimeInvalidItems.AsNoTracking()
.Where(x => resources.Contains(x.PeopleResourceId) &&
!alreadyExtractedNptItems.Contains(x.NonProjectTimeId));
if (ishistory)
invalidNptItemsQry = invalidNptItemsQry.Where(x => x.EndDate.HasValue && (x.EndDate.Value < DateTime.Today));
else
invalidNptItemsQry = invalidNptItemsQry.Where(x => x.EndDate.HasValue && (x.EndDate.Value >= DateTime.Today));
var invalidNptItemsAsModels = invalidNptItemsQry.ToList().Select(x => new NonProjectTimeListItemModel()
{
Id = x.NonProjectTimeId,
Name = x.NonProjectTimeName,
CategoryName = x.NonProjectTimeCategoryName,
StartDate = x.StartDate,
EndDate = x.EndDate,
IsPercentMode = false,
Details = x.Details.TruncateWithContinuation(60),
Permanent = false,
IsFromTeam = false,
IsInvalid = true
}).ToList();
npTimes.AddRange(invalidNptItemsAsModels);
}
return npTimes;
}
/// <summary>
/// Returns NonProjectTime items for specified Team on a date point
/// </summary>
public List<NonProjectTimeListItemModel> GetNonProjectTimes4Team(Guid teamId, bool permanent, bool ishistory,
DateTime datePointUtc)
{
if (teamId == Guid.Empty)
return new List<NonProjectTimeListItemModel>();
DateTime dt = datePointUtc.ToUniversalTime().Date;
FiscalCalendarManager fcMngr = new FiscalCalendarManager(DbContext);
PeopleResourcesManager prMngr = new PeopleResourcesManager(DbContext);
var teamResourcesModels = prMngr.LoadPeopleResourcesByTeamNonStrict(teamId, false, dt);
var resources = teamResourcesModels.Select(x => x.Id).ToList();
var uoms = (new PeopleResourcesManager(DbContext)).GetResourcesUOM(resources);
var allocations = GetAllocationsForTeam(teamId);
var weekEndings = allocations.SelectMany(x => x.Value).Select(x => x.WeekEndingDate).Distinct().ToList();
var fcWeekEndings =
fcMngr.GetFiscalCalendar(FiscalCalendarModel.FiscalYearType.Week, weekEndings, true, null, false)
.ToDictionary(x => x.EndDate);
var query = NonProjectTimeListItemModelBaseQuery;
if (ishistory)
query = query.Where(x => x.EndDate < dt);
else
{
if (!permanent)
query = query.Where(x => x.EndDate >= dt);
}
query = query.Where(x => allocations.Keys.Contains(x.Id) && x.Permanent == permanent);
var npTimes = query.ToList();
// Remove NPT items, which have no allocations in current team on focused date range period
List<Guid> npTimeItemsToRemove = new List<Guid>();
foreach (Guid npTimeId in allocations.Keys)
{
int allocItemsCount = 0;
if (ishistory)
// Historical NPT item should be not displayed, if it has no allocations in the past
allocItemsCount = allocations[npTimeId]
.Where(x => x.TeamId.Equals(teamId) && (x.WeekEndingDate < dt)).Count();
else
// Future NPT item should be not displayed, if it has no allocations today and in future
allocItemsCount = allocations[npTimeId]
.Where(x => x.TeamId.Equals(teamId) && (x.WeekEndingDate >= dt)).Count();
if (allocItemsCount < 1)
npTimeItemsToRemove.Add(npTimeId);
}
if (npTimeItemsToRemove.Count > 0)
npTimes.RemoveAll(x => npTimeItemsToRemove.Contains(x.Id));
foreach (var npTime in npTimes)
{
if (!allocations.ContainsKey(npTime.Id))
continue;
var calculatedPercentsOff = new List<decimal>();
var npTimeAllocations = allocations[npTime.Id];
var maxUom = npTimeAllocations.Where(x => uoms.ContainsKey(x.PeopleResourceId))
.Select(x => uoms[x.PeopleResourceId]).Max();
foreach (var allocation in npTimeAllocations)
{
if (!uoms.ContainsKey(allocation.PeopleResourceId))
continue;
if (!fcWeekEndings.ContainsKey(allocation.WeekEndingDate))
continue;
var distribution = CalculateWeekDistribution(fcWeekEndings[allocation.WeekEndingDate], npTime.StartDate, npTime.EndDate, uoms[allocation.PeopleResourceId]);
if (distribution == null)
continue;
npTime.TotalUOMHours += distribution.HoursOff;
npTime.TotalHours += allocation.HoursOff;
if (maxUom > 0)
{
var currentPercents = allocation.HoursOff / maxUom * 100;
if (npTime.TotalPercents == 0)
{
npTime.MinPercents = currentPercents;
npTime.MaxPercents = npTime.MinPercents;
}
else
{
// Set flag, if final percents are
npTime.ApproximatePercents = npTime.ApproximatePercents || (Math.Abs(npTime.TotalPercents - currentPercents) >= 1);
if (npTime.MinPercents > currentPercents)
npTime.MinPercents = Math.Round(currentPercents);
if (npTime.MaxPercents < currentPercents)
npTime.MaxPercents = Math.Round(currentPercents);
}
calculatedPercentsOff.Add(currentPercents);
}
npTime.TotalPercents = calculatedPercentsOff.Count > 0 ? calculatedPercentsOff.Average() : 0;
}
// for permanent resources we should take only one entri that is actual for current date
var actualValue4Today = allocations[npTime.Id].FirstOrDefault(x => x.WeekEndingDate >= DateTime.UtcNow.Date);
if (actualValue4Today != null)
{
npTime.ActualHours4Today = actualValue4Today.HoursOff;
if (uoms.Any(q => q.Key == actualValue4Today.PeopleResourceId))
{
npTime.ActualPercents4Today = Convert.ToInt32(actualValue4Today.HoursOff / uoms[actualValue4Today.PeopleResourceId] * 100);
}
else
{
var defaultUom = (new PeopleResourcesManager(DbContext)).GetDafaultUOM();
npTime.ActualPercents4Today = Convert.ToInt32(actualValue4Today.HoursOff / defaultUom * 100);
}
}
npTime.TotalPercents = Math.Round(npTime.TotalPercents);
npTime.MinPercents = Math.Round(npTime.MinPercents > 0 ? (npTime.MinPercents < 100 ? npTime.MinPercents : 100) : 0);
npTime.MaxPercents = Math.Round(npTime.MaxPercents > 0 ? (npTime.MaxPercents < 100 ? npTime.MaxPercents : 100) : 0);
npTime.Details = npTime.Details.TruncateWithContinuation(60);
}
return npTimes;
}
public Dictionary<Guid, List<VW_NonProjectTimeAllocation>> GetAllocations(Guid resourceId, Guid? npTimeId = null)
{
return GetAllocations(new List<Guid> { resourceId }, npTimeId);
}
public Dictionary<Guid, List<VW_NonProjectTimeAllocation>> GetAllocations(List<Guid> resources, Guid? npTimeId = null)
{
var npTimeAllocationsQuery = DbContext.VW_NonProjectTimeAllocation.AsNoTracking()
.Where(x => resources.Contains(x.PeopleResourceId));
if (npTimeId.HasValue)
npTimeAllocationsQuery = npTimeAllocationsQuery.Where(x => x.NonProjectTimeId == npTimeId.Value);
var npTimeAllocations = npTimeAllocationsQuery.ToList()
.GroupBy(x => x.NonProjectTimeId)
.ToDictionary(x => x.Key, x => x.OrderBy(s => s.WeekEndingDate).ToList());
return npTimeAllocations;
}
public List<Guid> GetResources(Guid npTimeId)
{
if (npTimeId == Guid.Empty)
return new List<Guid>();
var resources = DbContext.NonProjectTime2Resource.AsNoTracking()
.Where(x => x.NonProjectTimeId == npTimeId)
.OrderBy(x => x.Position)
.Select(x => x.PeopleResourceId)
.ToList();
return resources;
}
public List<Guid> GetTeams(Guid npTimeId)
{
if (npTimeId == Guid.Empty)
return new List<Guid>();
var teams = DbContext.NonProjectTime2Team.AsNoTracking()
.Where(x => x.NonProjectTimeId == npTimeId)
.OrderBy(x => x.Position)
.Select(x => x.TeamId)
.ToList();
return teams;
}
public Dictionary<string, NonProjectTimeDistributionModel> RecalculateNPTimeAllocations(DateTime startDate, DateTime? endDate, List<FiscalCalendar> weeks, IEnumerable<Guid> resourceIds, IEnumerable<Guid> teamIds)
{
var result = new Dictionary<string, NonProjectTimeDistributionModel>();
var query = (from res in DbContext.VW_TeamResource.AsNoTracking()
join ec in DbContext.ExpenditureCategory on res.ExpenditureCategoryId equals ec.Id
join uom in DbContext.UOMs on ec.UOMId equals uom.Id
select new
{
res.Id,
res.TeamId,
res.TeamStartDate,
res.TeamEndDate,
uom.UOMValue
}).AsQueryable();
if (resourceIds != null && resourceIds.Any())
query = query.Where(t => resourceIds.Contains(t.Id));
if (teamIds != null && teamIds.Any())
query = query.Where(t => teamIds.Contains(t.TeamId));
var resources = query.Distinct().ToArray();
var firstWeek = weeks.FirstOrDefault() == null ? startDate : weeks.FirstOrDefault().EndDate;
var lastWeek = weeks.LastOrDefault() == null ? endDate : weeks.LastOrDefault().EndDate;
var allResHolidays = (new FiscalCalendarManager(DbContext)).GetHolidayAllocationsByResource(firstWeek, lastWeek, resourceIds: resources.Select(t => t.Id));
if (endDate.HasValue && startDate > endDate.Value)
return result;
if (weeks == null || weeks.Count <= 0)
return result;
foreach (var week in weeks)
{
// find maxumum resource UOM adjusted by holidays
var uomValue = 0M;
foreach (var resource in resources)
{
var holidayKoeff = allResHolidays.ContainsKey(resource.Id) && allResHolidays[resource.Id].ContainsKey(week.EndDate) ? allResHolidays[resource.Id][week.EndDate] : 1;
uomValue = Math.Max(uomValue, resource.UOMValue * holidayKoeff);
}
var distribution = CalculateWeekDistribution(week, startDate, endDate, uomValue);
if (distribution == null)
continue;
result.Add(Utils.ConvertToUnixDate(week.EndDate).ToString(), distribution);
}
return result;
}
#region NPT dates checks
public NonProjectTimeDatesCheckResultModel CheckDateRangeViolationByResources(List<Guid> resources, DateTime startDate, DateTime? endDate)
{
// Create dummy team, because we focused on check resource teams membership range,
// and don't care of the teams, they are member of
var result = new NonProjectTimeDatesCheckResultModel();
var dummyTeamItem = new NonProjectTimeDatesCheckResultModel.TeamCheckModel()
{
Id = Guid.Empty,
Resources = new List<NonProjectTimeDatesCheckResultModel.ResourceCheckModel>()
};
result.Teams.Add(dummyTeamItem);
result.HasViolation = false;
if ((resources == null) || (resources.Count < 1))
return result;
var inactiveResources = DbContext.PeopleResources.Where(x => resources.Contains(x.Id) && !x.IsActiveEmployee).Select(x => x.Id).ToList();
var validationResults = new Dictionary<Guid, NonProjectTimeDatesCheckResultModel.ResourceCheckModel>();
// For every checkable resource we get the teams, he is member of with dates of membership
var prMngr = new PeopleResourcesManager(this.DbContext);
var resourcesWithTeams = prMngr.GetResourcesTeams(resources);
// Get the summary teams membership date range, for every resource (through all resource's teams)
List<TeamMembershipModel> resourceExistingPeriod = resourcesWithTeams.Keys.Where(key => !inactiveResources.Contains(key) && (resourcesWithTeams[key].Count > 0))
.Select(key => new TeamMembershipModel
{
Id = key,
StartDate = resourcesWithTeams[key].Select(x => x.StartDate).Min(),
EndDate = resourcesWithTeams[key].Select(x => x.EndDate).Max(),
}).ToList();
// Get resources, that are not member of any team
var nonTeamResources = resources.Except(inactiveResources).Except(resourceExistingPeriod.Select(x => x.Id)).ToList();
nonTeamResources.ForEach(x =>
validationResults.Add(x, new NonProjectTimeDatesCheckResultModel.ResourceCheckModel
{
Id = x,
ViolationType = NonProjectTimeDatesCheckResultModel.ViolationType.NotMemberOfAnyTeam
}));
inactiveResources.ForEach(x =>
validationResults.Add(x, new NonProjectTimeDatesCheckResultModel.ResourceCheckModel
{
Id = x,
ViolationType = NonProjectTimeDatesCheckResultModel.ViolationType.IsInactive
}));
// Check dates violation for reasources with teams
foreach (var rmModel in resourceExistingPeriod)
{
bool skipNextChecks = false;
var itemCheckResult = new NonProjectTimeDatesCheckResultModel.ResourceCheckModel();
itemCheckResult.Id = rmModel.Id;
itemCheckResult.ViolationType = NonProjectTimeDatesCheckResultModel.ViolationType.NoViolation;
if (endDate.HasValue && (endDate.Value < rmModel.StartDate))
{
// NPT is completelly in the past
itemCheckResult.ViolationType = NonProjectTimeDatesCheckResultModel.ViolationType.NptCompletlyBeforeResourceStartDate;
skipNextChecks = true;
}
if (!skipNextChecks && rmModel.EndDate.HasValue && (startDate > rmModel.EndDate.Value))
{
// NPT is completelly in future
itemCheckResult.ViolationType = NonProjectTimeDatesCheckResultModel.ViolationType.NptCompletlyAfterResourceEndDate;
skipNextChecks = true;
}
if (!skipNextChecks && (startDate < rmModel.StartDate) && rmModel.EndDate.HasValue &&
endDate.HasValue && (endDate.Value > rmModel.EndDate.Value))
{
// NPT is out of both resource's dates
itemCheckResult.ViolationType = NonProjectTimeDatesCheckResultModel.ViolationType.NptOutBothResourceDates;
skipNextChecks = true;
}
if (!skipNextChecks && (startDate < rmModel.StartDate))
{
// NPT is partially in the past
itemCheckResult.ViolationType = NonProjectTimeDatesCheckResultModel.ViolationType.NptPartiallyBeforeResourceStartDate;
skipNextChecks = true;
}
if (!skipNextChecks && rmModel.EndDate.HasValue && (startDate < rmModel.EndDate.Value) &&
endDate.HasValue && (endDate.Value > rmModel.EndDate.Value))
{
// Now for permanent NPT's we don't register a violation, if a resource has limited End Date
// (not permanent), though NPT continues after resource finish date.
// To change the logic to treat this situation as violation, set the condition for this 'if' as
// follows:
// if (!skipNextChecks && rmModel.EndDate.HasValue && (startDate < rmModel.EndDate.Value) &&
// (!endDate.HasValue || (endDate.HasValue && (endDate.Value > rmModel.EndDate.Value)))
itemCheckResult.ViolationType = NonProjectTimeDatesCheckResultModel.ViolationType.NptPartiallyAfterResourceEndDate;
}
if (itemCheckResult.ViolationType != NonProjectTimeDatesCheckResultModel.ViolationType.NoViolation)
validationResults.Add(itemCheckResult.Id, itemCheckResult);
}
if (validationResults.Count > 0)
{
// Add resource attributes and sorting
var vialotedResourceIds = validationResults.Keys.ToList();
dummyTeamItem.Resources =
DbContext.PeopleResources.AsNoTracking().Where(x => vialotedResourceIds.Contains(x.Id))
.OrderBy(x => x.LastName).ThenBy(x => x.FirstName).ToList()
.Select(x => new NonProjectTimeDatesCheckResultModel.ResourceCheckModel()
{
Id = x.Id,
Name = String.Format("{0} {1}", x.FirstName, x.LastName),
ViolationType = validationResults[x.Id].ViolationType
}).ToList();
result.HasViolation = validationResults.Values.Any(x =>
x.ViolationType != NonProjectTimeDatesCheckResultModel.ViolationType.NoViolation);
}
return result;
}
public class TeamCheckModel
{
public Guid Id;
public string Name;
public DateTime StartDate;
public DateTime? EndDate;
public NonProjectTimeDatesCheckResultModel.ViolationType Violation =
NonProjectTimeDatesCheckResultModel.ViolationType.NoViolation;
}
public class ResourceCheckModel
{
public Guid Id;
public string FirstName;
public string LastName;
public Dictionary<Guid, TeamCheckModel> Teams;
public NonProjectTimeDatesCheckResultModel.ViolationType Violation =
NonProjectTimeDatesCheckResultModel.ViolationType.NoViolation;
}
public NonProjectTimeDatesCheckResultModel CheckDateRangeViolationByTeams(List<Guid> teams, DateTime startDate, DateTime? endDate)
{
// Idea:
// For each resource we check the consequence of his teams (team membership records) to fit the dates
// of incoming NPT. We check the only team records for incoming teams list. And throw out all other
// resource's team membership records, that are not specified in incoming teams list.
// If all the team records of a resource, we found in DB, are completelly out of NPT dates, we
// treat this situation as OK. It meant absent of any violation. So, the NPT doesn't touch this resource.
// We avoid checks for inactive resources also.
// We perform checks for the only resources, who has at least one team membership record with dates,
// that are are completelly or partially 'covered' with NPT dates period.
//
// First, we get a complete list of active resources for incoming teams list. To perform checks well,
// we must take a look to every resource as a consequent list of his teams. To get this point of view
// we must turn inside out data, we got from TeamManager. We make a struct, that is a plain list of resource,
// where every resource contains a list of his team membership records.
// Then for every resource we check every team membership record dates againt NPT dates.
// Browsing of all possible variants of the check results, we got some rules for identification of
// different type of violations.
// 1. If all team membership records for a resource got the result 'NptOutBothResourceDates', the NPT seems to
// begin before the resource started in his teams, and finished after, the resource finished in all his teams.
// 2. If we got at least one team membership record with the result 'NptPartiallyBeforeResourceStartDate' and at the same
// time got at least one team membership record with the result 'NptPartiallyAfterResourceStartDate', the
// resource doesn't have any violation. The case seems to be the situation, when NPT started, when the resource
// was a member of one team, and finished, when the resource was a member of some other team. But NPT completelly
// 'covers' resource existing dates. So, it's ok.
// 3. If we got at least one team membership records with the result 'NptPartiallyBeforeResourceStartDate', the
// violation take place, because NPT started before the resource started in all his teams (from incoming teams list).
// 4. If we got at least one team membership records with the result 'NptPartiallyAfterResourceStartDate', the
// violation take place, because NPT finished after the resource finished in all his teams (from incoming teams list).
// 5. If we got any other variants of team membership records check results. It seems the resource has no violation, and
// it is good.
// Get resources for teams
var teamMngr = new TeamManager(DbContext);
var teamsWithResources = teamMngr.LoadTeamsWithResourcesByUser(null, teams);
// Turn data inside out: from resources to his teams
var checkTempResults = new Dictionary<Guid, ResourceCheckModel>();
foreach (var team in teamsWithResources)
{
if ((team.PeopleResources != null) && (team.PeopleResources.Count > 0))
{
foreach (var resource in team.PeopleResources)
{
if (!resource.IsActiveEmployee)
// Exclude inactive resources from check
continue;
if (!checkTempResults.ContainsKey(resource.Id))
{
var resourceCheckModel = new ResourceCheckModel()
{
Id = resource.Id,
FirstName = resource.FirstName,
LastName = resource.LastName,
Teams = new Dictionary<Guid, TeamCheckModel>()
};
checkTempResults.Add(resource.Id, resourceCheckModel);
}
if (!checkTempResults[resource.Id].Teams.ContainsKey(team.Id))
{
var teamCheckModel = new TeamCheckModel()
{
Id = team.Id,
Name = team.Name,
StartDate = resource.StartDate,
EndDate = resource.EndDate
};
checkTempResults[resource.Id].Teams.Add(team.Id, teamCheckModel);
}
}
}
}
// Check resource's team membership records and register violations
foreach (var resource in checkTempResults.Values)
{
if ((resource.Teams != null) && (resource.Teams.Count > 0))
{
foreach (var team in resource.Teams.Values)
{
bool skipNextChecks = false;
var itemCheckResult = NonProjectTimeDatesCheckResultModel.ViolationType.NoViolation;
if (endDate.HasValue && (endDate.Value < team.StartDate))
{
// NPT is completelly in the past
itemCheckResult = NonProjectTimeDatesCheckResultModel.ViolationType.NptCompletlyBeforeResourceStartDate;
skipNextChecks = true;
}
if (!skipNextChecks && team.EndDate.HasValue && (startDate > team.EndDate.Value))
{
// NPT is completelly in future
itemCheckResult = NonProjectTimeDatesCheckResultModel.ViolationType.NptCompletlyAfterResourceEndDate;
skipNextChecks = true;
}
if (!skipNextChecks && (startDate < team.StartDate) && team.EndDate.HasValue &&
endDate.HasValue && (endDate.Value > team.EndDate.Value))
{
// NPT is out of both resource's dates
itemCheckResult = NonProjectTimeDatesCheckResultModel.ViolationType.NptOutBothResourceDates;
skipNextChecks = true;
}
if (!skipNextChecks && (startDate < team.StartDate))
{
// NPT is partially in the past
itemCheckResult = NonProjectTimeDatesCheckResultModel.ViolationType.NptPartiallyBeforeResourceStartDate;
skipNextChecks = true;
}
if (!skipNextChecks && team.EndDate.HasValue && (startDate < team.EndDate.Value) &&
endDate.HasValue && (endDate.Value > team.EndDate.Value))
{
itemCheckResult = NonProjectTimeDatesCheckResultModel.ViolationType.NptPartiallyAfterResourceEndDate;
}
team.Violation = itemCheckResult;
}
}
}
// Analyze in complex the results for team membership records of every resource
foreach (var resource in checkTempResults.Values)
{
if ((resource.Teams != null) && (resource.Teams.Count > 0))
{
var teamCheckModels = resource.Teams.Values.ToList();
bool skipNextChecks = false;
if (teamCheckModels.TrueForAll(x =>
x.Violation == NonProjectTimeDatesCheckResultModel.ViolationType.NptOutBothResourceDates))
{
// Rule 1 from the list above
resource.Violation = NonProjectTimeDatesCheckResultModel.ViolationType.NptOutBothResourceDates;
skipNextChecks = true;
}
if (!skipNextChecks &&
teamCheckModels.Any(x => x.Violation == NonProjectTimeDatesCheckResultModel.ViolationType.NptPartiallyBeforeResourceStartDate) &&
teamCheckModels.Any(x => x.Violation == NonProjectTimeDatesCheckResultModel.ViolationType.NptPartiallyAfterResourceEndDate))
{
// Rule 2 from the list above
resource.Violation = NonProjectTimeDatesCheckResultModel.ViolationType.NoViolation;
skipNextChecks = true;
}
if (!skipNextChecks &&
teamCheckModels.Any(x => x.Violation == NonProjectTimeDatesCheckResultModel.ViolationType.NptPartiallyBeforeResourceStartDate))
{
// Rule 3 from the list above
resource.Violation = NonProjectTimeDatesCheckResultModel.ViolationType.NptPartiallyBeforeResourceStartDate;
skipNextChecks = true;
}
if (!skipNextChecks &&
teamCheckModels.Any(x => x.Violation == NonProjectTimeDatesCheckResultModel.ViolationType.NptPartiallyAfterResourceEndDate))
{
// Rule 4 from the list above
resource.Violation = NonProjectTimeDatesCheckResultModel.ViolationType.NptPartiallyAfterResourceEndDate;
skipNextChecks = true;
}
if (!skipNextChecks)
{
// Rule 5 from the list above
resource.Violation = NonProjectTimeDatesCheckResultModel.ViolationType.NoViolation;
}
}
}
// Remove resources with no violation
var resourceRecordsToRemove = new List<Guid>();
foreach (var key in checkTempResults.Keys)
{
if (checkTempResults[key].Violation == NonProjectTimeDatesCheckResultModel.ViolationType.NoViolation)
resourceRecordsToRemove.Add(key);
}
foreach (var key in resourceRecordsToRemove)
checkTempResults.Remove(key);
// Format data to resulted structs
NonProjectTimeDatesCheckResultModel result = new NonProjectTimeDatesCheckResultModel();
NonProjectTimeDatesCheckResultModel.TeamCheckModel dummyTeamItem = new NonProjectTimeDatesCheckResultModel.TeamCheckModel()
{
Id = Guid.Empty,
Resources = new List<NonProjectTimeDatesCheckResultModel.ResourceCheckModel>()
};
result.Teams.Add(dummyTeamItem);
result.HasViolation = false;
dummyTeamItem.Resources =
checkTempResults.Values.OrderBy(x => x.LastName).ThenBy(x => x.FirstName).Select(r =>
new NonProjectTimeDatesCheckResultModel.ResourceCheckModel()
{
Id = r.Id,
Name = $"{r.FirstName} {r.LastName}",
ViolationType = r.Violation
}).ToList();
result.HasViolation = (dummyTeamItem.Resources.Count > 0);
return result;
}
#endregion
#region DAL methods
private IQueryable<VW_NonProjectTimeAllocation> BuildBasicNonProjectTimeAllocationsQuery(IEnumerable<Guid> resources = null, IEnumerable<Guid> teamIds = null, List<Guid> npTimeIds = null, DateTime? startDate = null, DateTime? endDate = null)
{
var npTimesAllocationQuery = DbContext.VW_NonProjectTimeAllocation.AsNoTracking().AsQueryable();
if (resources != null && resources.Any())
npTimesAllocationQuery = npTimesAllocationQuery.Where(x => resources.Contains(x.PeopleResourceId));
if (teamIds != null && teamIds.Any())
npTimesAllocationQuery = npTimesAllocationQuery.Where(x => teamIds.Contains(x.TeamId));
if (npTimeIds != null && npTimeIds.Count > 0)
npTimesAllocationQuery = npTimesAllocationQuery.Where(x => npTimeIds.Contains(x.NonProjectTimeId));
if (startDate.HasValue)
npTimesAllocationQuery = npTimesAllocationQuery.Where(x => x.WeekEndingDate >= startDate.Value);
if (endDate.HasValue)
npTimesAllocationQuery = npTimesAllocationQuery.Where(x => x.WeekEndingDate <= endDate.Value);
return npTimesAllocationQuery;
}
public Dictionary<Guid, List<NonProjectTimeResourceAllocation>> GetNPTResourceAllocationsByTeam(List<Guid> nptIds, Guid teamId)
{
if (nptIds == null || nptIds.Count == 0 || Guid.Empty.Equals(teamId))
return new Dictionary<Guid, List<NonProjectTimeResourceAllocation>>();
var npTimeAllocationsQuery = (from alloc in DbContext.NonProjectTimeResourceAllocations
join npt2res in DbContext.NonProjectTime2Resource on alloc.NonProjectTime2ResourceId equals npt2res.Id
join npt in DbContext.NonProjectTimes on npt2res.NonProjectTimeId equals npt.Id
join res in DbContext.VW_TeamResource on npt2res.PeopleResourceId equals res.Id
where nptIds.Contains(npt.Id) && teamId == res.TeamId
select alloc).Include(t => t.NonProjectTime2Resource).Distinct();
var npTimeAllocations = npTimeAllocationsQuery.ToArray()
.GroupBy(t => t.NonProjectTime2Resource.NonProjectTimeId)
.ToDictionary(x => x.Key, x => x.ToList());
return npTimeAllocations;
}
public Dictionary<Guid, List<NonProjectTimeResourceAllocation>> GetNPTResourceAllocationsByView(List<Guid> nptIds, Guid viewId)
{
if (nptIds == null || nptIds.Count == 0 || Guid.Empty.Equals(viewId))
return new Dictionary<Guid, List<NonProjectTimeResourceAllocation>>();
var npTimeAllocationsQuery = (from alloc in DbContext.NonProjectTimeResourceAllocations
join npt2res in DbContext.NonProjectTime2Resource on alloc.NonProjectTime2ResourceId equals npt2res.Id
join npt in DbContext.NonProjectTimes on npt2res.NonProjectTimeId equals npt.Id
join res in DbContext.VW_TeamResource on npt2res.PeopleResourceId equals res.Id
join t2v in DbContext.VW_Team2View on res.TeamId equals t2v.TeamId
where nptIds.Contains(npt.Id) && viewId == t2v.ViewId
select alloc).Include(t => t.NonProjectTime2Resource).Distinct();
var npTimeAllocations = npTimeAllocationsQuery.ToArray()
.GroupBy(t => t.NonProjectTime2Resource.NonProjectTimeId)
.ToDictionary(x => x.Key, x => x.ToList());
return npTimeAllocations;
}
public Dictionary<Guid, List<NonProjectTimeResourceAllocation>> GetNPTResourceAllocationsByCompany(List<Guid> nptIds, Guid companyId)
{
if (nptIds == null || nptIds.Count == 0 || Guid.Empty.Equals(companyId))
return new Dictionary<Guid, List<NonProjectTimeResourceAllocation>>();
var companyIds = DbContext.Companies.Where(c => c.ParentCompanyId == companyId).Select(c => c.Id).ToList();
companyIds.Add(companyId);
var npTimeAllocationsQuery = (from alloc in DbContext.NonProjectTimeResourceAllocations
join npt2res in DbContext.NonProjectTime2Resource on alloc.NonProjectTime2ResourceId equals npt2res.Id
join npt in DbContext.NonProjectTimes on npt2res.NonProjectTimeId equals npt.Id
join res in DbContext.VW_TeamResource on npt2res.PeopleResourceId equals res.Id
join t in DbContext.Teams on res.TeamId equals t.Id
where nptIds.Contains(npt.Id) && t.CompanyId.HasValue && companyIds.Contains(t.CompanyId.Value)
select alloc).Include(t => t.NonProjectTime2Resource).Distinct();
var npTimeAllocations = npTimeAllocationsQuery.ToArray()
.GroupBy(t => t.NonProjectTime2Resource.NonProjectTimeId)
.ToDictionary(x => x.Key, x => x.ToList());
return npTimeAllocations;
}
public Dictionary<Guid, List<NonProjectTimeResourceAllocation>> GetNPTResourceAllocations(List<Guid> nptIds, Guid resourceId)
{
if (nptIds == null || nptIds.Count == 0 || Guid.Empty.Equals(resourceId))
return new Dictionary<Guid, List<NonProjectTimeResourceAllocation>>();
var npTimeAllocationsQuery = (from alloc in DbContext.NonProjectTimeResourceAllocations
join npt2res in DbContext.NonProjectTime2Resource on alloc.NonProjectTime2ResourceId equals npt2res.Id
join npt in DbContext.NonProjectTimes on npt2res.NonProjectTimeId equals npt.Id
where nptIds.Contains(npt.Id) && resourceId == npt2res.PeopleResourceId
select alloc).Include(t => t.NonProjectTime2Resource).Distinct();
var npTimeAllocations = npTimeAllocationsQuery.ToArray()
.GroupBy(t => t.NonProjectTime2Resource.NonProjectTimeId)
.ToDictionary(x => x.Key, x => x.ToList());
return npTimeAllocations;
}
public Dictionary<Guid, List<NonProjectTimeTeamAllocation>> GetNPTTeamAllocationsByTeam(List<Guid> nptIds, Guid teamId)
{
if (nptIds == null || nptIds.Count == 0 || Guid.Empty.Equals(teamId))
return new Dictionary<Guid, List<NonProjectTimeTeamAllocation>>();
var npTimeAllocationsQuery = (from alloc in DbContext.NonProjectTimeTeamAllocations
join npt2t in DbContext.NonProjectTime2Team on alloc.NonProjectTime2TeamId equals npt2t.Id
join npt in DbContext.NonProjectTimes on npt2t.NonProjectTimeId equals npt.Id
where nptIds.Contains(npt.Id) && teamId == npt2t.TeamId
select alloc).Include(t => t.NonProjectTime2Team).Distinct();
var npTimeAllocations = npTimeAllocationsQuery.ToArray()
.GroupBy(t => t.NonProjectTime2Team.NonProjectTimeId)
.ToDictionary(x => x.Key, x => x.ToList());
return npTimeAllocations;
}
public Dictionary<Guid, List<NonProjectTimeTeamAllocation>> GetNPTTeamAllocationsByView(List<Guid> nptIds, Guid viewId)
{
if (nptIds == null || nptIds.Count == 0 || Guid.Empty.Equals(viewId))
return new Dictionary<Guid, List<NonProjectTimeTeamAllocation>>();
var npTimeAllocationsQuery = (from alloc in DbContext.NonProjectTimeTeamAllocations
join npt2t in DbContext.NonProjectTime2Team on alloc.NonProjectTime2TeamId equals npt2t.Id
join npt in DbContext.NonProjectTimes on npt2t.NonProjectTimeId equals npt.Id
join t2v in DbContext.VW_Team2View on npt2t.TeamId equals t2v.TeamId
where nptIds.Contains(npt.Id) && viewId == t2v.ViewId
select alloc).Include(t => t.NonProjectTime2Team).Distinct();
var npTimeAllocations = npTimeAllocationsQuery.ToArray()
.GroupBy(t => t.NonProjectTime2Team.NonProjectTimeId)
.ToDictionary(x => x.Key, x => x.ToList());
return npTimeAllocations;
}
public Dictionary<Guid, List<NonProjectTimeTeamAllocation>> GetNPTTeamAllocationsByCompany(List<Guid> nptIds, Guid companyId)
{
if (nptIds == null || nptIds.Count == 0 || Guid.Empty.Equals(companyId))
return new Dictionary<Guid, List<NonProjectTimeTeamAllocation>>();
var companyIds = DbContext.Companies.Where(c => c.ParentCompanyId == companyId).Select(c => c.Id).ToList();
companyIds.Add(companyId);
var npTimeAllocationsQuery = (from alloc in DbContext.NonProjectTimeTeamAllocations
join npt2t in DbContext.NonProjectTime2Team on alloc.NonProjectTime2TeamId equals npt2t.Id
join npt in DbContext.NonProjectTimes on npt2t.NonProjectTimeId equals npt.Id
join t in DbContext.Teams on npt2t.TeamId equals t.Id
where nptIds.Contains(npt.Id) && t.CompanyId.HasValue && companyIds.Contains(t.CompanyId.Value)
select alloc).Include(t => t.NonProjectTime2Team).Distinct();
var npTimeAllocations = npTimeAllocationsQuery.ToArray()
.GroupBy(t => t.NonProjectTime2Team.NonProjectTimeId)
.ToDictionary(x => x.Key, x => x.ToList());
return npTimeAllocations;
}
/// <summary>
/// Returns UOMs for each team in the specified NPT-list. UOM for team is the Max UOM of all it's resources
/// </summary>
/// <remarks>Team level NPTs are processed only</remarks>
public Dictionary<Guid, decimal> GetNPTUOMs(List<Guid> nptIds)
{
if (nptIds == null || nptIds.Count < 1)
return new Dictionary<Guid, decimal>();
var teamUoms = (from npt in DbContext.NonProjectTime2Team.AsNoTracking()
join p in DbContext.VW_TeamResource.AsNoTracking() on npt.TeamId equals p.TeamId
join ec in DbContext.ExpenditureCategory.AsNoTracking() on p.ExpenditureCategoryId equals ec.Id
join uom in DbContext.UOMs.AsNoTracking() on ec.UOMId equals uom.Id
where nptIds.Contains(npt.NonProjectTimeId) && npt.NonProjectTime.IsTeamMode
select new
{
NonProjectTimeId = npt.NonProjectTimeId,
UOMValue = uom.UOMValue
}).Distinct().ToArray();
return teamUoms.GroupBy(x => x.NonProjectTimeId, (key, grp) => new
{
NonProjectTimeId = key,
UOMValue = grp.Select(j => j.UOMValue).Max()
}).ToDictionary(x => x.NonProjectTimeId, x => x.UOMValue);
}
#endregion
#region BLL methods
/// <summary>
/// Returns all NonProjectTime allocations for specified team and NPT. Key is NPT
/// </summary>
/// <param name="teamId">An Id of the target team which NPT allocations we're going to receive.</param>
/// <param name="npTimeId">An Id of the NPT record which allocations we're going to receive.</param>
public Dictionary<Guid, List<VW_NonProjectTimeAllocation>> GetAllocationsForTeam(Guid teamId, Guid? npTimeId = null, DateTime? startDate = null, DateTime? endDate = null)
{
if (npTimeId.HasValue)
return GetAllocationsForTeam(new List<Guid> { teamId }, new List<Guid> { npTimeId.Value }, startDate, endDate);
else
return GetAllocationsForTeam(new List<Guid> { teamId }, null, startDate, endDate);
}
/// <summary>
/// Returns all NonProjectTime allocations for specified team and NPT. Key is NPT
/// </summary>
/// <param name="teamIds">A list of team Ids which NPT allocations we're going to receive.</param>
/// <param name="npTimeIds">A list of NPT record Ids which allocations we're going to receive.</param>
public Dictionary<Guid, List<VW_NonProjectTimeAllocation>> GetAllocationsForTeam(List<Guid> teamIds, List<Guid> npTimeIds = null, DateTime? startDate = null, DateTime? endDate = null)
{
if ((teamIds == null || teamIds.Count == 0) && (npTimeIds == null || npTimeIds.Count == 0))
throw new ArgumentNullException("teamIds", "Either teamIds or npTimeIds are requried");
var npTimeAllocationsQuery = BuildBasicNonProjectTimeAllocationsQuery(teamIds: teamIds, startDate: startDate, endDate: endDate);
var npTimeAllocations = npTimeAllocationsQuery.ToList()
.GroupBy(x => x.NonProjectTimeId)
.ToDictionary(x => x.Key, x => x.OrderBy(s => s.WeekEndingDate).ToList());
return npTimeAllocations;
}
/// <summary>
/// Returns total non-project times grouped by resource and then by non-project time category and then by week for the specified period and the specified set of resource Ids.
/// </summary>
public Dictionary<Guid, Dictionary<string, ResourceNptModel>> GetNonProjectTimes4Teams(IEnumerable<Guid> teams, DateTime? startDate = null, DateTime? endDate = null)
{
var resourceAllocations = GetNPTAllocation4Team(teams, startDate, endDate)
.GroupBy(t => t.PeopleResourceId)
.ToDictionary(k => k.Key, v => v.ToArray());
var result = new Dictionary<Guid, Dictionary<string, ResourceNptModel>>();
foreach (var resItem in resourceAllocations)
{
// init resource level dictionary
if (!result.Keys.Contains(resItem.Key))
result.Add(resItem.Key, new Dictionary<string, ResourceNptModel>());
var resourceResult = result[resItem.Key];
// iterate through allocations specific for current resource grouped by NPT Category
foreach (var catItem in resItem.Value.GroupBy(k => new
{
NonProjectTimeCategoryId = k.NonProjectTimeCategoryId,
k.NonProjectTimeCategoryName,
NonProjectTimeId = k.NonProjectTimeId,
k.NonProjectTimeName,
k.IsFromTeam,
k.ExpenditureCategoryId
}))
{
// init NPT Category level dictionary
if (!resourceResult.Keys.Contains(catItem.Key.NonProjectTimeId.ToString()))
resourceResult.Add(catItem.Key.NonProjectTimeId.ToString(), new ResourceNptModel
{
Id = catItem.Key.NonProjectTimeId,
Name = catItem.Key.NonProjectTimeName,
isTeamWide = catItem.Key.IsFromTeam,
CategoryId = catItem.Key.NonProjectTimeCategoryId,
ExpenditureCategoryId = catItem.Key.ExpenditureCategoryId,
Allocations = catItem.GroupBy(k => k.WeekEndingDate)
.ToDictionary(v => Utils.ConvertToUnixDate(v.Key).ToString(), val => Convert.ToDecimal(val.Sum(s => s.HoursOff))),
Costs = catItem.GroupBy(k => k.WeekEndingDate)
.ToDictionary(v => Utils.ConvertToUnixDate(v.Key).ToString(), val => Convert.ToDecimal(val.Sum(s => s.Cost)))
});
}
}
return result;
}
public NonProjectTimeSummaryItem[] GetNPTAllocation4Team(IEnumerable<Guid> teamIds, DateTime? startDate = null, DateTime? endDate = null)
{
// To-Do: test me!
var npTimesAllocationQuery = BuildBasicNonProjectTimeAllocationsQuery(teamIds: teamIds, startDate: startDate, endDate: endDate);
return ConvertNptQuery2Array(npTimesAllocationQuery);
}
public NonProjectTimeSummaryItem[] GetNonProjectTimesByResources(IEnumerable<Guid> resources, DateTime? startDate = null, DateTime? endDate = null)
{
var npTimesAllocationQuery = BuildBasicNonProjectTimeAllocationsQuery(resources: resources, startDate: startDate, endDate: endDate);
return ConvertNptQuery2Array(npTimesAllocationQuery);
}
public void UpdateNptAllocationsOnResourceExpCatChange(Guid resourceId, Guid newExpCatId)
{
if (resourceId.Equals(Guid.Empty))
throw new ArgumentNullException(nameof(resourceId));
if (newExpCatId.Equals(Guid.Empty))
throw new ArgumentNullException(nameof(newExpCatId));
// Get Unit of measure for new EC to limit hours-off for npt allocations (make them not greater, than new EC uom)
var ecMngr = new ExpenditureCategoryManager(DbContext);
var newExpCat = ecMngr.GetExpenditureDetails(newExpCatId);
if (newExpCat == null)
throw new ArgumentException($"There is no EC with Id = {newExpCatId}");
// Perform checks and update for NPTs
var npt2ResourceRecords = DbContext.NonProjectTime2Resource
.Where(x => x.PeopleResourceId.Equals(resourceId))
.Include(x => x.NonProjectTimeResourceAllocations).ToArray();
foreach (var resourceRecord in npt2ResourceRecords)
{
if (resourceRecord.NonProjectTimeResourceAllocations == null)
continue;
foreach (var allocation in resourceRecord.NonProjectTimeResourceAllocations)
if (allocation.HoursOff > newExpCat.UOMValue)
allocation.HoursOff = (int)newExpCat.UOMValue;
}
}
#endregion
#region Private Members
private IQueryable<NonProjectTimeListItemModel> NonProjectTimeListItemModelBaseQuery
{
get
{
var query = DbContext.NonProjectTimes.AsNoTracking()
.Select(x => new NonProjectTimeListItemModel
{
Id = x.Id,
Name = x.Name,
CategoryName = x.NonProjectTimeCategory.Name,
StartDate = x.StartDate,
EndDate = x.EndDate,
Cost = x.Cost,
Details = x.Details,
IsPercentMode = x.IsPercentsMode,
Permanent = x.Permanent,
IsFromTeam = x.IsTeamMode
});
return query;
}
}
private IQueryable<NonProjectTimeModel> NonProjectTimeModelBaseQuery
{
get
{
var query = DbContext.NonProjectTimes.AsNoTracking().Include(t => t.NonProjectTimeCategory)
.Select(x => new NonProjectTimeModel
{
Id = x.Id,
NonProjectTimeName = x.Name,
NonProjectTimeCategoryId = x.NonProjectTimeCategoryId,
NonProjectTimeCategoryName = x.NonProjectTimeCategory.Name,
NonProjectTimeStartDate = x.StartDate,
NonProjectTimeEndDate = x.EndDate,
NonProjectTimeCost = x.Cost,
Permanent = x.Permanent,
Details = x.Details,
IsPercentsMode = x.IsPercentsMode,
IsTeamAssignmentMode = x.IsTeamMode,
IsHistory = (x.EndDate.HasValue && x.EndDate.Value < DateTime.Today)
});
return query;
}
}
private NonProjectTimeDistributionModel CalculateWeekDistribution(FiscalCalendar week, DateTime startDate, DateTime? endDate, decimal uomValue)
{
if (week == null)
throw new ArgumentNullException("week");
var fcWeekLength = (week.EndDate - week.StartDate).Days + 1;
if (fcWeekLength <= 0)
throw new InvalidOperationException(string.Format("Cannot calculate NP Time distribution because of incorrect week range in the fiscal calendar: {0:MM/dd/yyyy} - {1:MM/dd/yyyy}", week.StartDate, week.EndDate));
var npWeekLength = fcWeekLength;
if (week.StartDate <= startDate && week.EndDate >= startDate)
{
if (endDate.HasValue && week.StartDate <= endDate.Value && week.EndDate >= endDate.Value)
npWeekLength = (endDate.Value - startDate).Days + 1; // if full range is in the middle of the week
else
npWeekLength = (week.EndDate - startDate).Days + 1; // if start date is in the middle of the week
}
else if (endDate.HasValue)
{
// e.g. we have the following week endings in the fiscal calendar: 2016-12-30, 2017-01-13, 2017-01-06 does not exist
// user types end date for NP Time to 2016-12-31 and we show him week from 2017-01-07 to 2017-01-13
// and until user's end date does not cover any day from the existing week we should show this week with zero allocation
if (endDate.Value < week.StartDate)
npWeekLength = 0;
// if end date is in the middle of the week
if (week.StartDate <= endDate.Value && week.EndDate >= endDate.Value)
npWeekLength = (endDate.Value - week.StartDate).Days + 1;
}
var hoursOff = uomValue * (npWeekLength / (decimal)fcWeekLength);
var percentOff = uomValue == 0 ? 0 : hoursOff / uomValue * 100;
var distribution = new NonProjectTimeDistributionModel()
{
StartDate = Utils.ConvertToUnixDate(week.StartDate),
EndDate = Utils.ConvertToUnixDate(week.EndDate),
HoursOff = (int)hoursOff,
PercentOff = Convert.ToInt32(percentOff),
UOMValue = uomValue
};
return distribution;
}
private NonProjectTimeSummaryItem[] ConvertNptQuery2Array(IEnumerable<VW_NonProjectTimeAllocation> query)
{
return query.Select(x => new
{
x.NonProjectTimeId,
x.NonProjectTimeName,
x.PeopleResourceId,
x.ExpenditureCategoryId,
x.NonProjectTimeCategoryName,
x.NonProjectTimeCategoryId,
x.WeekEndingDate,
x.HoursOff,
x.IsTeamNonProjectTime,
x.Rate
})
.GroupBy(x => new
{
x.NonProjectTimeId,
x.NonProjectTimeName,
x.ExpenditureCategoryId,
x.PeopleResourceId,
x.NonProjectTimeCategoryId,
x.NonProjectTimeCategoryName,
x.WeekEndingDate,
x.IsTeamNonProjectTime
})
.Select(x => new NonProjectTimeSummaryItem
{
NonProjectTimeId = x.Key.NonProjectTimeId,
NonProjectTimeName = x.Key.NonProjectTimeName,
ExpenditureCategoryId = x.Key.ExpenditureCategoryId,
PeopleResourceId = x.Key.PeopleResourceId,
NonProjectTimeCategoryId = x.Key.NonProjectTimeCategoryId,
NonProjectTimeCategoryName = x.Key.NonProjectTimeCategoryName,
WeekEndingDate = x.Key.WeekEndingDate,
HoursOff = x.Sum(r => r.HoursOff),
Cost = x.Sum(r => (decimal)r.HoursOff * r.Rate),
IsFromTeam = x.Key.IsTeamNonProjectTime
})
.ToArray();
}
#endregion
}
}