1939 lines
104 KiB
C#
1939 lines
104 KiB
C#
using EnVisage.Models;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Data.Entity;
|
|
using System.Data.Entity.Infrastructure;
|
|
using System.Linq;
|
|
using System.Web;
|
|
using System.Web.Mvc;
|
|
|
|
namespace EnVisage.Code.BLL
|
|
{
|
|
public class PeopleResourcesManager : ManagerBase<PeopleResource, PeopleResourceModel>
|
|
{
|
|
public override DbSet<PeopleResource> DataTable
|
|
{
|
|
get
|
|
{
|
|
return DbContext.PeopleResources;
|
|
}
|
|
}
|
|
|
|
public PeopleResourcesManager(EnVisageEntities dbContext)
|
|
: base(dbContext)
|
|
{ }
|
|
|
|
protected override PeopleResource InitInstance()
|
|
{
|
|
return new PeopleResource
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
};
|
|
}
|
|
|
|
protected override PeopleResource RetrieveReadOnlyById(Guid key)
|
|
{
|
|
return DataTable.AsNoTracking().FirstOrDefault(t => t.Id == key);
|
|
}
|
|
public PeopleResource RetrieveReadOnlyByEmail(string email)
|
|
{
|
|
return DataTable.AsNoTracking().FirstOrDefault(t => t.Email == email);
|
|
}
|
|
public PeopleResourceModel LoadPeopleResourceAsIs(Guid resourceId, DateTime datePoint)
|
|
{
|
|
// Load base resource info
|
|
PeopleResourceModel result = null;
|
|
PeopleResource prRecords = this.Load(resourceId, true);
|
|
|
|
if (prRecords != null)
|
|
{
|
|
result = (PeopleResourceModel)prRecords;
|
|
|
|
// Load team membership info
|
|
var teamMembershipId = DbContext.PeopleResource2Team.Where(x => x.PeopleResourceId.Equals(resourceId) &&
|
|
(x.StartDate <= datePoint.Date) &&
|
|
(!x.EndDate.HasValue || x.EndDate.Value >= datePoint.Date))
|
|
.Select(x => x.TeamId).FirstOrDefault();
|
|
|
|
if ((teamMembershipId != null) && !teamMembershipId.Equals(Guid.Empty))
|
|
result.TeamId = teamMembershipId;
|
|
else
|
|
result.TeamId = null;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
public PeopleResourceModel LoadPeopleResource(Guid resourceId, DateTime datePoint,
|
|
bool setDatesFromCurrentTeam, bool withQueuedChanges, bool withAllocationsBriefInfo)
|
|
{
|
|
var resource2Teams = DbContext.PeopleResource2Team.AsNoTracking()
|
|
.Where(x => x.PeopleResourceId.Equals(resourceId)).Include(x => x.Team).ToList();
|
|
|
|
DateTime dt = datePoint.Date;
|
|
|
|
PeopleResource prRecords = this.Load(resourceId, true);
|
|
PeopleResourceModel result = (PeopleResourceModel)prRecords;
|
|
|
|
PeopleResource2Team membershipRecord = resource2Teams.Where(t => (t.StartDate <= dt) &&
|
|
(!t.EndDate.HasValue || (t.EndDate >= dt))).FirstOrDefault();
|
|
|
|
if (membershipRecord == null)
|
|
{
|
|
// Recently created active resource is considered to be a member of the team, he was initially included to,
|
|
// when was created (the may be some days lag between the resource StartDate and the StartDate of a
|
|
// a team membership record, because StartDate for a resource is any day at the fiscal week, and
|
|
// team membership begins at the next full fiscal week, the created recource exists).
|
|
// Existing resource, which has no any team membership at the data extraction date, is considered
|
|
// to be a member of the next team, he is queued to be a member.
|
|
membershipRecord = resource2Teams.Where(x => (x.StartDate > dt)).OrderBy(x => x.StartDate).FirstOrDefault();
|
|
|
|
if (membershipRecord == null)
|
|
{
|
|
// Inactive resource is considered to stay a member of the last team, he was involved to,
|
|
// inspite of the EndDate for his last team membership is expired.
|
|
// We consider an active resource without team membership stay the member of his last team,
|
|
// if he was excluded from some team and was not queud to become a member of any other team.
|
|
membershipRecord = resource2Teams.Where(x => x.EndDate.HasValue && x.EndDate.Value < dt).OrderByDescending(x => x.EndDate).FirstOrDefault();
|
|
}
|
|
}
|
|
|
|
if ((membershipRecord != null) && (membershipRecord.Team != null))
|
|
{
|
|
result.Team = membershipRecord.Team;
|
|
result.TeamId = membershipRecord.TeamId;
|
|
result.TeamAllocation = membershipRecord.Allocation;
|
|
|
|
if (setDatesFromCurrentTeam)
|
|
{
|
|
// Set dates from team membership record
|
|
result.StartDate = membershipRecord.StartDate;
|
|
result.EndDate = membershipRecord.EndDate;
|
|
}
|
|
}
|
|
|
|
if (withQueuedChanges)
|
|
{
|
|
// Get next queued team
|
|
result.TeamChangeQueued = resource2Teams.Where(x => (!result.TeamId.HasValue ||
|
|
result.TeamId.Value.Equals(Guid.Empty) || !x.TeamId.Equals(result.TeamId.Value)) && (x.StartDate > dt))
|
|
.OrderBy(x => x.StartDate).Select(x => new PeopleResourceModel.TeamInfo()
|
|
{
|
|
Id = x.TeamId,
|
|
Name = x.Team.Name,
|
|
ChangeDate = x.StartDate
|
|
}).FirstOrDefault();
|
|
}
|
|
|
|
if (withAllocationsBriefInfo)
|
|
{
|
|
// Get brief nfo abount resource allocations existance
|
|
var mixMngr = new MongoMixManager(DbContext, String.Empty);
|
|
var resourceIds = new List<Guid>() { resourceId };
|
|
|
|
var allocationsInfoinLiveDb = this.GetResourceAllocationsInfo(resourceIds);
|
|
var allocationsInfoMixes = mixMngr.GetResourceAllocationsInfo(resourceIds);
|
|
|
|
result.ResourceScenarioAllocationsInfo = new PeopleResourceAllocationsInfoModel();
|
|
result.ResourceActualsInfo = new PeopleResourceAllocationsInfoModel();
|
|
result.ResourceMixAllocationsInfo = new PeopleResourceAllocationsInfoModel();
|
|
|
|
if (allocationsInfoinLiveDb.ContainsKey(resourceId))
|
|
{
|
|
var resourceInfo = allocationsInfoinLiveDb[resourceId];
|
|
var portfolioAllocationsExist = resourceInfo.ContainsKey(ScenarioType.Portfolio) && resourceInfo[ScenarioType.Portfolio].HasAllocations;
|
|
var actualAllocationsExist = resourceInfo.ContainsKey(ScenarioType.Actuals) && resourceInfo[ScenarioType.Actuals].HasAllocations;
|
|
|
|
result.ResourceScenarioAllocationsInfo.HasAllocations = portfolioAllocationsExist;
|
|
result.ResourceScenarioAllocationsInfo.AllocationsMinDate = portfolioAllocationsExist ? resourceInfo[ScenarioType.Portfolio].AllocationsMinDate : (DateTime?)null;
|
|
result.ResourceScenarioAllocationsInfo.AllocationsMaxDate = portfolioAllocationsExist ? resourceInfo[ScenarioType.Portfolio].AllocationsMaxDate : (DateTime?)null;
|
|
|
|
result.ResourceActualsInfo.HasAllocations = actualAllocationsExist;
|
|
result.ResourceActualsInfo.AllocationsMinDate = actualAllocationsExist ? resourceInfo[ScenarioType.Actuals].AllocationsMinDate : (DateTime?)null;
|
|
result.ResourceActualsInfo.AllocationsMaxDate = actualAllocationsExist ? resourceInfo[ScenarioType.Actuals].AllocationsMaxDate : (DateTime?)null;
|
|
}
|
|
|
|
if (allocationsInfoMixes.ContainsKey(resourceId))
|
|
{
|
|
var resourceInfo = allocationsInfoMixes[resourceId];
|
|
result.ResourceMixAllocationsInfo.HasAllocations = resourceInfo.HasAllocations;
|
|
result.ResourceMixAllocationsInfo.AllocationsMinDate = resourceInfo.AllocationsMinDate;
|
|
result.ResourceMixAllocationsInfo.AllocationsMaxDate = resourceInfo.AllocationsMaxDate;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Loads a list of people resources for the specified list of project Ids <paramref name="projectIds"/>.
|
|
/// </summary>
|
|
/// <param name="projectIds">A list of project Ids.</param>
|
|
/// <returns>
|
|
/// An list of <see cref="PeopleResourceModel"/> objects with prefilled resource, expenditure and team properties.
|
|
/// </returns>
|
|
public IEnumerable<PeopleResourceModel> LoadPeopleResourcesByProjects(IEnumerable<Guid> projectIds)
|
|
{
|
|
var result = (from t2p in DbContext.Team2Project.AsNoTracking()
|
|
join pr in DbContext.VW_TeamResource.AsNoTracking() on t2p.TeamId equals pr.TeamId
|
|
join t in DbContext.Teams.AsNoTracking() on pr.TeamId equals t.Id
|
|
join ec in DbContext.ExpenditureCategory.AsNoTracking() on pr.ExpenditureCategoryId equals ec.Id
|
|
where projectIds.Contains(t2p.ProjectId)
|
|
orderby pr.TeamStartDate descending
|
|
select new
|
|
{
|
|
Resource = pr,
|
|
ExpCat = ec,
|
|
Team = t
|
|
}).Distinct().ToList().Select(t => PeopleResourceModel.GetPeopleResourceModel(t.Resource, t.ExpCat, t.Team));
|
|
return result;
|
|
}
|
|
|
|
public static bool ResourceByEmailExists(string email, Guid? excludeResourceId)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(email) || string.IsNullOrWhiteSpace(email.Trim()))
|
|
return false;
|
|
var searchValue = email.Trim();
|
|
using (var dbContext = new EnVisageEntities())
|
|
{
|
|
if (excludeResourceId.HasValue && !Guid.Empty.Equals(excludeResourceId))
|
|
return dbContext.PeopleResources.Any(x => x.Email == searchValue && x.Id != excludeResourceId);
|
|
else
|
|
return dbContext.PeopleResources.Any(x => x.Email == searchValue);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Loads a list of people resources for the specified team.
|
|
/// </summary>
|
|
/// <param name="includeResourceAllocationsInfo">True to include info about existing resource allocations to result</param>
|
|
/// <param name="filterDateUtc">A date point when resource has been assigned to the specified team. This parameter used to filter resources by PeopleResource2Team dates.</param>
|
|
public IEnumerable<PeopleResourceModel> LoadPeopleResourcesByTeamNonStrict(Guid teamId, bool includeResourceAllocationsInfo, DateTime? filterDateUtc = null)
|
|
{
|
|
List<Guid> teams = new List<Guid>() { teamId };
|
|
return LoadPeopleResourcesByTeamsNonStrict(teams, includeResourceAllocationsInfo, filterDateUtc);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Loads a people resources for the specified teams as displayed in teamboard
|
|
/// </summary>
|
|
/// <param name="includeResourceAllocationsInfo">True to include info about existing resource allocations to result</param>
|
|
/// <param name="filterDateUtc">A date point when resource has been assigned to the specified team. This parameter used to filter resources by PeopleResource2Team dates.</param>
|
|
/// <remarks>Resources with no team membership on date included to their future teams.
|
|
/// Resources with no team membership on date and in future incleded to their past teams
|
|
/// </remarks>
|
|
public IEnumerable<PeopleResourceModel> LoadPeopleResourcesByTeamsNonStrict(List<Guid> teams, bool includeResourceAllocationsInfo, DateTime? filterDateUtc = null)
|
|
{
|
|
if (teams == null)
|
|
throw new ArgumentNullException(nameof(teams));
|
|
|
|
var resourcesToCheck = DbContext.PeopleResource2Team.AsNoTracking()
|
|
.Where(x => teams.Contains(x.TeamId)).Select(x => x.PeopleResourceId).Distinct().ToList();
|
|
var resources2Teams = DbContext.PeopleResource2Team.AsNoTracking()
|
|
.Where(x => resourcesToCheck.Contains(x.PeopleResourceId)).Include(x => x.Team).ToList();
|
|
|
|
DateTime dt = filterDateUtc.HasValue ? filterDateUtc.Value.Date : DateTime.UtcNow.Date;
|
|
|
|
var result = DbContext.PeopleResources.AsNoTracking()
|
|
.Where(pr => resourcesToCheck.Contains(pr.Id)).ToList().Select(pr => (PeopleResourceModel)pr)
|
|
.ToList();
|
|
|
|
foreach (var item in result)
|
|
{
|
|
var membershipRecord = resources2Teams.Where(t => t.PeopleResourceId.Equals(item.Id) &&
|
|
(t.StartDate <= dt) && (!t.EndDate.HasValue || (t.EndDate >= dt))).FirstOrDefault();
|
|
if (membershipRecord != null)
|
|
{
|
|
item.Team = membershipRecord.Team;
|
|
item.TeamId = membershipRecord.TeamId;
|
|
item.TeamAllocation = membershipRecord.Allocation;
|
|
item.StartDate = membershipRecord.StartDate;
|
|
item.EndDate = membershipRecord.EndDate;
|
|
}
|
|
}
|
|
|
|
var resourcesWithNoTeam = result.Where(x => (x.Team == null)).ToList();
|
|
|
|
foreach (var res in resourcesWithNoTeam)
|
|
{
|
|
PeopleResource2Team membershipRecord = null;
|
|
|
|
// Recently created active resource is considered to be a member of the team, he was included to,
|
|
// when was created (the may be some days lag between the resource StartDate and the StartDate of a
|
|
// a team membership record, because StartDate for a resource is any day at the fiscal week, and
|
|
// team membership begins at the next full fiscal week, the created recource exists).
|
|
// Existing resource, which has no any team membership at the data extraction date, is considered
|
|
// to be a member of the next team, he is queued to be a member.
|
|
membershipRecord = resources2Teams.Where(x => x.PeopleResourceId.Equals(res.Id) &&
|
|
(x.StartDate > dt)).OrderBy(x => x.StartDate).FirstOrDefault();
|
|
|
|
if (membershipRecord == null)
|
|
{
|
|
// Inactive resource is considered to stay a member of the last team, he was involved to,
|
|
// inspite of the EndDate for his last team membership is expired.
|
|
// We consider an active resource without team membership stay the member of his last team,
|
|
// if he was excluded from some team and was not queud to become a member of any other team.
|
|
membershipRecord = resources2Teams.Where(x => x.PeopleResourceId.Equals(res.Id) &&
|
|
x.EndDate.HasValue && x.EndDate.Value < dt).OrderByDescending(x => x.EndDate).FirstOrDefault();
|
|
}
|
|
|
|
if (membershipRecord != null)
|
|
{
|
|
res.Team = membershipRecord.Team;
|
|
res.TeamId = membershipRecord.TeamId;
|
|
res.TeamAllocation = membershipRecord.Allocation;
|
|
res.StartDate = membershipRecord.StartDate;
|
|
res.EndDate = membershipRecord.EndDate;
|
|
}
|
|
}
|
|
|
|
// Remove resources without teams and if the team not suits the filtering list
|
|
result.RemoveAll(x => (x.Team == null) || !teams.Contains(x.Team.Id));
|
|
|
|
if (includeResourceAllocationsInfo)
|
|
{
|
|
// Load and add brief information about resource allocations and actuals existing in database
|
|
var mixMngr = new MongoMixManager(DbContext, String.Empty);
|
|
var resourceIds = result.Select(x => x.Id).ToList();
|
|
var allocationsByResourcesLiveDb = this.GetResourceAllocationsInfo(resourceIds);
|
|
var allocationsByResourcesMixes = mixMngr.GetResourceAllocationsInfo(resourceIds);
|
|
|
|
result.ForEach(x =>
|
|
{
|
|
x.ResourceScenarioAllocationsInfo = new PeopleResourceAllocationsInfoModel();
|
|
x.ResourceActualsInfo = new PeopleResourceAllocationsInfoModel();
|
|
x.ResourceMixAllocationsInfo = new PeopleResourceAllocationsInfoModel();
|
|
|
|
if (allocationsByResourcesLiveDb.ContainsKey(x.Id))
|
|
{
|
|
var resourceInfo = allocationsByResourcesLiveDb[x.Id];
|
|
var portfolioAllocationsExist = resourceInfo.ContainsKey(ScenarioType.Portfolio) && resourceInfo[ScenarioType.Portfolio].HasAllocations;
|
|
var actualAllocationsExist = resourceInfo.ContainsKey(ScenarioType.Actuals) && resourceInfo[ScenarioType.Actuals].HasAllocations;
|
|
|
|
x.ResourceScenarioAllocationsInfo.HasAllocations = portfolioAllocationsExist;
|
|
x.ResourceScenarioAllocationsInfo.AllocationsMinDate = portfolioAllocationsExist ? resourceInfo[ScenarioType.Portfolio].AllocationsMinDate : (DateTime?)null;
|
|
x.ResourceScenarioAllocationsInfo.AllocationsMaxDate = portfolioAllocationsExist ? resourceInfo[ScenarioType.Portfolio].AllocationsMaxDate : (DateTime?)null;
|
|
|
|
x.ResourceActualsInfo.HasAllocations = actualAllocationsExist;
|
|
x.ResourceActualsInfo.AllocationsMinDate = actualAllocationsExist ? resourceInfo[ScenarioType.Actuals].AllocationsMinDate : (DateTime?)null;
|
|
x.ResourceActualsInfo.AllocationsMaxDate = actualAllocationsExist ? resourceInfo[ScenarioType.Actuals].AllocationsMaxDate : (DateTime?)null;
|
|
}
|
|
|
|
if (allocationsByResourcesMixes.ContainsKey(x.Id))
|
|
{
|
|
var resourceInfo = allocationsByResourcesMixes[x.Id];
|
|
x.ResourceMixAllocationsInfo.HasAllocations = resourceInfo.HasAllocations;
|
|
x.ResourceMixAllocationsInfo.AllocationsMinDate = resourceInfo.AllocationsMinDate;
|
|
x.ResourceMixAllocationsInfo.AllocationsMaxDate = resourceInfo.AllocationsMaxDate;
|
|
}
|
|
});
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Loads people resources with information actual at the specified <paramref name="filterDateUtc"/>. If Resource is not hired yet or already fired, then
|
|
/// resource info contains closest membership info to the right from <paramref name="filterDateUtc"/> and then to the left if "to the right" does not exist.
|
|
/// </summary>
|
|
/// <param name="teams">A list of team Ids to filter resources by. Either teams or resources parameter is required.</param>
|
|
/// <param name="resources">A list of resource Ids to filter resources by. Either teams or resources parameter is required.</param>
|
|
/// <param name="filterDateUtc">A date point when resource has been assigned to the specified team.
|
|
/// This parameter used to filter resources by PeopleResource2Team dates. DateTime.UtcNow.Date is used if not set.</param>
|
|
/// <remarks>
|
|
/// Resources with no team membership on date included to their future teams.
|
|
/// Resources with no team membership on date and in future incleded to their past teams
|
|
/// </remarks>
|
|
public IEnumerable<PeopleResourceModel> LoadPeopleResourcesNonStrict(IEnumerable<Guid> teams = null, IEnumerable<Guid> resources = null, DateTime? filterDateUtc = null)
|
|
{
|
|
if (teams == null && resources == null)
|
|
return null;
|
|
if (!teams.Any() && !resources.Any())
|
|
return null;
|
|
|
|
DateTime dt = filterDateUtc.HasValue ? filterDateUtc.Value.Date : DateTime.UtcNow.Date;
|
|
|
|
// get resource Ids match filter (teams & rsources)
|
|
var resourcesToCheck = DbContext.PeopleResource2Team.AsNoTracking()
|
|
.Where(x => (!resources.Any() || resources.Contains(x.PeopleResourceId)) &&
|
|
(!teams.Any() || teams.Contains(x.TeamId))).Select(x => x.PeopleResourceId).Distinct().ToList();
|
|
// get membership info for filtered resource ids
|
|
var resources2Teams = DbContext.PeopleResource2Team.AsNoTracking()
|
|
.Where(x => resourcesToCheck.Contains(x.PeopleResourceId)).Include(x => x.Team).ToList();
|
|
// load filtered resources
|
|
var result = DbContext.PeopleResources.AsNoTracking()
|
|
.Where(pr => resourcesToCheck.Contains(pr.Id)).ToList().Select(pr => (PeopleResourceModel)pr)
|
|
.ToList();
|
|
// fill loaded resources with membership info actual at the specified date
|
|
foreach (var item in result)
|
|
{
|
|
var membershipRecord = resources2Teams.Where(t => t.PeopleResourceId.Equals(item.Id) &&
|
|
t.StartDate <= dt && (!t.EndDate.HasValue || t.EndDate >= dt)).FirstOrDefault();
|
|
if (membershipRecord != null)
|
|
{
|
|
item.Team = membershipRecord.Team;
|
|
item.TeamId = membershipRecord.TeamId;
|
|
item.TeamAllocation = membershipRecord.Allocation;
|
|
item.StartDate = membershipRecord.StartDate;
|
|
item.EndDate = membershipRecord.EndDate;
|
|
}
|
|
}
|
|
// if any loaded resource does not have memebership info at the specified date then we should load first closes info
|
|
var resourcesWithNoTeam = result.Where(x => (x.Team == null)).ToList();
|
|
foreach (var res in resourcesWithNoTeam)
|
|
{
|
|
PeopleResource2Team membershipRecord = null;
|
|
// Recently created active resource is considered to be a member of the team, he was included to,
|
|
// when was created (the may be some days lag between the resource StartDate and the StartDate of a
|
|
// a team membership record, because StartDate for a resource is any day at the fiscal week, and
|
|
// team membership begins at the next full fiscal week, the created recource exists).
|
|
// Existing resource, which has no any team membership at the data extraction date, is considered
|
|
// to be a member of the next team, he is queued to be a member.
|
|
membershipRecord = resources2Teams.Where(x => x.PeopleResourceId.Equals(res.Id) &&
|
|
x.StartDate > dt).OrderBy(x => x.StartDate).FirstOrDefault();
|
|
if (membershipRecord == null)
|
|
{
|
|
// Inactive resource is considered to stay a member of the last team, he was involved to,
|
|
// inspite of the EndDate for his last team membership is expired.
|
|
// We consider an active resource without team membership stay the member of his last team,
|
|
// if he was excluded from some team and was not queud to become a member of any other team.
|
|
membershipRecord = resources2Teams.Where(x => x.PeopleResourceId.Equals(res.Id) &&
|
|
x.EndDate.HasValue && x.EndDate.Value < dt).OrderByDescending(x => x.EndDate).FirstOrDefault();
|
|
}
|
|
if (membershipRecord != null)
|
|
{
|
|
res.Team = membershipRecord.Team;
|
|
res.TeamId = membershipRecord.TeamId;
|
|
res.TeamAllocation = membershipRecord.Allocation;
|
|
res.StartDate = membershipRecord.StartDate;
|
|
res.EndDate = membershipRecord.EndDate;
|
|
}
|
|
}
|
|
|
|
// Remove resources without teams and if the team not suits the filtering list
|
|
result.RemoveAll(x => (x.Team == null) || (teams.Any() && !teams.Contains(x.Team.Id)));
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Loads a list of people resources for the specified list of team Ids <paramref name="teamIds"/>.
|
|
/// </summary>
|
|
/// <param name="teamIds">A list of team Ids.</param>
|
|
/// <param name="filterDate">A date point when resource has been assigned to the specified team. This parameter used to filter resources by PeopleResource2Team dates.</param>
|
|
/// <returns>
|
|
/// An list of <see cref="PeopleResourceModel"/> objects with prefilled resource, expenditure and team properties.
|
|
/// </returns>
|
|
public IEnumerable<PeopleResourceModel> LoadPeopleResourcesByTeams(IEnumerable<Guid> teamIds, DateTime? filterDate = null)
|
|
{
|
|
var query = (from pr in DbContext.VW_TeamResource.AsNoTracking()
|
|
join t in DbContext.Teams.AsNoTracking() on pr.TeamId equals t.Id
|
|
join ec in DbContext.ExpenditureCategory.AsNoTracking() on pr.ExpenditureCategoryId equals ec.Id
|
|
where teamIds.Contains(pr.TeamId)
|
|
select new
|
|
{
|
|
Resource = pr,
|
|
ExpCat = ec,
|
|
Team = t
|
|
});
|
|
if (filterDate.HasValue)
|
|
query = query.Where(t => t.Resource.TeamStartDate <= filterDate.Value &&
|
|
(!t.Resource.TeamEndDate.HasValue || (t.Resource.TeamEndDate >= filterDate && t.Resource.TeamEndDate.HasValue)));
|
|
|
|
var result = new List<PeopleResourceModel>();
|
|
foreach (var model in query.Distinct())
|
|
{
|
|
var resourceModel = result.FirstOrDefault(x => x.Id.Equals(model.Resource.Id) && x.ExpenditureCategoryId.Equals(model.ExpCat.Id) &&
|
|
x.TeamId.Equals(model.Team.Id));
|
|
|
|
if (resourceModel == null)
|
|
{
|
|
// Add resource model to result
|
|
resourceModel = PeopleResourceModel.GetPeopleResourceModel(model.Resource, model.ExpCat, model.Team);
|
|
resourceModel.Teams = new List<TeamMembershipModel>();
|
|
result.Add(resourceModel);
|
|
}
|
|
else
|
|
{
|
|
// Extend dates on existing resource model
|
|
if (model.Resource.TeamStartDate < resourceModel.StartDate)
|
|
resourceModel.StartDate = model.Resource.TeamStartDate;
|
|
|
|
if (resourceModel.EndDate.HasValue)
|
|
{
|
|
if (!model.Resource.TeamEndDate.HasValue || (model.Resource.TeamEndDate.Value > resourceModel.EndDate.Value))
|
|
resourceModel.EndDate = model.Resource.TeamEndDate;
|
|
}
|
|
}
|
|
|
|
var teamMembership = new TeamMembershipModel()
|
|
{
|
|
TeamId = model.Team.Id,
|
|
StartDate = model.Resource.TeamStartDate,
|
|
EndDate = model.Resource.TeamEndDate
|
|
};
|
|
resourceModel.Teams.Add(teamMembership);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
public void DeleteResource(PeopleResourceModel model)
|
|
{
|
|
var scenarioManager = new ScenarioManager(DbContext);
|
|
var ecManager = (new ExpenditureCategoryManager(DbContext));
|
|
var rateManager = new RateManager(DbContext);
|
|
|
|
// Remove resource teams capacity
|
|
var resourceTeams = LoadResource2TeamsByResource(model.Id);
|
|
ChangeCapacityForResourceTeams(resourceTeams, null, model.ExpenditureCategoryId, model.ChangeCapacity);
|
|
|
|
var actualScenarios = GetActualScenariosByResource(model.Id);
|
|
var actualScenariosIds = actualScenarios.Select(x => x.Id);
|
|
var projects = scenarioManager.GetProjectsByScenarios(actualScenariosIds, true);
|
|
var projectsIds = projects.Select(x => x.Value.Id);
|
|
var forecastScenarios = scenarioManager.GetScenarios4Projects(projectsIds, ScenarioType.Portfolio, null)
|
|
.GroupBy(x => x.ParentId.Value)
|
|
.ToDictionary(x => x.Key, g => g.ToList());
|
|
var forecastScenariosIds = forecastScenarios.SelectMany(x => x.Value).Select(x => x.Id);
|
|
var forecastScenarioDetails = scenarioManager.GetScenarioDetails(forecastScenariosIds, readOnly: true)
|
|
.GroupBy(x => x.ParentID.Value)
|
|
.ToDictionary(x => x.Key, g => g.ToList());
|
|
var actualResourcesAllocations = GetResourceActualAllocationsByScenario(actualScenariosIds);
|
|
var actualScenarioDetails = scenarioManager.GetScenarioDetails(actualResourcesAllocations.Keys)
|
|
.GroupBy(x => x.ParentID.Value)
|
|
.ToDictionary(x => x.Key, g => g.ToList());
|
|
var expenditures = ecManager.GetExpenditureDetails();
|
|
var allScenarios = actualScenariosIds.Union(forecastScenariosIds);
|
|
var rates = rateManager.GetRatesDict(allScenarios, rateManager.LoadRatesByScenarios(allScenarios));
|
|
|
|
foreach (var actualScenario in actualScenarios)
|
|
{
|
|
var project = projects.ContainsKey(actualScenario.Id) ? projects[actualScenario.Id] : null;
|
|
var forecastScenarios4Project = forecastScenarios.ContainsKey(project.Id) ? forecastScenarios[project.Id] : new List<Scenario>();
|
|
var affectedScenarioDetails = actualResourcesAllocations.ContainsKey(actualScenario.Id) ?
|
|
actualResourcesAllocations[actualScenario.Id].Where(x => x.PeopleResourceId == model.Id).Select(x => x.ScenarioDetailId).Distinct().ToList() :
|
|
new List<Guid>();
|
|
|
|
// we should pass all resource allocations except of current one, because it'll be deleted right after that
|
|
var resourceAllocations4Scenario = actualResourcesAllocations.ContainsKey(actualScenario.Id) ?
|
|
actualResourcesAllocations[actualScenario.Id].Where(x => x.PeopleResourceId != model.Id).ToList() :
|
|
new List<ResourceActualAllocationModel>();
|
|
|
|
var actualScenarioDetails4Scenario = actualScenarioDetails.ContainsKey(actualScenario.Id) ? actualScenarioDetails[actualScenario.Id] : new List<ScenarioDetail>();
|
|
var actualScenarioDetailsToRecalculate = actualScenarioDetails4Scenario.Where(x => affectedScenarioDetails.Contains(x.Id)).ToList();
|
|
|
|
scenarioManager.RecalculateScenarioActuals(actualScenario, project, actualScenarioDetails4Scenario, resourceAllocations4Scenario, actualScenarioDetailsToRecalculate, forecastScenarios4Project, forecastScenarioDetails, expenditures, rates);
|
|
}
|
|
|
|
// Remove resource from DB
|
|
// TODO: create separate method that uses DeleteFromQuery or BulkDelete method from EF Extensions library
|
|
(DbContext as IObjectContextAdapter).ObjectContext.ExecuteStoreCommand($"exec sp_DeletePeopleResource '{model.Id}'");
|
|
}
|
|
public List<PeopleResourceWithAllocationModel> GetResourcesWithAllocationsByTeams(IEnumerable<Guid> teams, DateTime? startDate = null, DateTime? endDate = null)
|
|
{
|
|
if (teams == null || !teams.Any())
|
|
return new List<PeopleResourceWithAllocationModel>();
|
|
|
|
var query =
|
|
(from resource in DbContext.VW_TeamResource.Where(x => teams.Contains(x.TeamId))
|
|
select new
|
|
{
|
|
resource,
|
|
allocations = from pra in DbContext.PeopleResourceAllocations
|
|
join scenario in DbContext.Scenarios on pra.ScenarioId equals scenario.Id
|
|
where scenario.Status == (int)ScenarioStatus.Active &&
|
|
resource.Id == pra.PeopleResourceId &&
|
|
pra.TeamId == resource.TeamId &&
|
|
pra.WeekEndingDate >= resource.TeamStartDate &&
|
|
(!resource.TeamEndDate.HasValue || pra.WeekEndingDate <= resource.TeamEndDate.Value) &&
|
|
(!startDate.HasValue || pra.WeekEndingDate >= startDate.Value) &&
|
|
(!endDate.HasValue || pra.WeekEndingDate <= endDate.Value)
|
|
select pra
|
|
}).ToList();
|
|
|
|
return query.Select(x => new PeopleResourceWithAllocationModel(x.resource, x.allocations.ToList())).ToList();
|
|
}
|
|
|
|
public List<PeopleResourceWithAllocationModel> GetResourcesWithAllocationsByResourceId(Guid resourceId, DateTime? startDate = null, DateTime? endDate = null)
|
|
{
|
|
if (resourceId == Guid.Empty)
|
|
return new List<PeopleResourceWithAllocationModel>();
|
|
|
|
return GetResourcesWithAllocationsByResourceId(new List<Guid>() { resourceId }, startDate, endDate);
|
|
}
|
|
|
|
public List<PeopleResourceWithAllocationModel> GetResourcesWithAllocationsByResourceId(IEnumerable<Guid> resources, DateTime? startDate = null, DateTime? endDate = null)
|
|
{
|
|
if (resources == null || !resources.Any())
|
|
return new List<PeopleResourceWithAllocationModel>();
|
|
|
|
var query =
|
|
(from resource in DbContext.VW_TeamResource.Where(x => resources.Contains(x.Id))
|
|
select new
|
|
{
|
|
resource,
|
|
allocations = from pra in DbContext.PeopleResourceAllocations
|
|
join scenario in DbContext.Scenarios on pra.ScenarioId equals scenario.Id
|
|
where scenario.Status == (int)ScenarioStatus.Active &&
|
|
resource.Id == pra.PeopleResourceId &&
|
|
pra.TeamId == resource.TeamId &&
|
|
pra.WeekEndingDate >= resource.TeamStartDate &&
|
|
(!resource.TeamEndDate.HasValue || pra.WeekEndingDate <= resource.TeamEndDate.Value) &&
|
|
(!startDate.HasValue || pra.WeekEndingDate >= startDate.Value) &&
|
|
(!endDate.HasValue || pra.WeekEndingDate <= endDate.Value)
|
|
select pra
|
|
}).ToList();
|
|
|
|
return query.Select(x => new PeopleResourceWithAllocationModel(x.resource, x.allocations.ToList())).ToList();
|
|
}
|
|
|
|
public List<PeopleResourceAllocation> GetPeopleResourceAllocations(Guid scenarioId, bool readOnly = false)
|
|
{
|
|
if (scenarioId == Guid.Empty)
|
|
return new List<PeopleResourceAllocation>();
|
|
|
|
return GetPeopleResourceAllocations(new List<Guid> { scenarioId }, readOnly);
|
|
}
|
|
|
|
public List<PeopleResourceAllocation> GetPeopleResourceAllocations(IEnumerable<Guid> scenarios, bool readOnly = false)
|
|
{
|
|
if (scenarios == null || !scenarios.Any())
|
|
return new List<PeopleResourceAllocation>();
|
|
|
|
var resourceQuery = DbContext.PeopleResourceAllocations.AsQueryable();
|
|
if (readOnly)
|
|
resourceQuery = resourceQuery.AsNoTracking();
|
|
|
|
if (scenarios != null && scenarios.Any())
|
|
resourceQuery = resourceQuery.Where(x => scenarios.Contains(x.ScenarioId));
|
|
|
|
return resourceQuery.ToList();
|
|
}
|
|
|
|
public List<Scenario> GetActualScenariosByResource(Guid resourceId, bool readOnly = false)
|
|
{
|
|
if (resourceId == Guid.Empty)
|
|
return new List<Scenario>();
|
|
|
|
var query = (from resourceActual in DbContext.PeopleResourceActuals
|
|
join detail in DbContext.ScenarioDetail on resourceActual.ParentId equals detail.Id
|
|
join scenario in DbContext.Scenarios on detail.ParentID equals scenario.Id
|
|
where resourceActual.PeopleResourceId == resourceId && scenario.Type == (int)ScenarioType.Actuals
|
|
select scenario).Distinct();
|
|
|
|
if (readOnly)
|
|
query = query.AsNoTracking();
|
|
|
|
return query.ToList();
|
|
}
|
|
|
|
public List<ResourceActualAllocationModel> GetResourceActualAllocationsByScenario(Guid scenarioId)
|
|
{
|
|
if (scenarioId == Guid.Empty)
|
|
return new List<ResourceActualAllocationModel>();
|
|
|
|
var actuals = GetResourceActualAllocationsByScenario(new List<Guid> { scenarioId });
|
|
if (actuals != null && actuals.ContainsKey(scenarioId))
|
|
return actuals[scenarioId];
|
|
|
|
return new List<ResourceActualAllocationModel>();
|
|
}
|
|
|
|
public Dictionary<Guid, List<ResourceActualAllocationModel>> GetResourceActualAllocationsByScenario(IEnumerable<Guid> scenarios)
|
|
{
|
|
if (scenarios == null || !scenarios.Any())
|
|
return new Dictionary<Guid, List<ResourceActualAllocationModel>>();
|
|
|
|
var allocations = ResourceActualAllocationModelBaseQuery.Where(x => scenarios.Contains(x.ActualScenarioId))
|
|
.AsNoTracking().ToArray();
|
|
|
|
var model = allocations.GroupBy(x => x.ActualScenarioId)
|
|
.ToDictionary(x => x.Key, g => g.ToList());
|
|
|
|
return model;
|
|
}
|
|
|
|
public List<ResourceAllocationModel> GetActualsByResourceId(Guid resourceId, DateTime? startDate = null, DateTime? endDate = null)
|
|
{
|
|
if (resourceId == Guid.Empty)
|
|
return new List<ResourceAllocationModel>();
|
|
|
|
return GetActualsByResourceId(new List<Guid>() { resourceId }, startDate, endDate);
|
|
}
|
|
|
|
public List<ResourceAllocationModel> GetActualsByResourceId(IEnumerable<Guid> resources, DateTime? startDate = null, DateTime? endDate = null)
|
|
{
|
|
if (resources == null || !resources.Any())
|
|
return new List<ResourceAllocationModel>();
|
|
|
|
var query =
|
|
(from pra in DbContext.PeopleResourceActuals
|
|
join sd in DbContext.ScenarioDetail on pra.ParentId equals sd.Id
|
|
join actualScenario in DbContext.Scenarios on sd.ParentID equals actualScenario.Id
|
|
join foreScenario in DbContext.Scenarios on actualScenario.ParentId equals foreScenario.ParentId
|
|
join res2team in DbContext.VW_TeamResource on pra.PeopleResourceId equals res2team.Id
|
|
where (foreScenario.Status == (int)ScenarioStatus.Active) &&
|
|
sd.WeekEndingDate.HasValue &&
|
|
(sd.ExpenditureCategoryId == res2team.ExpenditureCategoryId) &&
|
|
resources.Contains(pra.PeopleResourceId) &&
|
|
(sd.WeekEndingDate >= res2team.TeamStartDate) &&
|
|
(!res2team.TeamEndDate.HasValue || sd.WeekEndingDate <= res2team.TeamEndDate.Value) &&
|
|
(!startDate.HasValue || sd.WeekEndingDate >= startDate.Value) &&
|
|
(!endDate.HasValue || sd.WeekEndingDate <= endDate.Value)
|
|
select new
|
|
{
|
|
Allocations = pra,
|
|
Details = sd,
|
|
ResourceToTeam = res2team,
|
|
ForecastScenarioId = foreScenario.Id
|
|
}).ToList();
|
|
|
|
var result =
|
|
query.Select(x => new ResourceAllocationModel()
|
|
{
|
|
ScenarioId = x.ForecastScenarioId,
|
|
PeopleResourceId = x.Allocations.PeopleResourceId,
|
|
ExpenditureCategoryId = x.ResourceToTeam.ExpenditureCategoryId,
|
|
TeamId = x.ResourceToTeam.TeamId,
|
|
WeekEndingDate = x.Details.WeekEndingDate.Value,
|
|
Quantity = x.Allocations.Quantity
|
|
}).ToList();
|
|
return result;
|
|
}
|
|
|
|
/// <summary>Returns tree-structure of resources: Teams[TeamId, ExpCats[ExpCatId, PeopleResourcesList]]</summary>
|
|
public Dictionary<Guid, Dictionary<Guid, List<PeopleResourceWithAllocationModel>>> CastPeopleResourceWithAllocationToTree(List<PeopleResourceWithAllocationModel> resources)
|
|
{
|
|
if (resources == null || !resources.Any())
|
|
return new Dictionary<Guid, Dictionary<Guid, List<PeopleResourceWithAllocationModel>>>();
|
|
|
|
var resourcesTree = resources.GroupBy(team => team.TeamId)
|
|
.ToDictionary(team => team.Key, expCats => expCats.GroupBy(expCat => expCat.ExpenditureCategoryId)
|
|
.ToDictionary(expCat => expCat.Key, res => res.OrderBy(r => r.LastName).ThenBy(r => r.FirstName).ToList()));
|
|
|
|
return resourcesTree;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Replaces own resource ExpenditureCategory with the ones, specified in his allocations
|
|
/// </summary>
|
|
/// <param name="resources"></param>
|
|
/// <remarks>If resource's allocations has different ECs, the resource record is copied. In the result
|
|
/// the number of records for every resource fit to ECs count, this resource is allocated to
|
|
/// </remarks>
|
|
public void ReplaceOwnResourceExpendituresWithExpendituresInAllocations(List<PeopleResourceWithAllocationModel> resources)
|
|
{
|
|
if (resources == null || !resources.Any())
|
|
return;
|
|
|
|
// Get all the resource records, where resource's ExpenditureCategoryId not fit to ExpCat, specified in Allocations collection
|
|
var resourcesToSuperExpCats = resources.Where(x => (x.Allocations != null) && (x.Allocations.Count > 0) &&
|
|
x.Allocations.Where(a => a.ExpenditureCategoryId != x.ExpenditureCategoryId).Any())
|
|
.ToList();
|
|
|
|
|
|
foreach (var resourceItem in resourcesToSuperExpCats)
|
|
{
|
|
// Get all the ECs, for which allocations are specified
|
|
var resourceItemAllocationsExpCats =
|
|
resourceItem.Allocations.Where(x => x.ExpenditureCategoryId != resourceItem.ExpenditureCategoryId)
|
|
.Select(x => x.ExpenditureCategoryId).Distinct().ToList();
|
|
|
|
foreach (var expCatId in resourceItemAllocationsExpCats)
|
|
{
|
|
// For every other ExpCat we create new record to original resources collection
|
|
var newResourceItem = resourceItem.Clone();
|
|
newResourceItem.ExpenditureCategoryId = expCatId;
|
|
newResourceItem.Allocations.RemoveAll(x => x.ExpenditureCategoryId != expCatId);
|
|
resources.Add(newResourceItem);
|
|
}
|
|
|
|
// Remove allocations for non-original resource EC
|
|
resourceItem.Allocations.RemoveAll(x => x.ExpenditureCategoryId != resourceItem.ExpenditureCategoryId); //.ToList().ForEach(x => x.Quantity = 0);
|
|
}
|
|
}
|
|
|
|
public override PeopleResource Save(PeopleResourceModel model)
|
|
{
|
|
PeopleResourceModel newModel = model;
|
|
PeopleResourceModel oldModel = null;
|
|
List<TeamMembershipModel> oldResourceTeams = null;
|
|
List<TeamMembershipModel> newResourceTeams = null;
|
|
|
|
if (!model.IsNew)
|
|
oldModel = this.LoadPeopleResource(newModel.Id, DateTime.Today, false, false, false);
|
|
|
|
if (newModel.PermanentResource)
|
|
newModel.EndDate = Constants.FISCAL_CALENDAR_MAX_DATE;
|
|
|
|
//Actual capacity change cases: 1. New resource, 2. Resource status change (active/inactive), 3. Resource category change, 4. Resource team change, 5. Start/End date change
|
|
//Planned capacity change cases: when actual capacity is changed AND [model.ChangeCapacity] is set
|
|
bool hasUnsavedDbChanges = false;
|
|
bool statuschanged = false;
|
|
bool teamchanged = false;
|
|
bool dateschanged = false;
|
|
|
|
if (!newModel.IsNew)
|
|
{
|
|
statuschanged = newModel.IsActiveEmployee != oldModel.IsActiveEmployee;
|
|
teamchanged = newModel.TeamId != oldModel.TeamId;
|
|
dateschanged = newModel.StartDate != oldModel.StartDate || newModel.EndDate != oldModel.EndDate;
|
|
}
|
|
|
|
// Get start and end dates of available team membership for new resource model
|
|
DateTime membershipStartDateWeNew = FiscalCalendarManager.GetDateForTeamMembershipStart(newModel.StartDate, this.DbContext);
|
|
DateTime? membershipEndDateWeNew = null;
|
|
|
|
if (!newModel.PermanentResource && newModel.EndDate.HasValue)
|
|
// For not permanent resource we, probably, have non-null end date
|
|
membershipEndDateWeNew = FiscalCalendarManager.GetDateForTeamMembershipEnd(newModel.EndDate.Value, this.DbContext);
|
|
|
|
if (oldModel != null)
|
|
{
|
|
// Create old resource team membership snapshot
|
|
oldResourceTeams = LoadResource2TeamsByResource(oldModel.Id);
|
|
if (dateschanged)
|
|
{
|
|
#region Clear resource allocations
|
|
if ((newModel.StartDate > oldModel.StartDate) || (!newModel.PermanentResource &&
|
|
(!oldModel.EndDate.HasValue || (newModel.EndDate.Value < oldModel.EndDate.Value))))
|
|
{
|
|
// Remove allocations out resource date range
|
|
var allocsToDrop = DbContext.PeopleResourceAllocations.Where(x => x.PeopleResourceId.Equals(newModel.Id) &&
|
|
((x.WeekEndingDate < membershipStartDateWeNew) ||
|
|
(membershipEndDateWeNew.HasValue && x.WeekEndingDate > membershipEndDateWeNew.Value))).ToList();
|
|
DbContext.PeopleResourceAllocations.RemoveRange(allocsToDrop);
|
|
|
|
// Remove team membership out of resource date range
|
|
var membershipToDrop = DbContext.PeopleResource2Team.Where(x => x.PeopleResourceId.Equals(newModel.Id) &&
|
|
((x.EndDate.HasValue && (x.EndDate < membershipStartDateWeNew)) ||
|
|
(membershipEndDateWeNew.HasValue && (x.StartDate > membershipEndDateWeNew.Value)))).ToList();
|
|
DbContext.PeopleResource2Team.RemoveRange(membershipToDrop);
|
|
|
|
hasUnsavedDbChanges = true;
|
|
}
|
|
|
|
if (hasUnsavedDbChanges)
|
|
{
|
|
// Save changes to prevent the deleted allocations and teams appear in future queries
|
|
DbContext.SaveChanges();
|
|
hasUnsavedDbChanges = false;
|
|
|
|
if (DbContext.PeopleResource2Team.Any(x => x.PeopleResourceId.Equals(newModel.Id)))
|
|
{
|
|
// do nothing if there are some records remaining
|
|
}
|
|
else
|
|
{
|
|
// if we removed all records for the specified PeopleResource then we need to create
|
|
// a new record for new dates. Otherwise we could have missing PR2Team reference issues
|
|
PeopleResource2Team newRec = new PeopleResource2Team()
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
TeamId = newModel.TeamId.Value,
|
|
PeopleResourceId = newModel.Id,
|
|
StartDate = membershipStartDateWeNew,
|
|
EndDate = membershipEndDateWeNew,
|
|
Allocation = 100,
|
|
};
|
|
DbContext.PeopleResource2Team.Add(newRec);
|
|
DbContext.SaveChanges();
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
#region Update PeopleResource2Team start/end dates if resource start/end dates were changed
|
|
|
|
// Get start and end dates of available team membership for OLD resource model
|
|
DateTime membershipStartDateWeOld = FiscalCalendarManager.GetDateForTeamMembershipStart(oldModel.StartDate, this.DbContext);
|
|
DateTime? membershipEndDateWeOld = null;
|
|
|
|
if (!oldModel.PermanentResource && oldModel.EndDate.HasValue)
|
|
membershipEndDateWeOld = FiscalCalendarManager.GetDateForTeamMembershipEnd(oldModel.EndDate.Value, this.DbContext);
|
|
|
|
List<PeopleResource2Team> membershipToFixStartDate = null;
|
|
List<PeopleResource2Team> membershipToFixEndDate = null;
|
|
|
|
// Update first PR2Team record if resource start date has been changed
|
|
if (membershipStartDateWeNew < membershipStartDateWeOld)
|
|
{
|
|
// Resource's start date was moved to past => Move to the past PR2Team record's start dates for
|
|
// teams, the resource was included, when created.
|
|
// To make sure, we found all the initial team membership records for the resource, we should
|
|
// look for them not by the exact old team membership starting date (which is the beginning
|
|
// of the fiscal week), but by the last day of this fiscal week.
|
|
// So, first, we should get the last date for the fiscal week, which contains membershipStartDateWeOld
|
|
var fiscalWeek = FiscalCalendarManager.GetFiscalCalendarWeekForDate(membershipStartDateWeOld, this.DbContext);
|
|
DateTime searchBoundDate = fiscalWeek.EndDate;
|
|
membershipToFixStartDate = DbContext.PeopleResource2Team.Where(x => x.PeopleResourceId.Equals(newModel.Id) &&
|
|
(x.StartDate < searchBoundDate)).ToList();
|
|
}
|
|
|
|
if (membershipStartDateWeNew > membershipStartDateWeOld)
|
|
{
|
|
// Resource's start date was moved to future => Move to the future PR2Team record's start dates for
|
|
// teams, the resource was included, when created
|
|
membershipToFixStartDate = DbContext.PeopleResource2Team.Where(x => x.PeopleResourceId.Equals(newModel.Id) &&
|
|
(x.StartDate < membershipStartDateWeNew)).ToList();
|
|
}
|
|
|
|
if (membershipEndDateWeNew != membershipEndDateWeOld)
|
|
{
|
|
if (membershipEndDateWeOld.HasValue && (!membershipEndDateWeNew.HasValue ||
|
|
(membershipEndDateWeNew.HasValue && (membershipEndDateWeNew.Value > membershipEndDateWeOld.Value))))
|
|
{
|
|
// Resource's end date was moved to the future => Move to the future PR2Team record's end dates for
|
|
// teams, the resource was included till to it's end date
|
|
DateTime? searchBoundDate = null;
|
|
if (membershipEndDateWeOld.HasValue)
|
|
{
|
|
// To make sure, we found all the last team membership records for the resource, we should
|
|
// look for them not by the exact old team membership end date, but by the first day of
|
|
// this fiscal week.
|
|
// So, first, we should get the first date for the fiscal week, which contains membershipEndDateWeOld
|
|
var fiscalWeek = FiscalCalendarManager.GetFiscalCalendarWeekForDate(membershipEndDateWeOld.Value, this.DbContext);
|
|
searchBoundDate = fiscalWeek.StartDate;
|
|
}
|
|
membershipToFixEndDate = DbContext.PeopleResource2Team.Where(x => x.PeopleResourceId.Equals(newModel.Id) &&
|
|
(x.EndDate.HasValue && (x.EndDate.Value > searchBoundDate.Value))).ToList();
|
|
}
|
|
|
|
if (membershipEndDateWeNew.HasValue && (!membershipEndDateWeOld.HasValue ||
|
|
(membershipEndDateWeOld.HasValue && (membershipEndDateWeOld.Value > membershipEndDateWeNew.Value))))
|
|
{
|
|
// Resource's end date was moved to the past => Move to the past PR2Team record's end dates for
|
|
// teams, the resource was included till to it's end date
|
|
membershipToFixEndDate = DbContext.PeopleResource2Team.Where(x => x.PeopleResourceId.Equals(newModel.Id) &&
|
|
(!x.EndDate.HasValue || (x.EndDate.HasValue && (x.EndDate.Value > membershipEndDateWeNew.Value)))).ToList();
|
|
}
|
|
}
|
|
|
|
if ((membershipToFixStartDate != null) && (membershipToFixStartDate.Count > 0))
|
|
{
|
|
membershipToFixStartDate.ForEach(x => x.StartDate = membershipStartDateWeNew);
|
|
hasUnsavedDbChanges = true;
|
|
}
|
|
|
|
if ((membershipToFixEndDate != null) && (membershipToFixEndDate.Count > 0))
|
|
{
|
|
membershipToFixEndDate.ForEach(x => x.EndDate = membershipEndDateWeNew);
|
|
hasUnsavedDbChanges = true;
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|
|
|
|
#region Create/Update PeopleResource record
|
|
PeopleResource savedRecord = !newModel.IsNew ? DataTable.Find(model.Id) : InitInstance();
|
|
model.CopyTo(savedRecord);
|
|
if (newModel.IsNew)
|
|
{
|
|
savedRecord.Id = model.Id;
|
|
DataTable.Add(savedRecord);
|
|
}
|
|
else
|
|
DbContext.Entry(savedRecord).State = EntityState.Modified;
|
|
|
|
DbContext.SaveChanges();
|
|
hasUnsavedDbChanges = false;
|
|
DateTime effectiveChangeStartDate = FiscalCalendarManager.GetDateForTeamMembershipStart(newModel.EffectiveChangeDate, this.DbContext);
|
|
#endregion
|
|
#region Saving new team to PeopleResource2Team table
|
|
|
|
#region Remove obsolete future teams and close (set new EndDate) current PR2Team record if team has been changed
|
|
if (teamchanged)
|
|
{
|
|
// Get the nearest full week start for the specified by user Effective Date of Change
|
|
if (effectiveChangeStartDate < membershipStartDateWeNew)
|
|
{
|
|
// Effective date of Change is before the Resource starts then set it to resource start date
|
|
effectiveChangeStartDate = membershipStartDateWeNew;
|
|
}
|
|
|
|
if (!membershipEndDateWeNew.HasValue || (effectiveChangeStartDate < membershipEndDateWeNew.Value))
|
|
{
|
|
// Delete team membership records in future
|
|
var recordsToDelete = DbContext.PeopleResource2Team.Where(x => x.PeopleResourceId.Equals(oldModel.Id) &&
|
|
(x.StartDate >= effectiveChangeStartDate)).ToList();
|
|
DbContext.PeopleResource2Team.RemoveRange(recordsToDelete);
|
|
|
|
// Close currect team membership record
|
|
var previousTeamCloseDate = FiscalCalendarManager.GetDateForTeamMembershipEnd(effectiveChangeStartDate, this.DbContext);
|
|
var recordsToClose = DbContext.PeopleResource2Team.Where(x => x.PeopleResourceId.Equals(oldModel.Id) &&
|
|
(x.StartDate < effectiveChangeStartDate) && (!x.EndDate.HasValue || x.EndDate.Value > effectiveChangeStartDate)).ToList();
|
|
recordsToClose.ForEach(x => x.EndDate = previousTeamCloseDate);
|
|
|
|
hasUnsavedDbChanges = true;
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Create new PeopleResource2Team records
|
|
if ((newModel.IsNew || teamchanged) && newModel.TeamId.HasValue)
|
|
{
|
|
DateTime teamStartDateWe = teamchanged ? effectiveChangeStartDate : membershipStartDateWeNew;
|
|
DateTime? teamEndDateWe = membershipEndDateWeNew;
|
|
|
|
PeopleResource2Team newRec = new PeopleResource2Team()
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
TeamId = newModel.TeamId.Value,
|
|
PeopleResourceId = newModel.Id,
|
|
StartDate = teamStartDateWe,
|
|
EndDate = teamEndDateWe,
|
|
Allocation = 100,
|
|
};
|
|
DbContext.PeopleResource2Team.Add(newRec);
|
|
hasUnsavedDbChanges = true;
|
|
}
|
|
#endregion
|
|
|
|
// Apply capacity changes
|
|
if (hasUnsavedDbChanges)
|
|
{
|
|
DbContext.SaveChanges();
|
|
hasUnsavedDbChanges = false;
|
|
}
|
|
#endregion
|
|
|
|
// Manage teams capacity changes
|
|
// Create new resource team membership snapshot and change capacity
|
|
newResourceTeams = LoadResource2TeamsByResource(newModel.Id);
|
|
|
|
if ((oldModel != null) && statuschanged)
|
|
{
|
|
if (oldModel.IsActiveEmployee && !newModel.IsActiveEmployee)
|
|
{
|
|
// Resource was deactivated => decrease teams capacity
|
|
newResourceTeams = null;
|
|
if (!newModel.ReassignOnDeactivation)
|
|
{
|
|
var dropAssignmentDate = FiscalCalendarManager.GetDateForTeamMembershipStart(newModel.EffectiveChangeDate, this.DbContext);
|
|
// Drop all resource allocation starting on EffectiveDate and to the future
|
|
var resourceAllocations = DbContext.PeopleResourceAllocations.Where(x =>
|
|
x.PeopleResourceId.Equals(oldModel.Id) && x.WeekEndingDate > dropAssignmentDate).ToList();
|
|
DbContext.PeopleResourceAllocations.RemoveRange(resourceAllocations);
|
|
}
|
|
}
|
|
|
|
if (!oldModel.IsActiveEmployee && newModel.IsActiveEmployee)
|
|
// Resource was activated => increase teams capacity
|
|
oldResourceTeams = null;
|
|
}
|
|
|
|
if ((oldModel == null) && !newModel.IsActiveEmployee)
|
|
{
|
|
// For newly created inactive resource, we don't increase capacity at all
|
|
oldResourceTeams = null;
|
|
newResourceTeams = null;
|
|
}
|
|
|
|
if ((oldResourceTeams != null) || (newResourceTeams != null))
|
|
ChangeCapacityForResourceTeams(oldResourceTeams, newResourceTeams, newModel.ExpenditureCategoryId, newModel.ChangeCapacity);
|
|
|
|
return savedRecord;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns list of teams, the resource is a member of
|
|
/// </summary>
|
|
/// <param name="resourceId"></param>
|
|
/// <returns></returns>
|
|
protected List<TeamMembershipModel> LoadResource2TeamsByResource(Guid resourceId)
|
|
{
|
|
var result = this.DbContext.PeopleResource2Team.AsNoTracking()
|
|
.Where(x => x.PeopleResourceId.Equals(resourceId))
|
|
.Select(x => new TeamMembershipModel()
|
|
{
|
|
Id = x.Id,
|
|
TeamId = x.TeamId,
|
|
StartDate = x.StartDate,
|
|
EndDate = x.EndDate
|
|
}).ToList();
|
|
return result;
|
|
}
|
|
|
|
protected void ChangeCapacityForResourceTeams(List<TeamMembershipModel> oldTeamMembershipSnapshot,
|
|
List<TeamMembershipModel> newTeamMembershipSnapshot, Guid expCatId, bool changePlannedCapacity)
|
|
{
|
|
var rateManager = new RateManager(DbContext);
|
|
var ecManager = new ExpenditureCategoryManager(DbContext);
|
|
var teamManager = new TeamManager(DbContext);
|
|
|
|
var capacityToAdd = new List<TeamMembershipModel>();
|
|
var capacityToRemove = new List<TeamMembershipModel>();
|
|
|
|
var oldTeamRecords = (oldTeamMembershipSnapshot != null) ?
|
|
oldTeamMembershipSnapshot.Select(x => x.Id).ToList() : new List<Guid>();
|
|
var newTeamRecords = (newTeamMembershipSnapshot != null) ?
|
|
newTeamMembershipSnapshot.Select(x => x.Id).ToList() : new List<Guid>();
|
|
|
|
#region Get added and removed teams
|
|
|
|
// Make records to remove capacity for removed teams
|
|
var removedTeamRecords = oldTeamRecords.Except(newTeamRecords).ToList();
|
|
|
|
if (removedTeamRecords.Count > 0)
|
|
{
|
|
capacityToRemove.AddRange(oldTeamMembershipSnapshot.Where(x => removedTeamRecords.Contains(x.Id))
|
|
.Select(x => new TeamMembershipModel()
|
|
{
|
|
TeamId = x.TeamId,
|
|
StartDate = x.StartDate,
|
|
EndDate = x.EndDate
|
|
}));
|
|
}
|
|
|
|
// Make records to add capacity for added teams
|
|
var addedTeamRecords = newTeamRecords.Except(oldTeamRecords).ToList();
|
|
|
|
if (addedTeamRecords.Count > 0)
|
|
{
|
|
capacityToAdd.AddRange(newTeamMembershipSnapshot.Where(x => addedTeamRecords.Contains(x.Id))
|
|
.Select(x => new TeamMembershipModel()
|
|
{
|
|
TeamId = x.TeamId,
|
|
StartDate = x.StartDate,
|
|
EndDate = x.EndDate
|
|
}));
|
|
}
|
|
|
|
#endregion
|
|
#region Analyse existing team membership changes
|
|
|
|
if ((newTeamMembershipSnapshot != null) && (oldTeamMembershipSnapshot != null))
|
|
{
|
|
var teamRecordsToCheckChanged = newTeamMembershipSnapshot.Where(x =>
|
|
!removedTeamRecords.Contains(x.Id) && !addedTeamRecords.Contains(x.Id)).Select(x => x.Id);
|
|
|
|
if (teamRecordsToCheckChanged.Count() > 0)
|
|
{
|
|
var oldTeamsIndexed = oldTeamMembershipSnapshot.Where(x => teamRecordsToCheckChanged.Contains(x.Id))
|
|
.ToDictionary(k => k.Id, v => v);
|
|
var newTeamsIndexed = newTeamMembershipSnapshot.Where(x => teamRecordsToCheckChanged.Contains(x.Id))
|
|
.ToDictionary(k => k.Id, v => v);
|
|
|
|
foreach (var teamRecId in teamRecordsToCheckChanged)
|
|
{
|
|
var oldTeamRec = oldTeamsIndexed[teamRecId];
|
|
var newTeamRec = newTeamsIndexed[teamRecId];
|
|
|
|
if ((newTeamRec.EndDate.HasValue && (newTeamRec.EndDate.Value < oldTeamRec.StartDate)) ||
|
|
(oldTeamRec.EndDate.HasValue && (newTeamRec.StartDate > oldTeamRec.EndDate.Value)))
|
|
{
|
|
// New record is completly before the old or is completly after the old one:
|
|
// old: |======| or |======|
|
|
// new: |======| |=====|
|
|
// Remove capacity for old team dates and add for new dates
|
|
capacityToRemove.Add(new TeamMembershipModel()
|
|
{
|
|
TeamId = oldTeamRec.TeamId,
|
|
StartDate = oldTeamRec.StartDate,
|
|
EndDate = oldTeamRec.EndDate
|
|
});
|
|
capacityToAdd.Add(new TeamMembershipModel()
|
|
{
|
|
TeamId = newTeamRec.TeamId,
|
|
StartDate = newTeamRec.StartDate,
|
|
EndDate = newTeamRec.EndDate
|
|
});
|
|
}
|
|
|
|
if (oldTeamRec.StartDate < newTeamRec.StartDate)
|
|
{
|
|
// Old record for team starts earlier the new one => remove capacity for period between start dates
|
|
// old: |=========
|
|
// new: |======
|
|
capacityToRemove.Add(new TeamMembershipModel()
|
|
{
|
|
TeamId = oldTeamRec.TeamId,
|
|
StartDate = oldTeamRec.StartDate,
|
|
EndDate = newTeamRec.StartDate
|
|
});
|
|
}
|
|
|
|
if (newTeamRec.EndDate.HasValue &&
|
|
(!oldTeamRec.EndDate.HasValue || (oldTeamRec.EndDate.Value > newTeamRec.EndDate.Value)))
|
|
{
|
|
// New record for team ends earlier the old one => remove capacity for period between end dates
|
|
// old: =========|
|
|
// new: ========|
|
|
capacityToRemove.Add(new TeamMembershipModel()
|
|
{
|
|
TeamId = oldTeamRec.TeamId,
|
|
StartDate = newTeamRec.EndDate.Value.AddDays(1),
|
|
EndDate = oldTeamRec.EndDate
|
|
});
|
|
}
|
|
|
|
if (oldTeamRec.StartDate > newTeamRec.StartDate)
|
|
{
|
|
// Old record for team starts later the new one => add capacity for period between start dates
|
|
// old: |====
|
|
// new: |======
|
|
capacityToAdd.Add(new TeamMembershipModel()
|
|
{
|
|
TeamId = newTeamRec.TeamId,
|
|
StartDate = newTeamRec.StartDate,
|
|
EndDate = oldTeamRec.StartDate
|
|
});
|
|
}
|
|
|
|
if (oldTeamRec.EndDate.HasValue &&
|
|
(!newTeamRec.EndDate.HasValue || (oldTeamRec.EndDate.Value < newTeamRec.EndDate.Value)))
|
|
{
|
|
// New record for team ends later the old one => add capacity for period between end dates
|
|
// old: =========|
|
|
// new: ========|
|
|
capacityToAdd.Add(new TeamMembershipModel()
|
|
{
|
|
TeamId = newTeamRec.TeamId,
|
|
StartDate = oldTeamRec.EndDate.Value.AddDays(1),
|
|
EndDate = newTeamRec.EndDate
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var capacityAll = capacityToAdd.Union(capacityToRemove).ToList();
|
|
if (capacityAll.Count < 1)
|
|
// Nothing to change in capacity
|
|
return;
|
|
|
|
#endregion
|
|
#region Caching of data to calculate capacity values
|
|
|
|
// Cache of rates
|
|
var globalRates = rateManager.GetRates(new List<Guid> { expCatId }, RateModel.RateType.Global);
|
|
|
|
// Get UOM for EC
|
|
var expCatUomValue = ecManager.GetExpenditureDetails(expCatId)?.UOMValue;
|
|
|
|
// Cache weekendings
|
|
var minWeekendingLimit = capacityAll.Min(x => x.StartDate);
|
|
var maxWeekendingLimit = capacityAll.Max(x => x.EndDate) ?? Constants.FISCAL_CALENDAR_MAX_DATE;
|
|
var weekendings = FiscalCalendarManager.GetWeekendingsByRange(minWeekendingLimit, maxWeekendingLimit, DbContext);
|
|
|
|
#endregion
|
|
#region Get values to change and perform changing
|
|
|
|
var capacityToRemoveByTeams = GetCapacityValuesForTeams(capacityToRemove, globalRates, expCatId, expCatUomValue, weekendings);
|
|
var capacityToAddByTeams = GetCapacityValuesForTeams(capacityToAdd, globalRates, expCatId, expCatUomValue, weekendings);
|
|
|
|
foreach (var teamId in capacityToRemoveByTeams.Keys)
|
|
{
|
|
if (changePlannedCapacity)
|
|
teamManager.RemoveCapacityFromTeam(expCatId, teamId, capacityToRemoveByTeams[teamId], true);
|
|
|
|
teamManager.RemoveCapacityFromTeam(expCatId, teamId, capacityToRemoveByTeams[teamId], false);
|
|
}
|
|
|
|
foreach (var teamId in capacityToAddByTeams.Keys)
|
|
{
|
|
if (changePlannedCapacity)
|
|
teamManager.AddCapacityToTeam(expCatId, teamId, capacityToAddByTeams[teamId], true);
|
|
|
|
teamManager.AddCapacityToTeam(expCatId, teamId, capacityToAddByTeams[teamId], false);
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
|
|
private Dictionary<Guid, List<PeopleResourceModel.CapacityValues>>
|
|
GetCapacityValuesForTeams(List<TeamMembershipModel> changes, Dictionary<Guid, List<Rate>> rates, Guid expCatId,
|
|
decimal? uomValue, List<DateTime> weekEndings)
|
|
{
|
|
var result = new Dictionary<Guid, List<PeopleResourceModel.CapacityValues>>();
|
|
|
|
foreach (var item in changes)
|
|
{
|
|
var teamCapacityValues = GetCapacityValuesForSingleTeam(item, rates, expCatId, uomValue, weekEndings);
|
|
|
|
if (!result.ContainsKey(item.TeamId))
|
|
result.Add(item.TeamId, teamCapacityValues);
|
|
else
|
|
{
|
|
var newValues = teamCapacityValues.Where(tc => !result[item.TeamId].Any(x => x.WeekEnding == tc.WeekEnding));
|
|
if (newValues.Any())
|
|
result[item.TeamId].AddRange(newValues);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private List<PeopleResourceModel.CapacityValues>
|
|
GetCapacityValuesForSingleTeam(TeamMembershipModel teamInfo, Dictionary<Guid, List<Rate>> rates, Guid expCatId,
|
|
decimal? uomValue, List<DateTime> weekendings)
|
|
{
|
|
var rateManager = new RateManager(DbContext);
|
|
var result = new List<PeopleResourceModel.CapacityValues>();
|
|
var endDateCorrected = teamInfo.EndDate ?? Constants.FISCAL_CALENDAR_MAX_DATE;
|
|
var filteredWeekendings = weekendings.Where(x => (x >= teamInfo.StartDate) && (x <= endDateCorrected))
|
|
.OrderBy(x => x);
|
|
|
|
foreach (var week in filteredWeekendings)
|
|
{
|
|
var rate = rateManager.GetRateValue(rates, expCatId, week);
|
|
var cost = uomValue.HasValue ? uomValue.Value * rate : 0;
|
|
|
|
result.Add(new PeopleResourceModel.CapacityValues()
|
|
{
|
|
Quantity = uomValue.HasValue ? uomValue.Value : 0,
|
|
Cost = cost,
|
|
WeekEnding = week
|
|
});
|
|
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Performs reassignment of the resource allocations according to made to resource item changes
|
|
/// and specified reassignment map. Doesn't save changes to DB
|
|
/// </summary>
|
|
/// <returns>Returns FALSE, if there is no changes to save in DB. Otherwise, TRUE</returns>
|
|
public bool ReassignResource(PeopleResourceModel model, PeopleResourceModel oldModel)
|
|
{
|
|
bool performChangesSaving = false;
|
|
|
|
// Get the nearest next week start date for specified by user Effective Date of Change
|
|
DateTime effectiveChangeDateWe = FiscalCalendarManager.GetDateForTeamMembershipStart(model.EffectiveChangeDate, this.DbContext);
|
|
|
|
bool resourceTeamsChanged = oldModel.TeamId.HasValue && model.TeamId.HasValue &&
|
|
(model.TeamId.Value != oldModel.TeamId.Value);
|
|
|
|
bool resourceDeactivated = (model.IsActiveEmployee != oldModel.IsActiveEmployee) && !model.IsActiveEmployee;
|
|
bool resourceDeactivatedWithUnassignment = resourceDeactivated && !model.ReassignOnDeactivation;
|
|
bool resourceDeactivatedWithReassignment = resourceDeactivated && model.ReassignOnDeactivation;
|
|
|
|
if (resourceTeamsChanged || resourceDeactivatedWithReassignment)
|
|
{
|
|
// Resource team changed or resource deacivated with reassignment of allocations to other resource.
|
|
// Use reassignment plan to perform resource reassignment
|
|
if (model.AllocationsReassignmentPlan != null)
|
|
{
|
|
foreach (var prReassignmentPlan in model.AllocationsReassignmentPlan)
|
|
{
|
|
ProcessSingleProjectReassignmentForResource(prReassignmentPlan, oldModel, model, effectiveChangeDateWe);
|
|
performChangesSaving = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (resourceDeactivatedWithUnassignment)
|
|
{
|
|
// Resource deactivated with full unassignment
|
|
var resourceAllocations = DbContext.PeopleResourceAllocations.Where(x =>
|
|
x.PeopleResourceId.Equals(model.Id) && x.WeekEndingDate >= effectiveChangeDateWe).ToList();
|
|
|
|
if (resourceAllocations.Count > 0)
|
|
{
|
|
DbContext.PeopleResourceAllocations.RemoveRange(resourceAllocations);
|
|
performChangesSaving = true;
|
|
}
|
|
}
|
|
|
|
return performChangesSaving;
|
|
}
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="reassignmentPlan"></param>
|
|
/// <param name="oldModel"></param>
|
|
/// <param name="newModel"></param>
|
|
/// <param name="effectiveChangeDateWe">Start date of next full fiscal week.</param>
|
|
public void ProcessSingleProjectReassignmentForResource(PeopleResourceReassignmentModel reassignmentPlan,
|
|
PeopleResourceModel oldModel, PeopleResourceModel newModel, DateTime effectiveChangeDateWe)
|
|
{
|
|
Guid resourceId = newModel.Id;
|
|
Guid oldExpCatId = oldModel.ExpenditureCategoryId;
|
|
Guid newExpCatId = !newModel.ExpenditureCategoryId.Equals(Guid.Empty) ? newModel.ExpenditureCategoryId : oldExpCatId;
|
|
Guid oldTeamId = oldModel.TeamId.Value;
|
|
Guid? newTeamId = newModel.TeamId;
|
|
string[] res = !string.IsNullOrWhiteSpace(reassignmentPlan.DestResource) ? reassignmentPlan.DestResource.Split(',') : new string[] { };
|
|
Guid newReassignResourceId = res.Length > 0 ? Guid.Parse(res[0]) : Guid.Empty;
|
|
Guid newReassignResourceTeamId = res.Length > 1 ? Guid.Parse(res[1]) : Guid.Empty;
|
|
|
|
// Get action to perform for current project assignments for resource
|
|
PeopleResourceReassignmentModel.MoveAction projectAction =
|
|
((reassignmentPlan.Action == PeopleResourceReassignmentModel.MoveAction.MoveToResource) &&
|
|
// Moving assignments to empty resource means drop operation
|
|
(newReassignResourceId.Equals(Guid.Empty) || newReassignResourceTeamId.Equals(Guid.Empty))) || !newTeamId.HasValue ?
|
|
PeopleResourceReassignmentModel.MoveAction.DropAssignments :
|
|
reassignmentPlan.Action;
|
|
|
|
ProjectManager prMngr = new ProjectManager(this.DbContext);
|
|
Project project = prMngr.Load(reassignmentPlan.ProjectId, false);
|
|
|
|
if (projectAction == PeopleResourceReassignmentModel.MoveAction.MoveToNewTeam)
|
|
{
|
|
// we have to update Team2Project records to make sure target team referenced to the project/scenario
|
|
prMngr.UpdateProjectTeams(project, new List<Guid>() { newTeamId.Value }, null, true);
|
|
}
|
|
else if (projectAction == PeopleResourceReassignmentModel.MoveAction.MoveToResource)
|
|
{
|
|
// we have to update Team2Project records to make sure target resource's team referenced to the project/scenario
|
|
prMngr.UpdateProjectTeams(project, new List<Guid>() { newReassignResourceTeamId }, null, true);
|
|
}
|
|
|
|
// Get project scenarios
|
|
var scenarioManager = new ScenarioManager(DbContext);
|
|
var projectScenarios = scenarioManager.GetProjectNonActualsScenarios(reassignmentPlan.ProjectId);
|
|
if (projectScenarios == null || !projectScenarios.Any())
|
|
return;
|
|
|
|
var resourceAllocations = DbContext.PeopleResourceAllocations.Where(x => projectScenarios.Contains(x.ScenarioId) &&
|
|
x.PeopleResourceId.Equals(resourceId) &&
|
|
x.ExpenditureCategoryId.Equals(oldExpCatId) &&
|
|
x.WeekEndingDate >= effectiveChangeDateWe)
|
|
.ToArray()
|
|
.GroupBy(x => x.ScenarioId)
|
|
.ToDictionary(x => x.Key,
|
|
x => x.GroupBy(g => g.TeamId)
|
|
.ToDictionary(k => k.Key, l => l.ToDictionary(k => k.WeekEndingDate)));
|
|
|
|
if (resourceAllocations.Count <= 0)
|
|
return;
|
|
|
|
var affectedScenarios = resourceAllocations.Keys.ToArray();
|
|
var teamAllocations = DbContext.TeamAllocations
|
|
.Where(x => affectedScenarios.Contains(x.ScenarioId) &&
|
|
x.ExpenditureCategoryId.Equals(oldExpCatId) &&
|
|
x.WeekEndingDate >= effectiveChangeDateWe)
|
|
.ToArray()
|
|
.GroupBy(x => x.ScenarioId)
|
|
.ToDictionary(x => x.Key, g => g.ToList());
|
|
|
|
foreach (var scenarioId in affectedScenarios)
|
|
{
|
|
// Get resource allocations for scenario
|
|
var resourceAllocations4Scenario = resourceAllocations[scenarioId];
|
|
if (resourceAllocations4Scenario.Keys.Count <= 0)
|
|
continue;
|
|
|
|
var teamAllocations4Scenario = teamAllocations.ContainsKey(scenarioId) ? teamAllocations[scenarioId] : new List<TeamAllocation>();
|
|
|
|
switch (projectAction)
|
|
{
|
|
case PeopleResourceReassignmentModel.MoveAction.MoveToNewTeam:
|
|
foreach (var team in resourceAllocations4Scenario)
|
|
{
|
|
// Move allocations from old team to new resource team
|
|
MoveAllocationsFromTeamToTeam(team.Value, teamAllocations4Scenario, scenarioId, newExpCatId,
|
|
team.Key, newTeamId.Value);
|
|
// Copy allocation from one resource to another
|
|
CopyAllocationsFromResourceToResource(team.Value, scenarioId, resourceId, newExpCatId, newTeamId.Value, effectiveChangeDateWe);
|
|
}
|
|
break;
|
|
|
|
case PeopleResourceReassignmentModel.MoveAction.MoveToResource:
|
|
if (newReassignResourceId.Equals(Guid.Empty) || newReassignResourceTeamId.Equals(Guid.Empty))
|
|
throw new BLLException(string.Format("Cannot reassign allocations for project {0} (ProjectId:{1};ScenarioId:{2}) to another team because target resource/team is undefined. /r/nTargetResourceId:{3}; TargetTeamId:{4}",
|
|
project.Name, project.Id, scenarioId, newReassignResourceId, newReassignResourceTeamId), false);
|
|
|
|
foreach (var team in resourceAllocations4Scenario)
|
|
{
|
|
// Check new resource belongs to the same team, as the reassignable one
|
|
if (!team.Key.Equals(newReassignResourceTeamId))
|
|
{
|
|
// We need to reassign team allocations from one team to another.
|
|
MoveAllocationsFromTeamToTeam(team.Value, teamAllocations4Scenario, scenarioId, newExpCatId,
|
|
team.Key, newReassignResourceTeamId);
|
|
}
|
|
|
|
// Copy allocation from one resource to another
|
|
CopyAllocationsFromResourceToResource(team.Value, scenarioId, newReassignResourceId, newExpCatId, newReassignResourceTeamId, effectiveChangeDateWe);
|
|
}
|
|
break;
|
|
|
|
case PeopleResourceReassignmentModel.MoveAction.DropAssignments:
|
|
default:
|
|
var resourceAllocationsToRemove = resourceAllocations4Scenario.SelectMany(x => x.Value.Values);
|
|
DbContext.PeopleResourceAllocations.RemoveRange(resourceAllocationsToRemove);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// Reduces old team aloocations by the amount of work equal to sum of resource allocations moved to
|
|
/// new team and then increases new team allocations by the same amount of work.
|
|
/// </summary>
|
|
/// <param name="allocations">Resource allocations for the specified team which we're going to move to new team.</param>
|
|
/// <param name="scenarioId">A primary key of the scenario which resource allocations we're going to move.</param>
|
|
/// <param name="srcExpCatId">A primary key of resource's old Expenditure Category.</param>
|
|
/// <param name="dstExpCatId">A primary key of resource's new Expenditure Category.</param>
|
|
/// <param name="srcTeamId">A primary key of resource's old Team.</param>
|
|
/// <param name="dstTeamId">A primary key of resource's new Team.</param>
|
|
protected void MoveAllocationsFromTeamToTeam(Dictionary<DateTime, PeopleResourceAllocation> allocations, IEnumerable<TeamAllocation> teamAllocations,
|
|
Guid scenarioId, Guid dstExpCatId, Guid srcTeamId, Guid dstTeamId)
|
|
{
|
|
if (allocations.Keys.Count < 1)
|
|
return;
|
|
|
|
// Move allocations from old team to new resource team
|
|
var oldTeamAllocations = teamAllocations.Where(x => x.TeamId.Equals(srcTeamId)).ToDictionary(k => k.WeekEndingDate, v => v);
|
|
var newTeamAllocations = teamAllocations.Where(x => x.TeamId.Equals(dstTeamId)).ToDictionary(k => k.WeekEndingDate, v => v);
|
|
var addedTeamAllocations = new List<TeamAllocation>();
|
|
|
|
foreach (DateTime we in allocations.Keys)
|
|
{
|
|
var allocItem = allocations[we];
|
|
|
|
if (oldTeamAllocations.ContainsKey(we))
|
|
{
|
|
var oldTeamAllocItem = oldTeamAllocations[we];
|
|
oldTeamAllocItem.Quantity = Math.Max(oldTeamAllocItem.Quantity - allocItem.Quantity, 0);
|
|
}
|
|
|
|
TeamAllocation newTeamAllocItem = null;
|
|
|
|
if (newTeamAllocations.ContainsKey(we))
|
|
{
|
|
newTeamAllocItem = newTeamAllocations[we];
|
|
newTeamAllocItem.Quantity = Math.Max(newTeamAllocItem.Quantity + allocItem.Quantity, 0);
|
|
}
|
|
else
|
|
{
|
|
newTeamAllocItem = new TeamAllocation()
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
ScenarioId = scenarioId,
|
|
ExpenditureCategoryId = dstExpCatId,
|
|
TeamId = dstTeamId,
|
|
WeekEndingDate = we,
|
|
Quantity = Math.Max(allocItem.Quantity, 0)
|
|
};
|
|
newTeamAllocations.Add(newTeamAllocItem.WeekEndingDate, newTeamAllocItem);
|
|
addedTeamAllocations.Add(newTeamAllocItem);
|
|
}
|
|
}
|
|
|
|
DbContext.TeamAllocations.AddRange(addedTeamAllocations);
|
|
}
|
|
|
|
protected void CopyAllocationsFromResourceToResource(Dictionary<DateTime, PeopleResourceAllocation> oldAllocations, Guid scenarioId, Guid dstResourceId,
|
|
Guid dstExpCatId, Guid dstTeamId, DateTime dateOfChangeWeekending)
|
|
{
|
|
var srcResourceAllocations = oldAllocations;
|
|
|
|
// TODO: review and move out to the upper level
|
|
var dstResourceAllocations = DbContext.PeopleResourceAllocations.Where(x =>
|
|
x.ScenarioId.Equals(scenarioId) && x.PeopleResourceId.Equals(dstResourceId) &&
|
|
x.TeamId.Equals(dstTeamId) && x.ExpenditureCategoryId.Equals(dstExpCatId) &&
|
|
x.WeekEndingDate >= dateOfChangeWeekending)
|
|
.ToDictionary(k => k.WeekEndingDate, v => v);
|
|
|
|
var addedResourceAllocations = new List<PeopleResourceAllocation>();
|
|
var removedResourceAllocations = new List<PeopleResourceAllocation>();
|
|
|
|
foreach (DateTime we in srcResourceAllocations.Keys)
|
|
{
|
|
PeopleResourceAllocation dstAllocItem;
|
|
|
|
if (dstResourceAllocations.ContainsKey(we))
|
|
{
|
|
dstAllocItem = dstResourceAllocations[we];
|
|
}
|
|
else
|
|
{
|
|
dstAllocItem = new PeopleResourceAllocation()
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
ScenarioId = scenarioId,
|
|
ExpenditureCategoryId = dstExpCatId,
|
|
TeamId = dstTeamId,
|
|
PeopleResourceId = dstResourceId,
|
|
WeekEndingDate = we,
|
|
Quantity = 0
|
|
};
|
|
addedResourceAllocations.Add(dstAllocItem);
|
|
}
|
|
|
|
// do not remove an item if we try to move item from one team to itself
|
|
if (dstAllocItem.Id != srcResourceAllocations[we].Id)
|
|
{
|
|
removedResourceAllocations.Add(srcResourceAllocations[we]);
|
|
dstAllocItem.Quantity = Math.Max(dstAllocItem.Quantity + srcResourceAllocations[we].Quantity, 0);
|
|
}
|
|
else
|
|
dstAllocItem.Quantity = Math.Max(srcResourceAllocations[we].Quantity, 0);
|
|
}
|
|
|
|
DbContext.PeopleResourceAllocations.RemoveRange(removedResourceAllocations);
|
|
DbContext.PeopleResourceAllocations.AddRange(addedResourceAllocations);
|
|
}
|
|
|
|
public decimal GetResourceUOM(Guid resourceId)
|
|
{
|
|
var resources = new List<Guid>() { resourceId };
|
|
var uoms = GetResourcesUOM(resources);
|
|
if (uoms == null || !uoms.ContainsKey(resourceId))
|
|
return 0;
|
|
|
|
return uoms[resourceId];
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns default UOM value
|
|
/// </summary>
|
|
public decimal GetDafaultUOM()
|
|
{
|
|
// TODO: Review and reimplement, when default UOM be implemented
|
|
var uoms = DbContext.UOMs.AsNoTracking().Select(x => x.UOMValue).ToList();
|
|
var result = ((uoms != null) && (uoms.Count > 0)) ? uoms.Max() : 0;
|
|
return result;
|
|
}
|
|
|
|
public Dictionary<Guid, decimal> GetResourcesUOM(List<Guid> resources)
|
|
{
|
|
if (resources == null || resources.Count < 1)
|
|
return new Dictionary<Guid, decimal>();
|
|
|
|
var uoms = (from p in DbContext.PeopleResources.AsNoTracking()
|
|
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 resources.Contains(p.Id)
|
|
select new { ResourceId = p.Id, UOMValue = uom.UOMValue }).ToDictionary(x => x.ResourceId, x => x.UOMValue);
|
|
|
|
return uoms;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns list of teams, the specified resource belongs to
|
|
/// </summary>
|
|
/// <param name="resourceId">Resource to get teams for</param>
|
|
/// <param name="datePointUtc">Date of actuality</param>
|
|
/// <returns></returns>
|
|
public List<Guid> GetResourceTeams(Guid resourceId, DateTime? datePointUtc = null)
|
|
{
|
|
var qry = DbContext.PeopleResource2Team.AsNoTracking().Where(x => x.PeopleResourceId.Equals(resourceId));
|
|
if (datePointUtc.HasValue)
|
|
{
|
|
var dt = datePointUtc.Value.Date;
|
|
qry = qry.Where(x => (x.StartDate <= dt) && (!x.EndDate.HasValue || (x.EndDate.Value >= dt)));
|
|
}
|
|
|
|
var result = qry.Select(x => x.TeamId).ToList();
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns list of teams, the specified resources belongs to
|
|
/// </summary>
|
|
/// <param name="resources">Resources to get teams for</param>
|
|
/// <param name="datePointUtc">Date of actuality</param>
|
|
/// <returns></returns>
|
|
public Dictionary<Guid, List<TeamMembershipModel>> GetResourcesTeams(List<Guid> resources, DateTime? datePointUtc = null)
|
|
{
|
|
var qry = DbContext.PeopleResource2Team.Where(x => resources.Contains(x.PeopleResourceId));
|
|
if (datePointUtc.HasValue)
|
|
{
|
|
var dt = datePointUtc.Value.Date;
|
|
qry = qry.Where(x => (x.StartDate <= dt) && (!x.EndDate.HasValue || (x.EndDate.Value >= dt)));
|
|
}
|
|
|
|
var result = qry.GroupBy(x => x.PeopleResourceId).ToDictionary(x => x.Key, grp =>
|
|
grp.Select(g => new TeamMembershipModel
|
|
{
|
|
Id = g.PeopleResourceId,
|
|
TeamId = g.TeamId,
|
|
StartDate = g.StartDate,
|
|
EndDate = g.EndDate
|
|
}).ToList());
|
|
|
|
return result;
|
|
}
|
|
|
|
public List<PeopleResourceReassignmentModel> GetResourceSubstitutions(Guid resourceId,
|
|
DateTime nextFullWeekStartDate, Guid userId)
|
|
{
|
|
List<PeopleResourceReassignmentModel> result;
|
|
|
|
// Get visible for current user complete list of teams
|
|
List<Guid> visibleToUserTeams = this.DbContext.User2Team.Where(x => (x.UserId == userId.ToString()))
|
|
.Select(x => x.TeamId).ToList();
|
|
|
|
// Get current resource EC in source team
|
|
var resource = LoadPeopleResource(resourceId, nextFullWeekStartDate, true, false, false);
|
|
if (resource == null)
|
|
throw new Exception(string.Format("Cannot find a people resource with Id: {0}", resourceId));
|
|
Guid resourceSrcTeamExpCatId = resource.ExpenditureCategoryId;
|
|
if (resource.TeamId == null || Guid.Empty.Equals(resource.TeamId))
|
|
return new List<PeopleResourceReassignmentModel>();
|
|
// get current resoruce's Team
|
|
var sourceTeamId = resource.TeamId.Value;
|
|
var resourceTeams = GetResourceTeams(resourceId, DateTime.UtcNow.Date);
|
|
if (resourceTeams != null && resourceTeams.Count > 0)
|
|
sourceTeamId = resourceTeams.FirstOrDefault();
|
|
|
|
// Get scenarios and projects, the resource is allocated for the specified date point and to the future
|
|
var allocationsDatesByProject = this.DbContext.PeopleResourceAllocations
|
|
.Where(x => x.PeopleResourceId.Equals(resourceId) && x.WeekEndingDate >= nextFullWeekStartDate &&
|
|
x.Scenario != null && x.Scenario.Type == (int)ScenarioType.Portfolio && x.Scenario.ParentId.HasValue)
|
|
.Select(x => new
|
|
{
|
|
ProjectId = x.Scenario.ParentId.Value,
|
|
WeekEndingDate = x.WeekEndingDate
|
|
}).GroupBy(t => t.ProjectId).ToDictionary(k => k.Key, el => new
|
|
{
|
|
MinDate = el.Min(r => r.WeekEndingDate),
|
|
MaxDate = el.Max(r => r.WeekEndingDate)
|
|
});
|
|
var resourceAllocatedProjects = allocationsDatesByProject.Keys;
|
|
|
|
// Get project list, which needs resource substitution
|
|
result = this.DbContext.Projects.Where(x => resourceAllocatedProjects.Contains(x.Id))
|
|
.Select(x => new PeopleResourceReassignmentModel()
|
|
{
|
|
Action = PeopleResourceReassignmentModel.MoveAction.MoveToNewTeam,
|
|
ProjectId = x.Id,
|
|
ProjectName = !x.ParentProjectId.HasValue ? x.Name :
|
|
// Get complex name, if the project is a project part
|
|
this.DbContext.Projects.Where(p => p.Id.Equals(x.ParentProjectId.Value))
|
|
.Select(p => p.Name).FirstOrDefault() + ": " + x.Name,
|
|
}).OrderBy(x => x.ProjectName).ToList();
|
|
|
|
// Get list of teams and resource options for substitution
|
|
result.ForEach(p =>
|
|
{
|
|
var minDate = DateTime.MinValue;
|
|
var maxDate = DateTime.MaxValue;
|
|
if (allocationsDatesByProject.ContainsKey(p.ProjectId))
|
|
{
|
|
minDate = allocationsDatesByProject[p.ProjectId].MinDate;
|
|
maxDate = allocationsDatesByProject[p.ProjectId].MaxDate;
|
|
}
|
|
p.SubstitutionOptions.AddRange(this.DbContext.Team2Project.Where(x => x.ProjectId.Equals(p.ProjectId) &&
|
|
visibleToUserTeams.Contains(x.TeamId))
|
|
.Select(x => new PeopleResourceReassignmentModel.TeamSubstitutionOptions()
|
|
{
|
|
TeamId = x.TeamId,
|
|
TeamName = x.Team.Name,
|
|
IsCurrentTeam = x.TeamId.Equals(sourceTeamId),
|
|
Resources = this.DbContext.PeopleResource2Team.Where(t => t.TeamId.Equals(x.TeamId) &&
|
|
t.PeopleResource.IsActiveEmployee && !t.PeopleResourceId.Equals(resourceId) &&
|
|
t.PeopleResource.ExpenditureCategoryId.Equals(resourceSrcTeamExpCatId) &&
|
|
t.StartDate < minDate && (!t.EndDate.HasValue || t.EndDate.Value >= maxDate))
|
|
.OrderBy(t => t.PeopleResource.LastName).ThenBy(t => t.PeopleResource.FirstName)
|
|
.Select(t => new SelectListItem()
|
|
{
|
|
Text = t.PeopleResource.FirstName + " " + t.PeopleResource.LastName,
|
|
Value = t.PeopleResourceId.ToString() + "," + x.TeamId.ToString()
|
|
}).ToList()
|
|
}).OrderBy(x => x.TeamName)
|
|
);
|
|
});
|
|
|
|
// Remove teams, which have no resources for substitution
|
|
result.ForEach(p => p.SubstitutionOptions.RemoveAll(t => t.Resources.Count < 1));
|
|
|
|
return result;
|
|
}
|
|
|
|
public PeopleResourceWithTeamsDataModel GetPeopleResourceWithTeams4Resource(Guid resourceId)
|
|
{
|
|
if (resourceId == Guid.Empty)
|
|
return null;
|
|
|
|
var resources = GetPeopleResourceWithTeams4Resources(new List<Guid>() { resourceId });
|
|
return resources?.FirstOrDefault(x => x.Id == resourceId);
|
|
}
|
|
|
|
public List<PeopleResourceWithTeamsDataModel> GetPeopleResourceWithTeams4Resources(IEnumerable<Guid> resourceIds)
|
|
{
|
|
if (resourceIds == null || !resourceIds.Any())
|
|
return new List<PeopleResourceWithTeamsDataModel>();
|
|
|
|
//var resources = (from p in PeopleResourceWithTeamsModelBaseQuery
|
|
// join id in resourceIds on p.Id equals id
|
|
// select p).ToList();
|
|
|
|
var resources = PeopleResourceWithTeamsModelBaseQuery.Where(x => resourceIds.Contains(x.Id))
|
|
.ToList();
|
|
|
|
return resources;
|
|
}
|
|
|
|
public List<PeopleResourceWithTeamsDataModel> GetPeopleResourceWithTeams4Teams(List<Guid> teamIds)
|
|
{
|
|
if (teamIds == null || !teamIds.Any())
|
|
return new List<PeopleResourceWithTeamsDataModel>();
|
|
|
|
var resources = PeopleResourceWithTeamsModelBaseQuery.Where(x => x.Teams.Any(s => teamIds.Contains(s.TeamId)))
|
|
.ToList();
|
|
|
|
return resources;
|
|
}
|
|
|
|
public Dictionary<Guid, Dictionary<ScenarioType, PeopleResourceAllocationsInfoModel>> GetResourceAllocationsInfo(List<Guid> resources)
|
|
{
|
|
if (resources == null)
|
|
throw new ArgumentNullException("resources");
|
|
|
|
if (resources.Count < 1)
|
|
return new Dictionary<Guid, Dictionary<ScenarioType, PeopleResourceAllocationsInfoModel>>();
|
|
|
|
var result = DbContext.VW_PeopleResourceAllocationsInfo.Where(x => resources.Contains(x.PeopleResourceId)).ToList()
|
|
.GroupBy(x => x.PeopleResourceId).ToDictionary(k1 => k1.Key, v1 => v1.GroupBy(z => z.Type)
|
|
.ToDictionary(k2 => (ScenarioType)k2.Key, v2 => new PeopleResourceAllocationsInfoModel
|
|
{
|
|
HasAllocations = v2.First().MinDate.HasValue || v2.First().MaxDate.HasValue,
|
|
AllocationsMinDate = v2.First().MinDate,
|
|
AllocationsMaxDate = v2.First().MaxDate
|
|
}));
|
|
|
|
return result;
|
|
}
|
|
|
|
public void ChangeResourceExpenditureCategory(Guid resourceId, Guid newExpCatId, bool changePlannedCapacity)
|
|
{
|
|
if ((resourceId == null) || resourceId.Equals(Guid.Empty))
|
|
throw new ArgumentNullException(nameof(resourceId));
|
|
|
|
if ((newExpCatId == null) || newExpCatId.Equals(Guid.Empty))
|
|
throw new ArgumentNullException(nameof(newExpCatId));
|
|
|
|
PeopleResourceModel resourceModel = LoadPeopleResource(resourceId, DateTime.UtcNow.Date, false, false, true);
|
|
var oldExpCatId = resourceModel.ExpenditureCategoryId;
|
|
|
|
if (resourceModel.ExpenditureCategoryId.Equals(newExpCatId))
|
|
// New EC is the same as current one. Nothing to do
|
|
return;
|
|
|
|
if (((resourceModel.ResourceScenarioAllocationsInfo != null) && resourceModel.ResourceScenarioAllocationsInfo.HasAllocations) ||
|
|
((resourceModel.ResourceActualsInfo != null) && resourceModel.ResourceActualsInfo.HasAllocations))
|
|
throw new BLLException("Expenditure Category for resource can't be changed: resources has allocations in scenarios or actuals");
|
|
|
|
// Update EC for resource
|
|
var resourceDatabaseRecord = this.Load(resourceId, false);
|
|
resourceDatabaseRecord.ExpenditureCategoryId = newExpCatId;
|
|
|
|
// Log category change
|
|
PeopleResourceExpCatChange changeLogRecord = new PeopleResourceExpCatChange()
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
PeopleResourceId = resourceId,
|
|
OldExpenditureCategoryId = oldExpCatId,
|
|
NewExpenditureCategoryId = newExpCatId,
|
|
DateOfChange = DateTime.UtcNow
|
|
};
|
|
DbContext.PeopleResourceExpCatChanges.Add(changeLogRecord);
|
|
|
|
// Get resource team membership records.
|
|
var resourceTeams = LoadResource2TeamsByResource(resourceId);
|
|
// Remove teams capacity for old EC
|
|
ChangeCapacityForResourceTeams(resourceTeams, null, oldExpCatId, changePlannedCapacity);
|
|
// Add teams capacity for new EC
|
|
ChangeCapacityForResourceTeams(null, resourceTeams, newExpCatId, changePlannedCapacity);
|
|
|
|
// Check and update NPT allocations to fit new EC Uom
|
|
var nptMngr = new NonProjectTimeManager(DbContext);
|
|
nptMngr.UpdateNptAllocationsOnResourceExpCatChange(resourceId, newExpCatId);
|
|
}
|
|
|
|
/// <summary>
|
|
/// For every Expenditure Category in the incoming list returns list of resources, attached to this category
|
|
/// </summary>
|
|
/// <param name="expCats">Categories filter</param>
|
|
/// <returns></returns>
|
|
public Dictionary<Guid, List<Guid>> GetResourcesByExpenditureCategories(List<Guid> expCats)
|
|
{
|
|
if (expCats == null)
|
|
throw new ArgumentNullException(nameof(expCats));
|
|
|
|
if (expCats.Count < 1)
|
|
return new Dictionary<Guid, List<Guid>>();
|
|
|
|
var result = DbContext.PeopleResources.AsNoTracking() //.AsParallel()
|
|
.Where(x => expCats.Contains(x.ExpenditureCategoryId))
|
|
.Select(x => new { x.ExpenditureCategoryId, x.Id })
|
|
.GroupBy(x => x.ExpenditureCategoryId)
|
|
.ToDictionary(k => k.Key, v => v.Select(z => z.Id).ToList());
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns Names for specified resources. Resulting models has Id, FirstName & LastName attributes filled only!
|
|
/// </summary>
|
|
/// <param name="resourceIds"></param>
|
|
/// <returns></returns>
|
|
public Dictionary<Guid, PeopleResourceModel> GetResourceNames(IEnumerable<Guid> resourceIds)
|
|
{
|
|
IQueryable<PeopleResource> dataset = DbContext.PeopleResources.AsNoTracking();
|
|
|
|
if ((resourceIds != null) && resourceIds.Any())
|
|
dataset = dataset.Where(x => resourceIds.Contains(x.Id));
|
|
|
|
var result = dataset.Select(x => new PeopleResourceModel()
|
|
{
|
|
Id = x.Id,
|
|
LastName = x.LastName,
|
|
FirstName = x.FirstName,
|
|
}).ToDictionary(k => k.Id, v => v);
|
|
|
|
return result;
|
|
}
|
|
|
|
#region Private Members
|
|
|
|
private IQueryable<PeopleResourceWithTeamsDataModel> PeopleResourceWithTeamsModelBaseQuery
|
|
{
|
|
get
|
|
{
|
|
var userId = SecurityManager.GetUserPrincipal();
|
|
var query = (from resource in DbContext.PeopleResources
|
|
join category in DbContext.ExpenditureCategory on resource.ExpenditureCategoryId equals category.Id
|
|
join uom in DbContext.UOMs on category.UOMId equals uom.Id
|
|
select new PeopleResourceWithTeamsDataModel()
|
|
{
|
|
Id = resource.Id,
|
|
FirstName = resource.FirstName,
|
|
LastName = resource.LastName,
|
|
ExpenditureCategoryId = resource.ExpenditureCategoryId,
|
|
Teams = DbContext.VW_TeamResource
|
|
.Where(x => x.Id == resource.Id)
|
|
.Select(x => new PeopleResourceTeamModel()
|
|
{
|
|
TeamId = x.TeamId,
|
|
ReadOnly = !DbContext.VW_User2Team.Any(s => (s.UserId == userId) && (s.TeamId == x.TeamId) && (s.Write == 1)),
|
|
StartDate = x.TeamStartDate,
|
|
EndDate = x.TeamEndDate
|
|
})
|
|
.ToList()
|
|
|
|
});
|
|
|
|
return query;
|
|
}
|
|
}
|
|
|
|
private IQueryable<ResourceActualAllocationModel> ResourceActualAllocationModelBaseQuery
|
|
{
|
|
get
|
|
{
|
|
var query = (from resourceActual in DbContext.PeopleResourceActuals
|
|
join detail in DbContext.ScenarioDetail on resourceActual.ParentId equals detail.Id
|
|
where detail.ParentID.HasValue && detail.ExpenditureCategoryId.HasValue && detail.WeekEndingDate.HasValue
|
|
select new ResourceActualAllocationModel
|
|
{
|
|
ActualScenarioId = detail.ParentID.Value,
|
|
ScenarioDetailId = detail.Id,
|
|
ExpenditureCategoryId = detail.ExpenditureCategoryId.Value,
|
|
PeopleResourceId = resourceActual.PeopleResourceId,
|
|
WeekEndingDate = detail.WeekEndingDate.Value,
|
|
Quantity = detail.Quantity ?? 0,
|
|
Cost = detail.Cost ?? 0,
|
|
});
|
|
|
|
return query;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
} |