using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data.Entity;
using System.Linq;
using System.Net;
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 System.Web.Script.Serialization;
using System.Text;
using EnVisage.Code.Validation;
using System.Threading.Tasks;
namespace EnVisage.Controllers
{
public class TeamController : BaseController
{
#region Actions
///
/// GET: /Teams/
///
/// Empty view
[HttpGet]
[AreaSecurity(area = Areas.Teams, level = AccessLevel.Read)]
public ActionResult Index()
{
return View();
}
///
/// Returns JSON teams list with filters and sort for jQuery DataTables
///
[HttpPost]
[AreaSecurity(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);
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, true);
return Json(new { Result = true, data = resultSet }, JsonRequestBehavior.AllowGet);
}
[HttpPost]
public ActionResult GetCapacitySimpleMode(string teamId, bool planned)
{
Guid gTeamId;
Guid.TryParse(teamId, out gTeamId);
Dictionary>> plannedCapacityDetails =
new Dictionary>>();
Dictionary>> actualCapacityDetails =
new Dictionary>>();
IQueryable subq = (new List()).AsQueryable();
TeamManager teamManager = new TeamManager(DbContext);
var categoriesInPlan = teamManager.GetPlannedCapacityCategoriesIds(gTeamId);
DateTime calendarMaxDate = DbContext.FiscalCalendars
.Where(c => c.Type == 0 && c.AdjustingPeriod == false && c.NonWorking == 0)
.OrderByDescending(c => c.StartDate)
.Select(c => 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);
}
[HttpPost]
public ActionResult GetResourcesForFillDecrease(Guid teamId, Guid categoryId, DateTime dateToFire)
{
// Get all resources in the team
PeopleResourcesManager mngr = new PeopleResourcesManager(this.DbContext);
var resources = mngr.LoadPeopleResourcesByTeamNonStrict(teamId, false, dateToFire.ToUniversalTime());
// Filter resources by EC
var resourcesOfEC = resources.ToList();
resourcesOfEC.RemoveAll(x => (x.StartDate >= dateToFire) || (x.EndDate.HasValue && (x.EndDate.Value <= dateToFire)));
resourcesOfEC.RemoveAll(x => !x.ExpenditureCategoryId.Equals(categoryId));
resourcesOfEC.RemoveAll(x => !x.IsActiveEmployee);
var result = resourcesOfEC.OrderBy(x => x.LastName).Select(x => new
{
Id = x.Id,
Name = x.FirstName + " " + x.LastName,
EndDate = !x.PermanentResource ? x.EndDate.Value : (DateTime?)null
});
return Json(new { Result = true, data = result }, JsonRequestBehavior.AllowGet);
}
[HttpPost]
public ActionResult FillDecreaseSubmit(Guid? resourceId, DateTime endDate)
{
PeopleResourcesManager resManager = new PeopleResourcesManager(DbContext);
using (var transaction = DbContext.Database.BeginTransaction())
{
var transactionId = DbContext.GetClientConnectionId();
var userId = User.Identity.GetID();
try
{
if (resourceId != null)
{
var resourceId1 = (Guid)resourceId;
PeopleResourceModel model = resManager.LoadPeopleResource(resourceId1, DateTime.UtcNow.Date,
false, false, false);
model.PermanentResource = false;
model.EndDate = endDate;
model.ChangeCapacity = false;
resManager.Save(model);
DbContext.SaveChanges();
transaction.Commit();
Task.Run(() => AuditProxy.CommitHistoryChanges(transactionId, userId));
return Json(new { Result = true }, JsonRequestBehavior.AllowGet);
}
throw new ArgumentException("Resource to fill capacity decrease can't be null.");
}
catch (Exception ex)
{
transaction.Rollback();
AuditProxy.ClearHistoryChanges(transactionId);
LogException(ex);
return Json(new { Result = false }, JsonRequestBehavior.AllowGet);
}
}
}
[AreaSecurity(area = Areas.Teams, level = AccessLevel.Read)]
public ActionResult Board()
{
// SA. ENV-804. Changed teams sorting
var model = new TeamboardModel();
var teamMngr = new TeamManager(DbContext);
// var teams = new TeamManager(DbContext).LoadTeamsWithResourcesByUser(Guid.Parse(User.Identity.GetID()));
var teams = teamMngr.GetTeamsByUser(Guid.Parse(User.Identity.GetID())).Select(t => t.TeamId);
if (!string.IsNullOrEmpty(Request.QueryString["teamId"]))
{
var tm = teams.FirstOrDefault(x => x.ToString() == Request.QueryString["teamId"].ToString());
if (tm.Equals(Guid.Empty))
return RedirectToAccessDenied();
var teamData = teamMngr.Load(tm, true);
model.Teams.Add((TeamWithResourcesModel)teamData);
}
else
{
var teamsData = teamMngr.LoadTeams(teams);
teamsData = teamsData.OrderBy(x => x.Name);
model.Teams = teamsData.ToList().Select(x => (TeamWithResourcesModel)x).ToList();
}
model.CalendarMaxDate = (from c in DbContext.FiscalCalendars where c.Type == (int)FiscalCalendarModel.FiscalYearType.Week && c.AdjustingPeriod == false && c.NonWorking == 0 orderby c.StartDate descending select c.EndDate).FirstOrDefault();
SetUserSelectedTeamId(model);
return View(model);
}
[HttpPost]
[AreaSecurity(area = Areas.Teams, level = AccessLevel.Read)]
[ValidateJsonAntiForgeryToken]
public ActionResult GetTeamsByExpenditureCategory(Guid id)
{
if ((id == null) || (id.Equals(Guid.Empty)))
throw new ArgumentNullException(nameof(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);
}
[HttpGet]
[AreaSecurity(area = Areas.Teams, level = AccessLevel.Write)]
public ActionResult Edit(Guid? id)
{
ViewBag.TeamId = id.ToString();
var model = new TeamModel();
try
{
if (id.HasValue)
{
var manager = new TeamManager(DbContext);
model = manager.LoadTeamModel(id.Value) ?? new TeamModel();
}
if (Guid.Empty.Equals(model.Id))
{
model.UserId = new List();
model.WorkFlowContacts = new List();
model.NotificationContacts = new List();
model.NotificationWorkFlowStates = new List();
}
else
{
model.UserId = DbContext.User2Team.Where(x => x.TeamId == model.Id).ToList().Select(x => Guid.Parse(x.UserId)).ToList();
model.WorkFlowContacts = (new WorkFlowManager(this.DbContext)).GetContactList(model.Id, WorkFlowContactNotificationType.None);
model.NotificationContacts = (new WorkFlowManager(this.DbContext)).GetContactList(model.Id, WorkFlowContactNotificationType.TeamScenarioAdd);
model.NotificationWorkFlowStates = (new WorkFlowManager(this.DbContext)).GetNotificationStates(model.Id, WorkFlowContactNotificationType.TeamScenarioAdd);
}
return PartialView("_editTeam", model);
}
catch (Exception exception)
{
LogException(exception);
}
return new HttpStatusCodeResult(HttpStatusCode.InternalServerError);
}
[HttpPost]
[ValidateAjax]
[ValidateAntiForgeryToken]
[AreaSecurity(area = Areas.Teams, level = AccessLevel.Write)]
public ActionResult Edit(TeamModel model)
{
if (model != null && model.Id != Guid.Empty && ContentLocker.IsLock("Team", model.Id.ToString(), User.Identity.GetUserName()))
{
ModelState.AddModelError(string.Empty, @"This team is currently being updated by another user. Please attempt your edit again later.");
return new FailedJsonResult(ModelState);
}
try
{
if (model == null)
throw new ArgumentNullException(nameof(model));
model.TrimStringProperties();
if (model.ReportToId.HasValue && model.ReportToId == Guid.Empty)
model.ReportToId = null;
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.GetUserName());
return new SuccessJsonResult();
}
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);
ModelState.AddModelError(string.Empty, @"Cannot save team. Try again later.");
}
}
catch (Exception exception) // handle any unexpected error
{
LogException(exception);
ModelState.AddModelError(string.Empty, @"Cannot save team. Try again later.");
}
return new FailedJsonResult(ModelState);
}
[HttpPost]
[ValidateAntiForgeryToken]
[AreaSecurity(area = Areas.Teams, level = AccessLevel.Write)]
public ActionResult Delete(Guid deleteTeamId)
{
try
{
if (deleteTeamId != Guid.Empty && ContentLocker.IsLock("Team", deleteTeamId.ToString(), User.Identity.GetUserName()))
{
ModelState.AddModelError(string.Empty, @"This team is currently being updated by another user. Please attempt your edit again later.");
return new FailedJsonResult(ModelState);
}
var manager = new TeamManager(DbContext);
var dbObj = manager.Load(deleteTeamId, false);
if (dbObj == null)
throw new InvalidOperationException(
$"System cannot delete team {deleteTeamId} because it does not exist");
if (!DbContext.VW_TeamResource.Any(t => t.TeamId == dbObj.Id))
{
manager.Delete(dbObj.Id);
ContentLocker.RemoveLock("Team", dbObj.Id.ToString(), User.Identity.GetUserName());
return new SuccessJsonResult();
}
ModelState.AddModelError(string.Empty, @"This team cannot be deleted, because it has some attached people resources.");
}
catch (BLLException blEx)
{
if (blEx.DisplayError)
ModelState.AddModelError(string.Empty, blEx.Message);
else
{
LogException(blEx);
ModelState.AddModelError(string.Empty, @"Cannot delete team. Try again later.");
}
}
catch (Exception exception)
{
LogException(exception);
ModelState.AddModelError(string.Empty, @"Cannot delete team. Try again later.");
}
return new FailedJsonResult(ModelState);
}
[AreaSecurity(area = Areas.Teams, level = AccessLevel.Write)]
public JsonResult ChangeCapacity(DateTime? startDate, DateTime? endDate, Guid expenditureCategoryId, int capacityValue, Guid teamId, bool? permanent)
{
#if DEBUG
var watch1 = new System.Diagnostics.Stopwatch();
watch1.Start();
Logger.Debug("Start ChangeCapacity");
#endif
var sb = new StringBuilder();
sb.AppendLine(
$"TeamController.ChangeCapacity method. teamId:{teamId}, startDate:{startDate}, endDate{endDate}, ExpCatId:{expenditureCategoryId}, permanent:{permanent}, capacityValue: {capacityValue}");
Logger.Debug(sb);
if (capacityValue == 0)
return null;
var rateManager = new RateManager(DbContext);
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);
capacityValue = (int)Math.Round(capacityValue / uomMultiplier);
var teamManager = new TeamManager(DbContext);
var team = teamManager.LoadTeamWithResourcesById(teamId);
Scenario plannedCapacityScenario = GetOrCreatePlannedCapacityScenario(team);
#if DEBUG
watch1.Stop();
System.Diagnostics.Debug.WriteLine($"ChangeCapacity Start Load has taken {watch1.ElapsedMilliseconds} ms");
Logger.Debug($"ChangeCapacity Start Load {watch1.ElapsedMilliseconds} ms");
watch1 = new System.Diagnostics.Stopwatch();
watch1.Start();
#endif
List weekendings;
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();
}
#if DEBUG
watch1.Stop();
System.Diagnostics.Debug.WriteLine($"ChangeCapacity List weekendings; has taken {watch1.ElapsedMilliseconds} ms");
Logger.Debug($"ChangeCapacity List weekendings; {watch1.ElapsedMilliseconds} ms");
watch1 = new System.Diagnostics.Stopwatch();
watch1.Start();
#endif
var rates = rateManager.GetRates(new List {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();
var scenarioDetailToAddCollection = new List();
var scenarioDetailToDeleteCollection = new List();
foreach (var week in weekendings)
{
var sd = (from s in sds where s.WeekEndingDate == week select s).FirstOrDefault();
if (sd != null)
{
sd.Quantity += capacityValue;
Logger.Debug("/Team/ChangeCapacity method. TeamId:{0}, capacityValue: {1}, sd.Quantity: {2}", teamId, capacityValue, sd.Quantity);
if (sd.Quantity <= 0)
{
scenarioDetailToDeleteCollection.Add(sd);
}
else
sd.Cost = sd.Quantity * rateManager.GetRateValue(rates, expenditureCategoryId, week);
}
else
{
if (capacityValue <= 0)
continue;
Logger.Debug("/Team/ChangeCapacity method. TeamId:{0}, capacityValue: {1}", teamId, capacityValue);
var newSd = new ScenarioDetail { Id = Guid.NewGuid(), ParentID = plannedCapacityScenario.Id, Quantity = capacityValue };
newSd.Cost = newSd.Quantity * rateManager.GetRateValue(rates, expenditureCategoryId, week);
newSd.ExpenditureCategoryId = expenditureCategoryId;
newSd.WeekEndingDate = week;
newSd.WeekOrdinal = 0;
scenarioDetailToAddCollection.Add(newSd);
}
}
DbContext.ScenarioDetail.AddRange(scenarioDetailToAddCollection);
DbContext.ScenarioDetail.RemoveRange(scenarioDetailToDeleteCollection);
#if DEBUG
watch1.Stop();
System.Diagnostics.Debug.WriteLine($"ChangeCapacity foreach (var week in weekendings) has taken {watch1.ElapsedMilliseconds} ms");
Logger.Debug($"ChangeCapacity foreach (var week in weekendings) {watch1.ElapsedMilliseconds} ms");
watch1 = new System.Diagnostics.Stopwatch();
watch1.Start();
#endif
DbContext.BulkSaveChanges();
// taken from ResetCapacity action method, original comment for reason of the second call is:
// 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
plannedCapacityScenario.EndDate = (from sd in DbContext.ScenarioDetail where sd.ParentID == plannedCapacityScenario.Id select sd.WeekEndingDate).Max();
if (plannedCapacityScenario.EndDate.HasValue)
plannedCapacityScenario.EndDate = plannedCapacityScenario.EndDate.Value.Date;
DbContext.SaveChanges();
#if DEBUG
watch1.Stop();
System.Diagnostics.Debug.WriteLine($"ChangeCapacity All has taken {watch1.ElapsedMilliseconds} ms");
Logger.Debug($"ChangeCapacity All {watch1.ElapsedMilliseconds} ms");
#endif
return Json(new { Result = true, data = weekendings }, JsonRequestBehavior.AllowGet);
}
[AreaSecurity(area = Areas.Teams, level = AccessLevel.Write)]
public JsonResult ResetCapacity(DateTime? startDate, DateTime? endDate, Guid? expenditureCategoryId, Guid teamId, bool? permanent)
{
#if DEBUG
var watch1 = new System.Diagnostics.Stopwatch();
watch1.Start();
Logger.Debug("Start ResetCapacity");
#endif
var sb = new StringBuilder();
sb.AppendLine($"TeamController.ResetCapacity method. teamId:{teamId}, startDate:{startDate}, endDate{endDate}, ExpCatId:{expenditureCategoryId}, permanent:{permanent}");
Logger.Debug(sb);
var rateManager = new RateManager(DbContext);
var teamManager = new TeamManager(DbContext);
var team = teamManager.LoadTeamWithResourcesById(teamId);
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 plannedCapacityScenario = GetOrCreatePlannedCapacityScenario(team);
var actualCapacityDetails = new Dictionary>>();
var existingPlannedDetails = new Dictionary>>();
List weekendings;
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();
if (expenditureCategoryId.HasValue)
{
actualCapacityDetails.Add(expenditureCategoryId.Value,
(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.Value,
(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()));
}
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()));
}
//get rates for actual capacity categories
var actualCategoriesIds = actualCapacityDetails.Keys.ToArray();
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());
var scenarioDetailToDeleteCollection = new List();
var sdToAdd = new List();
foreach (Guid categoryId in actualCapacityDetails.Keys)
{
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)
{
existingD.Quantity = capacityValue;
existingD.Cost = existingD.Quantity * rateManager.GetRateValue(categoriesRates, categoryId, week);
}
else
{
scenarioDetailToDeleteCollection.Add(existingD);
}
//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 = plannedCapacityScenario.Id,
Quantity = capacityValue,
Cost = capacityValue * rateManager.GetRateValue(categoriesRates, categoryId, week),
ExpenditureCategoryId = categoryId,
WeekEndingDate = week,
WeekOrdinal = 0
};
sdToAdd.Add(newSD);
}
}
}
var removedDetails = scenarioDetailToDeleteCollection;
foreach (var categoryId in existingPlannedDetails.Keys)
foreach (var weekEnding in existingPlannedDetails[categoryId].Keys)
removedDetails.AddRange(existingPlannedDetails[categoryId][weekEnding]);
DbContext.ScenarioDetail.RemoveRange(removedDetails);
DbContext.ScenarioDetail.AddRange(sdToAdd);
//We do not save scenario details history here - it is too time consuming on big calendars
DbContext.BulkSaveChanges();
//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
plannedCapacityScenario.EndDate = (from sd in DbContext.ScenarioDetail where sd.ParentID == plannedCapacityScenario.Id select sd.WeekEndingDate).Max();
if (plannedCapacityScenario.EndDate.HasValue)
plannedCapacityScenario.EndDate = plannedCapacityScenario.EndDate.Value.Date;
DbContext.SaveChanges();
#if DEBUG
watch1.Stop();
System.Diagnostics.Debug.WriteLine($"ResetCapacity All has taken {watch1.ElapsedMilliseconds} ms");
Logger.Debug($"ResetCapacity All {watch1.ElapsedMilliseconds} ms");
#endif
return Json(new { Result = true, data = "" }, JsonRequestBehavior.AllowGet);
}
[HttpPost]
[AreaSecurity(area = Areas.Teams, level = AccessLevel.Read)]
public ActionResult GetTeamResources(Guid teamId)
{
if (teamId.Equals(Guid.Empty))
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
PeopleResourcesManager mngr = new PeopleResourcesManager(DbContext);
List resources =
mngr.LoadPeopleResourcesByTeamNonStrict(teamId, true, DateTime.UtcNow).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?.ToShortDateString() ?? "",
ExpenditureCategory = t.ExpenditureCategory.GetView().ExpCategoryWithCcName,
ResourceScenarioAllocationsInfo = t.ResourceScenarioAllocationsInfo,
ResourceActualsInfo = t.ResourceActualsInfo,
ResourceMixAllocationsInfo = t.ResourceMixAllocationsInfo
}).ToList();
return Json(resources);
}
[HttpPost]
[ValidateJsonAntiForgeryToken]
public ActionResult GetTeamsById(List ids)
{
try
{
var teamManager = new TeamManager(DbContext);
var fiscalCalendar = (new FiscalCalendarManager(DbContext)).GetFiscalCalendar(FiscalCalendarModel.FiscalYearType.Week, null, null, true, null, false);
var teams = teamManager.GetTeamsByUserFiltered(User.Identity.GetID(), ids, null, null);
var teamModels = teamManager.GetTeamsInfo(teams, fiscalCalendar)
.ToDictionary(x => x.Id.ToString());
return BigJson(teamModels);
}
catch (Exception exception) // handle any unexpected error
{
LogException(exception);
}
return new HttpStatusCodeResult(HttpStatusCode.InternalServerError);
}
#endregion
#region Private Methods
private List GetPositionsSimpleMode(Dictionary>> plannedCapacityDetails, Dictionary>> actualCapacityDetails, Guid?[] categoriesInPlan, DateTime CalendarMaxDate, IQueryable subq, bool withoutSupperExpCat = false)
{
ExpenditureCategoryManager categoryManager = new ExpenditureCategoryManager(DbContext);
var allCategories = categoryManager.GetExpenditureDetails(!withoutSupperExpCat);
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 catId in allCategories.Keys)
{
var cat = allCategories[catId];
PlannedCapacitySimpleCategory resultItem = new PlannedCapacitySimpleCategory
{
Id = cat.ExpenditureCategoryId,
Name = cat.ExpenditureCategoryName,
InPlan = categoriesInPlan.Contains(cat.ExpenditureCategoryId)
};
var catDates = categoriesDates.ContainsKey(cat.ExpenditureCategoryId) ? categoriesDates[cat.ExpenditureCategoryId] : null;
if (catDates != null)
{
resultItem.Positions = new List();
Dictionary> categoryActuals = actualCapacityDetails.ContainsKey(cat.ExpenditureCategoryId) ?
actualCapacityDetails[cat.ExpenditureCategoryId] : new Dictionary>();
Dictionary> categoryPlans = plannedCapacityDetails.ContainsKey(cat.ExpenditureCategoryId) ?
plannedCapacityDetails[cat.ExpenditureCategoryId] : 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 { c.StartDate, 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.UOMValue;
}
}
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.UOMValue;
}
}
}
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.UOMValue;
}
}
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.UOMValue;
}
}
}
else //currentNeed == 0
{
if (newNeed > 0)
{
//open new position(s)
while (currentNeed < newNeed)
{
positionsStack.Push(new PlannedCapacityResourcePosition()
{
StartDate = range.StartDate,
Need = 1
});
currentNeed += cat.UOMValue;
}
}
else
{
//open new negative position(s)
while (currentNeed > newNeed)
{
positionsStack.Push(new PlannedCapacityResourcePosition()
{
StartDate = range.StartDate,
Need = -1
});
currentNeed -= cat.UOMValue;
}
}
}
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;
}
private IList GetTeams(int startIndex,
int pageSize,
ReadOnlyCollection sortedColumns,
out int totalRecordCount,
out int searchRecordCount,
string searchString)
{
var query = DbContext.Teams.Select(team => new ListTeam
{
Id = team.Id,
Name = team.Name,
Company = team.Company.Name,
CostCenter = team.CreditDepartment.Name,
CostCenterNumber = team.CreditDepartment.CreditNumber,
ColumnCostCenter = team.CreditDepartment.Name + (string.IsNullOrEmpty(team.CreditDepartment.CreditNumber) ?
string.Empty : " (" + team.CreditDepartment.CreditNumber + ")"),
ReportTo = team.Contact.FirstName + " " + team.Contact.LastName,
IsResourcesAttached = DbContext.PeopleResource2Team.Any(s => s.TeamId == team.Id),
Users = DbContext.User2Team
.Where(s => s.TeamId == team.Id)
.Select(x => x.AspNetUser.FirstName + " " + x.AspNetUser.LastName)
.ToList()
});
//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":
query = sortedColumn.Direction == SortingDirection.Ascending ? query.OrderBy(c => c.Id) : query.OrderByDescending(c => c.Id);
break;
case "Company":
query = sortedColumn.Direction == SortingDirection.Ascending ? query.OrderBy(c => c.Company) : query.OrderByDescending(c => c.Company);
break;
case "ColumnCostCenter":
query = sortedColumn.Direction == SortingDirection.Ascending ? query.OrderBy(c => c.CostCenter).ThenBy(c => c.CostCenterNumber) : query.OrderByDescending(c => c.CostCenter).ThenByDescending(c => c.CostCenterNumber);
break;
case "View":
query = sortedColumn.Direction == SortingDirection.Ascending ? query.OrderBy(c => c.GLAccount) : query.OrderByDescending(c => c.GLAccount);
break;
case "ReportTo":
query = sortedColumn.Direction == SortingDirection.Ascending ? query.OrderBy(c => c.ReportTo) : query.OrderByDescending(c => c.ReportTo);
break;
default:
query = sortedColumn.Direction == SortingDirection.Ascending ? query.OrderBy(c => c.Name) : query.OrderByDescending(c => c.Name);
break;
}
}
totalRecordCount = DbContext.Teams.Count();
searchRecordCount = query.Count();
return query.Skip(startIndex).Take(pageSize).ToList();
}
///
/// 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.Any())
{
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.Id = selectedTeamId;
model.SelectedTeamName = model.Teams.Where(x => x.Id.Equals(selectedTeamId)).Select(x => x.Name).First();
}
else
{
model.Id = teamIds.First();
model.SelectedTeamName = model.Teams.First().Name;
}
}
}
//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();
//}
private Scenario GetOrCreatePlannedCapacityScenario(TeamWithResourcesModel team)
{
var scenarioManager = new ScenarioManager(DbContext);
Scenario scen;
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, isReadOnly: false);
return scen;
}
#endregion
#region Models
///
/// 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 List 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; }
public long StartDateMs => Utils.ConvertToUnixDate(StartDate);
public long EndDateMs => EndDate.HasValue ? Utils.ConvertToUnixDate(EndDate.Value) : 0;
}
class TeamResourceModel
{
public Guid Id;
public string Name;
public bool IsActive;
public string StartDate;
public string EndDate;
public string ExpenditureCategory;
public PeopleResourceAllocationsInfoModel ResourceScenarioAllocationsInfo;
public PeopleResourceAllocationsInfoModel ResourceActualsInfo;
public PeopleResourceAllocationsInfoModel ResourceMixAllocationsInfo;
}
#endregion
}
}