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 { public override DbSet 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() { 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; } /// /// Loads a list of people resources for the specified list of project Ids . /// /// A list of project Ids. /// /// An list of objects with prefilled resource, expenditure and team properties. /// public IEnumerable LoadPeopleResourcesByProjects(IEnumerable 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); } } /// /// Loads a list of people resources for the specified team. /// /// True to include info about existing resource allocations to result /// A date point when resource has been assigned to the specified team. This parameter used to filter resources by PeopleResource2Team dates. public IEnumerable LoadPeopleResourcesByTeamNonStrict(Guid teamId, bool includeResourceAllocationsInfo, DateTime? filterDateUtc = null) { List teams = new List() { teamId }; return LoadPeopleResourcesByTeamsNonStrict(teams, includeResourceAllocationsInfo, filterDateUtc); } /// /// Loads a people resources for the specified teams as displayed in teamboard /// /// True to include info about existing resource allocations to result /// A date point when resource has been assigned to the specified team. This parameter used to filter resources by PeopleResource2Team dates. /// 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 /// public IEnumerable LoadPeopleResourcesByTeamsNonStrict(List 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; } /// /// Loads people resources with information actual at the specified . If Resource is not hired yet or already fired, then /// resource info contains closest membership info to the right from and then to the left if "to the right" does not exist. /// /// A list of team Ids to filter resources by. Either teams or resources parameter is required. /// A list of resource Ids to filter resources by. Either teams or resources parameter is required. /// 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. /// /// 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 /// public IEnumerable LoadPeopleResourcesNonStrict(IEnumerable teams = null, IEnumerable 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; } /// /// Loads a list of people resources for the specified list of team Ids . /// /// A list of team Ids. /// A date point when resource has been assigned to the specified team. This parameter used to filter resources by PeopleResource2Team dates. /// /// An list of objects with prefilled resource, expenditure and team properties. /// public IEnumerable LoadPeopleResourcesByTeams(IEnumerable 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(); 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(); 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(); var affectedScenarioDetails = actualResourcesAllocations.ContainsKey(actualScenario.Id) ? actualResourcesAllocations[actualScenario.Id].Where(x => x.PeopleResourceId == model.Id).Select(x => x.ScenarioDetailId).Distinct().ToList() : new List(); // 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(); var actualScenarioDetails4Scenario = actualScenarioDetails.ContainsKey(actualScenario.Id) ? actualScenarioDetails[actualScenario.Id] : new List(); 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 GetResourcesWithAllocationsByTeams(IEnumerable teams, DateTime? startDate = null, DateTime? endDate = null) { if (teams == null || !teams.Any()) return new List(); 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 GetResourcesWithAllocationsByResourceId(Guid resourceId, DateTime? startDate = null, DateTime? endDate = null) { if (resourceId == Guid.Empty) return new List(); return GetResourcesWithAllocationsByResourceId(new List() { resourceId }, startDate, endDate); } public List GetResourcesWithAllocationsByResourceId(IEnumerable resources, DateTime? startDate = null, DateTime? endDate = null) { if (resources == null || !resources.Any()) return new List(); 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 GetPeopleResourceAllocations(Guid scenarioId, bool readOnly = false) { if (scenarioId == Guid.Empty) return new List(); return GetPeopleResourceAllocations(new List { scenarioId }, readOnly); } public List GetPeopleResourceAllocations(IEnumerable scenarios, bool readOnly = false) { if (scenarios == null || !scenarios.Any()) return new List(); 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 GetActualScenariosByResource(Guid resourceId, bool readOnly = false) { if (resourceId == Guid.Empty) return new List(); 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 GetResourceActualAllocationsByScenario(Guid scenarioId) { if (scenarioId == Guid.Empty) return new List(); var actuals = GetResourceActualAllocationsByScenario(new List { scenarioId }); if (actuals != null && actuals.ContainsKey(scenarioId)) return actuals[scenarioId]; return new List(); } public Dictionary> GetResourceActualAllocationsByScenario(IEnumerable scenarios) { if (scenarios == null || !scenarios.Any()) return new Dictionary>(); 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 GetActualsByResourceId(Guid resourceId, DateTime? startDate = null, DateTime? endDate = null) { if (resourceId == Guid.Empty) return new List(); return GetActualsByResourceId(new List() { resourceId }, startDate, endDate); } public List GetActualsByResourceId(IEnumerable resources, DateTime? startDate = null, DateTime? endDate = null) { if (resources == null || !resources.Any()) return new List(); 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; } /// Returns tree-structure of resources: Teams[TeamId, ExpCats[ExpCatId, PeopleResourcesList]] public Dictionary>> CastPeopleResourceWithAllocationToTree(List resources) { if (resources == null || !resources.Any()) return new Dictionary>>(); 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; } /// /// Replaces own resource ExpenditureCategory with the ones, specified in his allocations /// /// /// 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 /// public void ReplaceOwnResourceExpendituresWithExpendituresInAllocations(List 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 oldResourceTeams = null; List 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 membershipToFixStartDate = null; List 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; } /// /// Returns list of teams, the resource is a member of /// /// /// protected List 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 oldTeamMembershipSnapshot, List newTeamMembershipSnapshot, Guid expCatId, bool changePlannedCapacity) { var rateManager = new RateManager(DbContext); var ecManager = new ExpenditureCategoryManager(DbContext); var teamManager = new TeamManager(DbContext); var capacityToAdd = new List(); var capacityToRemove = new List(); var oldTeamRecords = (oldTeamMembershipSnapshot != null) ? oldTeamMembershipSnapshot.Select(x => x.Id).ToList() : new List(); var newTeamRecords = (newTeamMembershipSnapshot != null) ? newTeamMembershipSnapshot.Select(x => x.Id).ToList() : new List(); #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 { 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> GetCapacityValuesForTeams(List changes, Dictionary> rates, Guid expCatId, decimal? uomValue, List weekEndings) { var result = new Dictionary>(); 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 GetCapacityValuesForSingleTeam(TeamMembershipModel teamInfo, Dictionary> rates, Guid expCatId, decimal? uomValue, List weekendings) { var rateManager = new RateManager(DbContext); var result = new List(); 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; } /// /// Performs reassignment of the resource allocations according to made to resource item changes /// and specified reassignment map. Doesn't save changes to DB /// /// Returns FALSE, if there is no changes to save in DB. Otherwise, TRUE 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; } /// /// /// /// /// /// /// Start date of next full fiscal week. 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() { 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() { 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(); 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; } } } /// /// 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. /// /// Resource allocations for the specified team which we're going to move to new team. /// A primary key of the scenario which resource allocations we're going to move. /// A primary key of resource's old Expenditure Category. /// A primary key of resource's new Expenditure Category. /// A primary key of resource's old Team. /// A primary key of resource's new Team. protected void MoveAllocationsFromTeamToTeam(Dictionary allocations, IEnumerable 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(); 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 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(); var removedResourceAllocations = new List(); 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() { resourceId }; var uoms = GetResourcesUOM(resources); if (uoms == null || !uoms.ContainsKey(resourceId)) return 0; return uoms[resourceId]; } /// /// Returns default UOM value /// 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 GetResourcesUOM(List resources) { if (resources == null || resources.Count < 1) return new Dictionary(); 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; } /// /// Returns list of teams, the specified resource belongs to /// /// Resource to get teams for /// Date of actuality /// public List 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; } /// /// Returns list of teams, the specified resources belongs to /// /// Resources to get teams for /// Date of actuality /// public Dictionary> GetResourcesTeams(List 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 GetResourceSubstitutions(Guid resourceId, DateTime nextFullWeekStartDate, Guid userId) { List result; // Get visible for current user complete list of teams List 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(); // 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() { resourceId }); return resources?.FirstOrDefault(x => x.Id == resourceId); } public List GetPeopleResourceWithTeams4Resources(IEnumerable resourceIds) { if (resourceIds == null || !resourceIds.Any()) return new List(); //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 GetPeopleResourceWithTeams4Teams(List teamIds) { if (teamIds == null || !teamIds.Any()) return new List(); var resources = PeopleResourceWithTeamsModelBaseQuery.Where(x => x.Teams.Any(s => teamIds.Contains(s.TeamId))) .ToList(); return resources; } public Dictionary> GetResourceAllocationsInfo(List resources) { if (resources == null) throw new ArgumentNullException("resources"); if (resources.Count < 1) return new Dictionary>(); 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); } /// /// For every Expenditure Category in the incoming list returns list of resources, attached to this category /// /// Categories filter /// public Dictionary> GetResourcesByExpenditureCategories(List expCats) { if (expCats == null) throw new ArgumentNullException(nameof(expCats)); if (expCats.Count < 1) return new Dictionary>(); 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; } /// /// Returns Names for specified resources. Resulting models has Id, FirstName & LastName attributes filled only! /// /// /// public Dictionary GetResourceNames(IEnumerable resourceIds) { IQueryable 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 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 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 } }