'use strict'; app .directive('optionClassExpr', function ($compile, $parse) { var NG_OPTIONS_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+group\s+by\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?$/; return { restrict: 'A', link: function optionClassExprPostLink(scope, elem, attrs) { var optionsExp = attrs.ngOptions; if (!optionsExp) return; var match = optionsExp.match(NG_OPTIONS_REGEXP); if (!match) return; var values = match[7]; scope.$watchCollection(function () { return elem.children(); }, function (newValue) { angular.forEach(newValue, function (child, index) { var child = angular.element(child); var val = child.val(); if (val) { child.attr('ng-class', values + '[' + (index - 1) + '].' + attrs.optionClassExpr); $compile(child)(scope); } }); }); } }; }) .directive('selectedSourceChanged', ['$timeout', function ($timeout) { return { link: function ($scope, element, attrs) { $scope.$on('selectedSourceChanged', function () { $timeout(function () { // You might need this timeout to be sure its run after DOM render. element.trigger("change"); }, 0, false); }) } }; }]) .controller('capacityManagementController', ['$scope', '$http', '$location', '$timeout', '$window', 'cellHighlightingService', 'scenarioDetailsService', 'dataSources', '$document', function ($scope, $http, $location, $timeout, $window, cellHighlightingService, scenarioDetailsService, dataSources, $document) { $scope.RowType = { Project: 0, ProjectExpenditureCategory: 1, Total: 2, Capacity: 3, BottomCategory: 4, Team: 5, CurrentlyAssigned: 6, NonProjectTimeTotal: 7, Actual: 8, NonProjectTimeCategory: 9, NonProjectTime: 10 } $scope.CollapsedIcon = 'fa-plus-square'; $scope.NonCollapsedIcon = 'fa-minus-square'; $scope.ScenarioCollapsedIcon = 'fa-plus-square-1'; $scope.ScenarioNonCollapsedIcon = 'fa-minus-square-1'; $scope.ExpCatCollapsedIcon = 'fa-plus-square-2'; $scope.ExpCatNonCollapsedIcon = 'fa-minus-square-2'; $scope.id = $location.absUrl().substr($location.absUrl().lastIndexOf('ls/') + 3, 36); //$scope.data = { // VisibleCellCount: 1 //}; $scope.dataSection = ""; $scope.pageTitle = ""; // SA. ENV-905 $scope.preferences = []; $scope.isOverAllocated = false; $scope.isMatchChecked = false; $scope.saveType = "0"; // save scenarios with preserving their current types (BU or TD) $scope.SaveAs = 0; $scope.isActiveScenario = true; $scope.ScenarioName = ""; $scope.dateError = false; $scope.isReadOnly = false; var scrollWidthHeight = $.getScrollBarWidthHeight(); $scope.calendarViewSettings = { headerWidth: 70, tableHeightMin: 450, scrollWidth: scrollWidthHeight[0], scrollHeight: scrollWidthHeight[1] }; $scope.calendarFilters = { CompanyId: null, StartDate: null, EndDate: null, ViewId: null, TeamId: null, ResourceId: null, IsUOMHours: null, PreferredTotalsDisplaying: null, ShowUpper: true, ShowLower: true, IsBarMode: true, ShowCapacity: 1, //"Total Allocated/Total Capacity" GroupByTeam: true, IsViewModeMonth: true, ShowAvgTotals: true, IsCapacityModeActuals: true, sortOrder: false, sortBy: 'Name' }; // SA. ENV-799 $scope.CalendarFilterMode = { IsCompany: true, IsView: false, IsTeam: false, IsResource: false, FilteredEntityTitle: "Company", SelectedItemId: null, } $scope.SpreadType = { Notspecified: -1, Equal: 0, Less: 1, Over: 2 }; $scope.availableFilterOptions = {}; $scope.data = null; $scope.data2Update = { IsOverAllocated: false, IsMatchChecked: false, SaveType: "0", SaveAs: 0, IsActiveScenario: false, ScenarioName: "", ChangedExpCats: [], NonProjectTimeCats: [], ChangedSupperExpCatProjects: [] }; $scope.grandtotalrow = null; $scope.$watch("saveType", function (newValue, oldValue) { if (newValue != oldValue) { if (newValue == "1") { $scope.isMatchChecked = true; } } }); function getExpCatById(scenarioId, ExpCatId, teamId) { for (var i = 0; i < $scope.data.Calendar.length; i++) { if ($scope.data.Calendar[i].ExpCatId != null && $scope.data.Calendar[i].ScenarioId == scenarioId && $scope.data.Calendar[i].ExpCatId == ExpCatId && (!$scope.calendarFilters.GroupByTeam || $scope.CalendarFilterMode.IsTeam || $scope.data.Calendar[i].TeamId == teamId)) { return $scope.data.Calendar[i]; } } return null; } function getNonProjectTimeCatById(nonProjectTimeCategoryId) { for (var i = 0; i < $scope.data.Calendar.length; i++) { if ($scope.data.Calendar[i].RowType == $scope.RowType.NonProjectTimeTotal) { if (nonProjectTimeCategoryId == null) { return $scope.data.Calendar[i]; } else { for (var j in $scope.data.Calendar[i].Categories) { var category = $scope.data.Calendar[i].Categories[j]; if (category.Id == nonProjectTimeCategoryId) { return category; } } } } } return null; } function getNonProjectTimeById(Id) { for (var i = 0; i < $scope.data.Calendar.length; i++) { if ($scope.data.Calendar[i].RowType == $scope.RowType.NonProjectTimeTotal) { for (var j in $scope.data.Calendar[i].Categories) { var category = $scope.data.Calendar[i].Categories[j]; if (category.Id == Id) { return category; } } } } return null; } function getResourceByNonProjectTime(category, resId) { for (var i = 0; i < category.Resources.length; i++) { if (category.Resources[i].Id == resId) { return category.Resources[i]; } } return null; } // this method returns first project row, but not related for any team // just for now it is using only for getting start/end date function getProjectById(projectId) { return getProjectById(projectId, null); } function getProjectById(projectId, teamId) { for (var i = 0; i < $scope.data.Calendar.length; i++) { if ($scope.data.Calendar[i].ExpCatId == null && $scope.data.Calendar[i].ProjectId == projectId && (teamId == null || teamId == $scope.data.Calendar[i].TeamId)) { return $scope.data.Calendar[i]; } } return null; } function isProjectHaveSupperExpCat(projectId) { for (var i = 0; i < $scope.data.Calendar.length; i++) { if ($scope.data.Calendar[i].ExpCatId != null && $scope.data.Calendar[i].ProjectId == projectId && $scope.data.Calendar[i].AllowResourceAssignment == false) { return true; } } return false; } function getResourceById(resId) { for (var i = 0; i < $scope.data.AllResources.length; i++) { if ($scope.data.AllResources[i].Id == resId) { return $scope.data.AllResources[i]; } } return null; } function getResourceByExpCat(expCat, resId) { if (expCat != null) { for (var i = 0; i < expCat.Resources.length; i++) { if (expCat.Resources[i].Id == resId) { return expCat.Resources[i]; } } } return null; } function getECsThatContainResource(resourceId) { if (!resourceId) return; var result = []; for (var i = 0; i < $scope.data.Calendar.length; i++) { var expCatTotal = $scope.data.Calendar[i]; if (expCatTotal.RowType != $scope.RowType.BottomCategory) continue; var resource = getResourceByExpCat(expCatTotal, resourceId); if (resource) { result.push(expCatTotal); } } return result; }; function getResourceActualByExpCat(expCat, resId) { if (expCat != null) { for (var i = 0; i < expCat.Resources.length; i++) { if (expCat.Resources[i].Id == resId) { return expCat.Resources[i]; } } } return null; } function setChildrenCollapsed(row, internal, parentCollapsed) { var isProject = row.ProjectId != null && row.ExpCatId == null; var isExpCat = row.ExpCatId != null; for (var i = 0; i < $scope.data.Calendar.length; i++) { var currRow = $scope.data.Calendar[i]; if (isProject && currRow.ExpCatId != null && currRow.ProjectId == row.ProjectId && (!$scope.calendarFilters.GroupByTeam || currRow.TeamId == row.TeamId)) { var expCat = currRow; if (internal) { expCat.IsParentCollapsed = parentCollapsed; setChildrenCollapsed(expCat, true, parentCollapsed); } else { expCat.IsParentCollapsed = row.ProjectCollapsed; setChildrenCollapsed(expCat, true, row.ProjectCollapsed); } } else if (isExpCat && currRow.ExpCatId == row.ExpCatId && currRow.ScenarioId == row.ScenarioId && (!$scope.calendarFilters.GroupByTeam || currRow.TeamId == row.TeamId)) { var resource = currRow; if (internal) { resource.IsParentCollapsed = parentCollapsed; } } } } function clearResource(scenarioId, expCatId, resource, teamId) { if (resource != null) { for (var i = 0; i < $scope.data.Headers.length; i++) { changeResourceValue(0, scenarioId, expCatId, resource.Id, teamId, i, false); } } } function updateTotals(expCat, expCatTotal, resourceTotal, resource, colIndex, newQuantity, oldQuantity, newCost, oldCost) { var monthStartIndex = colIndex; var monthEndIndex = colIndex; var weeks = 1; var project = getProjectById(expCat.ProjectId); var resourceEndDateWeekending = getNearestWeekending(resource.EndDate); var projectEndDateWeekending = getNearestWeekending(project.EndDate); var avg = ($scope.calendarFilters.ShowAvgTotals && !$scope.calendarFilters.IsUOMHours); //Get current month start/end weeks index and weeks count var montIndex = 0; for (var i = colIndex + 1; i < $scope.data.Headers.length; i++) { if ($scope.data.Headers[i].IsMonth) { monthEndIndex = i; montIndex = $scope.data.Headers[i].MonthIndex; weeks = $scope.data.Headers[i].Weeks.length; break; } } for (var i = 0; i < monthEndIndex; i++) { var currWeekending = $scope.data.Headers[i].Milliseconds; if (montIndex == $scope.data.Headers[i].MonthIndex && currWeekending >= resource.StartDate && currWeekending <= resourceEndDateWeekending && currWeekending >= project.StartDate && currWeekending <= projectEndDateWeekending) { if (!$scope.data.Headers[i].IsMonth) { monthStartIndex = i; break; } } } //Get grand total for all months and months count var firstWeekIndex = -1; var lastWeekIndex = -1; for (var i = 0; i < $scope.data.Headers.length; i++) { var currWeekending = $scope.data.Headers[i].Milliseconds; if ((currWeekending >= resource.StartDate && currWeekending <= resourceEndDateWeekending && currWeekending >= project.StartDate && currWeekending <= projectEndDateWeekending)) { if (!$scope.data.Headers[i].IsMonth && firstWeekIndex < 0) { firstWeekIndex = i; } lastWeekIndex = i; } } var totalWeeksQuantity = 0; var totalWeeks = 0; //In case when resource hire dates are later then project dates if (firstWeekIndex > -1 && lastWeekIndex > -1) { for (var i = firstWeekIndex; i <= lastWeekIndex; i++) { if (!$scope.data.Headers[i].IsMonth) { totalWeeksQuantity += (i == colIndex) ? newQuantity : resource.QuantityValues[i]; totalWeeks++; } } } //Calculate grand total if (avg) { resource.GrandTotalQuantity = Math.round(totalWeeksQuantity / totalWeeks * 1000) / 1000; } else { resource.GrandTotalQuantity = totalWeeksQuantity; } oldQuantity = resource.QuantityValues[monthEndIndex]; //Get current month total var monthTotal = 0; for (var i = monthStartIndex; i < monthEndIndex; i++) { monthTotal += (colIndex == i) ? newQuantity : resource.QuantityValues[i]; } //Calculate current month total if (avg) { newQuantity = Math.round(monthTotal / weeks * 1000) / 1000; } else { newQuantity = monthTotal; } //Change quantity for month //resource.QuantityValues[monthEndIndex] = newQuantity; if (resourceTotal != null) { if (avg) { recalcButtomPartRows(resourceTotal, expCatTotal, monthEndIndex); } else { if (expCat.ExpCatId === expCatTotal.ExpCatId) { resourceTotal.QuantityValues[monthEndIndex] += (newQuantity - oldQuantity); resourceTotal.GrandTotalQuantity += (newQuantity - oldQuantity); expCatTotal.QuantityTotalAllocatedValue[monthEndIndex] += (newQuantity - oldQuantity); } resourceTotal.AllocatedQuantityValues[monthEndIndex] += (newQuantity - oldQuantity); } } return { MonthEndIndex: monthEndIndex, NewQuantity: newQuantity }; } function updateNonProjectTimeTotals(nonProjectTimeCategory, nonProjectTimeTotal, nonProjectTime, resource, colIndex, newQuantity, oldQuantity) { var monthStartIndex = -1; var mounthWeeks = 1; var monthColIndex = 0; var categoryMonthTotal = 0; var monthNonProjectTimeTotalTotal = 0; var monthNonProjectTimeTotal = 0; var categoryMonth = 0; var month = 0; var monthNonProjectTime = 0; var firstWeekIndex = -1; var lastWeekIndex = -1; var totalWeeksQuantity = 0; var totalWeeks = 0; var monthTotal = 0; var monthIndex = $scope.data.Headers[colIndex].MonthIndex; var avg = ($scope.calendarFilters.ShowAvgTotals && !$scope.calendarFilters.IsUOMHours); var delta = 0; if (nonProjectTime.IsTeamMode) delta = newQuantity - oldQuantity; else delta = newQuantity - oldQuantity; //Change NPT, NPT Total and NPT Category values nonProjectTimeCategory.QuantityValues[colIndex] += delta; nonProjectTimeTotal.QuantityValues[colIndex] += delta; nonProjectTime.QuantityValues[colIndex] += delta; //Get grand total for all months and months count for (var i = 0; i < $scope.data.Headers.length; i++) { if ($scope.data.Headers[i].IsMonth && monthIndex == $scope.data.Headers[i].MonthIndex) { //monthEndIndex = i; //montIndex = $scope.data.Headers[i].MonthIndex; mounthWeeks = $scope.data.Headers[i].Weeks.length; monthColIndex = i; //break; } if (!$scope.data.Headers[i].IsMonth && monthIndex == $scope.data.Headers[i].MonthIndex && monthStartIndex == -1) { monthStartIndex = i; //break; } if (!$scope.data.Headers[i].IsMonth) { if (firstWeekIndex == -1) { firstWeekIndex = i; } lastWeekIndex = i; } } monthStartIndex = monthStartIndex == -1 ? colIndex : monthStartIndex; //In case when resource hire dates are later then project dates if (firstWeekIndex > -1 && lastWeekIndex > -1) { for (var i = firstWeekIndex; i <= lastWeekIndex; i++) { if (!$scope.data.Headers[i].IsMonth) { if (!nonProjectTime.IsTeamMode) totalWeeksQuantity += (i == colIndex) ? newQuantity : resource.QuantityValues[i]; else totalWeeksQuantity += (i == colIndex) ? newQuantity : nonProjectTime.QuantityValues[i]; totalWeeks++; } } } //Calculate resource grand total if (!nonProjectTime.IsTeamMode) resource.GrandTotalQuantity = avg ? Math.round(totalWeeksQuantity / totalWeeks * 1000) / 1000 : totalWeeksQuantity; else nonProjectTime.GrandTotalQuantity = avg ? Math.round(totalWeeksQuantity / totalWeeks * 1000) / 1000 : totalWeeksQuantity; //Get current resource month total for (var i = monthStartIndex; i < monthColIndex; i++) { if (!nonProjectTime.IsTeamMode) monthTotal += (colIndex == i) ? newQuantity : resource.QuantityValues[i]; else monthTotal += (colIndex == i) ? newQuantity : nonProjectTime.QuantityValues[i]; } //Calculate current month total var newMonthQuantity = avg ? Math.round(monthTotal / mounthWeeks * 1000) / 1000 : monthTotal; //Change quantity for month if (!nonProjectTime.IsTeamMode) resource.QuantityValues[monthColIndex] = newMonthQuantity; else nonProjectTime.QuantityValues[monthColIndex] = newMonthQuantity; //Agregate NPT, NPT Total and NPT Category month and grandtotal values for (var i = 0; i < $scope.data.Headers.length; i++) { if (!$scope.data.Headers[i].IsMonth) { if (monthIndex == $scope.data.Headers[i].MonthIndex) { categoryMonth += nonProjectTimeCategory.QuantityValues[i]; month += nonProjectTimeTotal.QuantityValues[i]; monthNonProjectTime += nonProjectTime.QuantityValues[i]; } categoryMonthTotal += nonProjectTimeCategory.QuantityValues[i]; monthNonProjectTimeTotalTotal += nonProjectTimeTotal.QuantityValues[i]; monthNonProjectTimeTotal += nonProjectTime.QuantityValues[i]; } } //Change NPT, NPT Total and NPT Category month values nonProjectTimeCategory.QuantityValues[monthColIndex] = avg ? categoryMonth / mounthWeeks : categoryMonth; nonProjectTimeTotal.QuantityValues[monthColIndex] = avg ? month / mounthWeeks : month; nonProjectTime.QuantityValues[monthColIndex] = avg ? monthNonProjectTime / mounthWeeks : monthNonProjectTime; //Change NPT, NPT Total and NPT Category grandtotal values nonProjectTimeCategory.GrandTotalQuantity = avg ? categoryMonthTotal / totalWeeks : categoryMonthTotal; nonProjectTimeTotal.GrandTotalQuantity = avg ? monthNonProjectTimeTotalTotal / totalWeeks : monthNonProjectTimeTotalTotal; nonProjectTime.GrandTotalQuantity = avg ? monthNonProjectTimeTotal / totalWeeks : monthNonProjectTimeTotal; } function updateActualTotals(expCat, expCatTotal, actualTotal, resource, colIndex, newQuantity, oldQuantity) { var monthStartIndex = colIndex; var monthEndIndex = colIndex; var weeks = 1; var project = getProjectById(expCat.ProjectId); var resourceEndDateWeekending = getNearestWeekending(resource.EndDate); var projectEndDateWeekending = getNearestWeekending(project.EndDate); var avg = ($scope.calendarFilters.ShowAvgTotals && !$scope.calendarFilters.IsUOMHours); //Get current month start/end weeks index and weeks count var montIndex = 0; for (var i = colIndex + 1; i < $scope.data.Headers.length; i++) { if ($scope.data.Headers[i].IsMonth) { monthEndIndex = i; montIndex = $scope.data.Headers[i].MonthIndex; weeks = $scope.data.Headers[i].Weeks.length; break; } } for (var i = 0; i < monthEndIndex; i++) { var currWeekending = $scope.data.Headers[i].Milliseconds; if (montIndex == $scope.data.Headers[i].MonthIndex && currWeekending >= resource.StartDate && currWeekending <= resourceEndDateWeekending && currWeekending >= project.StartDate && currWeekending <= projectEndDateWeekending) { if (!$scope.data.Headers[i].IsMonth) { monthStartIndex = i; break; } } } //Get grand total for all months and months count var firstWeekIndex = -1; var lastWeekIndex = -1; for (var i = 0; i < $scope.data.Headers.length; i++) { var currWeekending = $scope.data.Headers[i].Milliseconds; if ((currWeekending >= resource.StartDate && currWeekending <= resourceEndDateWeekending && currWeekending >= project.StartDate && currWeekending <= projectEndDateWeekending)) { if (!$scope.data.Headers[i].IsMonth && firstWeekIndex < 0) { firstWeekIndex = i; } lastWeekIndex = i; } } var totalWeeksQuantity = 0; var totalWeeks = 0; //In case when resource hire dates are later then project dates if (firstWeekIndex > -1 && lastWeekIndex > -1) { for (var i = firstWeekIndex; i <= lastWeekIndex; i++) { if (!$scope.data.Headers[i].IsMonth) { totalWeeksQuantity += (i == colIndex) ? newQuantity : resource.ActualQuantityValues[i]; totalWeeks++; } } } //Calculate grand total if (avg) { resource.GrandActualTotalQuantity = Math.round(totalWeeksQuantity / totalWeeks * 1000) / 1000; } else { resource.GrandActualTotalQuantity = totalWeeksQuantity; } oldQuantity = resource.ActualQuantityValues[monthEndIndex]; //Get current month total var monthTotal = 0; for (var i = monthStartIndex; i < monthEndIndex; i++) { monthTotal += (colIndex == i) ? newQuantity : resource.ActualQuantityValues[i]; } //Calculate current month total if (avg) { newQuantity = Math.round(monthTotal / weeks * 1000) / 1000; } else { newQuantity = monthTotal; } //Change quantity for month resource.ActualQuantityValues[monthEndIndex] = newQuantity; } function recalcButtomPartRows(resourceTotal, expCatTotal, monthEndIndex) { //recalc AVG for month var monthIndex = $scope.data.Headers[monthEndIndex].MonthIndex; var resourceValue = 0, resourceAllocatedValue = 0, allocatedValue = 0; for (var i = 0; i < monthEndIndex; i++) { if ($scope.data.Headers[i].MonthIndex == monthIndex) { resourceValue += resourceTotal.QuantityValues[i]; resourceAllocatedValue += resourceTotal.AllocatedQuantityValues[i]; allocatedValue += expCatTotal.QuantityTotalAllocatedValue[i]; } } resourceTotal.QuantityValues[monthEndIndex] = resourceValue / $scope.data.Headers[monthEndIndex].Weeks.length; resourceTotal.AllocatedQuantityValues[monthEndIndex] = resourceAllocatedValue / $scope.data.Headers[monthEndIndex].Weeks.length; expCatTotal.QuantityTotalAllocatedValue[monthEndIndex] = allocatedValue / $scope.data.Headers[monthEndIndex].Weeks.length; //recalc AVG for resource total resourceValue = 0 var weeksCount = 0; for (var i = 0; i < $scope.data.Headers.length; i++) { if (!$scope.data.Headers[i].IsMonth) { resourceValue += resourceTotal.QuantityValues[i]; weeksCount++; } } resourceTotal.GrandTotalQuantity = resourceValue / weeksCount; //recalc AVG for expcat total } function allocateResource(resource, expCat, create, allocateRest, reset) { if (resource != null) { var scenarioCalendarRow = null; var i; for (i = 0; i < $scope.data.Calendar.length; i++) { if ($scope.data.Calendar[i].RowType == $scope.RowType.Project && $scope.data.Calendar[i].ScenarioId == expCat.ScenarioId) { scenarioCalendarRow = $scope.data.Calendar[i]; break; } } // TODO: ENV-1649. Seems like resource.Teams contains all teams resource have been ever assigned to // and expCat.Teams contains all teams from calendar, so we cannot get one team for entire calendar // we should get it separately in a headers for loop var team = null; for (i = 0; i < expCat.Teams.length; i++) { if (findTeamInResourceTeams(resource.Teams, expCat.Teams[i].Id)) { team = expCat.Teams[i]; break; } } var res = getResourceById(resource.Id); for (i = 0; i < $scope.data.Headers.length; i++) { var cellReadonly = resource.ReadOnlyWeeks[i] || scenarioCalendarRow.ReadOnly[i]; if ($scope.data.Headers[i].IsMonth || (!reset && cellReadonly)) continue; var quantity = 0; if (create || reset) //Reset { if (create) resource.QuantityValues[i] = 0; if (reset && cellReadonly && resource.QuantityValues[i] == 0) // reset readonly cells only if any data exists in it continue; quantity = 0; } else if (allocateRest) { //AssignRest if (team != null) { var needToAssign = Math.max(team.QuantityValues[i] - team.AllocatedByResources[i], 0); } else { var needToAssign = 0; } var canBeAssigned = Math.max(res.CapacityQuantityValues[i] - res.AllocatedQuantityValues[i], 0); quantity = Math.min(needToAssign, canBeAssigned); if (quantity == 0) { continue; } quantity += resource.QuantityValues[i]; } else { //AssignAll if (team != null) { var needToAssign = Math.max(team.QuantityValues[i] - team.AllocatedByResources[i] + resource.QuantityValues[i], 0); } else { var needToAssign = 0; } var canBeAssigned = Math.max(res.CapacityQuantityValues[i], 0); quantity = Math.min(needToAssign, canBeAssigned); if (quantity == 0) { continue; } } changeResourceValue(quantity, expCat.ScenarioId, expCat.ExpCatId, resource.Id, expCat.TeamId, i, false); } } } function recalcSpreading(expCat, expCatTotal, resourceTotal, colIndex) { var i; if (expCat != null) { var scenarioCalendarRow = getProjectById(expCat.ProjectId, expCat.TeamId); var resTotal = 0; for (i = 0; i < expCat.Resources.length; i++) { resTotal += expCat.Resources[i].QuantityValues[colIndex]; } //Validate team allocation upper var compareRes = cellHighlightingService.compare(resTotal, expCat.QuantityValues[colIndex]); expCat.SpreadVal[colIndex] = compareRes == null ? $scope.SpreadType.Notspecified : compareRes < 0 ? $scope.SpreadType.Less : compareRes == 0 ? $scope.SpreadType.Equal : $scope.SpreadType.Over; updateCSSClass(expCat, null, colIndex); var projectClass = $scope.SpreadType.Notspecified; for (i = 0; i < $scope.data.Calendar.length; i++) { if ($scope.data.Calendar[i].ProjectId == expCat.ProjectId && $scope.data.Calendar[i].ExpCatId != null && $scope.data.Calendar[i].ScenarioId == expCat.ScenarioId && (!$scope.calendarFilters.GroupByTeam || $scope.data.Calendar[i].TeamId == expCat.TeamId)) { if ($scope.data.Calendar[i].SpreadVal[colIndex] == $scope.SpreadType.Over) { projectClass = $scope.SpreadType.Over; break; } else if ($scope.data.Calendar[i].SpreadVal[colIndex] == $scope.SpreadType.Less) { projectClass = $scope.SpreadType.Less; } } } //Validate SD allocation scenarioCalendarRow.SpreadVal[colIndex] = projectClass; updateCSSClass(scenarioCalendarRow, null, colIndex); } var isMonth = $scope.data.Headers[colIndex].IsMonth; var startIndex = colIndex; if (isMonth) { for (var mIndex = colIndex - 1; mIndex >= 0; mIndex--) { if ($scope.data.Headers[mIndex].IsMonth) break; startIndex--; } } //calc exp cat total row if (expCatTotal != null) { expCatTotal.SpreadVal[colIndex] = $scope.SpreadType.Notspecified; //Validate resource allocation bottom for (i = 0; i < expCatTotal.Resources.length; i++) { var res = expCatTotal.Resources[i]; if (isMonth) { var resMonthSpreadVal = $scope.SpreadType.Notspecified; for (var mIndex = startIndex; mIndex < colIndex; mIndex++) { if (res.SpreadVal[mIndex] == $scope.SpreadType.Over) resMonthSpreadVal = res.SpreadVal[mIndex]; else if (resMonthSpreadVal != $scope.SpreadType.Over && resMonthSpreadVal != $scope.SpreadType.Less && res.SpreadVal[mIndex] == $scope.SpreadType.Less) resMonthSpreadVal = res.SpreadVal[mIndex]; else if (resMonthSpreadVal != $scope.SpreadType.Over && resMonthSpreadVal != $scope.SpreadType.Less) resMonthSpreadVal = res.SpreadVal[mIndex]; } res.SpreadVal[colIndex] = resMonthSpreadVal; updateCSSClass(expCatTotal, res, colIndex); } else { var resValue = res.QuantityTotalResValue[colIndex]; var compareRes = cellHighlightingService.compare(res.QuantityValues[colIndex], resValue); res.SpreadVal[colIndex] = compareRes == null ? $scope.SpreadType.Notspecified : compareRes == 0 ? $scope.SpreadType.Equal : compareRes < 0 ? $scope.SpreadType.Less : $scope.SpreadType.Over; updateCSSClass(expCatTotal, res, colIndex); setParentCSSClass(colIndex, expCatTotal, res, null); updateCSSClass(expCatTotal, null, colIndex); } } } if (!expCatTotal.IsSuperEC) { for (i = 0; i < expCatTotal.Resources.length; i++) { var resBottom = expCatTotal.Resources[i]; for (var k = 0; k < $scope.data.Calendar.length; k++) { if ($scope.data.Calendar[k].ProjectId != null && $scope.data.Calendar[k].ExpCatId != null && $scope.data.Calendar[k].ScenarioId != null) { var nextExpCat = $scope.data.Calendar[k]; var project = getProjectById(nextExpCat.ProjectId, nextExpCat.TeamId); if (nextExpCat != null && nextExpCat.Resources != null) { for (var j = 0; j < nextExpCat.Resources.length; j++) { var resUpper = nextExpCat.Resources[j]; if (resUpper.Id == resBottom.Id && resUpper.QuantityValues[colIndex] > 0) { //Validate resource allocation upper when capacity exceeded //Upper resource allocation is red when exceeds capacity or not painted resUpper.SpreadVal[colIndex] = expCatTotal.SpreadVal[colIndex] == $scope.SpreadType.Over ? $scope.SpreadType.Over : $scope.SpreadType.Notspecified; updateCSSClass(nextExpCat, resUpper, colIndex); setParentCSSClass(colIndex, nextExpCat, resUpper, project); updateCSSClass(nextExpCat, null, colIndex); updateCSSClass(project, null, colIndex); //Validate resource allocation upper when resources allocation sum exceeded team allocation when resource capacity is not over allocated if (resUpper.SpreadVal[colIndex] != $scope.SpreadType.Over) { if (nextExpCat != null && nextExpCat.Resources != null) { var teamValue1 = nextExpCat.QuantityValues[colIndex]; var resValue1Sum = 0; for (var l = 0; l < nextExpCat.Resources.length; l++) { var res1 = nextExpCat.Resources[l]; resValue1Sum += res1.QuantityValues[colIndex]; } compareRes = cellHighlightingService.compare(resValue1Sum, teamValue1); for (var l = 0; l < nextExpCat.Resources.length; l++) { var res1 = nextExpCat.Resources[l]; res1.SpreadVal[colIndex] = compareRes == null || compareRes < 0 ? $scope.SpreadType.Notspecified : compareRes == 0 ? $scope.SpreadType.Equal : $scope.SpreadType.Over; updateCSSClass(nextExpCat, res1, colIndex); setParentCSSClass(colIndex, nextExpCat, res1, project); updateCSSClass(nextExpCat, null, colIndex); updateCSSClass(project, null, colIndex); } } } } } } } } } } } function setParentCSSClass(colIndex, expCat, res, project) { if (expCat.SpreadVal[colIndex] != $scope.SpreadType.Over) { if (expCat.SpreadVal[colIndex] != $scope.SpreadType.Less) { if (res.SpreadVal[colIndex] != $scope.SpreadType.Notspecified) { expCat.SpreadVal[colIndex] = res.SpreadVal[colIndex]; if(project != null) project.SpreadVal[colIndex] = expCat.SpreadVal[colIndex]; } } else { if (res.SpreadVal[colIndex] == $scope.SpreadType.Over) { expCat.SpreadVal[colIndex] = res.SpreadVal[colIndex]; if (project != null) project.SpreadVal[colIndex] = expCat.SpreadVal[colIndex]; } } } } function updateCSSClass(row, resRow, colIndex) { if (resRow == null) { // updating project/scenario/category row if (row == null || colIndex == null || row.SpreadVal == null || row.SpreadVal.length <= colIndex) return; row.CSSClass[colIndex] = (row.CSSClass[colIndex] || '').replace(/cellover/g, '').replace(/cellless/g, '').replace(/cellequal/g, '');; if (!row.ReadOnly[colIndex]) { if (row.SpreadVal[colIndex] == $scope.SpreadType.Equal) row.CSSClass[colIndex] = 'cellequal'; else if (row.SpreadVal[colIndex] == $scope.SpreadType.Over) row.CSSClass[colIndex] = 'cellover'; else if (row.SpreadVal[colIndex] == $scope.SpreadType.Less) row.CSSClass[colIndex] = 'cellless'; if (row.RowType == $scope.RowType.Project) { if (row.Color != null && row.Color.length > 0 && $scope.calendarFilters.IsBarMode) { row.BarStyle[colIndex] = "border-bottom: 1px solid " + row.Color + " !important;border-right-color: " + row.Color + " !important;background-color: " + row.Color + ";"; } else { row.BarStyle[colIndex] = null; } } } if (row.ProjectId == null && row.ExpCatId != null && row.RowType == $scope.RowType.BottomCategory) row.CSSClass[colIndex] += ' capacity-total'; // assign more classes if needed } else { // updating resource row if (row == null || colIndex == null || row.ReadOnly[colIndex]) return; if (resRow.SpreadVal == null || resRow.SpreadVal.length <= colIndex) return; resRow.CSSClass[colIndex] = (resRow.CSSClass[colIndex] || '').replace(/cellover/g, '').replace(/cellless/g, '').replace(/cellequal/g, '');; if (resRow.SpreadVal[colIndex] == $scope.SpreadType.Equal) resRow.CSSClass[colIndex] = 'cellequal'; else if (resRow.SpreadVal[colIndex] == $scope.SpreadType.Over) resRow.CSSClass[colIndex] = 'cellover'; else if (resRow.SpreadVal[colIndex] == $scope.SpreadType.Less) resRow.CSSClass[colIndex] = 'cellless'; // assign more classes if needed } setOverAllocated(); } function setOverAllocated() { var isOverAllocated = false; for (var i = 0; i < $scope.data.Calendar.length; i++) { if ($scope.data.Calendar[i].ExpCatId != null && $scope.data.Calendar[i].ScenarioId != null) { for (var j = 0; j < $scope.data.Calendar[i].SpreadVal.length; j++) { if ($scope.data.Calendar[i].SpreadVal[j] == $scope.SpreadType.Over && isInEdited($scope.data.Calendar[i].ExpCatId, $scope.data.Calendar[i].ScenarioId, $scope.data.Headers[j].Milliseconds)) { isOverAllocated = true; break; } } } if (isOverAllocated) break; } $scope.isOverAllocated = isOverAllocated; } function isInEdited(expCatId, scenarioId, milliseconds) { for (var ix = 0; ix < $scope.data2Update.ChangedExpCats.length; ix++) { if ($scope.data2Update.ChangedExpCats[ix].Id == expCatId && $scope.data2Update.ChangedExpCats[ix].ScenarioId == scenarioId) { for (var res in $scope.data2Update.ChangedExpCats[ix].Resources) { var resource = $scope.data2Update.ChangedExpCats[ix].Resources[res]; for (var val in resource.Values) { if (resource.Values[val].Milliseconds == milliseconds) return true; } } } } return false; } function recalcRest(expCat, colIndex) { var sum = 0; for (var i = 0; i < expCat.Resources.length; i++) { sum += expCat.Resources[i].QuantityValues[colIndex]; } if (expCat.QuantityValues[colIndex] < sum) expCat.RestQuantity[colIndex] = 0; else expCat.RestQuantity[colIndex] = expCat.QuantityValues[colIndex] - sum; } function changeResourceValue(newValue, scenarioId, expCatId, resId, teamId, colIndex, isRemoved) { var expCat = getExpCatById(scenarioId, expCatId, teamId); var expCatTotal = getExpCatById(null, expCatId, null); var resource = getResourceByExpCat(expCat, resId); var resourceTotal = getResourceByExpCat(expCatTotal, resId); var header = $scope.data.Headers[colIndex]; if (expCat != null && resource != null) { var team = null; // adjust team allocations for (var i = 0; i < expCat.Teams.length; i++) { var resTeam = findTeamInResourceTeams(resource.Teams, expCat.Teams[i].Id); if (resTeam == null || header.Milliseconds < resTeam.TeamStartDate || (resTeam.TeamEndDate != null && header.Milliseconds > resTeam.TeamEndDate)) continue; team = expCat.Teams[i]; team.AllocatedByResources[colIndex] += (newValue - resource.QuantityValues[colIndex]); } var res = getResourceById(resource.Id); res.AllocatedQuantityValues[colIndex] += (newValue - resource.QuantityValues[colIndex]); if (expCatTotal && resourceTotal) { expCatTotal.QuantityTotalAllocatedValue[colIndex] += (newValue - resource.QuantityValues[colIndex]); expCatTotal.QuantityValues[colIndex] += (newValue - resource.QuantityValues[colIndex]); resourceTotal.QuantityValues[colIndex] += (newValue - resource.QuantityValues[colIndex]); } var totalUpdateResult = {}; var totalECsWithResource = getECsThatContainResource(resId); if (totalECsWithResource) { for (var i = 0; i < totalECsWithResource.length; i++) { var totalEC = totalECsWithResource[i]; var totalResource = getResourceByExpCat(totalEC, resId); if (totalResource) { totalResource.AllocatedQuantityValues[colIndex] += (newValue - resource.QuantityValues[colIndex]); } totalUpdateResult = updateTotals(expCat, totalEC, totalResource, resource, colIndex, newValue, resource.QuantityValues[colIndex], 0, 0); } } resource.QuantityValues[colIndex] = newValue; if (totalUpdateResult) { resource.QuantityValues[totalUpdateResult.MonthEndIndex] = totalUpdateResult.NewQuantity; //recalcSpreading(expCat, expCatTotal, resourceTotal, totalUpdateResult.MonthEndIndex); //updateCSSClass(expCat, resource, totalUpdateResult.MonthEndIndex); } recalcRest(expCat, colIndex); applyCellChange(scenarioId, expCatId, $scope.data.Headers[colIndex].Milliseconds, expCat.DetailIds[colIndex], newValue, 0, resId, false, isRemoved, false, false, teamId); } //Recalc spreading for current cell recalcSpreading(expCat, expCatTotal, resourceTotal, colIndex); updateCSSClass(expCat, resource, colIndex); //Recalc spreading for month cell recalcSpreading(expCat, expCatTotal, resourceTotal, totalUpdateResult.MonthEndIndex); updateCSSClass(expCat, resource, totalUpdateResult.MonthEndIndex); recalcBottomPartTotals(); refreshTotalSpreadVal(expCatTotal); } function changeActualValue(newValue, scenarioId, expCatId, resId, teamId, colIndex, isRemoved) { var expCat = getExpCatById(scenarioId, expCatId, teamId); var expCatTotal = getExpCatById(null, expCatId, null); var actual = getResourceActualByExpCat(expCat, resId); var actualTotal = getResourceByExpCat(expCatTotal, resId); var header = $scope.data.Headers[colIndex]; if (expCat != null && actual != null) { updateActualTotals(expCat, expCatTotal, actualTotal, actual, colIndex, newValue, actual.ActualQuantityValues[colIndex]); actual.ActualQuantityValues[colIndex] = newValue; applyCellChange(scenarioId, expCatId, $scope.data.Headers[colIndex].Milliseconds, expCat.DetailIds[colIndex], newValue, 0, resId, false, isRemoved, true, false, teamId); } } function changeNonProjectTimeValue(newValue, nonProjectTimeId, nonProjectTimeCategoryId, resId, colIndex) { var category = getNonProjectTimeCatById(nonProjectTimeCategoryId); var nonProjectTimeTotal = getNonProjectTimeCatById(null); var nonProjectTime = getNonProjectTimeById(nonProjectTimeId); var resource = getResourceByNonProjectTime(nonProjectTime, resId); var header = $scope.data.Headers[colIndex]; if (category != null) { if (nonProjectTime.IsTeamMode) { // update Team-level NPT cell updateNonProjectTimeTotals(category, nonProjectTimeTotal, nonProjectTime, null, colIndex, newValue, nonProjectTime.QuantityValues[colIndex]); } else if (resource != null && resource.QuantityValues[colIndex] != null) { // update resource-level NPT cell updateNonProjectTimeTotals(category, nonProjectTimeTotal, nonProjectTime, resource, colIndex, newValue, resource.QuantityValues[colIndex]); resource.QuantityValues[colIndex] = newValue; } applyCellChange(null, nonProjectTimeId, $scope.data.Headers[colIndex].Milliseconds, null, newValue, 0, resId, false, false, false, true); } } function applyResourceCellChange(catIndex, cellMilliseconds, cellId, cellQuantity, cellCost, resId, isAdded, isRemoved, isActual) { var resIndex = -1; for (var dx = 0; dx < $scope.data2Update.ChangedExpCats[catIndex].Resources.length; dx++) { if ($scope.data2Update.ChangedExpCats[catIndex].Resources[dx].Id == resId) { resIndex = dx; break; } } if (resIndex == -1) { $scope.data2Update.ChangedExpCats[catIndex].Resources[$scope.data2Update.ChangedExpCats[catIndex].Resources.length] = { Id: resId, Values: [], Actuals: [], IsAdded: isAdded, IsRemoved: isRemoved }; resIndex = $scope.data2Update.ChangedExpCats[catIndex].Resources.length - 1; if (isAdded || isRemoved) return; } else { $scope.data2Update.ChangedExpCats[catIndex].Resources[resIndex].IsRemoved = isRemoved; if (isRemoved) { if ($scope.data2Update.ChangedExpCats[catIndex].Resources[resIndex].IsAdded) { $scope.data2Update.ChangedExpCats[catIndex].Resources.splice(resIndex, 1); } return; } } var dateIndex = -1; if (isActual) { for (var dx = 0; dx < $scope.data2Update.ChangedExpCats[catIndex].Resources[resIndex].Actuals.length; dx++) { if ($scope.data2Update.ChangedExpCats[catIndex].Resources[resIndex].Actuals[dx].Milliseconds == cellMilliseconds) { dateIndex = dx; break; } } if (dateIndex == -1) { $scope.data2Update.ChangedExpCats[catIndex].Resources[resIndex].Actuals[$scope.data2Update.ChangedExpCats[catIndex].Resources[resIndex].Actuals.length] = { Id: cellId, Milliseconds: cellMilliseconds, Values: [] }; dateIndex = $scope.data2Update.ChangedExpCats[catIndex].Resources[resIndex].Actuals.length - 1; } $scope.data2Update.ChangedExpCats[catIndex].Resources[resIndex].Actuals[dateIndex].Quantity = cellQuantity; } else { for (var dx = 0; dx < $scope.data2Update.ChangedExpCats[catIndex].Resources[resIndex].Values.length; dx++) { if ($scope.data2Update.ChangedExpCats[catIndex].Resources[resIndex].Values[dx].Milliseconds == cellMilliseconds) { dateIndex = dx; break; } } if (dateIndex == -1) { $scope.data2Update.ChangedExpCats[catIndex].Resources[resIndex].Values[$scope.data2Update.ChangedExpCats[catIndex].Resources[resIndex].Values.length] = { Id: cellId, Milliseconds: cellMilliseconds, Values: [] }; dateIndex = $scope.data2Update.ChangedExpCats[catIndex].Resources[resIndex].Values.length - 1; } //$scope.data2Update.ChangedExpCats[catIndex].Resources[resIndex].Values[dateIndex].Cost = cellCost; $scope.data2Update.ChangedExpCats[catIndex].Resources[resIndex].Values[dateIndex].Quantity = cellQuantity; } } function applyNPTCellChange(catIndex, cellMilliseconds, cellId, cellQuantity, cellCost, resId, isAdded, isRemoved) { var resIndex = -1; var dateIndex = -1; if (resId != null) { for (var dx = 0; dx < $scope.data2Update.NonProjectTimeCats[catIndex].Resources.length; dx++) { if ($scope.data2Update.NonProjectTimeCats[catIndex].Resources[dx].Id == resId) { resIndex = dx; break; } } if (resIndex == -1) { $scope.data2Update.NonProjectTimeCats[catIndex].Resources[$scope.data2Update.NonProjectTimeCats[catIndex].Resources.length] = { Id: resId, Values: [], Actuals: [], IsAdded: isAdded, IsRemoved: isRemoved }; resIndex = $scope.data2Update.NonProjectTimeCats[catIndex].Resources.length - 1; } else { $scope.data2Update.NonProjectTimeCats[catIndex].Resources[resIndex].IsRemoved = isRemoved; if (isRemoved) { if ($scope.data2Update.NonProjectTimeCats[catIndex].Resources[resIndex].IsAdded) { $scope.data2Update.NonProjectTimeCats[catIndex].Resources.splice(resIndex, 1); } return; } } } if (resIndex != -1) // update cell value for resource-level NPT { for (var dx = 0; dx < $scope.data2Update.NonProjectTimeCats[catIndex].Resources[resIndex].Values.length; dx++) { if ($scope.data2Update.NonProjectTimeCats[catIndex].Resources[resIndex].Values[dx].Milliseconds == cellMilliseconds) { dateIndex = dx; break; } } if (dateIndex == -1) { $scope.data2Update.NonProjectTimeCats[catIndex].Resources[resIndex].Values[$scope.data2Update.NonProjectTimeCats[catIndex].Resources[resIndex].Values.length] = { Id: cellId, Milliseconds: cellMilliseconds, Values: [] }; dateIndex = $scope.data2Update.NonProjectTimeCats[catIndex].Resources[resIndex].Values.length - 1; } $scope.data2Update.NonProjectTimeCats[catIndex].Resources[resIndex].Values[dateIndex].Quantity = cellQuantity; } else // update cell value for team-level NPT { for (var dx = 0; dx < $scope.data2Update.NonProjectTimeCats[catIndex].Values.length; dx++) { if ($scope.data2Update.NonProjectTimeCats[catIndex].Values[dx].Milliseconds == cellMilliseconds) { dateIndex = dx; break; } } if (dateIndex == -1) { $scope.data2Update.NonProjectTimeCats[catIndex].Values[$scope.data2Update.NonProjectTimeCats[catIndex].Values.length] = { Id: cellId, Milliseconds: cellMilliseconds, Values: [] }; dateIndex = $scope.data2Update.NonProjectTimeCats[catIndex].Values.length - 1; } var perTeamQuantity = cellQuantity; var nonProjectTime = getNonProjectTimeById($scope.data2Update.NonProjectTimeCats[catIndex].Id); if (nonProjectTime != null && nonProjectTime.Resources != null && nonProjectTime.Resources.length != 0) perTeamQuantity = perTeamQuantity / nonProjectTime.Resources.length; $scope.data2Update.NonProjectTimeCats[catIndex].Values[dateIndex].Quantity = perTeamQuantity; } } function applyExpCatCellChange(catIndex, cellMilliseconds, cellId, cellQuantity, cellCost) { var dateIndex = -1; for (var dx = 0; dx < $scope.data2Update.ChangedExpCats[catIndex].Values.length; dx++) { if ($scope.data2Update.ChangedExpCats[catIndex].Values[dx].Id == cellId) { dateIndex = dx; break; } } if (dateIndex == -1) { $scope.data2Update.ChangedExpCats[catIndex].Values[$scope.data2Update.ChangedExpCats[catIndex].Values.length] = { Id: cellId, Milliseconds: cellMilliseconds, Values: [] }; dateIndex = $scope.data2Update.ChangedExpCats[catIndex].Values.length - 1; } $scope.data2Update.ChangedExpCats[catIndex].Values[dateIndex].Quantity = cellQuantity; } function applyCellChange(scenarioId, rowId, cellMilliseconds, cellId, cellQuantity, cellCost, resId, isAdded, isRemoved, isActual, isNonProjectTime, teamId) { var catIndex = -1; if (isNonProjectTime) { for (var ix = 0; ix < $scope.data2Update.NonProjectTimeCats.length; ix++) { if ($scope.data2Update.NonProjectTimeCats[ix].Id == rowId) { catIndex = ix; break; } } if (catIndex == -1) { $scope.data2Update.NonProjectTimeCats[$scope.data2Update.NonProjectTimeCats.length] = { ScenarioId: scenarioId, Id: rowId, Values: [], Resources: [] }; catIndex = $scope.data2Update.NonProjectTimeCats.length - 1; } } else { for (var ix = 0; ix < $scope.data2Update.ChangedExpCats.length; ix++) { if ($scope.data2Update.ChangedExpCats[ix].Id == rowId && $scope.data2Update.ChangedExpCats[ix].ScenarioId == scenarioId) { catIndex = ix; break; } } if (catIndex == -1) { var expCat = getExpCatById(scenarioId, rowId, teamId); $scope.data2Update.ChangedExpCats[$scope.data2Update.ChangedExpCats.length] = { ScenarioId: scenarioId, Id: rowId, Values: [], Resources: [], AllowResourceAssignment: expCat.AllowResourceAssignment }; catIndex = $scope.data2Update.ChangedExpCats.length - 1; if (isProjectHaveSupperExpCat(expCat.ProjectId)) { var project = getProjectById(expCat.ProjectId); var itemInArray = false; for (var i = 0; i < $scope.data2Update.ChangedSupperExpCatProjects.length; i++) { if (expCat.ProjectId == $scope.data2Update.ChangedSupperExpCatProjects[i].ProjectId) { itemInArray = true; break; } } if (!itemInArray) $scope.data2Update.ChangedSupperExpCatProjects[$scope.data2Update.ChangedSupperExpCatProjects.length] = { ProjectId: expCat.ProjectId, Name: project.Name } } } } if (isNonProjectTime) { applyNPTCellChange(catIndex, cellMilliseconds, cellId, cellQuantity, cellCost, resId, isAdded, isRemoved); } else { if (resId == null) { applyExpCatCellChange(catIndex, cellMilliseconds, cellId, cellQuantity, cellCost); } else { applyResourceCellChange(catIndex, cellMilliseconds, cellId, cellQuantity, cellCost, resId, isAdded, isRemoved, isActual); } } } function refreshTotalSpreadVal(expCatTotal) { if (expCatTotal.RowType == $scope.RowType.BottomCategory) { for (var colIndex = 0; colIndex < $scope.data.Headers.length; colIndex++) { if(expCatTotal.IsSuperEC) { expCatTotal.SpreadVal[colIndex] = $scope.SpreadType.Equal; } else { var resValue = expCatTotal.QuantityTotalResValue[colIndex]; var total = 0; var allocated = 0; if ($scope.calendarFilters.ShowCapacity == 1) { // Allocated/Capacity allocated = expCatTotal.QuantityTotalAllocatedValue[colIndex]; total = expCatTotal.QuantityTotalResValue[colIndex]; } else if ($scope.calendarFilters.ShowCapacity == 2) { // Need/Capacity allocated = expCatTotal.QuantityValues[colIndex]; total = expCatTotal.QuantityTotalResValue[colIndex]; } else if ($scope.calendarFilters.ShowCapacity == 3) { // Allocated/Need allocated = expCatTotal.QuantityTotalAllocatedValue[colIndex]; total = expCatTotal.QuantityValues[colIndex]; } else if ($scope.calendarFilters.ShowCapacity == 4) { // Remaining/Capacity, display similar to Allocated/Capacity allocated = expCatTotal.QuantityValues[colIndex]; total = expCatTotal.QuantityTotalResValue[colIndex]; } var compareRes = cellHighlightingService.compare(allocated, total); expCatTotal.SpreadVal[colIndex] = compareRes > 0 ? $scope.SpreadType.Over : compareRes < 0 ? $scope.SpreadType.Less : compareRes == 0 ? $scope.SpreadType.Equal : $scope.SpreadType.Notspecified; //var ecClass = $scope.SpreadType.Notspecified; //for (var j in expCatTotal.Resources) //{ // if (ecClass != $scope.SpreadType.Over) { // if (ecClass != $scope.SpreadType.Less) { // if (expCatTotal.Resources[j].SpreadVal[colIndex] != $scope.SpreadType.Notspecified) { // ecClass = expCatTotal.Resources[j].SpreadVal[colIndex]; // } // } else { // if (expCatTotal.Resources[j].SpreadVal[colIndex] == $scope.SpreadType.Over) { // ecClass = expCatTotal.Resources[j].SpreadVal[colIndex] ; // } // } // } //} //expCatTotal.SpreadVal[colIndex] = ecClass; } // this code sets Equal by default. It means that 2 zeros will be displayed as green // so I decided to remove it to keep zeros without highligthing //var expCatSpread = $scope.SpreadType.Equal; //if (expCatTotal.SpreadVal[colIndex] == $scope.SpreadType.Over) { // expCatSpread = $scope.SpreadType.Over; //} else if (expCatTotal.SpreadVal[colIndex] == $scope.SpreadType.Less) { // expCatSpread = $scope.SpreadType.Less; //} //expCatTotal.SpreadVal[colIndex] = expCatSpread; updateCSSClass(expCatTotal, null, colIndex); } } //updateCSSClass(expCatTotal, null, colIndex); } function initCmMenuItems() { var menu = $('#' + $scope.calendarFilters.MenuId); menu.html(""); $.each($("#menuCalendarOptionsPrint li"), function (i, o) { $(o).appendTo(menu); }); $.each($("#menuCalendarOptions li"), function (i, o) { $(o).clone(true).appendTo(menu).change(function () { if (typeof onCMPreferencesItemClick === 'function') onCMPreferencesItemClick(menu); }); }); // SA. ENV-815 $("#menuCalendarOptions").find('*[data-key]').removeAttr('data-key'); } $scope.init = function (initData) { $scope.calendarFilters.ShowUpper = initData.ShowUpper; $scope.calendarFilters.ShowLower = initData.ShowLower; $scope.calendarFilters.IsUOMHours = initData.IsUOMHours; $scope.calendarFilters.PreferredTotalsDisplaying = initData.PreferredTotalsDisplaying; $scope.calendarFilters.IsBarMode = initData.IsBarMode; $scope.calendarFilters.IsViewModeMonth = initData.IsViewModeMonth; $scope.calendarFilters.IsCapacityModeActuals = initData.IsCapacityModeActuals; $scope.calendarFilters.GroupByTeam = initData.GroupByTeam; $scope.calendarFilters.ShowCapacity = initData.ShowCapacity; $scope.calendarFilters.StartDate = initData.StartDate; $scope.calendarFilters.EndDate = initData.EndDate; $scope.calendarFilters.ModelType = initData.ModelType; $scope.calendarFilters.MenuId = initData.MenuId; $scope.dataSection = initData.DataSection; $scope.pageTitle = initData.PageTitle; // SA. ENV-905 $scope.availableFilterOptions = initData.FilterOptions; // SA. ENV-799 initCmMenuItems(); var pagePrefArray; if (initData.PagePreferences && initData.PagePreferences.length > 0) { // SA. ENV-799 var filterModeInit = ""; var filterSelectedItemId = null; pagePrefArray = JSON.parse(initData.PagePreferences); $scope.preferences = pagePrefArray; for (var i = 0; i < pagePrefArray.length; i++) { switch (pagePrefArray[i].Key) { case "showChart": $scope.calendarFilters.ShowUpper = pagePrefArray[i].Value || (initData.ResourceId && (initData.ResourceId.length > 0)); break; case "showCriteria": $scope.calendarFilters.ShowLower = pagePrefArray[i].Value && !(initData.ResourceId && (initData.ResourceId.length > 0)); break; case "showOption": $scope.calendarFilters.ShowCapacity = pagePrefArray[i].Value != null ? pagePrefArray[i].Value : 1; //"Total Allocated/Total Need"; break; case "uomMode": $scope.calendarFilters.IsUOMHours = pagePrefArray[i].Value; break; case "capBarMode": $scope.calendarFilters.IsBarMode = pagePrefArray[i].Value; break; case "defaultView": $scope.calendarFilters.IsViewModeMonth = pagePrefArray[i].Value; break; case "groupByTeam": if ($scope.calendarFilters.ModelType != 'TeamboardModel') $scope.calendarFilters.GroupByTeam = pagePrefArray[i].Value; else $scope.calendarFilters.GroupByTeam = false; break; case "capacityView": $scope.calendarFilters.IsCapacityModeActuals = pagePrefArray[i].Value; break; case "capacityFilterStartDate": $scope.calendarFilters.StartDate = pagePrefArray[i].Value; break; case "capacityFilterEndDate": $scope.calendarFilters.EndDate = pagePrefArray[i].Value; break; case "filterCompanyList": if ((pagePrefArray[i].Value) && (filterModeInit.length < 1)) filterModeInit = "Company"; break; case "filterViewList": if ((pagePrefArray[i].Value) && (filterModeInit.length < 1)) filterModeInit = "View"; break; case "filterTeamList": if ((pagePrefArray[i].Value) && (filterModeInit.length < 1)) filterModeInit = "Team"; break; case "filteredCompany": filterSelectedItemId = pagePrefArray[i].Value; break; case "sortBy": $scope.calendarFilters.sortBy = pagePrefArray[i].Value; break; case "sortOrder": $scope.calendarFilters.sortOrder = pagePrefArray[i].Value; break; default: console.log("Restore page preferences (Activity calendar): data key not found in parsing loaded preferences (datakey=" + pagePrefArray[i].Key + ", dataSection=" + $scope.dataSection + ")"); } } } pagePrefArray = []; pagePrefArray.push({ Key: "showChart", Value: $scope.calendarFilters.ShowUpper }); pagePrefArray.push({ Key: "showCriteria", Value: $scope.calendarFilters.ShowLower }); pagePrefArray.push({ Key: "sortBy", Value: $scope.calendarFilters.sortBy }); pagePrefArray.push({ Key: "sortOrder", Value: $scope.calendarFilters.sortOrder }); pagePrefArray.push({ Key: "showOption", Value: $scope.calendarFilters.ShowCapacity }); pagePrefArray.push({ Key: "uomMode", Value: $scope.calendarFilters.IsUOMHours }); pagePrefArray.push({ Key: "capBarMode", Value: $scope.calendarFilters.IsBarMode }); pagePrefArray.push({ Key: "defaultView", Value: $scope.calendarFilters.IsViewModeMonth }); pagePrefArray.push({ Key: "groupByTeam", Value: $scope.calendarFilters.GroupByTeam }); pagePrefArray.push({ Key: "capacityView", Value: $scope.calendarFilters.IsCapacityModeActuals }); pagePrefArray.push({ Key: "capacityFilterStartDate", Value: $scope.calendarFilters.StartDate }); pagePrefArray.push({ Key: "capacityFilterEndDate", Value: $scope.calendarFilters.EndDate }); // pagePrefArray.push({ Key: "filteredCompany", Value: $scope.calendarFilters.CompanyId }); restorePreferences($scope.dataSection, pagePrefArray); $('#' + $scope.calendarFilters.MenuId).click(function (event) { event.stopPropagation(); }); // SA. ENV-799 if ($scope.IsEmpty(initData.CompanyId) && $scope.IsEmpty(initData.ViewId) && $scope.IsEmpty(initData.TeamId) && $scope.IsEmpty(initData.ResourceId)) { if (filterModeInit == "Company") initData.CompanyId = filterSelectedItemId; if (filterModeInit == "View") initData.ViewId = filterSelectedItemId; if (filterModeInit == "Team") initData.TeamId = filterSelectedItemId; if (filterModeInit == "Resource") initData.ResourceId = filterSelectedItemId; } var filterAttached = false; // if ($scope.calendarFilters.ModelType && ($scope.calendarFilters.ModelType == "EnVisage.Models.CapacityDetailsModel")) { if (initData.CompanyId && (initData.CompanyId.length > 0)) { $scope.CalendarFilterMode.SelectedItemId = initData.CompanyId; $scope.switchCompanyFilterMode(true); filterAttached = true; } if (initData.ViewId && (initData.ViewId.length > 0)) { $scope.CalendarFilterMode.SelectedItemId = initData.ViewId; $scope.switchViewFilterMode(true); filterAttached = true; } if (initData.TeamId && (initData.TeamId.length > 0)) { $scope.CalendarFilterMode.SelectedItemId = initData.TeamId; $scope.switchTeamFilterMode(true); filterAttached = true; } if (initData.ResourceId && (initData.ResourceId.length > 0)) { $scope.CalendarFilterMode.SelectedItemId = initData.ResourceId; $scope.switchResourceFilterMode(true); filterAttached = true; } if (!filterAttached) { $scope.switchCompanyFilterMode(); } // } // SA. ENV-799 $scope.loadCalendarData(); }; $scope.IsEmpty = function (value) { return !value || (value == null) || (value == ""); } $scope.getCalendar = function () { $("#loader").hide(); if ($scope.IsEmpty($scope.calendarFilters.CompanyId) && $scope.IsEmpty($scope.calendarFilters.TeamId) && $scope.IsEmpty($scope.calendarFilters.ViewId) && $scope.IsEmpty($scope.calendarFilters.ResourceId)) { return; } blockUI(); $scope.data = null; $("#loader").show(); var postData = JSON.parse(JSON.stringify($scope.calendarFilters)); $http.post('/CapacityManagement/LoadJsonCalendar', postData). success(function (data, status, headers, config) { if (data == null) $("#loader").hide(); if (data != null && data.Headers == null || data.Headers.length < 1) { $("#loader").hide(); unblockUI(); return; } $scope.data = data; $scope.calendarFilters.ShowAvgTotals = data.PreferredTotalsDisplaying; $scope.calendarFilters.IsUOMHours = data.IsUOMHours; swithViewMode(); for (var i = 0; i < $scope.data.Calendar.length; i++) { var row = $scope.data.Calendar[i]; row.IsUpperHidden = !$scope.calendarFilters.ShowUpper; row.IsLowerHidden = !$scope.calendarFilters.ShowLower; if (row.RowType != $scope.RowType.Team && row.ScenarioId && row.ExpCatId) { row.AvailableResources = getExpCatResources(row, row.TeamId); } if ($scope.calendarFilters.ResourceId != null && row.RowType == $scope.RowType.Project) { $scope.isReadOnly = !row.IsEdited; } } // initial setup css classes for (var rowIndex = 0; rowIndex < $scope.data.Calendar.length; rowIndex++) { if ($scope.data.Calendar[rowIndex].CSSClass == null) $scope.data.Calendar[rowIndex].CSSClass = new Array($scope.data.Headers.length); if ($scope.data.Calendar[rowIndex].BarStyle == null) $scope.data.Calendar[rowIndex].BarStyle = new Array($scope.data.Headers.length); refreshTotalSpreadVal($scope.data.Calendar[rowIndex]); for (var colIndex = 0; colIndex < $scope.data.Headers.length; colIndex++) { if ($scope.data.Calendar[rowIndex].RowType != $scope.RowType.BottomCategory) updateCSSClass($scope.data.Calendar[rowIndex], null, colIndex); if ($scope.data.Calendar[rowIndex].Resources != null && $scope.data.Calendar[rowIndex].Resources.length > 0) { for (var resIndex = 0; resIndex < $scope.data.Calendar[rowIndex].Resources.length; resIndex++) { if ($scope.data.Calendar[rowIndex].Resources[resIndex].CSSClass == null) $scope.data.Calendar[rowIndex].Resources[resIndex].CSSClass = new Array($scope.data.Headers.length); updateCSSClass($scope.data.Calendar[rowIndex], $scope.data.Calendar[rowIndex].Resources[resIndex], colIndex); } } } } for (i = 0; i < $scope.data.Calendar.length; i++) { if ($scope.data.Calendar[i].IsTotals && $scope.data.Calendar[i].RowType == $scope.RowType.Total) { $scope.grandtotalrow = $scope.data.Calendar[i]; break; } } recalcBottomPartTotals(); $("#loader").hide(); $timeout(function () { //To-Do: Replace it with ngSelect2 directive angular.element('[ng-model="row.ResourceToAssignId"]').select2({ allowClear: true, dropdownAutoWidth: true, placeholder: 'Select a person', dropdownCss: { 'font-size': '9pt' }, minimumResultsForSearch: 5, formatResult: $scope.formatPeopleResourceOption }); //To-Do: Remove if ($scope.CalendarFilterMode.SelectedItemId) $('[name="selFilterElement"]').select2('val', $scope.CalendarFilterMode.SelectedItemId); $scope.onResize(); }); unblockUI(); }). error(function (data, status, headers, config) { // called asynchronously if an error occurs // or server returns response with an error status. var errorMessage = 'An error occurred while loading calendar. ErrorCode = 000003'; showErrorModal('Oops!', errorMessage); $("#loader").hide(); unblockUI(); }); }; $scope.checkEditable = function (useType) { if (3 === useType) { return false; } return true; }; $scope.watchKeyInput = function (t) { $timeout(function () { if (t.$editable.inputEl.select) t.$editable.inputEl.select(); else if (t.$editable.inputEl.setSelectionRange) t.$editable.inputEl.setSelectionRange(0, t.$editable.inputEl.val().length); }, 3); t.$editable.inputEl.on('keydown', function (e) { if (e.which == 9) { //when tab key is pressed e.preventDefault(); var tab2Cell; if (e.shiftKey) { // when shift + tab use with 'onblur' set to 'submit' for automatic submission find the parent of the editable before this one in the markup grab the editable and display it tab2Cell = $(this).parentsUntil('table#table').prevAll(":has(.editable:visible):first").find(".editable:visible:last"); t.$form.$submit(); tab2Cell.click(); } else { // when just tab use with 'onblur' set to 'submit' for automatic submission find the parent of the editable after this one in the markup grab the editable and display it tab2Cell = $(this).parentsUntil('table#table').nextAll(":has(.editable:visible):first").find(".editable:visible:first"); t.$form.$submit(); tab2Cell.click(); } } }); }; $scope.onTxtBlur = function (txt) { txt.$form.$submit(); }; $scope.saveChanges = function () { // if user saves as new and selected TD or BU type for new scenarios if ($scope.SaveAs == "1" && $scope.saveType != "0") { var message = "Your new Scenario will be created for the full duration as the current Active Scenario."; if ($scope.saveType == "1") message = "Your new Scenario will be created for the full duration as the current Active Scenario. Please note: Only current resource-level allocations will be saved as project estimates."; bootbox.confirm({ message: message, callback: function (result) { $scope.$apply(function () { if (result) { saveChangesCallback(); } }); } }); } else { saveChangesCallback(); } }; function saveChangesCallback() { blockUI(); $scope.data2Update.IsOverAllocated = $scope.isOverAllocated, $scope.data2Update.IsMatchChecked = $scope.isMatchChecked; $scope.data2Update.SaveType = $scope.saveType; $scope.data2Update.SaveAs = $scope.SaveAs; $scope.data2Update.IsActiveScenario = $scope.isActiveScenario; $scope.data2Update.ScenarioName = $scope.ScenarioName; $scope.data2Update.ScenarioFilters = $scope.calendarFilters; $http.post('/CapacityManagement/SaveChanges', $scope.data2Update) .success(function (data, status, headers, config) { $scope.data2Update = { ScenarioId: $scope.id, ChangedExpCats: [], NonProjectTimeCats: [], ChangedSupperExpCatProjects: [] }; $scope.getCalendar(); $document.trigger('calendar.scenario-saved'); unblockUI(); }).error(function (data, status, headers, config) { var errorMessage = 'An error occurred while saving calendar changes. ErrorCode = 000001'; showErrorModal('Oops!', errorMessage); console.error(errorMessage); unblockUI(); }); }; $scope.takeAll = function (scenarioId, resId, expCatId, teamId) { var expCat = getExpCatById(scenarioId, expCatId, teamId); if (expCat != null) { var create = false; var allocateRest = false; var reset = false; allocateResource(getResourceByExpCat(expCat, resId), expCat, create, allocateRest, reset); // we should recalculate availability of changed resource for entire calendar recalculateAvailability4ResourceInEntireCalendar(resId); } }; $scope.takeRemaining = function (scenarioId, resId, expCatId, teamId) { var expCat = getExpCatById(scenarioId, expCatId, teamId); if (expCat != null) { var create = false; var allocateRest = true; var reset = false; allocateResource(getResourceByExpCat(expCat, resId), expCat, create, allocateRest, reset); // we should recalculate availability of changed resource for entire calendar recalculateAvailability4ResourceInEntireCalendar(resId); } }; $scope.zeroResource = function (scenarioId, resId, expCatId, teamId) { bootbox.confirm({ message: "Are you sure you want to fill this resource quantities with 0s?", callback: function (result) { $scope.$apply(function () { if (result) { var expCat = getExpCatById(scenarioId, expCatId, teamId); var create = false; var allocateRest = false; var reset = true; allocateResource(getResourceByExpCat(expCat, resId), expCat, create, allocateRest, reset); // we should recalculate availability of changed resource for entire calendar recalculateAvailability4ResourceInEntireCalendar(resId); } }); }, className: "bootbox-sm" }); }; $scope.assignResource = function (row, $event) { if (!row || !row.ResourceToAssignId || !row.ExpCatId || !row.TeamId) return; var resource = getResourceById(row.ResourceToAssignId); if (resource != null) { var project = getProjectById(row.ProjectId); var expCat = getExpCatById(row.ScenarioId, row.ExpCatId, row.TeamId); var expCatTotal = getExpCatById(null, row.ExpCatId, null); //Check the same resource assignment if (getResourceByExpCat(expCat, resource.Id) != null) { alert("The " + resource.Name + " has been assigned to the " + expCat.Name); return; } //Add exp cat resource //todo: just added resources are not validated against capacity values var newResource = { 'Id': resource.Id, 'Name': resource.Name, 'QuantityValues': new Array($scope.data.Headers.length), 'SpreadVal': new Array($scope.data.Headers.length), 'CSSClass': new Array($scope.data.Headers.length), 'CapacityQuantityValues': new Array($scope.data.Headers.length), 'AllocatedQuantityValues': new Array($scope.data.Headers.length), 'ReadOnlyWeeks': new Array($scope.data.Headers.length), 'ReadOnlyWeeksActuals': new Array($scope.data.Headers.length), 'Teams': resource.Teams, 'StartDate': resource.StartDate, 'EndDate': resource.EndDate, 'GrandTotalReadOnly': false, 'GrandTotalQuantity': 0 }; var i; for (i = 0; i < newResource.QuantityValues.length; i++) { if (isNaN(newResource.QuantityValues[i])) newResource.QuantityValues[i] = 0; } expCat.Resources.push(newResource); if (expCatTotal != null) { var resourceTotal = getResourceByExpCat(expCatTotal, resource.Id); var colIndex; if (resourceTotal == null) { //Add total exp cat resource resourceTotal = { 'Id': resource.Id, 'Name': resource.Name, 'QuantityValues': new Array($scope.data.Headers.length), 'SpreadVal': new Array($scope.data.Headers.length), 'CSSClass': new Array($scope.data.Headers.length), 'Teams': resource.Teams, 'QuantityTotalResValue': new Array($scope.data.Headers.length), 'AllocatedQuantityValues': new Array($scope.data.Headers.length) }; for (i = 0; i < resourceTotal.QuantityValues.length; i++) { if (isNaN(resourceTotal.QuantityValues[i])) resourceTotal.QuantityValues[i] = 0; } expCatTotal.QuantityTotalResValue = expCatTotal.QuantityResValue * (expCatTotal.Resources.length + 1); expCatTotal.Resources.push(resourceTotal); } var grandTotalReadOnly = true; var resourceMonthReadOnly = false; for (colIndex = 0; colIndex < $scope.data.Headers.length; colIndex++) { if (!$scope.data.Headers[colIndex].IsMonth) { newResource.ReadOnlyWeeks[colIndex] = resource.ReadOnlyWeeks[colIndex] || project.ReadOnly[colIndex]; newResource.ReadOnlyWeeksActuals[colIndex] = resource.ReadOnlyWeeksActuals[colIndex] || project.ReadOnly[colIndex]; if (!newResource.ReadOnlyWeeks[colIndex]) { var groupByTeamMode = $scope.calendarFilters.GroupByTeam && isGuidEmpty($scope.calendarFilters.TeamId) && isGuidEmpty($scope.calendarFilters.ResourceId); if (groupByTeamMode) { var resTeamItem = findTeamInResourceTeams(newResource.Teams, expCat.TeamId); if (resTeamItem) { newResource.ReadOnlyWeeks[colIndex] = resTeamItem.TeamStartDate > $scope.data.Headers[colIndex].Milliseconds || $scope.data.Headers[colIndex].Milliseconds > (resTeamItem.TeamEndDate || Number.MAX_VALUE); newResource.ReadOnlyWeeksActuals[colIndex] |= newResource.ReadOnlyWeeks[colIndex]; newResource.StartDate = resTeamItem.TeamStartDate; newResource.EndDate = resTeamItem.TeamEndDate || newResource.EndDate; } } else { var rdnly = true; for (var i = 0; i < (newResource.Teams || []).length; i++) { var rt = newResource.Teams[i]; if (rt.TeamStartDate < $scope.data.Headers[colIndex].Milliseconds && $scope.data.Headers[colIndex].Milliseconds <= (rt.TeamEndDate || Number.MAX_VALUE)) { rdnly = false; break; } } newResource.ReadOnlyWeeks[colIndex] = rdnly; newResource.ReadOnlyWeeksActuals[colIndex] |= rdnly; } } resourceMonthReadOnly |= newResource.ReadOnlyWeeks[colIndex]; grandTotalReadOnly &= newResource.ReadOnlyWeeks[colIndex]; } else { newResource.ReadOnlyWeeks[colIndex] = resourceMonthReadOnly; resourceMonthReadOnly = false; } } newResource.GrandTotalReadOnly = grandTotalReadOnly; } var create = true; var allocateRest = false; var reset = false; allocateResource(newResource, expCat, create, allocateRest, reset); //true, false, false); expCatTotal.EmptyScenario = expCatTotal.Resources.length == 0; row.AvailableResources = getExpCatResources(row, row.TeamId); //refreshAssignResourceSelect(row.TeamId, row.ProjectId, row.ExpCatId); resizeCalendar(); } row.ResourceToAssignId = null; }; $scope.removeResource = function (scenarioId, resId, expCatId, teamId) { bootbox.confirm({ message: "Are you sure you want to remove this resource?", callback: function (result) { $scope.$apply(function () { if (result) { var expCat = getExpCatById(scenarioId, expCatId, teamId); var create = false; var allocateRest = false; var reset = true; allocateResource(getResourceByExpCat(expCat, resId), expCat, create, allocateRest, reset); // we should recalculate availability of changed resource for entire calendar recalculateAvailability4ResourceInEntireCalendar(resId); //var expCat = getExpCatById(scenarioId, expCatId, teamId); var expCatTotal = getExpCatById(null, expCatId, null); for (var i = 0; i < expCat.Resources.length; i++) { if (expCat.Resources[i].Id == resId) { for (var k = 0; k < expCat.QuantityValues.length; k++) { changeResourceValue(0, expCat.ScenarioId, expCat.ExpCatId, expCat.Resources[i].Id, teamId, k, true); } expCat.Resources.splice(i, 1); applyCellChange(scenarioId, expCatId, 0, 0, 0, 0, resId, false, true, false, false); expCatTotal.EmptyScenario = expCatTotal.Resources.length == 0; expCat.AvailableResources = getExpCatResources(expCat, teamId); //refreshAssignResourceSelect(expCat.TeamId, expCat.ProjectId, expCat.ExpCatId, teamId); return; } } resizeCalendar(); } }); }, className: "bootbox-sm" }); }; function getNearestWeekending(date) { for (var i = 0; i < $scope.data.Headers.length; i++) { if ($scope.data.Headers[i].Milliseconds >= date) return $scope.data.Headers[i].Milliseconds; } return date; } $scope.checkActualValue = function (data, scenarioId, expCatId, resId, teamId, colIndex) { var newValue = parseFloat(data); if (isNaN(newValue)) newValue = 0; if (newValue < 0) { return "Value should not be less than zero"; } //alert("That is not implemented yet!") //return false; var expCat = getExpCatById(scenarioId, expCatId, teamId); //var resource = getResourceByExpCat(expCat, resId); var actual = getResourceActualByExpCat(expCat, resId); var avg = ($scope.calendarFilters.ShowAvgTotals && !$scope.calendarFilters.IsUOMHours); var project = getProjectById(expCat.ProjectId); var resourceEndDateWeekending = getNearestWeekending(actual.EndDate); var projectEndDateWeekending = getNearestWeekending(project.EndDate); //Edit month or week cell if (colIndex >= 0) { if ($scope.data.Headers[colIndex].IsMonth) { var oldValue = actual.QuantityValues[colIndex]; var weeks = 0; var coef = avg ? (newValue / (oldValue || 1)) : (oldValue > 0 ? newValue / oldValue : 1); //Get weekendings in entire month for (var j = colIndex - 1; j >= 0 && !$scope.data.Headers[j].IsMonth; j--) { var currWeekending = $scope.data.Headers[j].Milliseconds; if (currWeekending >= actual.StartDate && currWeekending >= project.StartDate && currWeekending <= resourceEndDateWeekending && currWeekending <= projectEndDateWeekending && !actual.ReadOnlyWeeksActuals[j]) { weeks++; } } var totalWeeks = $scope.data.Headers[colIndex].Weeks.length; for (var j = colIndex - 1; j >= 0 && !$scope.data.Headers[j].IsMonth; j--) { var currWeekending = $scope.data.Headers[j].Milliseconds; if (currWeekending >= actual.StartDate && currWeekending >= project.StartDate && currWeekending <= resourceEndDateWeekending && currWeekending <= projectEndDateWeekending && !actual.ReadOnlyWeeksActuals[j]) { var val = avg ? (oldValue > 0 ? actual.QuantityValues[j] * coef : newValue / weeks * totalWeeks) : (oldValue > 0 ? actual.QuantityValues[j] * coef : newValue / weeks); changeActualValue(val, scenarioId, expCatId, resId, teamId, j, false); } } } else { changeActualValue(newValue, scenarioId, expCatId, resId, teamId, colIndex, false); } //Edit total cell } else { var weeks = 0; var part1Val = 0; var part2Val = 0; var part1Week = 0; var part2Week = 0; for (var j = 0; j < $scope.data.Headers.length; j++) { var currWeekending = $scope.data.Headers[j].Milliseconds; if (currWeekending >= actual.StartDate && currWeekending >= project.StartDate && currWeekending <= resourceEndDateWeekending && currWeekending <= projectEndDateWeekending) { if (!$scope.data.Headers[j].IsMonth) { if (actual.ReadOnlyWeeksActuals[j]) { part1Val += actual.ActualQuantityValues[j]; part1Week++; } else { part2Val += actual.ActualQuantityValues[j]; part2Week++; weeks += 1; } } } } var newWeekValue = newValue / part2Week; //Calc avg for non r/o cells var coef = (actual.GrandActualTotalQuantity > 0 ? newValue / actual.GrandActualTotalQuantity : -1); var newTotal = part1Val + part2Val; if (coef > 0) coef = (coef * newTotal - part1Val) / part2Val; else newValue = (newValue * (part1Week + part2Week)) / part2Week; for (var j = 0; j < $scope.data.Headers.length; j++) { var currWeekending = $scope.data.Headers[j].Milliseconds; if (currWeekending >= actual.StartDate && currWeekending >= project.StartDate && currWeekending <= resourceEndDateWeekending && currWeekending <= projectEndDateWeekending && !$scope.data.Headers[j].IsMonth && !actual.ReadOnlyWeeksActuals[j]) { var oldValue = actual.ActualQuantityValues[j]; $scope.checkActualValue(avg ? (coef > 0 ? oldValue * coef : newValue) : coef > 0 ? oldValue * coef : newWeekValue, scenarioId, expCatId, resId, teamId, j); } } } // we should recalculate availability of changed resource for entire calendar //recalculateAvailability4ResourceInEntireCalendar(resId); return false; }; $scope.checkNonProjectTimeValue = function (data, nptId, catId, resId, colIndex) { var newValue = parseFloat(data); if (isNaN(newValue)) newValue = 0; if (newValue < 0) { return "Value should not be less than zero"; } var category = getNonProjectTimeById(nptId); var resource = getResourceByNonProjectTime(category, resId); var avg = ($scope.calendarFilters.ShowAvgTotals && !$scope.calendarFilters.IsUOMHours); if (colIndex >= 0) { if ($scope.data.Headers[colIndex].IsMonth) { var oldValue = category.IsTeamMode ? category.QuantityValues[colIndex] : resource.QuantityValues[colIndex]; if (oldValue == null) return; var weeks = $scope.data.Headers[colIndex].Weeks.length; var coef = avg ? (newValue / (oldValue != 0 ? oldValue : 1 || 1)) : (oldValue > 0 ? newValue / (oldValue != 0 ? oldValue : 1) : 1); var totalWeeks = $scope.data.Headers[colIndex].Weeks.length; for (var j = colIndex - 1; j >= 0 && !$scope.data.Headers[j].IsMonth; j--) { var val = 0; if (category.IsTeamMode) { val = avg ? (oldValue > 0 ? category.QuantityValues[j] * coef : newValue / ((weeks * totalWeeks) != 0 ? weeks * totalWeeks : 1)) : (oldValue > 0 ? category.QuantityValues[j] * coef : newValue / (weeks == 0 ? 1 : weeks)); } else { val = avg ? (oldValue > 0 ? resource.QuantityValues[j] * coef : newValue / ((weeks * totalWeeks) != 0 ? weeks * totalWeeks : 1)) : (oldValue > 0 ? resource.QuantityValues[j] * coef : newValue / (weeks == 0 ? 1 : weeks)); } changeNonProjectTimeValue(val, nptId, catId, resId, j); } } else { changeNonProjectTimeValue(newValue, nptId, catId, resId, colIndex); } //Edit total cell } else { var weeks = 0; var readOnlyRowSum = 0; var editableRowSum = 0; var readonlyWeekCount = 0; var editableWeekCount = 0; for (var j = 0; j < $scope.data.Headers.length; j++) { if (!$scope.data.Headers[j].IsMonth) { if (!category.IsTeamMode) { if (resource.ReadOnlyWeeks[j]) { readOnlyRowSum += (resource.QuantityValues[j] == null ? 0 : resource.QuantityValues[j]); readonlyWeekCount++; } else { editableRowSum += (resource.QuantityValues[j] == null ? 0 : resource.QuantityValues[j]); editableWeekCount++; weeks += 1; } } else { if (category.ReadOnly[j]) { readOnlyRowSum += (category.QuantityValues[j] == null ? 0 : category.QuantityValues[j]); readonlyWeekCount++; } else { editableRowSum += (category.QuantityValues[j] == null ? 0 : category.QuantityValues[j]); editableWeekCount++; weeks += 1; } } } } var newWeekValue = newValue / editableWeekCount; //Calc avg for non r/o cells var coef = -1; if (!category.IsTeamMode) coef = (resource.GrandTotalQuantity > 0 ? newValue / resource.GrandTotalQuantity : -1); else coef = (category.GrandTotalQuantity > 0 ? newValue / category.GrandTotalQuantity : -1); var newTotal = readOnlyRowSum + editableRowSum; if (coef > 0) coef = (coef * newTotal - readOnlyRowSum) / editableRowSum; else newValue = (newValue * (readonlyWeekCount + editableWeekCount)) / editableWeekCount; for (var j = 0; j < $scope.data.Headers.length; j++) { if (!$scope.data.Headers[j].IsMonth) { if (category.IsTeamMode && category.ReadOnly[j]) continue; if (!category.IsTeamMode && resource.ReadOnlyWeeks[j]) continue; var oldValue = 0; if (category.IsTeamMode) oldValue = category.QuantityValues[j]; else oldValue = resource.QuantityValues[j]; $scope.checkNonProjectTimeValue(avg ? (coef > 0 ? oldValue * coef : newValue) : coef > 0 ? oldValue * coef : newWeekValue, nptId, catId, resId, j); } } } return false; } $scope.checkResourceValue = function (data, scenarioId, expCatId, resId, teamId, colIndex) { var newValue = parseFloat(data); if (isNaN(newValue)) newValue = 0; if (newValue < 0) { return "Value should not be less than zero"; } var expCat = getExpCatById(scenarioId, expCatId, teamId); var resource = getResourceByExpCat(expCat, resId); var avg = ($scope.calendarFilters.ShowAvgTotals && !$scope.calendarFilters.IsUOMHours); var project = getProjectById(expCat.ProjectId); var resourceEndDateWeekending = getNearestWeekending(resource.EndDate); var projectEndDateWeekending = getNearestWeekending(project.EndDate); //Edit month or week cell if (colIndex >= 0) { if ($scope.data.Headers[colIndex].IsMonth) { var oldValue = resource.QuantityValues[colIndex]; var weeks = 0; var coef = avg ? (newValue / (oldValue || 1)) : (oldValue > 0 ? newValue / oldValue : 1); //Get weekendings in entire month for (var j = colIndex - 1; j >= 0 && !$scope.data.Headers[j].IsMonth; j--) { var currWeekending = $scope.data.Headers[j].Milliseconds; if (currWeekending >= resource.StartDate && currWeekending >= project.StartDate && currWeekending <= resourceEndDateWeekending && currWeekending <= projectEndDateWeekending && !resource.ReadOnlyWeeks[j]) { weeks++; } } var totalWeeks = $scope.data.Headers[colIndex].Weeks.length; for (var j = colIndex - 1; j >= 0 && !$scope.data.Headers[j].IsMonth; j--) { var currWeekending = $scope.data.Headers[j].Milliseconds; if (currWeekending >= resource.StartDate && currWeekending >= project.StartDate && currWeekending <= resourceEndDateWeekending && currWeekending <= projectEndDateWeekending && !resource.ReadOnlyWeeks[j]) { var val = avg ? (oldValue > 0 ? resource.QuantityValues[j] * coef : newValue / weeks * totalWeeks) : (oldValue > 0 ? resource.QuantityValues[j] * coef : newValue / weeks); changeResourceValue(val, scenarioId, expCatId, resId, teamId, j, false); } } } else { changeResourceValue(newValue, scenarioId, expCatId, resId, teamId, colIndex, false); } //Edit total cell } else { var weeks = 0; var part1Val = 0; var part2Val = 0; var part1Week = 0; var part2Week = 0; for (var j = 0; j < $scope.data.Headers.length; j++) { var currWeekending = $scope.data.Headers[j].Milliseconds; if (currWeekending >= resource.StartDate && currWeekending >= project.StartDate && currWeekending <= resourceEndDateWeekending && currWeekending <= projectEndDateWeekending) { if (!$scope.data.Headers[j].IsMonth) { if (resource.ReadOnlyWeeks[j]) { part1Val += resource.QuantityValues[j]; part1Week++; } else { part2Val += resource.QuantityValues[j]; part2Week++; weeks += 1; } } } } var newWeekValue = newValue / part2Week; //Calc avg for non r/o cells var coef = (resource.GrandTotalQuantity > 0 ? newValue / resource.GrandTotalQuantity : -1); var newTotal = part1Val + part2Val; if (coef > 0) coef = (coef * newTotal - part1Val) / part2Val; else newValue = (newValue * (part1Week + part2Week)) / part2Week; for (var j = 0; j < $scope.data.Headers.length; j++) { var currWeekending = $scope.data.Headers[j].Milliseconds; if (currWeekending >= resource.StartDate && currWeekending >= project.StartDate && currWeekending <= resourceEndDateWeekending && currWeekending <= projectEndDateWeekending && !$scope.data.Headers[j].IsMonth && !resource.ReadOnlyWeeks[j]) { var oldValue = resource.QuantityValues[j]; $scope.checkResourceValue(avg ? (coef > 0 ? oldValue * coef : newValue) : coef > 0 ? oldValue * coef : newWeekValue, scenarioId, expCatId, resId, teamId, j); } } } // we should recalculate availability of changed resource for entire calendar recalculateAvailability4ResourceInEntireCalendar(resId); return false; }; function getExpCatResources(expCat, teamId) { var resources = []; for (var i = 0; i < $scope.data.AllResources.length; i++) { var resource = $scope.data.AllResources[i]; var resourceMatch = resource.ExpedentureCategoryId == expCat.ExpCatId && resource.IsActiveEmployee; // condition for general EC if (expCat.AllowResourceAssignment == false) { resourceMatch = resource.IsActiveEmployee; // condition for Super EC } if (resourceMatch) { var isProjectFound = false; for (var j = 0; j < resource.ProjectIds.length; j++) { if (resource.ProjectIds[j] == expCat.ProjectId && (!$scope.calendarFilters.GroupByTeam || $scope.CalendarFilterMode.IsTeam || findTeamInResourceTeams(resource.Teams, expCat.TeamId))) { isProjectFound = true; break; } } if (isProjectFound) { var isFound = false; if (expCat.Resources != null) { for (var j = 0; j < expCat.Resources.length; j++) { if (expCat.Resources[j].Id == resource.Id && (!$scope.calendarFilters.GroupByTeam || teamsAnyOf(expCat.Resources[j].Teams, resource.Teams))) { isFound = true; break; } } } if (!isFound) { for (var j = 0; j < resources.length; j++) { if (resources[j].Id == resource.Id && (!$scope.calendarFilters.GroupByTeam || teamsAnyOf(resources[j].Teams, resource.Teams))) { isFound = true; break; } } } if (!isFound && resource.AssignedToTeam) { if ($scope.calendarFilters.ResourceId == null || resource.Id == $scope.calendarFilters.ResourceId) { resources.push(resource); } } } } } /* Calculation of availability for resources */ return getResources4Assign(resources, expCat.ProjectId, teamId); }; function getResources4Assign(resources, projectId, teamId) { var resources4Assign = {}; if (!resources || resources.length <= 0 || !projectId) return null; var project = getProjectById(projectId); if (project) { var startDate = project.StartDate, endDate = getNearestWeekending(project.EndDate); for (var i = 0; i < resources.length; i++) { var resource4Assign = getResource4Assign(resources[i], startDate, endDate, teamId); if (resource4Assign) resources4Assign[resource4Assign.id] = resource4Assign; } } return Object.keys(resources4Assign).length > 0 ? resources4Assign : null; }; function getResource4Assign(resource, startDate, endDate, teamId) { if (!resource || (startDate || 0) <= 0 || (endDate || 0) <= 0) return; var returnFlag = false; for (var count = 0; count < resource.Teams.length; count++) { if (resource.Teams[count].TeamStartDate > endDate || (resource.Teams[count].TeamEndDate || Number.MAX_VALUE) < startDate) { returnFlag = false || returnFlag; } else { returnFlag = true; } } if (!returnFlag) { return null; } var resource4Assign = { id: resource.Id, name: resource.Name, minAvailability: Number.MAX_VALUE, maxAvailability: -Number.MAX_VALUE, avgAvailability: 0 } var expCats = dataSources.getExpenditures(); var uomValue = 0; if (expCats != null) { var expCat = expCats[resource.ExpedentureCategoryId]; if (expCat != null) uomValue = expCat.UOMValue || 0; // UOMValue always in hours and we should convert it in resources depending on activity calendar mode if (!$scope.calendarFilters.IsUOMHours && uomValue > 0) uomValue /= uomValue; } var today = new Date(); today = new Date(today.getUTCFullYear(), today.getUTCMonth(), today.getUTCDate()); var UTCmilliseconds = today.getTime() - today.getTimezoneOffset() * 60 * 1000; var weeks = 0; for (var i = 0; i < $scope.data.Headers.length; i++) { var header = $scope.data.Headers[i]; if (!header || header.IsMonth || header.Milliseconds < startDate || header.Milliseconds > endDate) continue; if (UTCmilliseconds > header.Milliseconds + 86400000 - 1) continue; var availability = 0; if (resource.ReadOnlyWeeks[i]) availability = 0; else { var isTeamFound = false; for (var teamIndex = 0; teamIndex < resource.Teams.length; teamIndex++) { if (resource.Teams[teamIndex].TeamStartDate <= header.Milliseconds && (resource.Teams[teamIndex].TeamEndDate || Number.MAX_VALUE) >= header.Milliseconds) { if (!isGuidEmpty(teamId)) { if (resource.Teams[teamIndex].TeamId == teamId) { isTeamFound = true; break; } } else { isTeamFound = true; break; } } } if (!isTeamFound) { availability = 0; } else { var remainingCapacity = uomValue - resource.AllocatedQuantityValues[i]; if (resource.NonProjectTime) { for (var npCategoryId in resource.NonProjectTime) remainingCapacity -= resource.NonProjectTime[npCategoryId][i] || 0; } availability = Math.round(100 * (uomValue > 0 ? (remainingCapacity / uomValue) : 0)); } } if (resource4Assign.minAvailability > availability) resource4Assign.minAvailability = availability; if (resource4Assign.maxAvailability < availability) resource4Assign.maxAvailability = availability; resource4Assign.avgAvailability += availability; weeks++; } if (weeks <= 0) resource4Assign.avgAvailability = 0; else resource4Assign.avgAvailability = Math.round(resource4Assign.avgAvailability / weeks); return resource4Assign; }; function recalculateAvailability4ResourceInEntireCalendar(resourceId) { if (!resourceId) return; var resource = getResourceById(resourceId); if (!resource) return; for (var i = 0; i < $scope.data.Calendar.length; i++) { var row = $scope.data.Calendar[i]; if (!row || !row.AvailableResources || !row.AvailableResources[resourceId]) continue; var project = getProjectById(row.ProjectId); if (!project) continue; var startDate = project.StartDate, endDate = getNearestWeekending(project.EndDate); row.AvailableResources[resourceId] = getResource4Assign(resource, startDate, endDate, row.TeamId); } }; $scope.isTakeAllDisabled = function (expCat, res) { for (var j = 0; j < expCat.Resources.length; j++) { if (expCat.Resources[j].ReadOnly) { return true; } } return false; }; $scope.hideCalendarPart = function (upper) { if (upper) $scope.calendarFilters.ShowUpper = !$scope.calendarFilters.ShowUpper; else $scope.calendarFilters.ShowLower = !$scope.calendarFilters.ShowLower; if (upper && !$scope.calendarFilters.ShowUpper) $("#showButtomMode").switcher('disable'); else $("#showButtomMode").switcher('enable'); if (!upper && !$scope.calendarFilters.ShowLower) $("#showTopMode").switcher('disable'); else $("#showTopMode").switcher('enable'); if ($scope.data != null) { for (var i = 0; i < $scope.data.Calendar.length; i++) { $scope.data.Calendar[i].IsLowerHidden = !$scope.calendarFilters.ShowLower; $scope.data.Calendar[i].IsUpperHidden = !$scope.calendarFilters.ShowUpper; } } // force digest cycle as this method could being called from JS $scope.$digest(); }; $scope.switchUOMMode = function (value) { var newValue = value != null ? value : !$scope.calendarFilters.IsUOMHours; $scope.calendarFilters.IsUOMHours = newValue; $scope.getCalendar(); }; $scope.switchGroupByTeam = function (value) { var newValue = value != null ? value : !$scope.calendarFilters.GroupByTeam; $scope.calendarFilters.GroupByTeam = newValue; $scope.getCalendar(); }; $scope.switchCapacityVew = function (value) { var newValue = value != null ? value : !$scope.calendarFilters.IsCapacityModeActuals; $scope.calendarFilters.IsCapacityModeActuals = newValue; $scope.getCalendar(); }; $scope.switchViewMode = function (value) { var newValue = value != null ? value : !$scope.calendarFilters.IsViewModeMonth; $scope.calendarFilters.IsViewModeMonth = newValue; swithViewMode(); // force digest cycle as this method could being called from JS $scope.$digest(); resizeCalendar(); }; function swithViewMode() { if (!$scope.data) return; var newValue = $scope.calendarFilters.IsViewModeMonth; var j; for (j = 0; j < $scope.data.YearHeaders.length; j++) { $scope.data.YearHeaders[j].SpanCount = 0; } for (var i = 0; i < $scope.data.Headers.length; i++) { var header = $scope.data.Headers[i]; if (header.IsMonth) { header.Collapsed = newValue; header.Show = newValue; header.CollapsedClass = newValue ? $scope.CollapsedIcon : $scope.NonCollapsedIcon; for (j = 0; j < $scope.data.YearHeaders.length; j++) { if ($scope.data.YearHeaders[j].Title == header.Year) { if (newValue) { $scope.data.YearHeaders[j].SpanCount++; // -= header.Weeks.length - 1; } else { $scope.data.YearHeaders[j].SpanCount += header.Weeks.length; } //break; } } } else { header.Collapsed = newValue; header.Show = !newValue; } header.Initialized = header.Initialized || header.Show; } getVisibleCellCount(); } function getVisibleCellCount() { var count = 0; for (var i = 0; i < $scope.data.Headers.length; i++) { var header = $scope.data.Headers[i]; if (header.Show) count++; } $scope.data.VisibleCellCount = count; } $scope.switchBarMode = function () { var newValue = !$scope.calendarFilters.IsBarMode; $scope.calendarFilters.IsBarMode = newValue; if (!$scope.data) return; // update project rows CSS for (var rowIndex = 0; rowIndex < $scope.data.Calendar.length; rowIndex++) { if ($scope.data.Calendar[rowIndex].RowType == $scope.RowType.Project) for (var colIndex = 0; colIndex < $scope.data.Headers.length; colIndex++) { updateCSSClass($scope.data.Calendar[rowIndex], null, colIndex); } } // force digest cycle as this method could being called from JS $scope.$digest(); }; $scope.changeCapacity = function (value) { $scope.calendarFilters.ShowCapacity = value; if (!$scope.data) return; for (var i = 0; i < $scope.data.Calendar.length; i++) { if ($scope.data.Calendar[i].RowType == $scope.RowType.BottomCategory) { var expCatTotal = $scope.data.Calendar[i]; refreshTotalSpreadVal(expCatTotal); } } recalcBottomPartTotals(); }; $scope.changeSortBy = function (value) { $scope.calendarFilters.sortBy = value; $scope.getCalendar(); }; $scope.switchSortOrder = function () { var newValue = !$scope.calendarFilters.sortOrder; $scope.calendarFilters.sortOrder = newValue; $scope.getCalendar(); }; $scope.onMonthHeaderClick = function (header) { header.Collapsed = !header.Collapsed; header.Show = !header.Show; header.CollapsedClass = header.Collapsed ? $scope.CollapsedIcon : $scope.NonCollapsedIcon; header.Initialized = header.Initialized || header.Show; var i; if (header.Weeks && header.Weeks.length > 0) for (i = 0; i < header.Weeks.length; i++) { $scope.data.Headers[header.Weeks[i]].Collapsed = header.Collapsed; $scope.data.Headers[header.Weeks[i]].Show = !header.Collapsed; $scope.data.Headers[header.Weeks[i]].Initialized = $scope.data.Headers[header.Weeks[i]].Initialized || $scope.data.Headers[header.Weeks[i]].Show; } for (i = 0; i < $scope.data.YearHeaders.length; i++) { if ($scope.data.YearHeaders[i].Title == header.Year) { if (header.Collapsed) { $scope.data.YearHeaders[i].SpanCount -= header.Weeks.length - 1; } else { $scope.data.YearHeaders[i].SpanCount += header.Weeks.length - 1; } break; } } getVisibleCellCount(); resizeCalendar(); }; $scope.anyUpdated = function () { return ($scope.data2Update && (($scope.data2Update.ChangedExpCats && $scope.data2Update.ChangedExpCats.length > 0) || ($scope.data2Update.NonProjectTimeCats && $scope.data2Update.NonProjectTimeCats.length > 0))); }; $scope.onParentNodeClick = function (row, $event) { if ($($event.target).parents('.scenario-name').length > 0 || $($event.target).parents('.menuGroup').length > 0) return; if (row.ProjectId != null && row.ExpCatId == null) { row.ProjectCollapsed = !row.ProjectCollapsed; row.CollapsedClass = row.ProjectCollapsed ? $scope.CollapsedIcon : $scope.NonCollapsedIcon; } else if (row.ExpCatId != null) { row.ExpCatCollapsed = !row.ExpCatCollapsed; if (row.ScenarioId != null) row.CollapsedClass = row.ExpCatCollapsed ? $scope.ExpCatCollapsedIcon : $scope.ExpCatNonCollapsedIcon; else row.CollapsedClass = row.ExpCatCollapsed ? $scope.CollapsedIcon : $scope.NonCollapsedIcon; } setChildrenCollapsed(row, false, row.IsParentCollapsed); resizeCalendar(); }; $scope.toggleNonProjectTotalRow = function (row) { if (!row || (row.RowType != $scope.RowType.NonProjectTimeTotal && row.RowType != $scope.RowType.NonProjectTimeCategory && row.RowType != $scope.RowType.NonProjectTime) || (row.RowType == $scope.RowType.NonProjectTime && row.IsTeamMode)) return; row.IsCollapsed = !row.IsCollapsed; row.CollapsedClass = row.IsCollapsed ? $scope.CollapsedIcon : $scope.NonCollapsedIcon; if (row.RowType == $scope.RowType.NonProjectTimeCategory) { for (var i = 0; i < $scope.data.Calendar.length; i++) { if ($scope.data.Calendar[i].RowType == $scope.RowType.NonProjectTimeTotal) { var parent = $scope.data.Calendar[i]; for (var j = 0; j < parent.Categories.length; j++) { var category = parent.Categories[j]; if (category.ParentId == row.Id) category.IsParentCollapsed = row.IsCollapsed; } } } } resizeCalendar(); }; $scope.toggleTotalRow = function (row) { if (!row || (row.RowType != $scope.RowType.Total)) return; row.IsCollapsed = !row.IsCollapsed; row.CollapsedClass = row.IsCollapsed ? $scope.CollapsedIcon : $scope.NonCollapsedIcon; }; $scope.CheckLock = function ($event, tableId, fieldId, url) { if (CheckLock(getEventTargetId($event), tableId, fieldId)) { // SA. ENV-905. Added backurl to navigation link var currentUrl = document.location.pathname + document.location.search; var backUrl = "backUrl=" + encodeURIComponent(currentUrl); var backName = ""; var navigateToUrl = url + '/' + fieldId + "?" + backUrl; if ($scope.pageTitle && ($scope.pageTitle.length > 0)) // SA. The title of the page to return. Used to display in scenario form button backName = "&backName=" + encodeURIComponent($scope.pageTitle); var navigateToUrl = url + '/' + fieldId + "?" + backUrl + backName; document.location.href = navigateToUrl; } }; $scope.ToggleStatus = function ($event, tableId, fieldId) { if (CheckLock(getEventTargetId($event), tableId, fieldId)) { if (fieldId == null || fieldId == "") return ""; var url = "/ForecastDashboard/CheckIfActive/"; var request = { 'scenarioId': fieldId }; $.get(url, request, function (data) { if (data == null || data == "") { $scope.ToggleStatusConfirmed(fieldId); return ''; } else { bootbox.confirm({ message: "There is an active scenario for this project already. Are you sure you want to activate this scenario instead of active one?", callback: function (result) { $scope.$apply(function () { if (result) { $scope.ToggleStatusConfirmed(fieldId); } }); }, className: "bootbox-sm" }); } }); } }; $scope.ToggleStatusConfirmed = function (scenarioId) { if (scenarioId == null || scenarioId == "") return ""; var url = "/ForecastDashboard/ToggleStatus/"; var request = { 'scenarioId': scenarioId }; $.get(url, request, function (data) { $scope.getCalendar(); }); return ''; }; $scope.CalcRemainingCapacity = function (capacity, index) { return (capacity - $scope.grandtotalrow.QuantityValues[index]); }; $scope.CalcRemainingCapacityTotal = function (capacityTotal) { return (capacityTotal - $scope.grandtotalrow.GrandTotalQuantity); }; function recalcBottomPartTotals() { //Skip totals calculating for bottom part of the people resource calendar if ($scope.calendarFilters.ResourceId != null) return; var avg = ($scope.calendarFilters.ShowAvgTotals && !$scope.calendarFilters.IsUOMHours); for (var i in $scope.data.Calendar) { var expCatTotal = $scope.data.Calendar[i]; if (expCatTotal.RowType == $scope.RowType.BottomCategory) { var allocated = 0; var weeksCount = 0; for (var colIndex = 0; colIndex < $scope.data.Headers.length; colIndex++) { if (colIndex == 0) expCatTotal.GrandTotalQuantity = 0; if (!$scope.data.Headers[colIndex].IsMonth) { switch ($scope.calendarFilters.ShowCapacity) { case "1": // Allocated/Capacity expCatTotal.GrandTotalQuantity += expCatTotal.QuantityTotalAllocatedValue[colIndex]; break; case "2": // Need/Capacity expCatTotal.GrandTotalQuantity += expCatTotal.QuantityValues[colIndex]; break; case "3": // Allocated/Need expCatTotal.GrandTotalQuantity += expCatTotal.QuantityTotalAllocatedValue[colIndex]; break; case "4": // Remaining/Capacity, display similar to Allocated/Capacity expCatTotal.GrandTotalQuantity += (expCatTotal.QuantityTotalResValue[colIndex] - expCatTotal.QuantityValues[colIndex]); break; } for (var j in expCatTotal.Resources) { var resourceTotal = expCatTotal.Resources[j]; if (colIndex == 0) resourceTotal.GrandTotalQuantity = 0; switch ($scope.calendarFilters.ShowCapacity) { case "1": // Allocated/Capacity resourceTotal.GrandTotalQuantity += resourceTotal.QuantityValues[colIndex]; break; case "2": // Need/Capacity resourceTotal.GrandTotalQuantity += resourceTotal.QuantityValues[colIndex]; break; case "3": // Allocated/Need resourceTotal.GrandTotalQuantity += resourceTotal.QuantityValues[colIndex]; break; case "4": // Remaining/Capacity, display similar to Allocated/Capacity resourceTotal.GrandTotalQuantity += (resourceTotal.QuantityTotalResValue[colIndex] - resourceTotal.AllocatedQuantityValues[colIndex]); break; } } weeksCount++; } } if (avg) { expCatTotal.GrandTotalQuantity = expCatTotal.GrandTotalQuantity / weeksCount; for (var j in expCatTotal.Resources) { var resourceTotal = expCatTotal.Resources[j]; resourceTotal.GrandTotalQuantity = resourceTotal.GrandTotalQuantity / weeksCount; } } } } } $scope.CopyScenario = function ($event, scenarioId) { if (CheckLock(getEventTargetId($event), 'Scenario', scenarioId)) { if (scenarioId == null || scenarioId == "") return ""; var url = "/Scenarios/Details/" + scenarioId + '?ptab=copy'; var backUrl = '&backUrl=' + encodeURIComponent('/CapacityManagement'); if (!!$scope.calendarFilters.TeamId) backUrl = '&backUrl=' + encodeURIComponent('/Team/Board?ptab=calendar'); else if (!!$scope.calendarFilters.ViewId) backUrl = '&backUrl=' + encodeURIComponent('/View/Board?ptab=calendar'); document.location.href = url; //+backUrl; } }; $scope.AddScenario = function ($event, projectId) { if (typeof loadScenario === 'function') loadScenario(projectId); }; function getEventTargetId($event) { if (!$event.target.id || $event.target.id === '') return $event.target.parentElement.id; return $event.target.id; } $scope.SelectedFilterElementChanged = function () { if ($scope.CalendarFilterMode.SelectedItemId && ($scope.CalendarFilterMode.SelectedItemId.length > 0)) { if ($scope.dataSection && ($scope.dataSection.length > 0)) { var prefs = collectPreferences($scope.dataSection); prefs.push({ Key: "filteredCompany", Value: $scope.CalendarFilterMode.SelectedItemId }); saveUserPagePreferences(prefs, $scope.dataSection); } } // SA. ENV-799 $scope.loadCalendarData(); // ENV-539. For some reason select2 jQuery control loose selected value just after $scope.digest() // so we need to set value again on next JS iteration. $timeout(function () { $('[name=selFilterElement]').select2("val", $scope.CalendarFilterMode.SelectedItemId); }, 0, false); } $scope.loadCalendarData = function () { // SA. ENV-799. Begin if (!$('#filterForm').valid()) return; $scope.calendarFilters.CompanyId = null; $scope.calendarFilters.ViewId = null; $scope.calendarFilters.TeamId = null; $scope.calendarFilters.ResourceId = null; if ($scope.CalendarFilterMode.IsCompany) $scope.calendarFilters.CompanyId = $scope.CalendarFilterMode.SelectedItemId; if ($scope.CalendarFilterMode.IsView) $scope.calendarFilters.ViewId = $scope.CalendarFilterMode.SelectedItemId; if ($scope.CalendarFilterMode.IsTeam) $scope.calendarFilters.TeamId = $scope.CalendarFilterMode.SelectedItemId; if ($scope.CalendarFilterMode.IsResource) $scope.calendarFilters.ResourceId = $scope.CalendarFilterMode.SelectedItemId; // SA. ENV-799. End $scope.getCalendar(); } // SA. ENV-799. Begin $scope.switchCompanyFilterMode = function (preserverSelection) { if (!$scope.CalendarFilterMode.IsCompany) $scope.CalendarFilterMode.IsCompany = true; $scope.CalendarFilterMode.IsView = false; $scope.CalendarFilterMode.IsTeam = false; $scope.CalendarFilterMode.IsResource = false; $scope.CalendarFilterMode.FilteredEntityTitle = "Company"; if (($scope.availableFilterOptions != null) && ($scope.availableFilterOptions.Companies != null)) { $scope.displayedOptions = $scope.availableFilterOptions.Companies; for (var i = 0; i < $scope.displayedOptions.length; i++) { var option = $scope.displayedOptions[i]; option.CSSClass = 'ddl-level-item'; if (option.Group && option.Group.Name && option.Group.Name.length > 0) { option.CSSClass += ' pad-left'; } } } if (!preserverSelection) { $scope.CalendarFilterMode.SelectedItemId = null; $scope.$broadcast('selectedSourceChanged'); } }; $scope.switchViewFilterMode = function (preserverSelection) { if (!$scope.CalendarFilterMode.IsView) $scope.CalendarFilterMode.IsView = true; $scope.CalendarFilterMode.IsCompany = false; $scope.CalendarFilterMode.IsTeam = false; $scope.CalendarFilterMode.IsResource = false; $scope.CalendarFilterMode.FilteredEntityTitle = "View"; if (($scope.availableFilterOptions != null) && ($scope.availableFilterOptions.Views != null)) $scope.displayedOptions = $scope.availableFilterOptions.Views; if (!preserverSelection) { $scope.CalendarFilterMode.SelectedItemId = null; $scope.$broadcast('selectedSourceChanged'); } }; $scope.switchTeamFilterMode = function (preserverSelection) { if (!$scope.CalendarFilterMode.IsTeam) $scope.CalendarFilterMode.IsTeam = true; $scope.CalendarFilterMode.IsCompany = false; $scope.CalendarFilterMode.IsView = false; $scope.CalendarFilterMode.IsResource = false; $scope.CalendarFilterMode.FilteredEntityTitle = "Team"; if (($scope.availableFilterOptions != null) && ($scope.availableFilterOptions.Teams != null)) $scope.displayedOptions = $scope.availableFilterOptions.Teams; if (!preserverSelection) { $scope.CalendarFilterMode.SelectedItemId = null; $scope.$broadcast('selectedSourceChanged'); } }; $scope.switchResourceFilterMode = function (preserverSelection) { if (!$scope.CalendarFilterMode.IsResource) $scope.CalendarFilterMode.IsResource = true; $scope.CalendarFilterMode.IsCompany = false; $scope.CalendarFilterMode.IsView = false; $scope.CalendarFilterMode.IsTeam = false; $scope.CalendarFilterMode.FilteredEntityTitle = "Resource"; $scope.IsCapacityModeActuals = true; // SA. ENV-886. Individual resource calendar shows actuals only if (!preserverSelection) { $scope.CalendarFilterMode.SelectedItemId = null; $scope.$broadcast('selectedSourceChanged'); } }; $scope.scrollHeader = function (evt) { //if (flag) { // return; //} var e = evt ? evt : window.event; var t = e.target ? e.target : e.srcElement; if (t.nodeType == 3) { t = t.parentNode; } var tid = t.id.replace(':scroller', ''); var fh = ge$(tid + ':scroller:fx'); var sd = ge$(tid + ':scroller'); fh.style.left = (0 - sd.scrollLeft) + 'px'; var cf = ge$(tid + '_CFB'); if (cf) { cf.style.marginTop = (0 - (sd.scrollTop)) + 'px'; } }; function ge$(d) { return document.getElementById(d); } $scope.clearWidth = function () { $scope.monthCol = []; $scope.weekCol = []; } $scope.onResize = function () { if ($scope.data) { //resize conteiner height var isAC = angular.element(".ac").length > 0, firstCellWidth = angular.element("div[id*='fxcol']").width(), capacityTable = angular.element("#capacity-table"), capacityTableHeight = capacityTable.height() || 0, capacityTableWidth = capacityTable.width() || 0, browserNonIE = ["ie", "ie1"].indexOf(checkBrowser()) < 0, //IE places scrollbars above the content and does not need additional space for scrollbar heightContainer = angular.element(".height-container"), heightContainerHeight = heightContainer.height(), widthContainer = angular.element(".width-container"); var noVScroll = ($(".ac-no-v-scroll").length > 0); if (noVScroll || (heightContainerHeight <= $scope.calendarViewSettings.tableHeightMin && capacityTableHeight > 0)) { heightContainerHeight = capacityTableHeight + $scope.calendarViewSettings.scrollHeight; heightContainer.height(heightContainerHeight); } //resize conteiner width var vertScrollShown = capacityTableHeight > heightContainerHeight; if (isAC) { widthContainer.width(Math.min(($scope.calendarViewSettings.headerWidth * $scope.data.VisibleCellCount) + (browserNonIE && vertScrollShown ? $scope.calendarViewSettings.scrollWidth : 0) + 1, $("#controller1").outerWidth() - firstCellWidth)); } else { widthContainer.width(Math.min($scope.calendarViewSettings.headerWidth * $scope.data.VisibleCellCount + (browserNonIE && vertScrollShown ? $scope.calendarViewSettings.scrollWidth : 0), $("#controller1").outerWidth() - firstCellWidth - 1)); } //resize conteiner height again when horizont scroll bar appears if (noVScroll && browserNonIE) { var heightContainerWidth = heightContainer.width(); var horizontScrollShown = capacityTableWidth > heightContainerWidth; if (horizontScrollShown) { heightContainerHeight += $scope.calendarViewSettings.scrollHeight; heightContainer.height(heightContainerHeight); } } } }; function refreshSelect2(obj) { $timeout(function () { // You might need this timeout to be sure its run after DOM render. obj.trigger("change"); }, 0, false); }; //function refreshAssignResourceSelect(teamId, projectId, expCatId) { // var control = angular.element('#assign-resource-select-' + teamId + '-' + projectId + '-' + expCatId); // control.select2('val', ''); // refreshSelect2(control); //}; $scope.formatPeopleResourceOption = function (result, container, query, escapeMarkup) { var $optionScope = angular.element(result.element).scope(); /* $optionScope.resource property is declared in the expression in the view: (resourceId, resource) in row.AvailableResources track by $index */ if ($optionScope && $optionScope.resource) { return scenarioDetailsService.getAssignableResourceOptionHtml($optionScope.resource); } }; function findTeamInResourceTeams(teams, teamId) { if (teams == null || teams.length == null || teams.length <= 0 || teamId == null) return null; for (var i = 0; i < teams.length; i++) { if (teams[i].TeamId == teamId) return teams[i]; } return null; } function teamsAnyOf(existingTeams, teams2Search) { if (existingTeams == null || existingTeams.length == null || existingTeams.length <= 0 || teams2Search == null || teams2Search.length == null || teams2Search.length <= 0) return false; for (var i = 0; i < existingTeams.length; i++) { for (var j = 0; j < teams2Search.length; j++) { if (existingTeams[i].TeamId == teams2Search[j].TeamId) return true; } } return false; }; function resizeCalendar() { $timeout(function () { $scope.onResize(); }); }; }]) .directive('scrollHeader', function () { return { restrict: 'A', link: function (scope, elem, attr, ctrl) { elem.bind('scroll', function (e) { scope.scrollHeader(e); }); } }; });