using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Data.Entity; using System.Linq; using System.Net; using System.Web; using System.Web.Mvc; using EnVisage.Code; using EnVisage.Code.Cache; using jQuery.DataTables.Mvc; using EnVisage.Models; using EnVisage.Code.BLL; using EnVisage.App_Start; using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity.EntityFramework; using System.Data.Entity.Infrastructure; using System.Web.Script.Serialization; namespace EnVisage.Controllers { public class TeamController : BaseController { /// /// An UI representation of Team to be displayed as list items /// public class ListTeam { public Guid Id { get; set; } public string Name { get; set; } public string Users { get; set; } public string Company { get; set; } public string CostCenter { get; set; } public string CostCenterNumber { get; set; } public string ColumnCostCenter { get; set; } public string GLAccount { get; set; } public string ReportTo { get; set; } public bool IsResourcesAttached { get; set; } } /// /// An UI representation of category and open positions for planned capacity simple mode /// public class PlannedCapacitySimpleCategory { public Guid Id { get; set; } public string Name { get; set; } public bool InPlan { get; set; } public List Positions { get; set; } } public class PlannedCapacityResourcePosition { public int Need { get; set; } public DateTime StartDate { get; set; } public DateTime? EndDate { get; set; } } /// /// GET: /Teams/ /// /// Empty view [HttpGet] [AreaSecurityAttribute(area = Areas.Teams, level = AccessLevel.Read)] public ActionResult Index() { return View(); } /// /// Returns JSON teams list with filters and sort for jQuery DataTables /// [HttpPost] [AreaSecurityAttribute(area = Areas.Teams, level = AccessLevel.Read)] public JsonResult Index(JQueryDataTablesModel jQueryDataTablesModel) { int totalRecordCount; int searchRecordCount; var teams = GetTeams(startIndex: jQueryDataTablesModel.iDisplayStart, pageSize: jQueryDataTablesModel.iDisplayLength, sortedColumns: jQueryDataTablesModel.GetSortedColumns(), totalRecordCount: out totalRecordCount, searchRecordCount: out searchRecordCount, searchString: jQueryDataTablesModel.sSearch); return this.DataTablesJson(items: teams, totalRecords: totalRecordCount, totalDisplayRecords: searchRecordCount, sEcho: jQueryDataTablesModel.sEcho); } [HttpPost] public ActionResult GetPlanCapacitySimpleMode(string teamId) { Guid gTeamId = Guid.Parse(teamId); TeamManager teamManager = new TeamManager(DbContext); ExpenditureCategoryManager categoryManager = new ExpenditureCategoryManager(DbContext); var categoriesInPlan = teamManager.GetPlannedCapacityCategoriesIds(Guid.Parse(teamId)); DateTime CalendarMaxDate = (from c in DbContext.FiscalCalendars where c.Type == 0 && c.AdjustingPeriod == false orderby c.StartDate descending select c.EndDate ).FirstOrDefault(); //first of all, we need to get categories + dates when category starts in either plan or reality - //these ranges allow us to work with this date range vs. entire fiscal calendar var subq = (from sdPlan in DbContext.ScenarioDetail join t in DbContext.Teams on sdPlan.ParentID equals t.PlannedCapacityScenarioId where t.Id == gTeamId select sdPlan).Union (from sdActual in DbContext.ScenarioDetail join t in DbContext.Teams on sdActual.ParentID equals t.ActualCapacityScenarioId where t.Id == gTeamId select sdActual); //In addition to that, we need a data set of details for both planned and actual capacities to check them for need increase or decrease while we iterating through FC' weeks Dictionary>> plannedCapacityDetails = (from sdPlan in DbContext.ScenarioDetail join t in DbContext.Teams on sdPlan.ParentID equals t.PlannedCapacityScenarioId where t.Id == gTeamId select sdPlan).GroupBy(sd => sd.ExpenditureCategoryId).ToDictionary(k1 => k1.Key, g1 => g1.GroupBy(sd2 => sd2.WeekEndingDate).ToDictionary(k2 => k2.Key, g2 => g2.ToList())); Dictionary>> actualCapacityDetails = (from sdPlan in DbContext.ScenarioDetail join t in DbContext.Teams on sdPlan.ParentID equals t.ActualCapacityScenarioId where t.Id == gTeamId select sdPlan).GroupBy(sd => sd.ExpenditureCategoryId).ToDictionary(k1 => k1.Key, g1 => g1.GroupBy(sd2 => sd2.WeekEndingDate).ToDictionary(k2 => k2.Key, g2 => g2.ToList())); var resultSet = GetPositionsSimpleMode(plannedCapacityDetails, actualCapacityDetails, categoriesInPlan, CalendarMaxDate, subq); return Json(new { Result = true, data = resultSet }, JsonRequestBehavior.AllowGet); } [HttpPost] public ActionResult GetCapacitySimpleMode(string teamId, bool planned) { Guid gTeamId = Guid.Empty; Guid.TryParse(teamId, out gTeamId); Dictionary>> plannedCapacityDetails = new Dictionary>>(); Dictionary>> actualCapacityDetails = new Dictionary>>(); IQueryable subq = (new List()).AsQueryable(); TeamManager teamManager = new TeamManager(DbContext); ExpenditureCategoryManager categoryManager = new ExpenditureCategoryManager(DbContext); var categoriesInPlan = teamManager.GetPlannedCapacityCategoriesIds(gTeamId); DateTime CalendarMaxDate = (from c in DbContext.FiscalCalendars where c.Type == 0 && c.AdjustingPeriod == false && c.NonWorking == 0 orderby c.StartDate descending select c.EndDate).FirstOrDefault(); if (!gTeamId.Equals(Guid.Empty)) { //first of all, we need to get categories + dates when category starts in either plan or reality - //these ranges allow us to work with this date range vs. entire fiscal calendar if (planned) subq = from sdPlan in DbContext.ScenarioDetail join t in DbContext.Teams on sdPlan.ParentID equals t.PlannedCapacityScenarioId where t.Id == gTeamId select sdPlan; else subq = from sdActual in DbContext.ScenarioDetail join t in DbContext.Teams on sdActual.ParentID equals t.ActualCapacityScenarioId where t.Id == gTeamId select sdActual; //In addition to that, we need a data set of details for both planned and actual capacities to check them for need increase or decrease while we iterating through FC' weeks if (planned) plannedCapacityDetails = (from sdPlan in DbContext.ScenarioDetail join t in DbContext.Teams on sdPlan.ParentID equals t.PlannedCapacityScenarioId where t.Id == gTeamId select sdPlan).GroupBy(sd => sd.ExpenditureCategoryId).ToDictionary(k1 => k1.Key, g1 => g1.GroupBy(sd2 => sd2.WeekEndingDate).ToDictionary(k2 => k2.Key, g2 => g2.ToList())); else plannedCapacityDetails = (from sdPlan in DbContext.ScenarioDetail join t in DbContext.Teams on sdPlan.ParentID equals t.ActualCapacityScenarioId where t.Id == gTeamId select sdPlan).GroupBy(sd => sd.ExpenditureCategoryId).ToDictionary(k1 => k1.Key, g1 => g1.GroupBy(sd2 => sd2.WeekEndingDate).ToDictionary(k2 => k2.Key, g2 => g2.ToList())); } var resultSet = GetPositionsSimpleMode(plannedCapacityDetails, actualCapacityDetails, categoriesInPlan, CalendarMaxDate, subq); return Json(new { Result = true, data = resultSet }, JsonRequestBehavior.AllowGet); } private List GetPositionsSimpleMode(Dictionary>> plannedCapacityDetails, Dictionary>> actualCapacityDetails, Guid?[] categoriesInPlan, DateTime CalendarMaxDate, IQueryable subq) { ExpenditureCategoryManager categoryManager = new ExpenditureCategoryManager(DbContext); List> allCategories = categoryManager.GetCategoriesWithUoMValues(); var categoriesDates = (from r in subq group r by r.ExpenditureCategoryId into rg select new { ExpenditureCategoryId = rg.Key, StartDate = rg.Min(z => z.WeekEndingDate), EndDate = rg.Max(z => z.WeekEndingDate) }).ToDictionary(c => c.ExpenditureCategoryId); List resultSet = new List(); foreach (var cat in allCategories) { PlannedCapacitySimpleCategory resultItem = new PlannedCapacitySimpleCategory(); resultItem.Id = cat.Item1; resultItem.Name = cat.Item2; resultItem.InPlan = categoriesInPlan.Contains(cat.Item1); var catDates = categoriesDates.ContainsKey(cat.Item1) ? categoriesDates[cat.Item1] : null; if (catDates != null) { resultItem.Positions = new List(); Dictionary> categoryActuals = actualCapacityDetails.ContainsKey(cat.Item1) ? actualCapacityDetails[cat.Item1] : new Dictionary>(); Dictionary> categoryPlans = plannedCapacityDetails.ContainsKey(cat.Item1) ? plannedCapacityDetails[cat.Item1] : new Dictionary>(); //AG: I did not generate single FiscalCalendar weeks set for all categories intentionally as once we change FiscalCalendar generation to on-the-fly calculation, it //does not make sense to keep redundant data for all categories as we can simply re-generate FC weeks for each of them var wkRanges = (from c in DbContext.FiscalCalendars where c.Type == (int)FiscalCalendarModel.FiscalYearType.Week && c.EndDate >= catDates.StartDate && c.EndDate <= catDates.EndDate && c.AdjustingPeriod == false && c.NonWorking == 0 orderby c.StartDate select new { StartDate = c.StartDate, EndDate = c.EndDate }).ToList(); decimal currentNeed = 0; decimal newNeed = 0; DateTime prevEndDate = DateTime.MinValue; Stack positionsStack = new Stack(); foreach (var range in wkRanges) { newNeed = 0; ScenarioDetail weeklyPlan = categoryPlans.ContainsKey(range.EndDate) ? categoryPlans[range.EndDate].FirstOrDefault() : null; ScenarioDetail weeklyActual = categoryActuals.ContainsKey(range.EndDate) ? categoryActuals[range.EndDate].FirstOrDefault() : null; if (weeklyPlan != null) newNeed = weeklyPlan.Quantity.HasValue ? Math.Round(weeklyPlan.Quantity.Value) : 0; if (weeklyActual != null) newNeed -= weeklyActual.Quantity.HasValue ? Math.Round(weeklyActual.Quantity.Value) : 0; if (currentNeed != newNeed) { #region Positions management if (currentNeed > 0) { if (newNeed > currentNeed) { //open new position(s) while (currentNeed < newNeed) { positionsStack.Push(new PlannedCapacityResourcePosition() { StartDate = range.StartDate, Need = 1 }); currentNeed += cat.Item3; } } else { //close latest position(s) while we are above the zero, then open new negative positions while (currentNeed > newNeed) { if (currentNeed > 0) { if (positionsStack.Count == 0) throw new InvalidOperationException("There is a bug somewhere in the planned capacity positions builder algorithm - the stack should not be empty here - positive position close"); PlannedCapacityResourcePosition pos = positionsStack.Pop(); if (pos.Need < 0) throw new InvalidOperationException("There is a bug somewhere in the planned capacity positions builder algorithm - previously opened position should be positive"); pos.EndDate = prevEndDate < CalendarMaxDate ? prevEndDate : (DateTime?)null; resultItem.Positions.Add(pos); } else { positionsStack.Push(new PlannedCapacityResourcePosition() { StartDate = range.StartDate, Need = -1 }); } currentNeed -= cat.Item3; } } } else if (currentNeed < 0) { if (newNeed < currentNeed) { //open new negative position(s) while (currentNeed > newNeed) { positionsStack.Push(new PlannedCapacityResourcePosition() { StartDate = range.StartDate, Need = -1 }); currentNeed -= cat.Item3; } } else { //close latest negative position(s) while we less than 0; open new positive position when we passed 0 while (currentNeed < newNeed) { if (currentNeed < 0) { if (positionsStack.Count == 0) throw new InvalidOperationException("There is a bug somewhere in the planned capacity positions builder algorithm - the stack should not be empty here - negative position close"); PlannedCapacityResourcePosition pos = positionsStack.Pop(); if (pos.Need > 0) throw new InvalidOperationException("There is a bug somewhere in the planned capacity positions builder algorithm - previously opened position should be negative"); pos.EndDate = prevEndDate < CalendarMaxDate ? prevEndDate : (DateTime?)null; resultItem.Positions.Add(pos); } else { positionsStack.Push(new PlannedCapacityResourcePosition() { StartDate = range.StartDate, Need = 1 }); } currentNeed += cat.Item3; } } } else //currentNeed == 0 { if (newNeed > 0) { //open new position(s) while (currentNeed < newNeed) { positionsStack.Push(new PlannedCapacityResourcePosition() { StartDate = range.StartDate, Need = 1 }); currentNeed += cat.Item3; } } else { //open new negative position(s) while (currentNeed > newNeed) { positionsStack.Push(new PlannedCapacityResourcePosition() { StartDate = range.StartDate, Need = -1 }); currentNeed -= cat.Item3; } } } currentNeed = newNeed; #endregion } prevEndDate = range.EndDate; } //we may have several positions still in stack as we retrieve only a significant portion of fiscal calendar. //we just need to put all of these positions into the list while (positionsStack.Count > 0) { PlannedCapacityResourcePosition pos = positionsStack.Pop(); pos.EndDate = wkRanges[wkRanges.Count - 1].EndDate < CalendarMaxDate ? wkRanges[wkRanges.Count - 1].EndDate : (DateTime?)null; resultItem.Positions.Add(pos); } } resultSet.Add(resultItem); } return resultSet; } [HttpPost] public ActionResult GetResourcesForFillDecrease(Guid teamId, Guid categoryId, DateTime dateToFire) { DateTime CalendarMaxDate = (from c in DbContext.FiscalCalendars where c.Type == 0 && c.AdjustingPeriod == false && c.NonWorking == 0 orderby c.StartDate descending select c.EndDate).FirstOrDefault(); DateTime dateToFireResourceGap = dateToFire.AddDays(-7); var resources = (from r in DbContext.PeopleResources where r.TeamId == teamId && r.ExpenditureCategoryId == categoryId && r.IsActiveEmployee && r.EndDate >= dateToFire && r.StartDate <= dateToFireResourceGap orderby r.LastName select new { Id = r.Id, Name = r.FirstName + " " + r.LastName, EndDate = r.EndDate != CalendarMaxDate ? r.EndDate : (DateTime?)null }); return Json(new { Result = true, data = resources }, JsonRequestBehavior.AllowGet); } [HttpPost] public ActionResult FillDecreaseSubmit(Guid resourceId, DateTime endDate) { PeopleResourcesManager resManager = new PeopleResourcesManager(DbContext); PeopleResourceController resController = new PeopleResourceController(); PeopleResourceModel model = (PeopleResourceModel)resManager.Load(resourceId); resController.RemoveCapacity(model, false); model.EndDate = endDate; resManager.Save(model); DbContext.SaveChanges(); //we need to reset weekly capacities then add capacity through resource controller method model = null; model = (PeopleResourceModel)resManager.Load(resourceId); resController.AddCapacity(model, false); return Json(new { Result = true }, JsonRequestBehavior.AllowGet); } private List GetUserName(List Ids) { var ac = new ApplicationDbContext(); var usermanager = new UserManager(new UserStore(ac)); var users = new Dictionary(); foreach(var Id in Ids) { var user = usermanager.FindById(Id); var lastName = user.LastName; while (users.Keys.Contains(lastName)) lastName = lastName + "_"; users.Add(lastName, user.FirstName + " " + user.LastName); } return users.OrderBy(x=>x.Key).Select(x=>x.Value).ToList(); } private IList GetTeams(int startIndex, int pageSize, ReadOnlyCollection sortedColumns, out int totalRecordCount, out int searchRecordCount, string searchString) { var query = from c in DbContext.Teams select new ListTeam() { Id = c.Id, Name = c.Name, Company = c.Company.Name, CostCenter = c.CreditDepartment.Name, CostCenterNumber = c.CreditDepartment.CreditNumber, ColumnCostCenter = c.CreditDepartment.Name + (string.IsNullOrEmpty(c.CreditDepartment.CreditNumber) ? string.Empty : " (" + c.CreditDepartment.CreditNumber + ")"), ReportTo = c.Contact.FirstName + " " + c.Contact.LastName, IsResourcesAttached = c.PeopleResources.Any() }; //filter if (!string.IsNullOrWhiteSpace(searchString)) { query = query.Where(c => c.Name.ToLower().Contains(searchString.ToLower())); } //sort foreach (var sortedColumn in sortedColumns) { switch (sortedColumn.PropertyName) { case "Id": if (sortedColumn.Direction == SortingDirection.Ascending) query = query.OrderBy(c => c.Id); else query = query.OrderByDescending(c => c.Id); break; case "Company": if (sortedColumn.Direction == SortingDirection.Ascending) query = query.OrderBy(c => c.Company); else query = query.OrderByDescending(c => c.Company); break; case "ColumnCostCenter": if (sortedColumn.Direction == SortingDirection.Ascending) query = query.OrderBy(c => c.CostCenter).ThenBy(c => c.CostCenterNumber); else query = query.OrderByDescending(c => c.CostCenter).ThenByDescending(c => c.CostCenterNumber); break; case "View": if (sortedColumn.Direction == SortingDirection.Ascending) query = query.OrderBy(c => c.GLAccount); else query = query.OrderByDescending(c => c.GLAccount); break; case "ReportTo": if (sortedColumn.Direction == SortingDirection.Ascending) query = query.OrderBy(c => c.ReportTo); else query = query.OrderByDescending(c => c.ReportTo); break; default: if (sortedColumn.Direction == SortingDirection.Ascending) query = query.OrderBy(c => c.Name); else query = query.OrderByDescending(c => c.Name); break; } } totalRecordCount = DbContext.Teams.Count(); searchRecordCount = query.Count(); var list = query.Skip(startIndex).Take(pageSize).ToList(); var usersList = new List(); foreach (var q in list) { usersList.Clear(); var users = DbContext.User2Team.Where(x => x.TeamId == q.Id).Select(x=>x.UserId).ToList(); usersList.AddRange(GetUserName(users.Select(user => user).ToList())); q.Users = string.Join("; ", usersList.ToArray());// usersList.ToArray()); //q.Users = q.Users.TrimEnd(new char[]{' ',';'});//q.Users.TrimEnd(' ').TrimEnd(';'); } return list; } // GET: /Team/Details/5 [AreaSecurityAttribute(area = Areas.Teams, level = AccessLevel.Read)] public ActionResult Board() { // SA. ENV-804. Changed teams sorting var model = new TeamboardModel(); var teams = new TeamManager(DbContext).GetTeamsByUser(Guid.Parse(User.Identity.GetID())); if (!string.IsNullOrEmpty(Request.QueryString["teamId"])) model.Teams.Add(teams.Where(x => x.Id.ToString() == Request.QueryString["teamId"].ToString()).FirstOrDefault()); else model.Teams = teams.OrderBy(x => x.Name).ToList(); model.CalendarMaxDate = (from c in DbContext.FiscalCalendars where c.Type == 0 && c.AdjustingPeriod == false && c.NonWorking == 0 orderby c.StartDate descending select c.EndDate).FirstOrDefault(); // SA. ENV-815. Get User Preferences SetUserSelectedTeamId(model); var user = new UsersCache().Value.FirstOrDefault(x => x.Id == new Guid(HttpContext.User.Identity.GetID())); if (user != null) ViewBag.IsUOMHours = !user.PreferredResourceAllocation; return View(model); } /// /// Set initially selected team according to user preferences /// /// /// SA. ENV-815 private void SetUserSelectedTeamId(TeamboardModel model) { Guid selectedTeamId = Guid.Empty; string userIdAsText = User.Identity.GetID(); Guid userId = new Guid(userIdAsText); string pageUrl = HttpContext.Request.Url.AbsolutePath; var prefRecords = DbContext.UserPreferences.Where(x => x.UserId.Equals(userId) && x.Url.Equals(pageUrl, StringComparison.InvariantCultureIgnoreCase) && x.Section.Equals("teamsBlock", StringComparison.InvariantCultureIgnoreCase)); if (prefRecords.Count() > 0) { string prefData = prefRecords.First().Data; JavaScriptSerializer ser = new JavaScriptSerializer(); var data = ser.Deserialize>(prefData); var selectedTeamPrefs = data.Where(x => x.Key.Equals("pageSelectedTeam", StringComparison.InvariantCultureIgnoreCase) && x.Value.Length > 0).ToList(); if (selectedTeamPrefs.Count > 0) Guid.TryParse(selectedTeamPrefs.First().Value, out selectedTeamId); } if (model.Teams.Count > 0) { List teamIds = model.Teams.Select(x => x.Id).ToList(); if (!selectedTeamId.Equals(Guid.Empty) && teamIds.Contains(selectedTeamId)) { model.SelectedTeamId = selectedTeamId; model.SelectedTeamName = model.Teams.Where(x => x.Id.Equals(selectedTeamId)).Select(x => x.Name).First(); } else { model.SelectedTeamId = teamIds.First(); model.SelectedTeamName = model.Teams.First().Name; } } } // SA. ENV-1254 [HttpPost] [AreaSecurity(area = Areas.Teams, level = AccessLevel.Read)] [ValidateJsonAntiForgeryToken] public ActionResult GetTeamsByExpenditureCategory(Guid id) { if ((id == null) || (id.Equals(Guid.Empty))) throw new ArgumentNullException("id"); Guid UserId = SecurityManager.GetUserPrincipal(); TeamManager mngr = new TeamManager(DbContext); IQueryable teams = mngr.GetTeamsByExpenditureCategory(id, UserId); Dictionary result = teams.ToDictionary(k => k.Id.ToString(), v => v.Name); return Json(result); } // GET: /Team/Edit/5 [AreaSecurityAttribute(area = Areas.Teams, level = AccessLevel.Write)] public ActionResult Edit(Guid? id) { ViewBag.TeamId = id.ToString(); var model = new TeamModel(); try { var manager = new TeamManager(DbContext); model = (TeamModel)manager.Load(id) ?? new TeamModel(); if (Guid.Empty.Equals(model.Id)) { model.UserId = new List(); } else { model.UserId = DbContext.User2Team.Where(x => x.TeamId == model.Id).ToList().Select(x => Guid.Parse(x.UserId)).ToList(); } return PartialView("_editTeam", model); } catch (BLLException blEx) { if (blEx.DisplayError) { //SetErrorScript(message: blEx.Message); ModelState.AddModelError(string.Empty, "Cannot save view. Try again later."); } else { LogException(blEx); //SetErrorScript(); ModelState.AddModelError(string.Empty, "Cannot save view. Try again later."); } } catch (Exception exception) { LogException(exception); SetErrorScript(); } HttpContext.Response.StatusCode = 500; HttpContext.Response.Clear(); return PartialView("_editTeam", model); } [HttpPost] [ValidateAntiForgeryToken] [AreaSecurityAttribute(area = Areas.Teams, level = AccessLevel.Write)] public ActionResult Edit(TeamModel model) { if (model == null || ContentLocker.IsLock("Team", model.Id.ToString(), User.Identity.Name)) return new HttpStatusCodeResult(HttpStatusCode.BadRequest); model.TrimStringProperties(); if (model.ReportToId.HasValue && model.ReportToId == Guid.Empty) model.ReportToId = null; if (ModelState.IsValid) { try { var context = new EnVisageEntities(); var manager = new TeamManager(context); manager.Save(model); context.SaveChanges(); (new ProjectAccessCache()).Invalidate(); ContentLocker.RemoveLock("Team", model.Id.ToString(), User.Identity.Name); return PartialView("_editTeam", model); } catch (BLLException blEx) // handle any system specific error { // display error message if required if (blEx.DisplayError) ModelState.AddModelError(string.Empty, blEx.Message); else // if display not requried then display modal form with general error message { LogException(blEx); //SetErrorScript(); ModelState.AddModelError(string.Empty, "Cannot save team. Try again later."); } } catch (Exception exception) // handle any unexpected error { LogException(exception); //SetErrorScript(); ModelState.AddModelError(string.Empty, "Cannot save team. Try again later."); } } // return empty model with validation messages (if any) HttpContext.Response.StatusCode = 500; HttpContext.Response.Clear(); return PartialView("_editTeam", model); } [HttpPost] [ValidateAntiForgeryToken] [AreaSecurityAttribute(area = Areas.Teams, level = AccessLevel.Write)] public ActionResult Delete(Guid deleteTeamId) { try { if (ContentLocker.IsLock("Team", deleteTeamId.ToString(), User.Identity.Name)) return new HttpStatusCodeResult(HttpStatusCode.BadRequest); var manager = new TeamManager(DbContext); var dbObj = manager.Load(deleteTeamId, false); if (dbObj == null) return HttpNotFound(); if (!dbObj.PeopleResources.Any()) { var capacityScenarioId = dbObj.PlannedCapacityScenarioId; var actualCapacityScenarioId = dbObj.ActualCapacityScenarioId; DbContext.User2Team.RemoveRange(DbContext.User2Team.Where(c2s => c2s.TeamId == dbObj.Id)); DbContext.Team2Scenario.RemoveRange(DbContext.Team2Scenario.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.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)); ContentLocker.RemoveLock("Team", dbObj.Id.ToString(), User.Identity.Name); return RedirectToAction("Index"); } //ModelState.AddModelError("error", "This team can't be deleted, because it's has some attached people resources."); SetErrorScript(message: "This team cannot be deleted, because it has some attached people resources."); } catch (BLLException blEx) { if (blEx.DisplayError) SetErrorScript(message: blEx.Message); else { LogException(blEx); SetErrorScript(); } } catch (Exception exception) { LogException(exception); SetErrorScript(); } return RedirectToAction("Index"); } /// /// /// /// /// /// /// /// /// A value indicating whether graph data is in hours or in resources. If null or true - Hours, false - Resources. /// [AreaSecurityAttribute(area = Areas.Teams, level = AccessLevel.Write)] public JsonResult ChangeCapacity(DateTime? startDate, DateTime? endDate, string expCat, string capacityValue, Guid teamId, bool? permanent) { if (string.IsNullOrEmpty(capacityValue) || capacityValue == "undefined" || capacityValue == "0") return null; var rateManager = new RateManager(DbContext); var expenditureCategoryId = new Guid(expCat); var val = int.Parse(capacityValue); var expCats = DbContext.ExpenditureCategory.Where(x => x.Id == expenditureCategoryId).ToDictionary(x => x.Id); var uomIds = expCats.Select(t => t.Value.UOMId); var uoms = DbContext.UOMs.Where(x => uomIds.Contains(x.Id)).ToDictionary(x => x.Id); var uomMultiplier = Utils.GetUOMMultiplier(expCats, uoms, expenditureCategoryId, false); val = (int)Math.Round((decimal)val / uomMultiplier); var teamManager = new TeamManager(DbContext); var team = teamManager.Load(teamId, false); var scenarioManager = new ScenarioManager(DbContext); Scenario scen = GetOrCreatePlannedCapacityScenario(team); List weekendings = null; if (permanent != null && (bool)permanent) { weekendings = (from c in DbContext.FiscalCalendars where c.Type == 0 && ((startDate >= c.StartDate && startDate <= c.EndDate) || (c.StartDate >= startDate)) orderby c.StartDate select c.EndDate).ToList(); } else { weekendings = (from c in DbContext.FiscalCalendars where c.Type == 0 && c.AdjustingPeriod == false && c.NonWorking == 0 && ((startDate >= c.StartDate && startDate <= c.EndDate) || (c.StartDate >= startDate && c.EndDate <= endDate) || (endDate >= c.StartDate && endDate <= c.EndDate)) orderby c.StartDate select c.EndDate).ToList(); } var rates = rateManager.GetRates(expenditureCategoryId, RateModel.RateType.Global); var sds = (from sd in DbContext.ScenarioDetail where sd.ParentID == team.PlannedCapacityScenarioId && sd.ExpenditureCategoryId == expenditureCategoryId && weekendings.Contains(sd.WeekEndingDate.Value) select sd).ToList(); foreach (var week in weekendings) { var sd = (from s in sds where s.WeekEndingDate == week select s).FirstOrDefault(); if (sd != null) { sd.Quantity += val; if (sd.Quantity < 0) sd.Quantity = 0; sd.Cost = sd.Quantity * rateManager.GetRateValue(rates, expenditureCategoryId, week); } else { if (val < 0) continue; var newSD = new ScenarioDetail { Id = Guid.NewGuid(), ParentID = scen.Id, Quantity = val }; newSD.Cost = newSD.Quantity * rateManager.GetRateValue(rates, expenditureCategoryId, week); newSD.ExpenditureCategoryId = expenditureCategoryId; newSD.WeekEndingDate = week; newSD.WeekOrdinal = 0; DbContext.ScenarioDetail.Add(newSD); } } DbContext.SaveChanges(); return Json(new { Result = true, data = weekendings }, JsonRequestBehavior.AllowGet); } [AreaSecurityAttribute(area = Areas.Teams, level = AccessLevel.Write)] public JsonResult ResetCapacity(DateTime? startDate, DateTime? endDate, string expCat, Guid teamId, bool? permanent) { RateManager rateManager = new RateManager(DbContext); TeamManager teamManager = new TeamManager(DbContext); Team team = teamManager.Load(teamId, false); Scenario scen = GetOrCreatePlannedCapacityScenario(team); if (team.ActualCapacityScenarioId == null || Guid.Empty == team.ActualCapacityScenarioId) return Json(new { Result = false, data = "Cannot reset capacity - Team does not have Actual Capacity scenario" }, JsonRequestBehavior.AllowGet); Scenario actualCapacityScenario = (new ScenarioManager(DbContext)).Load(team.ActualCapacityScenarioId, true); Guid expenditureCategoryId = !string.IsNullOrEmpty(expCat) ? new Guid(expCat) : Guid.Empty; Dictionary> teamResourcesByCategory = team.PeopleResources.Where(r => r.IsActiveEmployee).GroupBy(r => r.ExpenditureCategoryId).ToDictionary(key => key.Key, group => group.ToList()); Dictionary>> actualCapacityDetails = new Dictionary>>(); Dictionary>> existingPlannedDetails = new Dictionary>>(); List weekendings = null; if (permanent != null && (bool)permanent) weekendings = (from c in DbContext.FiscalCalendars.AsNoTracking() where c.Type == 0 && c.StartDate >= startDate && c.AdjustingPeriod == false && c.NonWorking == 0 orderby c.StartDate select c.EndDate).ToList(); else weekendings = (from c in DbContext.FiscalCalendars.AsNoTracking() where c.Type == 0 && c.StartDate >= startDate && c.EndDate <= endDate && c.AdjustingPeriod == false && c.NonWorking == 0 orderby c.StartDate select c.EndDate).ToList(); //List detailsToDelete; if (expenditureCategoryId != Guid.Empty) { actualCapacityDetails.Add(expenditureCategoryId, (from sd in DbContext.ScenarioDetail.AsNoTracking() where sd.ParentID == team.ActualCapacityScenarioId && sd.ExpenditureCategoryId == expenditureCategoryId && weekendings.Contains(sd.WeekEndingDate.Value) select sd) .GroupBy(sd => sd.WeekEndingDate).ToDictionary(key => key.Key, group => group.ToList())); existingPlannedDetails.Add(expenditureCategoryId, (from sd in DbContext.ScenarioDetail where sd.ParentID == team.PlannedCapacityScenarioId && sd.ExpenditureCategoryId == expenditureCategoryId && weekendings.Contains(sd.WeekEndingDate.Value) select sd) .GroupBy(sd => sd.WeekEndingDate).ToDictionary(key => key.Key, group => group.ToList())); //detailsToDelete = (from sd in DbContext.ScenarioDetail where sd.ParentID == team.PlannedCapacityScenarioId && sd.ExpenditureCategoryId == expenditureCategoryId && weekendings.Contains(sd.WeekEndingDate.Value) select sd).ToList(); } else { actualCapacityDetails = (from sd in DbContext.ScenarioDetail.AsNoTracking() where sd.ParentID == team.ActualCapacityScenarioId && weekendings.Contains(sd.WeekEndingDate.Value) select sd) .GroupBy(sd => sd.ExpenditureCategoryId.Value).ToDictionary(key => key.Key, group => group.GroupBy(x => x.WeekEndingDate).ToDictionary(k2 => k2.Key, g2 => g2.ToList())); existingPlannedDetails = (from sd in DbContext.ScenarioDetail where sd.ParentID == team.PlannedCapacityScenarioId && weekendings.Contains(sd.WeekEndingDate.Value) select sd) .GroupBy(sd => sd.ExpenditureCategoryId.Value).ToDictionary(key => key.Key, group => group.GroupBy(x => x.WeekEndingDate).ToDictionary(k2 => k2.Key, g2 => g2.ToList())); //detailsToDelete = (from sd in DbContext.ScenarioDetail where sd.ParentID == team.PlannedCapacityScenarioId && weekendings.Contains(sd.WeekEndingDate.Value) select sd).ToList(); } //get rates for actual capacity categories Guid[] actualCategoriesIds = actualCapacityDetails.Keys.ToArray(); //Dictionary categoriesUoMValues = (from ec in DbContext.ExpenditureCategory where actualCategoriesIds.Contains(ec.Id) select new { Id = ec.Id, Value = ec.UOM != null ? ec.UOM.UOMValue : 0 }).ToDictionary(x => x.Id, y => y.Value); Dictionary> categoriesRates = DbContext.Rates.AsNoTracking().Where(r => actualCategoriesIds.Contains(r.ExpenditureCategoryId) && r.Type == (int)RateModel.RateType.Global) .GroupBy(x => x.ExpenditureCategoryId).ToDictionary(key => key.Key, group => group.ToList()); //DbContext.ScenarioDetail.RemoveRange(detailsToDelete); List sdToAdd = new List(); foreach (Guid categoryId in actualCapacityDetails.Keys) { List rates = categoriesRates.ContainsKey(categoryId) ? categoriesRates[categoryId] : new List(); Dictionary> actualCapacities = actualCapacityDetails.ContainsKey(categoryId) ? actualCapacityDetails[categoryId] : new Dictionary>(); //We do not need UoM stuff here as we can just copy values from Actual Capacity week details into Planned Capacity week details Dictionary> existingDetails = existingPlannedDetails.ContainsKey(categoryId) ? existingPlannedDetails[categoryId] : new Dictionary>(); foreach (var week in weekendings) { ScenarioDetail existingD = existingDetails.ContainsKey(week) ? existingDetails[week].First() : null; decimal capacityValue = actualCapacities.ContainsKey(week) ? actualCapacities[week].FirstOrDefault().Quantity ?? 0 : 0; if (existingD != null) { if (capacityValue < 0) capacityValue = 0; var currrate = rates.FirstOrDefault(x => week >= x.StartDate && week <= x.EndDate); existingD.Quantity = capacityValue; existingD.Cost = existingD.Quantity * ((currrate != null) ? currrate.Rate1 : 0); DbContext.Entry(existingD).State = EntityState.Modified; //we also need to remove this existing detail record from the main dicionary to mark it as "processed". //we should delete all remaining items because they do not get into our "new" list of categories for planned capacity - //e.g. old planned capacity had more categories than actual capacity has existingPlannedDetails[categoryId].Remove(week); } else { if (capacityValue <= 0) continue; var newSD = new ScenarioDetail { Id = Guid.NewGuid(), ParentID = scen.Id, Quantity = capacityValue }; var currrate = rates.FirstOrDefault(x => week >= x.StartDate && week <= x.EndDate); newSD.Cost = newSD.Quantity * ((currrate != null) ? currrate.Rate1 : 0); newSD.ExpenditureCategoryId = categoryId; newSD.WeekEndingDate = week; newSD.WeekOrdinal = 0; sdToAdd.Add(newSD); } } } foreach (Guid catId in existingPlannedDetails.Keys) { foreach (DateTime? wk in existingPlannedDetails[catId].Keys) { DbContext.ScenarioDetail.RemoveRange(existingPlannedDetails[catId][wk]); } } DbContext.ScenarioDetail.AddRange(sdToAdd); //We do not save scenario details history here - it is too time consuming on big calendars DbContext.SaveChangesWithoutHistory(); //AG: Second SaveChanges call made intentionally because it is much slower to search for max scenariodetails date in both DB and DbContext.Local collections than just a single simple query //Plus, upper call does not save history and it is good to at least save the fact of scenario change scen.EndDate = (from sd in DbContext.ScenarioDetail where sd.ParentID == scen.Id select sd.WeekEndingDate).Max(); DbContext.Entry(scen).State = EntityState.Modified; DbContext.SaveChanges(); return Json(new { Result = true, data = "" }, JsonRequestBehavior.AllowGet); } private Scenario GetOrCreatePlannedCapacityScenario(Team team) { var scenarioManager = new ScenarioManager(DbContext); Scenario scen = null; if (team.PlannedCapacityScenarioId == null || team.PlannedCapacityScenarioId == Guid.Empty) { scen = new Scenario { Name = team.Name.Trim() + " Planned Capacity", Type = (int)ScenarioType.TeamPlannedCapacity, Id = Guid.NewGuid() }; team.PlannedCapacityScenarioId = scen.Id; DbContext.Scenarios.Add(scen); DbContext.SaveChanges(); } else scen = scenarioManager.Load(team.PlannedCapacityScenarioId); return scen; } [HttpPost] [AreaSecurityAttribute(area = Areas.Teams, level = AccessLevel.Read)] public ActionResult GetTeamResourcesInfo(Guid? teamId) { var model = new TeamboardModel { Teams = new List(new Team[] { new TeamManager(DbContext).Load(teamId) }), CalendarMaxDate = (from c in DbContext.FiscalCalendars where c.Type == 0 && c.AdjustingPeriod == false && c.NonWorking == 0 orderby c.StartDate descending select c.EndDate).FirstOrDefault() }; return PartialView("~/Views/PeopleResource/_resourcesList.cshtml", model); } // SA. ENV-492 class TeamResourceModel { public Guid Id; public string Name; public bool IsActive; public string StartDate; public string EndDate; public string ExpenditureCategory; } [HttpPost] [AreaSecurityAttribute(area = Areas.Teams, level = AccessLevel.Read)] public ActionResult GetTeamResources(Guid teamId) { if (teamId.Equals(Guid.Empty)) return new HttpStatusCodeResult(HttpStatusCode.BadRequest); TeamManager mngr = new TeamManager(DbContext); Team team = mngr.Load(teamId); var calendarMaxDate = (from c in DbContext.FiscalCalendars where c.Type == 0 && c.AdjustingPeriod == false && c.NonWorking == 0 orderby c.StartDate descending select c.EndDate).FirstOrDefault(); List resources = team.PeopleResources.OrderBy(x => x.LastName).Select(t => new TeamResourceModel() { Id = t.Id, Name = t.FirstName + " " + t.LastName, IsActive = t.IsActiveEmployee, StartDate = t.StartDate.ToShortDateString(), EndDate = t.EndDate != calendarMaxDate ? t.EndDate.ToShortDateString() : "", ExpenditureCategory = t.ExpenditureCategory.GetView().ExpCategoryWithCcName }).ToList(); return Json(resources); } private List FillMissingWeeends(List list, IEnumerable weekEnds) { if (list.Count < weekEnds.Count()) { var result = weekEnds.Except(list.Select(x => x[0])); list.AddRange(result.Select(x => new long[] { x, 0 })); } return list.OrderBy(x => x[0]).ToList(); } } }