EnVisageOnline/Main-RMO/Source/EnVisage/Controllers/TeamController.cs

1028 lines
54 KiB
C#

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
{
/// <summary>
/// An UI representation of Team to be displayed as list items
/// </summary>
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; }
}
/// <summary>
/// An UI representation of category and open positions for planned capacity simple mode
/// </summary>
public class PlannedCapacitySimpleCategory
{
public Guid Id { get; set; }
public string Name { get; set; }
public bool InPlan { get; set; }
public List<PlannedCapacityResourcePosition> Positions { get; set; }
}
public class PlannedCapacityResourcePosition
{
public int Need { get; set; }
public DateTime StartDate { get; set; }
public DateTime? EndDate { get; set; }
}
/// <summary>
/// GET: /Teams/
/// </summary>
/// <returns>Empty view</returns>
[HttpGet]
[AreaSecurityAttribute(area = Areas.Teams, level = AccessLevel.Read)]
public ActionResult Index()
{
return View();
}
/// <summary>
/// Returns JSON teams list with filters and sort for jQuery DataTables
/// </summary>
[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<Guid?, Dictionary<DateTime?, List<ScenarioDetail>>> 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<Guid?, Dictionary<DateTime?, List<ScenarioDetail>>> 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<Guid?, Dictionary<DateTime?, List<ScenarioDetail>>> plannedCapacityDetails =
new Dictionary<Guid?, Dictionary<DateTime?, List<ScenarioDetail>>>();
Dictionary<Guid?, Dictionary<DateTime?, List<ScenarioDetail>>> actualCapacityDetails =
new Dictionary<Guid?, Dictionary<DateTime?, List<ScenarioDetail>>>();
IQueryable<ScenarioDetail> subq = (new List<ScenarioDetail>()).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<PlannedCapacitySimpleCategory> GetPositionsSimpleMode(Dictionary<Guid?, Dictionary<DateTime?, List<ScenarioDetail>>> plannedCapacityDetails, Dictionary<Guid?, Dictionary<DateTime?, List<ScenarioDetail>>> actualCapacityDetails, Guid?[] categoriesInPlan, DateTime CalendarMaxDate, IQueryable<ScenarioDetail> subq)
{
ExpenditureCategoryManager categoryManager = new ExpenditureCategoryManager(DbContext);
List<Tuple<Guid, string, decimal>> 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<PlannedCapacitySimpleCategory> resultSet = new List<PlannedCapacitySimpleCategory>();
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<PlannedCapacityResourcePosition>();
Dictionary<DateTime?, List<ScenarioDetail>> categoryActuals = actualCapacityDetails.ContainsKey(cat.Item1) ?
actualCapacityDetails[cat.Item1] : new Dictionary<DateTime?, List<ScenarioDetail>>();
Dictionary<DateTime?, List<ScenarioDetail>> categoryPlans = plannedCapacityDetails.ContainsKey(cat.Item1) ?
plannedCapacityDetails[cat.Item1] : new Dictionary<DateTime?, List<ScenarioDetail>>();
//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<PlannedCapacityResourcePosition> positionsStack = new Stack<PlannedCapacityResourcePosition>();
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<string> GetUserName(List<string> Ids)
{
var ac = new ApplicationDbContext();
var usermanager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(ac));
var users = new Dictionary<string, string>();
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<ListTeam> GetTeams(int startIndex,
int pageSize,
ReadOnlyCollection<SortedColumn> 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<string>();
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);
}
/// <summary>
/// Set initially selected team according to user preferences
/// </summary>
/// <param name="model"></param>
/// <remarks>SA. ENV-815</remarks>
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<List<PreferencesItem>>(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<Guid> 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<Team> teams = mngr.GetTeamsByExpenditureCategory(id, UserId);
Dictionary<string, string> 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<Guid>();
}
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");
}
/// <summary>
///
/// </summary>
/// <param name="startDate"></param>
/// <param name="endDate"></param>
/// <param name="expCat"></param>
/// <param name="capacityValue"></param>
/// <param name="teamId"></param>
/// <param name="isUOMHours">A value indicating whether graph data is in hours or in resources. If <b>null</b> or <b>true</b> - Hours, false - Resources.</param>
/// <returns></returns>
[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<DateTime> 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<Guid, List<PeopleResource>> teamResourcesByCategory = team.PeopleResources.Where(r => r.IsActiveEmployee).GroupBy(r => r.ExpenditureCategoryId).ToDictionary(key => key.Key, group => group.ToList());
Dictionary<Guid, Dictionary<DateTime?, List<ScenarioDetail>>> actualCapacityDetails = new Dictionary<Guid, Dictionary<DateTime?, List<ScenarioDetail>>>();
Dictionary<Guid, Dictionary<DateTime?, List<ScenarioDetail>>> existingPlannedDetails = new Dictionary<Guid, Dictionary<DateTime?, List<ScenarioDetail>>>();
List<DateTime> 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<ScenarioDetail> 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<Guid, decimal> 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<Guid, List<Rate>> 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<ScenarioDetail> sdToAdd = new List<ScenarioDetail>();
foreach (Guid categoryId in actualCapacityDetails.Keys)
{
List<Rate> rates = categoriesRates.ContainsKey(categoryId) ? categoriesRates[categoryId] : new List<Rate>();
Dictionary<DateTime?, List<ScenarioDetail>> actualCapacities = actualCapacityDetails.ContainsKey(categoryId) ?
actualCapacityDetails[categoryId] : new Dictionary<DateTime?, List<ScenarioDetail>>();
//We do not need UoM stuff here as we can just copy values from Actual Capacity week details into Planned Capacity week details
Dictionary<DateTime?, List<ScenarioDetail>> existingDetails = existingPlannedDetails.ContainsKey(categoryId) ?
existingPlannedDetails[categoryId] : new Dictionary<DateTime?, List<ScenarioDetail>>();
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<Team>(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<TeamResourceModel> 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<long[]> FillMissingWeeends(List<long[]> list, IEnumerable<long> 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();
}
}
}