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

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
}
}