1526 lines
81 KiB
C#
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
|
|
}
|
|
} |