using System; using System.Collections.Generic; using System.Data.Entity; using System.Linq; using EnVisage.Models; using System.Text; using System.Data.Entity.Infrastructure; namespace EnVisage.Code.BLL { public class TeamManager : ManagerBase { public TeamManager(EnVisageEntities dbContext) : base(dbContext) { } protected override Team InitInstance() { return new Team { Id = Guid.NewGuid() }; } protected override Team RetrieveReadOnlyById(Guid key) { return DataTable.AsNoTracking().FirstOrDefault(t => t.Id == key); } public override DbSet DataTable { get { return DbContext.Teams; } } public IQueryable LoadTeams(IEnumerable teams) { if (teams == null) throw new ArgumentNullException("teams"); var result = DbContext.Teams.Where(x => teams.Contains(x.Id)); return result; } public TeamModel LoadTeamModel(Guid teamId) { if (teamId == Guid.Empty) return null; return TeamModelBasicQuery.FirstOrDefault(x => x.Id == teamId); } public void Delete(Guid teamId) { var nptManager = new NonProjectTimeManager(this.DbContext); var dbObj = this.Load(teamId, false); var npts = dbObj.NonProjectTime2Team.ToList(); foreach (var npt in npts) { nptManager.DeleteNPT(npt.NonProjectTimeId, null); } var capacityScenarioId = dbObj.PlannedCapacityScenarioId; var actualCapacityScenarioId = dbObj.ActualCapacityScenarioId; DbContext.User2Team.RemoveRange(DbContext.User2Team.Where(c2s => c2s.TeamId == dbObj.Id)); DbContext.Team2Project.RemoveRange(DbContext.Team2Project.Where(x => x.TeamId == dbObj.Id)); DbContext.Team2View.RemoveRange(DbContext.Team2View.Where(tv => tv.TeamId == dbObj.Id)); DbContext.TeamAllocations.RemoveRange(DbContext.TeamAllocations.Where(x => x.TeamId == dbObj.Id)); DbContext.Holiday2Team.RemoveRange(dbObj.Holiday2Team); DbContext.Teams.Remove(dbObj); DbContext.SaveChanges(); if (capacityScenarioId != null) (DbContext as IObjectContextAdapter).ObjectContext.ExecuteStoreCommand(string.Format("exec sp_DeleteScenario '{0}'", capacityScenarioId)); if (actualCapacityScenarioId != null) (DbContext as IObjectContextAdapter).ObjectContext.ExecuteStoreCommand(string.Format("exec sp_DeleteScenario '{0}'", actualCapacityScenarioId)); } public IList LoadTeamsWithResourcesByUser(Guid? userId, List teams = null) { bool filterByTeams = (teams != null); var query = (from ut in DbContext.VW_User2Team join t in DbContext.Teams on ut.TeamId equals t.Id join pr in DbContext.VW_TeamResource on t.Id equals pr.TeamId into prl from p in prl.DefaultIfEmpty() join exp in DbContext.ExpenditureCategory on p.ExpenditureCategoryId equals exp.Id into egr from e in egr.DefaultIfEmpty() where (!userId.HasValue || (ut.UserId == userId.Value)) orderby t.Name select new { Team = t, PeopleResource = p, ExpCat = e }).AsNoTracking().Distinct(); var result = new Dictionary(); foreach (var item in query) { if (!filterByTeams || teams.Contains(item.Team.Id)) { if (!result.ContainsKey(item.Team.Id)) { result.Add(item.Team.Id, (TeamWithResourcesModel)item.Team); } if (item.PeopleResource != null) result[item.Team.Id].PeopleResources.Add(PeopleResourceModel.GetPeopleResourceModel(item.PeopleResource, item.ExpCat, item.Team)); } } var foundTeams = result.Values.ToList(); foundTeams.ForEach(t => t.PeopleResources = t.PeopleResources.OrderBy(r => r.FirstName + r.LastName).ToList()); return foundTeams; } public TeamWithResourcesModel LoadTeamWithResourcesById(Guid teamId) { if (Guid.Empty.Equals(teamId)) return null; var query = (from t in DbContext.Teams join pr in DbContext.VW_TeamResource on t.Id equals pr.TeamId into prl from p in prl.DefaultIfEmpty() join exp in DbContext.ExpenditureCategory on p.ExpenditureCategoryId equals exp.Id into egr from e in egr.DefaultIfEmpty() where t.Id == teamId orderby t.Name select new { Team = t, PeopleResource = p, ExpCat = e }).AsNoTracking().Distinct(); TeamWithResourcesModel team = null; foreach (var item in query) { if (team == null) { team = (TeamWithResourcesModel)item.Team; } if (item.PeopleResource != null) team.PeopleResources.Add(PeopleResourceModel.GetPeopleResourceModel(item.PeopleResource, item.ExpCat, item.Team)); } team.PeopleResources.OrderBy(r => r.FirstName + r.LastName); return team; } /// /// Loads all teams for the specified list of actual capacity scenarios. Each Team will be prefilled with resources of specified Expenditure Category. /// /// A list of Expenditure Category Ids. /// A list of Sceanrio Ids. /// public List LoadTeamsWithResourcesByActualCapacityScenarios(List expCatIds, List actualCapacityScenarioIds) { if (actualCapacityScenarioIds == null || actualCapacityScenarioIds.Count == 0) return null; var query = (from t in DbContext.Teams join pr in DbContext.VW_TeamResource on t.Id equals pr.TeamId into prl from p in prl.DefaultIfEmpty() join exp in DbContext.ExpenditureCategory on p.ExpenditureCategoryId equals exp.Id into egr from e in egr.DefaultIfEmpty() where t.ActualCapacityScenarioId.HasValue && actualCapacityScenarioIds.Contains(t.ActualCapacityScenarioId.Value) orderby t.Name select new { Team = t, PeopleResource = p, ExpCat = e }).AsNoTracking().Distinct(); var result = new Dictionary(); foreach (var item in query) { if (!result.ContainsKey(item.Team.Id)) { result.Add(item.Team.Id, (TeamWithResourcesModel)item.Team); } if (item.PeopleResource != null) result[item.Team.Id].PeopleResources.Add(PeopleResourceModel.GetPeopleResourceModel(item.PeopleResource, item.ExpCat, item.Team)); } var teams = result.Values.ToList(); teams.ForEach(t => t.PeopleResources = t.PeopleResources.OrderBy(r => r.FirstName + r.LastName).ToList()); return teams; } public override Team Save(TeamModel model) { if (model == null) throw new ArgumentNullException("model"); return Save(model, model.Id == Guid.Empty); } public Team Save(TeamModel model, bool saveAsNew) { Team team = null; if (saveAsNew && !DbContext.Teams.Any(x => x.Id == model.Id)) { team = new Team() { Id = model.Id }; model.CopyTo(team); var capScen = DbContext.Scenarios.Create(); capScen.Type = (int)ScenarioType.TeamPlannedCapacity; capScen.Name = model.Name + " Planned Capacity"; capScen.Id = Guid.NewGuid(); team.PlannedCapacityScenarioId = capScen.Id; var actCapScen = DbContext.Scenarios.Create(); actCapScen.Type = (int)ScenarioType.TeamActualCapacity; actCapScen.Name = model.Name + " Actual Capacity"; actCapScen.Id = Guid.NewGuid(); team.ActualCapacityScenarioId = actCapScen.Id; if (team.Id == Guid.Empty) team.Id = Guid.NewGuid(); DbContext.Teams.Add(team); DbContext.Scenarios.Add(capScen); DbContext.Scenarios.Add(actCapScen); if (model.UserId != null) { foreach (var userId in model.UserId) { DbContext.User2Team.Add(new User2Team { Id = Guid.NewGuid(), TeamId = team.Id, UserId = userId.ToString() }); } } if (model.WorkFlowContacts != null) { (new WorkFlowManager(this.DbContext)).SaveContacts(model.WorkFlowContacts.ToList(), null, team.Id, WorkFlowContactNotificationType.None); } if (model.NotificationContacts != null && model.NotificationWorkFlowStates != null) { (new WorkFlowManager(this.DbContext)).SaveContacts(model.NotificationContacts.ToList(), model.NotificationWorkFlowStates.ToList(), team.Id, WorkFlowContactNotificationType.TeamScenarioAdd); } } else { team = base.Save(model); var projectsWithParents = (from c in DbContext.Team2Project where c.TeamId == model.Id select new { c.ProjectId, c.Project.ParentProjectId }).Distinct().ToList(); var projects = projectsWithParents.Select(x => x.ProjectId).Distinct().ToList(); if (projectsWithParents.Any(x => x.ParentProjectId.HasValue)) projects.AddRange(projectsWithParents.Where(x => x.ParentProjectId.HasValue).Select(x => x.ParentProjectId.Value).Distinct()); var parts = DbContext.Projects.Where(x => x.ParentProjectId.HasValue && projects.Contains(x.ParentProjectId.Value)).Select(x => x.Id).ToList(); projects.AddRange(parts.Where(x => !projects.Contains(x))); var users = DbContext.User2Team.Where(x => x.TeamId == model.Id).ToList(); var users2remove = users.Where(x => model.UserId == null || !model.UserId.Contains(Guid.Parse(x.UserId))).Select(x => x.UserId).ToList(); users.Where(x => model.UserId == null || !model.UserId.Contains(Guid.Parse(x.UserId))).ToList(). ForEach(x => DbContext.Entry(x).State = EntityState.Deleted); if (model.UserId != null) { foreach (var userId in model.UserId.Where(c => users.All(u => !c.ToString().Equals(u.UserId, StringComparison.InvariantCultureIgnoreCase)))) { DbContext.User2Team.Add(new User2Team { Id = Guid.NewGuid(), TeamId = team.Id, UserId = userId.ToString() }); } } if (model.WorkFlowContacts != null) { (new WorkFlowManager(this.DbContext)).SaveContacts(model.WorkFlowContacts.ToList(), null, team.Id, WorkFlowContactNotificationType.None); } if (model.NotificationContacts != null && model.ProjectAddNotification) { if (model.NotificationWorkFlowStates == null) model.NotificationWorkFlowStates = new List(); (new WorkFlowManager(this.DbContext)).SaveContacts(model.NotificationContacts.ToList(), model.NotificationWorkFlowStates.ToList(), team.Id, WorkFlowContactNotificationType.TeamScenarioAdd); } foreach (var project in projects.Distinct()) { var projectUsers = (from c in DbContext.ProjectAccesses where c.ProjectId == project select c.PrincipalId).Distinct().ToList(); DbContext.ProjectAccesses.RemoveRange(DbContext.ProjectAccesses.Where(x => x.ProjectId == project && users2remove.Contains(x.PrincipalId.ToString())).ToList()); if (model.UserId != null) foreach (var userId in (from c in model.UserId where !projectUsers.Contains(c) select c).Distinct().ToList()) { DbContext.ProjectAccesses.Add(new ProjectAccess() { PrincipalId = userId, ProjectId = project, Read = 1, Write = 1 }); } } } if (IsContextLocal) DbContext.SaveChanges(); return team; } /// /// Returns team list, which contains specified expenditure category. For Super EC /// returns all teams, available to user /// /// Visible for this user /// Category to filter teams /// Query to get teams public IQueryable GetTeamsByExpenditureCategory(Guid expCatId, Guid? userId) { if (expCatId.Equals(Guid.Empty)) throw new ArgumentNullException("expCatId"); // Get info about EC var eManager = new ExpenditureCategoryManager(this.DbContext); var expCatItem = eManager.Load(expCatId, true); IQueryable teams = null; if (expCatItem == null) throw new Exception(String.Format("Expenditure Category (Id={0}) not found", expCatId.ToString())); if (expCatItem.AllowResourceAssignment) { // EC is ordinary IQueryable teamsP = from Team t in DbContext.Teams join VW_ExpCategoriesInScenario vv in DbContext.VW_ExpCategoriesInScenario on t.PlannedCapacityScenarioId equals vv.ScenarioID where (vv.Id == expCatId) select t; IQueryable teamsA = from Team t in DbContext.Teams join VW_ExpCategoriesInScenario vv in DbContext.VW_ExpCategoriesInScenario on t.ActualCapacityScenarioId equals vv.ScenarioID where (vv.Id == expCatId) select t; teams = teamsP.Union(teamsA); } else { // EC is Super EC. Return all teams teams = DbContext.Teams; } if (userId.HasValue) { string userIdAsText = userId.Value.ToString(); teams.Join(DbContext.User2Team.Where(x => x.UserId.Equals(userIdAsText)), k => k.Id, l => l.TeamId, (k, l) => k); } return teams.Distinct(); } public Guid?[] GetPlannedCapacityCategoriesIds(Guid teamId) { return (from sd in DbContext.ScenarioDetail join s in DbContext.Scenarios on sd.ParentID equals s.Id join t in DbContext.Teams on s.Id equals t.PlannedCapacityScenarioId where t.Id == teamId select sd.ExpenditureCategoryId).Distinct().ToArray(); } public List GetTeamsAllocation(IEnumerable teams, IEnumerable scenarios, IEnumerable expenditureCategories, DateTime? startDate, DateTime? endDate) { if (teams == null || !teams.Any()) return new List(); // TODO: add ability to filter with partial week (need to join to Fiscal Calendar and filter data by Start/End date of fiscal week instead of allocation.WeekEnding, see FiscalCalendarManager.FilterWeeks method) var teamsAllocation = DbContext.TeamAllocations.Where(x => teams.Contains(x.TeamId)); if (scenarios != null && scenarios.Any()) teamsAllocation = teamsAllocation.Where(x => scenarios.Contains(x.ScenarioId)); if (expenditureCategories != null && expenditureCategories.Any()) teamsAllocation = teamsAllocation.Where(x => expenditureCategories.Contains(x.ExpenditureCategoryId)); if (startDate.HasValue) teamsAllocation = teamsAllocation.Where(x => x.WeekEndingDate >= startDate.Value); if (endDate.HasValue) teamsAllocation = teamsAllocation.Where(x => x.WeekEndingDate <= endDate.Value); return teamsAllocation.ToList(); } public List GetTeamsAllocation(Guid scenarioId, DateTime? startDate = null, DateTime? endDate = null, bool readOnly = false) { if (scenarioId == Guid.Empty) return new List(); return GetTeamsAllocation(new List { scenarioId }, startDate, endDate, readOnly); } public List GetTeamsAllocation(IEnumerable scenarios, DateTime? startDate = null, DateTime? endDate = null, bool readOnly = false) { // TODO: add ability to filter with partial week (need to join to Fiscal Calendar and filter data by Start/End date of fiscal week instead of allocation.WeekEnding, see FiscalCalendarManager.FilterWeeks method) var taQuery = DbContext.TeamAllocations.AsQueryable(); if (readOnly) taQuery = taQuery.AsNoTracking(); if (scenarios != null && scenarios.Any()) taQuery = taQuery.Where(x => scenarios.Contains(x.ScenarioId)); if (startDate.HasValue) taQuery = taQuery.Where(x => x.WeekEndingDate >= startDate.Value); if (endDate.HasValue) taQuery = taQuery.Where(x => x.WeekEndingDate <= endDate.Value); return taQuery.ToList(); } /// Returns tree-structure of teams allocation: Teams[TeamId, Scenarios[ScenarioId, ExpCats[ExpCatId, [{WeekEnding: TeamAllocation}]]] public Dictionary>>> GetTeamsAllocationTree(List teams, List scenarios, List expenditureCategories, DateTime? startDate, DateTime? endDate) { return GetTeamsAllocation(teams, scenarios, expenditureCategories, startDate, endDate) .GroupBy(x => x.TeamId) .ToDictionary(tms => tms.Key, sc => sc.GroupBy(scenario => scenario.ScenarioId) .ToDictionary(scenario => scenario.Key, expCats => expCats.GroupBy(expCat => expCat.ExpenditureCategoryId) .ToDictionary(expCat => expCat.Key, allocations => allocations.ToDictionary(allocation => allocation.WeekEndingDate)))); } /// /// Returns tree-structure of teams to scenario relations: Teams[TeamId, Team2ScenarioList] /// /// /// /// /// public Dictionary> GetRelationsWithScenariosTree(List teams, ScenarioType? scenarioType, ScenarioStatus? scenarioStatus) { var team2ScenarioQuery = from t2pr in DbContext.Team2Project join proj in DbContext.Projects on t2pr.ProjectId equals proj.Id join scen in DbContext.Scenarios on proj.Id equals scen.ParentId select new { ScenarioId = scen.Id, ScenarioType = scen.Type, ScenarioStatus = scen.Status, TeamId = t2pr.TeamId }; if ((teams != null) && (teams.Count > 0)) team2ScenarioQuery = team2ScenarioQuery.Where(x => teams.Contains(x.TeamId)); if (scenarioType.HasValue) team2ScenarioQuery = team2ScenarioQuery.Where(x => x.ScenarioType == (int)scenarioType.Value); if (scenarioStatus.HasValue) team2ScenarioQuery = team2ScenarioQuery.Where(x => x.ScenarioStatus == (int)scenarioStatus.Value); var result = team2ScenarioQuery.Select(x => new { TeamId = x.TeamId, ScenarioId = x.ScenarioId }) .Distinct().ToList().GroupBy(x => x.TeamId) .ToDictionary(x => x.Key, g => g.Select(k => k.ScenarioId).ToList()); return result; } public List GetTeamsByCompanyName(string CompanyName) { return DbContext.Teams.Where(x => x.Company.Name == CompanyName).ToList(); } public List GetTeamsByCompanyId(Guid companyId) { var userId = SecurityManager.GetUserPrincipal(); var teams = GetTeamsByCompanies(userId, new List { companyId }); return teams; } public TeamModel GetTeamByName(string name) { return (TeamModel)this.TeamModelBasicQuery.Where(x => x.Name == name).FirstOrDefault(); } public List GetTeamsByCompanies(Guid userId, List companies) { if (Guid.Empty.Equals(userId)) throw new ArgumentNullException("userId"); if (companies == null) throw new ArgumentNullException("companies"); if (companies.Count < 1) return new List(); var result = (from ut in DbContext.VW_User2Team join c in DbContext.Companies on ut.CompanyId equals c.Id join t in DbContext.Teams on ut.TeamId equals t.Id where ut.CompanyId.HasValue && (companies.Contains(c.Id) || (c.ParentCompanyId.HasValue && companies.Contains(c.ParentCompanyId.Value))) && ut.UserId.Equals(userId) && (ut.Read == 1 || ut.Write == 1) select new TeamWithPermissionsModel() { Name = ut.TeamName, UserId = ut.UserId.ToString(), TeamId = ut.TeamId, Read = ut.Read == 1, Write = ut.Write == 1 }).Distinct().ToList(); return result; } public List GetTeamsByViewsByUser(List views, Guid userId) { if (views == null) throw new ArgumentNullException("views"); if (userId.Equals(Guid.Empty)) throw new ArgumentNullException("userId"); if (views.Count < 1) return new List(); var result = (from t2v in DbContext.VW_Team2View join userTeams in DbContext.VW_User2Team on t2v.TeamId equals userTeams.TeamId join t in DbContext.Teams on userTeams.TeamId equals t.Id where userTeams.UserId == userId && views.Contains(t2v.ViewId) && ((userTeams.Read == 1) || (userTeams.Write == 1)) select new { Name = t.Name, UserId = userTeams.UserId, TeamId = userTeams.TeamId, Read = userTeams.Read == 1, Write = userTeams.Write == 1 }).Distinct().ToArray().Select(t => new TeamWithPermissionsModel { Name = t.Name, UserId = t.UserId.ToString(), TeamId = t.TeamId, Read = t.Read, Write = t.Write }).ToList(); return result; } [Obsolete("Seems like it's old, use GetTeamsByViewsByUser instead")] public List GetTeamsByViews(List views) { if (views == null) throw new ArgumentNullException("views"); if (views.Count < 1) return new List(); // Via direct links views to teams var teamsDirect = DbContext.Team2View.Where(x => views.Contains(x.ViewId)) .Select(x => x.TeamId).ToList(); // Via links views to companies var teamsThroughCompanies = (from t2v in DbContext.VW_Company2View join team in DbContext.Teams on t2v.CompanyId equals team.CompanyId where views.Contains(t2v.ViewId) select team.Id).ToList(); var result = teamsDirect.Union(teamsThroughCompanies).Distinct().ToList(); return result; } public List GetTeamsByUser(Guid userId) { if (userId.Equals(Guid.Empty)) throw new ArgumentNullException(nameof(userId)); return GetTeamWithPermissionsBasicQuery(userId).Distinct().ToList(); } public List GetTeamsByUserFiltered(string userId, List teams, List views, List companies) { if (String.IsNullOrEmpty(userId)) throw new ArgumentNullException(nameof(userId)); Guid userIdAsGuid = new Guid(userId); List result; bool filteredByTeams = teams != null && teams.Count > 0; bool filteredByViews = views != null && views.Count > 0; bool filteredByCompanies = companies != null && companies.Count > 0; bool filtered = filteredByTeams || filteredByViews || filteredByCompanies; if (filtered) { List teamsByTeams = new List(); List teamsByViews = new List(); List teamsByCompanies = new List(); if (filteredByTeams) { teamsByTeams = this.GetTeamsByUser(userIdAsGuid); teamsByTeams = teamsByTeams.Where(t => teams.Contains(t.TeamId)).ToList(); } if (filteredByViews) teamsByViews = this.GetTeamsByViewsByUser(views, userIdAsGuid); if (filteredByCompanies) teamsByCompanies = this.GetTeamsByCompanies(userIdAsGuid, companies); result = teamsByTeams.Union(teamsByViews).Union(teamsByCompanies).Distinct().ToList(); } else { result = this.GetTeamsByUser(userIdAsGuid); } return result; } public List GetTeamsByResources(string userId, List resources) { if (string.IsNullOrWhiteSpace(userId) || resources == null || resources.Count <= 0) return new List(); var userIdAsGuid = Guid.Parse(userId); var userAvailableTeams = this.GetTeamsByUser(userIdAsGuid); var resourceTeams = DbContext.VW_TeamResource.Where(x => resources.Contains(x.Id)).Select(x => x.TeamId).Distinct().ToList(); return userAvailableTeams.Where(t => resourceTeams.Contains(t.TeamId)).ToList(); } public List GetTeamsByProjects(Guid userId, IEnumerable projects, IEnumerable teams) { if (userId == Guid.Empty) throw new ArgumentException("Argument cannot be empty", nameof(userId)); if (projects == null || !projects.Any()) return new List(); var query = (from team in GetTeamWithPermissionsBasicQuery(userId) join project in DbContext.Team2Project on team.TeamId equals project.TeamId where projects.Contains(project.ProjectId) select team).Distinct(); if (teams != null && teams.Any()) query = query.Where(x => teams.Contains(x.TeamId)); var result = query.ToList(); return result; } public List GetProjectsIds(IEnumerable teams) { if (teams == null || teams.Count() <= 0) return new List(); var projects = DbContext.Team2Project.AsNoTracking() .Where(x => teams.Contains(x.TeamId)) .Select(x => x.ProjectId) .Distinct() .ToList(); return projects; } /// Returns teams by passed ids and views with checking of user rights for teams public List GetTeamsInfoByUser(string userId, List teamIds, List viewIds, List fiscalCalendar) { if ((teamIds == null || teamIds.Count <= 0) && (viewIds == null || viewIds.Count <= 0)) return new List(); var teams = GetTeamsByUserFiltered(userId, teamIds, viewIds, null); var teamsData = LoadTeams(teams.Select(t => t.TeamId)).ToList(); if (teamsData.Count < 1) return new List(); return ConvertTeamsToSummaryInfoModel(teamsData, teams, fiscalCalendar); } /// Returns teams by passed ids w/o checking of user rights for teams public List GetTeamsInfo(List teams, List fiscalCalendar, bool ignoreScenarioStatus = false) { if (teams == null || teams.Count <= 0) return new List(); var teamIds = teams.Select(t => t.TeamId); var teamInfos = DbContext.Teams.Where(x => teamIds.Contains(x.Id)) .AsNoTracking() .ToList(); return ConvertTeamsToSummaryInfoModel(teamInfos, teams, fiscalCalendar, ignoreScenarioStatus); } /// /// /// /// /// /// /// /// /// ToDo: Using of two team parameters is a quick temp solution they should be joined. /// private List ConvertTeamsToSummaryInfoModel(List teams, IEnumerable teamPermissions, List fiscalCalendar, bool ignoreScenarioStatus = false) { var result = new List(); if (teams == null || teams.Count <= 0) return result; if (fiscalCalendar == null) fiscalCalendar = new List(); var startDate = fiscalCalendar.Count > 0 ? fiscalCalendar.FirstOrDefault().StartDate : (DateTime?)null; var endDate = fiscalCalendar.Count > 0 ? fiscalCalendar.LastOrDefault().EndDate : (DateTime?)null; var scenarioManager = (new ScenarioManager(DbContext)); var prManager = (new PeopleResourcesManager(DbContext)); var nptManager = new NonProjectTimeManager(DbContext); var uoms = (new UOMManager(DbContext)).GetAsDictionary(); var teamIds = teams.Select(x => x.Id).ToList(); var teamPermissionsDict = teamPermissions.GroupBy(t => t.TeamId).ToDictionary(gr => gr.Key, el => el.FirstOrDefault().AccessLevel); var teams2Scenarios = GetRelationsWithScenariosTree(teamIds, ScenarioType.Portfolio, ignoreScenarioStatus ? (ScenarioStatus?)null : ScenarioStatus.Active); var activeScenarios = teams2Scenarios.SelectMany(x => x.Value).Distinct().ToList(); var plannedCapacityScenariosId = teams.Where(x => x.PlannedCapacityScenarioId.HasValue).Select(x => x.PlannedCapacityScenarioId.Value).ToList(); var actualCapacityScenariosId = teams.Where(x => x.ActualCapacityScenarioId.HasValue).Select(x => x.ActualCapacityScenarioId.Value).ToList(); var capacityScenarios = plannedCapacityScenariosId.Union(actualCapacityScenariosId).ToList(); // Get ExpCats from capacity scenarios var expCatsFromCapacityScenarios = scenarioManager.GetCategoriesInScenarios(capacityScenarios, null); var expCatsFromResources = GetECsByTeamResources(teamIds); var expCatsInTeams = expCatsFromCapacityScenarios .SelectMany(x => x.Value.Values).Union(expCatsFromResources.SelectMany(x => x.Value)) .Select(x => new { x.Id, x.UOMId, x.ExpCategoryWithCcName, x.AllowResourceAssignment }).Distinct().ToDictionary(x => x.Id); // Get ECs from active scenarios, the incoming teams are involved to (it is necessary to extract SuperECs) var expCatsFromActiveScenarios = scenarioManager.GetCategoriesInScenarios(activeScenarios, expCatsInTeams.Keys.ToList()); // Add superECs to the main ECs list var superExpCatsInTeams = expCatsFromActiveScenarios.SelectMany(x => x.Value.Values) .Select(x => new { x.Id, x.UOMId, x.ExpCategoryWithCcName, x.AllowResourceAssignment }).Distinct().ToDictionary(x => x.Id); superExpCatsInTeams.Keys.ToList().ForEach(expCatId => expCatsInTeams.Add(expCatId, superExpCatsInTeams[expCatId])); // for reducing number of retrieving scenario details rows we should retrieve only by ECs which exists in teams var scenarioDetails = scenarioManager.GetScenarioDetailsTree(activeScenarios.ToList(), expCatsInTeams.Keys.ToList()); var plannedCapacityScenarios = scenarioManager.GetPlanningCapacityAdjusted(plannedCapacityScenariosId, expCatsInTeams.Keys.ToList(), null, null); // for reducing number of retrieving teams allocations rows we should retrieve only by active scenarios and ECs which exists in teams var teamsAllocations = GetTeamsAllocationTree(teams.Select(x => x.Id).ToList(), activeScenarios, expCatsInTeams.Keys.ToList(), null, null); var resources = prManager.GetResourcesWithAllocationsByTeams(teams.Select(x => x.Id)); prManager.ReplaceOwnResourceExpendituresWithExpendituresInAllocations(resources); var resourcesTree = prManager.CastPeopleResourceWithAllocationToTree(resources); var resourcesIds = resourcesTree.SelectMany(x => x.Value.SelectMany(r => r.Value.Select(rv => rv.Id))); var npTimes = nptManager.GetNonProjectTimes4Teams(teamIds, startDate, endDate); var allResHolidays = (new FiscalCalendarManager(DbContext)).GetHolidayAllocationsByResource(null, null, resourceIds: resourcesIds); foreach (var team in teams) { var teamAllocations = teamsAllocations.ContainsKey(team.Id) ? teamsAllocations[team.Id] : null; var teamResources = resourcesTree.ContainsKey(team.Id) ? resourcesTree[team.Id] : null; var teamSummaryInfoModel = new TeamSummaryInfoModel() { Id = team.Id.ToString(), Name = team.Name, CompanyId = team.CompanyId, AccessLevel = teamPermissionsDict.ContainsKey(team.Id) ? teamPermissionsDict[team.Id] : AccessLevel.None }; // Get Expenditures from team Planned and Actual capacity scenarios var plannedCapacityScenario = (team.PlannedCapacityScenarioId.HasValue && plannedCapacityScenarios.ContainsKey(team.PlannedCapacityScenarioId.Value)) ? plannedCapacityScenarios[team.PlannedCapacityScenarioId.Value] : null; var actualCapacityExpenditures = (team.ActualCapacityScenarioId.HasValue && expCatsFromCapacityScenarios.ContainsKey(team.ActualCapacityScenarioId.Value)) ? expCatsFromCapacityScenarios[team.ActualCapacityScenarioId.Value] : null; var expCatsInTeam = new List(); if (plannedCapacityScenario != null && plannedCapacityScenario.Keys.Count > 0) expCatsInTeam.AddRange(plannedCapacityScenario.Keys); if (actualCapacityExpenditures != null && actualCapacityExpenditures.Keys.Count > 0) expCatsInTeam.AddRange(actualCapacityExpenditures.Keys); if (expCatsFromResources.ContainsKey(team.Id)) expCatsInTeam.AddRange(expCatsFromResources[team.Id].Select(x => x.Id)); if (teamAllocations != null) { // Append Expenditures from team allocations records for this team (necessary to get SuperECs) teamAllocations.Keys.ToList().ForEach(scenarioId => { var scenarioECs = teamAllocations[scenarioId]; if (scenarioECs != null) { var superExpCats = scenarioECs.Keys.Except(expCatsInTeam); expCatsInTeam.AddRange(superExpCats); } }); } expCatsInTeam = expCatsInTeam.Distinct().ToList(); foreach (var expCatId in expCatsInTeam) { var uomId = expCatsInTeams[expCatId].UOMId; var activityCategory = new ExpCategorySummaryInfoModel() { Id = expCatId, Name = expCatsInTeams[expCatId].ExpCategoryWithCcName, UomValue = uoms[uomId].UOMValue, ECScenario = ((plannedCapacityScenario != null && plannedCapacityScenario.Keys.Contains(expCatId) ? ExpCategorySummaryInfoModel.EC_Scenario.PLANNED : 0) | (actualCapacityExpenditures != null && actualCapacityExpenditures.Keys.Contains(expCatId) ? ExpCategorySummaryInfoModel.EC_Scenario.ACTUALS : 0) | (teamResources != null && teamResources.SelectMany(r => r.Value.Select(r1 => r1.ExpenditureCategoryId)).Contains(expCatId) ? ExpCategorySummaryInfoModel.EC_Scenario.TEAM : 0)), AllowResourceAssignment = expCatsInTeams[expCatId].AllowResourceAssignment }; if (plannedCapacityScenario != null && plannedCapacityScenario.ContainsKey(expCatId)) { activityCategory.PlannedCapacityValues = plannedCapacityScenario[expCatId] .Where(x => x.WeekEndingDate.HasValue) .OrderBy(x => x.WeekEndingDateMs) .ToDictionary(x => x.WeekEndingDateMs.ToString(), g => g.Quantity); } if (teams2Scenarios.ContainsKey(team.Id)) { foreach (var scenarioId in teams2Scenarios[team.Id]) { var teamAllocationsInScenarioAndEC = (teamAllocations != null && teamAllocations.ContainsKey(scenarioId) && teamAllocations[scenarioId].ContainsKey(expCatId)) ? teamAllocations[scenarioId][expCatId] : new Dictionary(); if (!scenarioDetails.ContainsKey(scenarioId)) continue; if (!scenarioDetails[scenarioId].ContainsKey(expCatId)) continue; foreach (var detail in scenarioDetails[scenarioId][expCatId]) { decimal quantity = 0; if (teamAllocationsInScenarioAndEC.ContainsKey(detail.WeekEndingDate.Value)) quantity = teamAllocationsInScenarioAndEC[detail.WeekEndingDate.Value].Quantity; else //quantity = (team2scenario.Allocation * (detail.Quantity ?? 0)) / (decimal)100.0; // SA: Since all team allocations are saved, when scenario created, and there // is no any virtual team allocations (calculated on the fly), we consider // quantity is zero for records, that have no allocations in db table quantity = 0; var sdKey = Utils.ConvertToUnixDate(detail.WeekEndingDate.Value).ToString(); if (!activityCategory.NeedCapacity.ContainsKey(sdKey)) activityCategory.NeedCapacity.Add(sdKey, 0); activityCategory.NeedCapacity[sdKey] += quantity; } } } if (teamResources != null && teamResources.ContainsKey(expCatId)) { foreach (var resource in teamResources[expCatId]) { decimal resourceUomValue = activityCategory.UomValue; ResourceSummaryInfoModel resourceModel = null; if (activityCategory.Resources.ContainsKey(resource.Id.ToString())) { // Get existing resource model (the resource seems to be the team member twice or more times) resourceModel = activityCategory.Resources[resource.Id.ToString()]; } else { resourceModel = new ResourceSummaryInfoModel() { Id = resource.Id, }; activityCategory.Resources.Add(resourceModel.Id.ToString(), resourceModel); } // Store the resource's team membership info var teamMembershipInfo = new ResourceSummaryInfoModel.TeamMembershipModel() { TeamId = team.Id, StartDate = Utils.ConvertToUnixDate(resource.StartDate), EndDate = resource.EndDate.HasValue ? Utils.ConvertToUnixDate(resource.EndDate.Value) : (long?)null }; resourceModel.MergeTeam(teamMembershipInfo); if (resource.OwnExpenditureCategoryId != resource.ExpenditureCategoryId) { // Get the resource's UOM, if it is assigned to Super EC. UOM should be taken from Super EC if (!expCatsInTeams.ContainsKey(resource.OwnExpenditureCategoryId)) throw new Exception("Own expenditure category for resource not found in the loaded category list"); var resourceOwnExpCat = expCatsInTeams[resource.OwnExpenditureCategoryId]; if (!uoms.ContainsKey(resourceOwnExpCat.UOMId)) throw new Exception("UOM not found in the loaded list"); resourceUomValue = uoms[resourceOwnExpCat.UOMId].UOMValue; } var fiscalCalendarWeeks4Resource = fiscalCalendar.Where(x => x.StartDate >= resource.StartDate && (!resource.EndDate.HasValue || (x.EndDate <= resource.EndDate))).Select(x => x.EndDate).Distinct().ToDictionary(key => key); if (activityCategory.AllowResourceAssignment) { // Attach NPT items to the only resources in ordinary ECs var teamResourceNptAllocations = (npTimes.ContainsKey(resource.Id) ? npTimes[resource.Id] : new Dictionary()); // get only npt allocations related to the current team foreach (var item in teamResourceNptAllocations) { ResourceNptModel nptItem = null; if (resourceModel.NonProjectTime.ContainsKey(item.Key)) nptItem = resourceModel.NonProjectTime[item.Key]; else nptItem = new ResourceNptModel { Id = item.Value.Id, Name = item.Value.Name, CategoryId = item.Value.CategoryId, isTeamWide = item.Value.isTeamWide, Allocations = new Dictionary() }; foreach (var alloc in item.Value.Allocations) { if (teamMembershipInfo.StartDate < Convert.ToInt64(alloc.Key) && (!teamMembershipInfo.EndDate.HasValue || teamMembershipInfo.EndDate >= Convert.ToInt64(alloc.Key))) nptItem.Allocations.Add(alloc.Key, alloc.Value); } // do not add NPT items for which we do not have any allocations // e.g. NPT record for resource of unaccessible team if (nptItem.Allocations.Count > 0 && !resourceModel.NonProjectTime.ContainsKey(item.Key)) resourceModel.NonProjectTime.Add(item.Key, nptItem); } } foreach (var weekEnding in fiscalCalendarWeeks4Resource.Keys) { var rKey = Utils.ConvertToUnixDate(weekEnding).ToString(); var holidayKoeff = allResHolidays.ContainsKey(resource.Id) && allResHolidays[resource.Id].ContainsKey(weekEnding) ? allResHolidays[resource.Id][weekEnding] : 1; var resourceCapacity = resourceUomValue * holidayKoeff; if (!resourceModel.TotalCapacity.ContainsKey(rKey)) resourceModel.TotalCapacity.Add(rKey, resourceCapacity); if (activityCategory.AllowResourceAssignment) { // Calculate EC's Actual capacity values for normal ECs only, // and skip this for SuperECs as they have no own capacity if (!activityCategory.ActualCapacityValues.ContainsKey(rKey)) activityCategory.ActualCapacityValues.Add(rKey, 0); activityCategory.ActualCapacityValues[rKey] += resourceCapacity; } } foreach (var rAllocation in resource.Allocations) { if (!fiscalCalendarWeeks4Resource.ContainsKey(rAllocation.WeekEndingDate)) continue; var rKey = Utils.ConvertToUnixDate(rAllocation.WeekEndingDate).ToString(); if (!resourceModel.AllocatedCapacity.ContainsKey(rKey)) resourceModel.AllocatedCapacity.Add(rKey, 0); resourceModel.AllocatedCapacity[rKey] += rAllocation.Quantity; if (activityCategory.AllowResourceAssignment) { if (!activityCategory.AllocatedCapacity.ContainsKey(rKey)) activityCategory.AllocatedCapacity.Add(rKey, 0); // Calculate EC's Actual capacity values for normal ECs only, // and skip this for SuperECs as they have no own capacity activityCategory.AllocatedCapacity[rKey] += rAllocation.Quantity; } } } } teamSummaryInfoModel.ExpCategories.Add(activityCategory.Id.ToString(), activityCategory); } result.Add(teamSummaryInfoModel); } return result; } public void AddCapacityToTeam(Guid expCatId, Guid teamId, IEnumerable values, bool isPlanned) { if (expCatId.Equals(Guid.Empty)) throw new ArgumentException(nameof(expCatId)); if (teamId.Equals(Guid.Empty)) throw new ArgumentException(nameof(teamId)); if (values == null || !values.Any()) return; // Nothing to do // Set incoming values indexed var valuesIndexed = values.ToDictionary(k => k.WeekEnding, v => v); var incomingWeekendings = valuesIndexed.Keys; var scenarioManager = new ScenarioManager(DbContext); var teamManager = new TeamManager(DbContext); var team = teamManager.Load(teamId); if (team == null) throw new Exception($"Team '{teamId}' not found"); var parentId = isPlanned ? team.PlannedCapacityScenarioId : team.ActualCapacityScenarioId; if (!parentId.HasValue) { string scenarioTypeAsText = isPlanned ? "planned" : "actual"; throw new Exception($"Team '{team.Name}' doesn't has {scenarioTypeAsText} capacity scenario"); } // Existing capacity values in DB var existingCapacityDetailsIndexed = scenarioManager.GetScenarioDetails(parentId.Value, new List { expCatId }, incomingWeekendings.Min(), incomingWeekendings.Max(), false) .ToDictionary(k => k.WeekEndingDate.Value, v => v); // Weekending, for which capacity values must be created var newCapacityDates = incomingWeekendings.Except(existingCapacityDetailsIndexed.Keys).ToList(); #region Debug Info var sb = new StringBuilder(); sb.AppendLine("TeamManager.AddCapacityToTeam method. existingCapacityDetailsIndexed.Values:"); existingCapacityDetailsIndexed.Values.DebugObjectProperties(sb); sb.AppendLine("newCapacityDates:"); newCapacityDates.DebugObjectProperties(sb); Logger.Debug(sb); #endregion // Process existing capacity values foreach (var detailItem in existingCapacityDetailsIndexed.Values) { var vc = valuesIndexed[detailItem.WeekEndingDate.Value]; detailItem.Quantity = detailItem.Quantity + vc.Quantity; detailItem.Cost = (detailItem.Cost + vc.Cost) > 0 ? detailItem.Cost + vc.Cost : 0; }; var addedValues = new List(newCapacityDates.Count); var removedValues = existingCapacityDetailsIndexed.Values.Where(x => x.Quantity <= 0); foreach (var weekEnding in newCapacityDates) { var vc = valuesIndexed[weekEnding]; if (vc.Quantity <= 0) continue; var capacityValue = new ScenarioDetail { Id = Guid.NewGuid(), ParentID = parentId.Value, Quantity = vc.Quantity, Cost = vc.Cost, ExpenditureCategoryId = expCatId, WeekEndingDate = weekEnding, WeekOrdinal = 0 }; addedValues.Add(capacityValue); } DbContext.ScenarioDetail.RemoveRange(removedValues); DbContext.ScenarioDetail.AddRange(addedValues); } public void RemoveCapacityFromTeam(Guid expCatId, Guid teamId, List values, bool isPlanned) { if ((values == null) || (values.Count < 1)) // Nothing to do return; // Make list of negative values var negativeValues = values.Select(x => new PeopleResourceModel.CapacityValues() { WeekEnding = x.WeekEnding, Cost = -x.Cost, Quantity = -x.Quantity }).ToList(); AddCapacityToTeam(expCatId, teamId, negativeValues, isPlanned); } public List GetTeamsByProject(Guid projectId) { List results = new List(); if (projectId == null) return results; var t2p = this.DbContext.Team2Project.Where(x => x.ProjectId == projectId).ToList(); foreach (Team2Project t in t2p) { results.Add(t.Team); } return results; } public Dictionary GetTeamNames(IEnumerable teams) { Dictionary result = new Dictionary(); if ((teams == null) || !teams.Any()) return result; result = DbContext.Teams.AsNoTracking().Where(x => teams.Contains(x.Id)) .OrderBy(x => x.Name).ToDictionary(k => k.Id, v => v.Name); return result; } /// /// Returns UOMs for each team in the specified list. UOM for team is the Max UOM of all it's resources /// public Dictionary GetTeamsUOM(List teams, DateTime datePoint) { if (teams == null || teams.Count < 1) return new Dictionary(); var dp = datePoint.ToUniversalTime().Date; var uoms = (from p in DbContext.PeopleResources.AsNoTracking() join p2t in DbContext.PeopleResource2Team.AsNoTracking() on p.Id equals p2t.PeopleResourceId 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 teams.Contains(p2t.TeamId) && (p2t.StartDate <= dp) && (!p2t.EndDate.HasValue || (p2t.EndDate.Value >= dp)) select new { TeamId = p2t.TeamId, UOMValue = uom.UOMValue }) .GroupBy(x => x.TeamId, (key, grp) => new { TeamId = key, UOMValue = grp.Select(j => j.UOMValue).Max() }) .ToDictionary(x => x.TeamId, x => x.UOMValue); return uoms; } public Dictionary> GetECsByTeamResources(List teamIds) { if (teamIds == null || !teamIds.Any()) return new Dictionary>(); var query = (from resource in DbContext.VW_TeamResource join category in DbContext.VW_ExpenditureCategory on resource.ExpenditureCategoryId equals category.Id where teamIds.Contains(resource.TeamId) select new { TeamId = resource.TeamId, ExpenditureCategory = category }); var result = query.ToList() .GroupBy(x => x.TeamId) .ToDictionary(x => x.Key, g => g.Select(x => x.ExpenditureCategory) .ToList()); return result; } public List GetProjectAddNotificationList(Guid teamId, string state) { var contacts = (from c in this.DbContext.WorkFlowContacts join st in this.DbContext.NotificationToWorkFlowStates on c.ContactId equals st.WorkFlowContactId where c.PartentId == teamId && c.NotificationType == (int)WorkFlowContactNotificationType.TeamScenarioAdd && st.State == state select c.Id).ToList(); return contacts; } public List GetProjectAddNotificationList(Guid teamId) { var contacts = (from c in this.DbContext.WorkFlowContacts where c.PartentId == teamId && c.NotificationType == (int)WorkFlowContactNotificationType.TeamScenarioAdd select c.Id).ToList(); return contacts; } #region Private Members private IQueryable TeamModelBasicQuery { get { var query = DataTable.AsNoTracking() .Select(x => new TeamModel() { Id = x.Id, Name = x.Name, CompanyId = x.CompanyId, CostCenterId = x.CostCenterId, ReportToId = x.ReportsTo, PlannedCapacityScenarioId = x.PlannedCapacityScenarioId, ActualCapacityScenarioId = x.ActualCapacityScenarioId, ProjectAddNotification = x.ProjectAddNotification.HasValue ? x.ProjectAddNotification.Value : false }); return query; } } private IQueryable GetTeamWithPermissionsBasicQuery(Guid userId) { var query = DbContext.VW_User2Team.Where(x => x.UserId.Equals(userId) && (x.Read == 1 || x.Write == 1)) .Select(x => new TeamWithPermissionsModel { Name = x.TeamName, UserId = x.UserId.ToString(), TeamId = x.TeamId, Read = (x.Read == 1), Write = (x.Write == 1) }); return query; } #endregion } }