'use strict'; app.controller('teamInfoController', ['$scope', '$http', 'hoursResourcesConverter', 'cellHighlightingService', 'teamInfoService', 'dataSources', 'roundService', '$filter', '$q', '$timeout', function ($scope, $http, hoursResourcesConverter, cellHighlightingService, teamInfoService, dataSources, roundService, $filter, $q, $timeout) { var collapsedIcon = 'fa-plus-square', nonCollapsedIcon = 'fa-minus-square'; var groupByEnum = { Category: 'Category', Team: 'Team', Company: 'Business Unit', CostCenter: 'Cost Center' }; $scope.RowType = { NonProjectTime: 1, Team: 2, ExpenditureCategory: 3, Company: 4, CostCenter: 5 }; $scope.ExpendituresByCostCentersCache = {}; $scope.Weekendings = []; $scope.InternalData = null; //DAL data model $scope.IsRedrawRequired = false; setupJSEvents(); $scope.DisplayMode = { // Page Options IsUOMHours: true, // display data in Hours GroupBy: groupByEnum.Category, // group capacity by EC by default TotalsAs: 1, // Allocated/Capacity CapacityView: false, // Planned IsAvgMode: false, // Totals average mode ShowResources: true, // display bottom part (starting Non-Project Time row until the bottom of the grid) DisplayPriorityColumn: false }; $scope.DisplayData = {}; // UI data model $scope.DisplayDataOrder = []; $scope.$on('rebindTeamInfo', function (event, data) { if (!data) return; // we must create view model only after all services data will be loaded hoursResourcesConverter.load().then(function () { $scope.InternalData = { Header: data.Header || {} }; initOptions(data.DisplayMode); initRows(); }); }); $scope.$on('teaminfo.recreateRows', function (event) { initRows(); }); $scope.$on('queue.recreateRows', function (event) { if (!$scope.IsRedrawRequired) { $scope.IsRedrawRequired = true; $timeout(function () { initRows(); $scope.IsRedrawRequired = false; }); } }); $scope.$on('groupByChanged', function (event, data) { $scope.DisplayMode.GroupBy = data; initRows(); }); $scope.$on('changeUOMMode', function (event, data) { $scope.DisplayMode.IsUOMHours = data; zeroRowsData($scope.DisplayData); initRowsData(); }); $scope.$on('totalsAsChanged', function (event, data) { $scope.DisplayMode.TotalsAs = data; initRows(); }); $scope.$on('capacityViewChanged', function (event, data) { $scope.DisplayMode.CapacityView = data; zeroRowsData($scope.DisplayData); initRows(); }); $scope.$on('showResourcesChanged', function (event, data) { $scope.DisplayMode.ShowResources = data; initRows(); }); $scope.$on('resourceValueChanged', function (event, data) { if (!data) return; resourceValueChanged(data.TeamId, data.ExpenditureCategoryId, data.ResourceId, data.WeekEnding); }); $scope.$on('teamValueChanged', function (event, data) { if (!data) return; teamValueChanged(data.TeamId, data.ExpenditureCategoryId, data.WeekEnding); }); $scope.$on('resourceNonProjectTimeChanged', function (event, data) { if (!data) return; // TODO: Rewrite to update the only changed cells initRows(); }); $scope.toggleParentRow = function (key) { $scope.DisplayData[key].IsCollapsed = !$scope.DisplayData[key].IsCollapsed; $.each($scope.DisplayData, function (rowKey, row) { if (row.Type == $scope.RowType.ExpenditureCategory && row.ParentId == $scope.DisplayData[key].Id) { row.Initialized = true; row.Show = !$scope.DisplayData[key].IsCollapsed; } }); }; $scope.toggleRow = function (key) { $scope.DisplayData[key].IsCollapsed = !$scope.DisplayData[key].IsCollapsed; if ($scope.DisplayData[key].Resources) { for (var resourceIndex = 0; resourceIndex < $scope.DisplayData[key].Resources.length; resourceIndex++) { $scope.DisplayData[key].Resources[resourceIndex].Initialized = true; } } }; $scope.showTooltip = function (event, cell, rowType) { var $target = $(event.currentTarget); if ($target.data('bs.tooltip')) { $target.attr('data-original-title', getTooltipContent(cell, rowType)) return; } var opts = { trigger: 'click', html: true, title: getTooltipContent(cell, rowType) }; $target.tooltip(opts).on('show.bs.tooltip', hideRedundantTooltips); $target.tooltip('show'); }; $scope.isAvgMode = function () { return $scope.DisplayMode.IsAvgMode && !$scope.DisplayMode.IsUOMHours; }; // set page options with values received from outer scope function initOptions(options) { if (!options) return; var modes = Object.keys(options); if (modes.indexOf('IsUOMHours') >= 0) $scope.DisplayMode.IsUOMHours = options.IsUOMHours; if (modes.indexOf('GroupBy') >= 0) $scope.DisplayMode.GroupBy = options.GroupBy; if (modes.indexOf('TotalsAs') >= 0) $scope.DisplayMode.TotalsAs = options.TotalsAs; if (modes.indexOf('CapacityView') >= 0) $scope.DisplayMode.CapacityView = options.CapacityView; if (modes.indexOf('IsAvgMode') >= 0) $scope.DisplayMode.IsAvgMode = options.IsAvgMode; if (modes.indexOf('ShowResources') >= 0) $scope.DisplayMode.ShowResources = options.ShowResources; if (modes.indexOf('DisplayPriorityColumn') >= 0) $scope.DisplayMode.DisplayPriorityColumn = options.DisplayPriorityColumn; }; //==========Start Convert data model to UI data model methods=============// function initNonProjectTimeTotalRow() { return { Name: "Non-Project Time", Type: $scope.RowType.NonProjectTime, IsCollapsed: true, Categories: {}, Cells: new Array($scope.InternalData.Header.Weeks.length) }; }; function initNonProjectTimeRow(name) { return { Name: name, Cells: new Array($scope.InternalData.Header.Weeks.length) }; }; function initTopLevelRow(id, name, type) { if (!topLevelExists()) { return null; } return { Id: id, Name: name, Type: type, IsCollapsed: true, Cells: new Array($scope.InternalData.Header.Weeks.length), CSSClass: new Array($scope.InternalData.Header.Weeks.length), Total: { Value1: 0, Value2: 0, Allocated: 0, Needed: 0, Capacity: 0, NonProjectTime: 0, SuperECAllocations: 0 }, }; }; function initExpCategoryRow(category, parentId, isTeamCollapsed) { return { Id: category.Id, ParentId: parentId, Name: category.Name, Type: $scope.RowType.ExpenditureCategory, AllowResourceAssignment: category.AllowResourceAssignment, IsCollapsed: true, Initialized: !isTeamCollapsed, Show: !isTeamCollapsed, Cells: new Array($scope.InternalData.Header.Weeks.length), Resources: [], CSSClass: new Array($scope.InternalData.Header.Weeks.length), Total: { Value1: '-', Value2: '-', Allocated: 0, Needed: 0, Capacity: 0, NonProjectTime: 0, SuperECAllocations: 0 }, }; }; function initResourceRow(resource, catId) { return { Id: resource.Id, ExpCatId: catId, Name: resource.Name, Initialized: false, Cells: new Array($scope.InternalData.Header.Weeks.length), CSSClass: new Array($scope.InternalData.Header.Weeks.length), Total: { Value1: 0, Value2: 0, Allocated: 0, Needed: 0, Capacity: 0, NonProjectTime: 0, SuperECAllocations: 0 }, }; }; function initRows() { var nptCatsTask = dataSources.getNonProjectTimeCategories(); var expCatsTask = dataSources.load(); var companiesTask = dataSources.loadCompanies(); var costCentersTask = dataSources.loadCostCenters(); $q.all([nptCatsTask, expCatsTask, companiesTask, costCentersTask]) .then(function () { initRowsCallback(); }) .then(null, function () { showErrorModal(null, null, '000010'); }); }; function initRowsCallback() { if (!$scope.InternalData) return; $scope.ExpendituresByCostCentersCache = {}; if (!$scope.DisplayMode.ShowResources) { $scope.DisplayData = {}; $scope.DisplayDataOrder = []; return; } $scope.DisplayData = { "NonProjectTime": initNonProjectTimeTotalRow(), }; $scope.DisplayDataOrder = ["NonProjectTime"]; $scope.Weekendings = getWeekendingFromHeader($scope.InternalData); switch ($scope.DisplayMode.GroupBy) { case groupByEnum.CostCenter: initRows4GroupByCostCenterMode(); break; default: initRows4OtherGroupModes(); } initRowsData(); }; function initRows4OtherGroupModes() { var totalsAsModeParsed = parseInt($scope.DisplayMode.TotalsAs); var companies = $scope.DisplayMode.GroupBy === groupByEnum.Company ? Object.keys(dataSources.getCompanies()) : [null]; for (var companyIndex = 0; companyIndex < companies.length; companyIndex++) { var companyId = companies[companyIndex]; var teams = !!companyId ? teamInfoService.getTeamsByCompanyId(companyId) : teamInfoService.getAll(); if (teams) { for (var teamId in teams) { var team = teams[teamId]; var topLevelItemId = undefined; switch ($scope.DisplayMode.GroupBy) { case groupByEnum.Team: topLevelItemId = teamId; break; case groupByEnum.Company: topLevelItemId = team.CompanyId; break; } var topLevelItem = getTopLevelItem(topLevelItemId); var topLevelItemKey = null; if (topLevelExists() && topLevelItem) { topLevelItemKey = topLevelItem.Key; if (!(topLevelItemKey in $scope.DisplayData)) { $scope.DisplayData[topLevelItemKey] = initTopLevelRow(topLevelItemKey, topLevelItem.Name, topLevelItem.Type); $scope.DisplayDataOrder.push(topLevelItemKey); } } if (!team.ExpCategories) { continue; } // sort expCats so general ECs go first, this way we could reallocate resources to already existing general EC rows var expCategories = $filter('sortObjectsBy')(team.ExpCategories, 'AllowResourceAssignment', true); for (var expCatIndex in expCategories) { var ec = expCategories[expCatIndex]; if (!isExpenditureVisible(ec)) // Prevent displaying Super EC, if it doesn't has resources continue; // prepare EC row var ecKey = getECKey(ec.Id, teamId, team.CompanyId); if ((ec.AllowResourceAssignment || (totalsAsModeParsed != 1)) && !$scope.DisplayData[ecKey]) { // Dispaly ordinary ECs always // Display SuperECs for all modes, except TotalsAsMode=1 (Allocated / Capacity) $scope.DisplayData[ecKey] = initExpCategoryRow(ec, topLevelItemKey, (!!topLevelItemKey && $scope.DisplayData[topLevelItemKey]) ? $scope.DisplayData[topLevelItemKey].IsCollapsed : false); } // prepare EC's resource rows if (!ec.Resources) { continue; } for (var resourceId in ec.Resources) { var resource = teamInfoService.extendResourceModelWithAttributes(ec.Resources[resourceId], teamId); var resourceRow = null; var originalEcKey = getECKey(resource.OwnExpenditureCategoryId, teamId, team.CompanyId); if ($scope.DisplayData[originalEcKey]) { // If EC is visible due to current TotalsAs settings for (var idx = 0; idx < $scope.DisplayData[originalEcKey].Resources.length; idx++) { if ($scope.DisplayData[originalEcKey].Resources[idx].Id == resourceId) { resourceRow = initResourceRow(resource, resource.OwnExpenditureCategoryId); break; } } if (!resourceRow) { resourceRow = initResourceRow(resource, resource.OwnExpenditureCategoryId); $scope.DisplayData[originalEcKey].Resources.push(resourceRow); } } if (!resource.NonProjectTime) { continue; } for (var npTimeId in resource.NonProjectTime) { var npTimeCategoryId = resource.NonProjectTime[npTimeId].CategoryId; if (!$scope.DisplayData["NonProjectTime"].Categories[npTimeCategoryId]) { var npTimeCategory = dataSources.getNPTCategory(npTimeCategoryId); var npTimeCategoryRow = initNonProjectTimeRow(npTimeCategory); $scope.DisplayData["NonProjectTime"].Categories[npTimeCategoryId] = npTimeCategoryRow; } } } } // sort ECs by Name var sortedByName = $filter('sortObjectsBy')(team.ExpCategories, 'Name', false); for (var i in sortedByName) { var ecKey = getECKey(sortedByName[i].Id, teamId, team.CompanyId); if ($scope.DisplayData[ecKey] && $.inArray(ecKey, $scope.DisplayDataOrder) == -1) { $scope.DisplayDataOrder.push(ecKey); } } } } } }; function initRows4GroupByCostCenterMode() { var totalsAsModeParsed = parseInt($scope.DisplayMode.TotalsAs); $scope.ExpendituresByCostCentersCache = teamInfoService.getExpendituresByCostCentersSummary(); if (!$scope.ExpendituresByCostCentersCache) return; // Get all available cost centers var allCostCenters = dataSources.getCostCenters(); if (!allCostCenters) return; // Prepare cache for creating view-model rows for (var costCenterId in $scope.ExpendituresByCostCentersCache) { var costCenterCached = $scope.ExpendituresByCostCentersCache[costCenterId]; if (costCenterCached && (costCenterId in allCostCenters)) { // Get Cost Center names costCenterCached.Id = costCenterId; costCenterCached.Name = allCostCenters[costCenterId].Name; if (costCenterCached.Expenditures) { for (var expCatId in costCenterCached.Expenditures) { // Get EC Names and other necessary attributes var expCat = dataSources.getExpenditureById(expCatId); if (expCat) { var expCatData = costCenterCached.Expenditures[expCatId]; expCatData.Id = expCatId; expCatData.Name = expCat.ExpenditureCategoryName; expCatData.AllowResourceAssignment = expCat.AllowResourceAssignment; } } } } } var costCentersSorted = $filter('sortObjectsBy')($scope.ExpendituresByCostCentersCache, 'Name', false); var costCentersCount = costCentersSorted && angular.isArray(costCentersSorted) && costCentersSorted.length ? costCentersSorted.length : 0; for (var ccIndex = 0; ccIndex < costCentersCount; ccIndex++) { var costCenter = costCentersSorted[ccIndex]; var topLevelItem = getTopLevelItem(costCenter.Id); var topLevelItemKey = null; if (topLevelItem) { topLevelItemKey = topLevelItem.Key; if (!(topLevelItemKey in $scope.DisplayData)) { $scope.DisplayData[topLevelItemKey] = initTopLevelRow(topLevelItemKey, topLevelItem.Name, topLevelItem.Type); $scope.DisplayDataOrder.push(topLevelItemKey); } if (!costCenter.Expenditures) { continue; } // sort expCats so general ECs go first, this way we could reallocate resources to already existing general EC rows var expCatsSorted = $filter('sortObjectsBy')(costCenter.Expenditures, 'AllowResourceAssignment', true); var expCatsSortedCount = expCatsSorted && angular.isArray(expCatsSorted) && expCatsSorted.length ? expCatsSorted.length : 0; for (var eIndex = 0; eIndex < expCatsSortedCount; eIndex++) { var ec = expCatsSorted[eIndex]; if (ec && ec.Teams) { var expCatId = expCatsSorted[eIndex].Id; var expCatData = teamInfoService.mergeExpenditureInTeams(expCatId, ec.Teams, true); if (!isExpenditureVisible(expCatData)) { // Prevent displaying Super EC, if it doesn't has resources continue; } // prepare EC row var ecKey = getECKey(expCatData.Id, null, costCenter.Id); if ((expCatData.AllowResourceAssignment || (totalsAsModeParsed != 1)) && !$scope.DisplayData[ecKey]) { // Dispaly all ordinary ECs + SuperECs for al modes, except TotalsAsMode=1 (Allocated/Capacity) $scope.DisplayData[ecKey] = initExpCategoryRow(expCatData, topLevelItemKey, (!!topLevelItemKey && $scope.DisplayData[topLevelItemKey]) ? $scope.DisplayData[topLevelItemKey].IsCollapsed : false); } // prepare EC's resource rows var expCatResources = teamInfoService.mergeExpenditureResources(expCatId, ec.Teams, false); if (!expCatResources) { continue; } var expCatResourcesSorted = $filter('sortObjectsBy')(expCatResources, 'SortingKey', false); var expCatResourcesSortedCount = expCatResourcesSorted && angular.isArray(expCatResourcesSorted) && expCatResourcesSorted.length ? expCatResourcesSorted.length : 0; for (var rIndex = 0; rIndex < expCatResourcesSortedCount; rIndex++) { var resource = expCatResourcesSorted[rIndex]; var resourceId = resource.Id; var resourceRow = null; var originalEcKey = getECKey(resource.OwnExpenditureCategoryId, null, costCenter.Id); if ($scope.DisplayData[originalEcKey]) { // If EC is visible due to current TotalsAs settings for (var idx = 0; idx < $scope.DisplayData[originalEcKey].Resources.length; idx++) { if ($scope.DisplayData[originalEcKey].Resources[idx].Id == resourceId) { resourceRow = initResourceRow(resource, resource.OwnExpenditureCategoryId); break; } } if (!resourceRow) { resourceRow = initResourceRow(resource, resource.OwnExpenditureCategoryId); $scope.DisplayData[originalEcKey].Resources.push(resourceRow); } } if (!resource.NonProjectTime) { continue; } for (var npTimeId in resource.NonProjectTime) { var npTimeCategoryId = resource.NonProjectTime[npTimeId].CategoryId; if (!$scope.DisplayData["NonProjectTime"].Categories[npTimeCategoryId]) { var npTimeCategory = dataSources.getNPTCategory(npTimeCategoryId); var npTimeCategoryRow = initNonProjectTimeRow(npTimeCategory); $scope.DisplayData["NonProjectTime"].Categories[npTimeCategoryId] = npTimeCategoryRow; } } } } // sort ECs by Name var sortedByName = $filter('sortObjectsBy')(costCenter.Expenditures, 'Name', false); for (var i in sortedByName) { var ecKey = getECKey(sortedByName[i].Id, null, costCenter.Id); if ($scope.DisplayData[ecKey] && $.inArray(ecKey, $scope.DisplayDataOrder) == -1) { $scope.DisplayDataOrder.push(ecKey); } } } } // for by Cost Centers } }; function zeroRowsData(rowsObject) { if (!rowsObject) { return; } for (var key in rowsObject) { var row = rowsObject[key]; if (!row) continue; if (row.Cells) { row.Cells = new Array($scope.InternalData.Header.Weeks.length); } if (row.Total) { if (angular.isNumber(row.Total.Value1)) { row.Total.Value1 = 0; } if (angular.isNumber(row.Total.Value2)) { row.Total.Value2 = 0; } row.Total.Allocated = 0; row.Total.Needed = 0; row.Total.Capacity = 0; row.Total.NonProjectTime = 0; row.Total.SuperECAllocations = 0; } if (typeof row === 'object') { zeroRowsData(row); } }; }; function initRowsData() { hideRedundantTooltips(); if (!$scope.InternalData.Header.Months || !$scope.InternalData.Header.Weeks) return; for (var dataKey in $scope.DisplayData) { var fillCellFunction = undefined, originalRow = $scope.DisplayData[dataKey]; switch (originalRow.Type) { case $scope.RowType.NonProjectTime: fillCellFunction = fillNonProjectTimeCell; break; case $scope.RowType.Team: fillCellFunction = fillTeamCell; break; case $scope.RowType.ExpenditureCategory: fillCellFunction = fillExpCatCell; break; case $scope.RowType.Company: fillCellFunction = fillCompanyCell; break; case $scope.RowType.CostCenter: fillCellFunction = fillCostCenterCell; break; } for (var mIndex in $scope.InternalData.Header.Months) { var month = $scope.InternalData.Header.Months[mIndex]; if (!month.Childs) continue; for (var wIndex in month.Childs) { if (fillCellFunction == undefined || typeof fillCellFunction !== 'function') { updateTotalRowCell(originalRow, month.Childs[wIndex], month.SelfIndexInWeeks, [0, 0]); } else { fillCellFunction(month.Childs[wIndex], month.SelfIndexInWeeks, originalRow); } } } } }; function fillNonProjectTimeCell(wIndex, mIndex, originalRow) { var week = $scope.InternalData.Header.Weeks[wIndex]; var totalValue = 0; var categories = {}; var teams = teamInfoService.getAll(); if (teams) { for (var teamId in teams) { var team = teams[teamId]; if (!team || !team.ExpCategories) continue; for (var expCatId in team.ExpCategories) { var category = team.ExpCategories[expCatId]; if (!category || !category.Resources || !category.AllowResourceAssignment) continue; for (var resourceId in category.Resources) { if (!category.Resources[resourceId]) continue; var resource = teamInfoService.extendResourceModelWithAttributes(category.Resources[resourceId], teamId); if (!resource || !resource.NonProjectTime) continue; for (var npTimeId in resource.NonProjectTime) { var npTime = resource.NonProjectTime[npTimeId]; var npTimeCategoryId = npTime.CategoryId; var v = npTime.Allocations[week.Milliseconds] || 0; if (!$scope.DisplayMode.IsUOMHours) { v = hoursResourcesConverter.convertToResources(resource.OwnExpenditureCategoryId, v); } if (categories[npTimeCategoryId] == undefined) categories[npTimeCategoryId] = 0; categories[npTimeCategoryId] += v; } } } } } angular.forEach(categories, function (value, key) { totalValue += (value || 0); updateSimpleCell($scope.DisplayData["NonProjectTime"].Categories[key], wIndex, mIndex, value || 0); }); updateSimpleCell(originalRow, wIndex, mIndex, totalValue); }; function fillExpCatCell(wIndex, mIndex, originalRow) { if (!originalRow || !originalRow.Id) { return; } var teamIds = null; var values = undefined; var week = $scope.InternalData.Header.Weeks[wIndex]; var expCatId = originalRow.Id; switch ($scope.DisplayMode.GroupBy) { case groupByEnum.Team: // Expenditures grouped by teams teamIds = [originalRow.ParentId]; break; case groupByEnum.Company: // Expenditures grouped by companies var companyTeams = teamInfoService.getTeamsByCompanyId(originalRow.ParentId); if (companyTeams) { teamIds = Object.keys(companyTeams); } break; default: // Get data by all teams, expenditure belongs to teamIds = null; } // Get expcenditure cell values with expenditure assigned resources values = getExpCatCellValue(expCatId, teamIds, week.Milliseconds, true); values.Value1 = angular.isNumber(values.Value1) ? roundService.roundQuantity(values.Value1) : values.Value1; values.Value2 = angular.isNumber(values.Value2) ? roundService.roundQuantity(values.Value2) : values.Value2; values.Allocated = roundService.roundQuantity(values.Allocated); values.Needed = roundService.roundQuantity(values.Needed); values.Capacity = roundService.roundQuantity(values.Capacity); values.NonProjectTime = roundService.roundQuantity(values.NonProjectTime); values.SuperECAllocations = roundService.roundQuantity(values.SuperECAllocations); updateTotalRowCell(originalRow, wIndex, mIndex, values); if (values.Resources) { updateResourcesCell(originalRow, wIndex, mIndex, values.Resources, week.Milliseconds); } }; function fillTeamCell(wIndex, mIndex, originalRow) { if (!originalRow || !originalRow.Id) { return; } var week = $scope.InternalData.Header.Weeks[wIndex]; var result = { Value1: 0, Value2: 0, Allocated: 0, Needed: 0, Capacity: 0, NonProjectTime: 0, SuperECAllocations: 0 }; var totalsAsParsed = parseInt($scope.DisplayMode.TotalsAs); var teamId = originalRow.Id; var teamIds = [teamId]; var teamExpCats = teamInfoService.getExpenditureCategoriesInTeam(teamId); if (!teamExpCats || !angular.isArray(teamExpCats) || !teamExpCats.length) { return result; } for (var eIndex = 0; eIndex < teamExpCats.length; eIndex++) { var expCatId = teamExpCats[eIndex]; var expCatInfo = dataSources.getExpenditureById(expCatId); var isProjectRole = !expCatInfo.AllowResourceAssignment; // In Allocated/Capacity mode perform sum only by ordinary ExpCats, because project role are not displayed var takeExpCatInAccount = (totalsAsParsed != 1) || !isProjectRole; if (takeExpCatInAccount) { var val = getExpCatCellValue(expCatId, teamIds, week.Milliseconds, false); if (angular.isNumber(val.Value1)) result.Value1 += val.Value1; if (angular.isNumber(val.Value2)) result.Value2 += val.Value2; result.Allocated += val.Allocated || 0; result.Needed += (!isProjectRole ? (val.Needed || 0) : (val.RemainingNeed || 0)); result.Capacity += val.Capacity || 0; result.NonProjectTime += val.NonProjectTime || 0; result.SuperECAllocations += val.SuperECAllocations || 0; } } result.Value1 = roundService.roundQuantity(result.Value1); result.Value2 = roundService.roundQuantity(result.Value2); result.Allocated = roundService.roundQuantity(result.Allocated); result.Needed = roundService.roundQuantity(result.Needed); result.Capacity = roundService.roundQuantity(result.Capacity); result.NonProjectTime = roundService.roundQuantity(result.NonProjectTime); result.SuperECAllocations = roundService.roundQuantity(result.SuperECAllocations); updateTotalRowCell(originalRow, wIndex, mIndex, result); }; function fillCompanyCell(wIndex, mIndex, originalRow) { if (!originalRow || !originalRow.Id) { return; } var week = $scope.InternalData.Header.Weeks[wIndex]; var result = { Value1: 0, Value2: 0, Allocated: 0, Needed: 0, Capacity: 0, NonProjectTime: 0, SuperECAllocations: 0 }; var totalsAsParsed = parseInt($scope.DisplayMode.TotalsAs); var companyId = originalRow.Id; var teams = teamInfoService.getTeamsByCompanyId(companyId); if (teams) { var teamIds = Object.keys(teams); var expCatIds = teamInfoService.getExpenditureCategoriesInTeams(teamIds); // Iterate by Categories instead of teams, because we have a ready-to-use method to get total values for // a category by a set of teams for (var eIndex = 0; eIndex < expCatIds.length; eIndex++) { var expCatId = expCatIds[eIndex]; var expCatInfo = dataSources.getExpenditureById(expCatId); var isProjectRole = !expCatInfo.AllowResourceAssignment; // In Allocated/Capacity mode perform sum only by ordinary ExpCats, because project role are not displayed var takeExpCatInAccount = (totalsAsParsed != 1) || !isProjectRole; if (takeExpCatInAccount) { var val = getExpCatCellValue(expCatId, teamIds, week.Milliseconds, false); if (angular.isNumber(val.Value1)) result.Value1 += val.Value1; if (angular.isNumber(val.Value2)) result.Value2 += val.Value2; result.Allocated += val.Allocated || 0; result.Needed += (!isProjectRole ? (val.Needed || 0) : (val.RemainingNeed || 0)); result.Capacity += val.Capacity || 0; result.NonProjectTime += val.NonProjectTime || 0; result.SuperECAllocations += val.SuperECAllocations || 0; } } } result.Value1 = roundService.roundQuantity(result.Value1); result.Value2 = roundService.roundQuantity(result.Value2); result.Allocated = roundService.roundQuantity(result.Allocated); result.Needed = roundService.roundQuantity(result.Needed); result.Capacity = roundService.roundQuantity(result.Capacity); result.NonProjectTime = roundService.roundQuantity(result.NonProjectTime); result.SuperECAllocations = roundService.roundQuantity(result.SuperECAllocations); updateTotalRowCell(originalRow, wIndex, mIndex, result); }; function fillCostCenterCell(wIndex, mIndex, originalRow) { if (!originalRow || !originalRow.Id || !$scope.ExpendituresByCostCentersCache) { return; } var totalsAsParsed = parseInt($scope.DisplayMode.TotalsAs); var costCenterId = originalRow.Id; if (!(costCenterId in $scope.ExpendituresByCostCentersCache)) return; var week = $scope.InternalData.Header.Weeks[wIndex]; var result = { Value1: 0, Value2: 0, Allocated: 0, Needed: 0, Capacity: 0, NonProjectTime: 0, SuperECAllocations: 0 }; var costCenterData = $scope.ExpendituresByCostCentersCache[costCenterId]; if (costCenterData && costCenterData.Expenditures) { for (var expCatId in costCenterData.Expenditures) { var expCatInfo = dataSources.getExpenditureById(expCatId); var isProjectRole = !expCatInfo.AllowResourceAssignment; // In Allocated/Capacity mode perform sum only by ordinary ExpCats, because project role are not displayed var takeExpCatInAccount = (totalsAsParsed != 1) || !isProjectRole; if (takeExpCatInAccount) { var expCatItem = costCenterData.Expenditures[expCatId]; if (expCatItem && expCatItem.Teams && angular.isArray(expCatItem.Teams) && expCatItem.Teams.length) { var val = getExpCatCellValue(expCatId, expCatItem.Teams, week.Milliseconds, false); if (angular.isNumber(val.Value1)) result.Value1 += val.Value1; if (angular.isNumber(val.Value2)) result.Value2 += val.Value2; result.Allocated += val.Allocated || 0; result.Needed += (!isProjectRole ? (val.Needed || 0) : (val.RemainingNeed || 0)); result.Capacity += val.Capacity || 0; result.NonProjectTime += val.NonProjectTime || 0; result.SuperECAllocations += val.SuperECAllocations || 0; } } } result.Value1 = roundService.roundQuantity(result.Value1); result.Value2 = roundService.roundQuantity(result.Value2); result.Allocated = roundService.roundQuantity(result.Allocated); result.Needed = roundService.roundQuantity(result.Needed); result.Capacity = roundService.roundQuantity(result.Capacity); result.NonProjectTime = roundService.roundQuantity(result.NonProjectTime); result.SuperECAllocations = roundService.roundQuantity(result.SuperECAllocations); updateTotalRowCell(originalRow, wIndex, mIndex, result); } }; function updateSimpleCell(originalRow, weekIndex, monthIndex, newValue) { if (!originalRow || !originalRow.Cells) return; if (originalRow.Cells[monthIndex] == undefined) originalRow.Cells[monthIndex] = 0; if (originalRow.Cells[weekIndex] == undefined) originalRow.Cells[weekIndex] = 0; // we should increase/decrease month total value by delta between new and old values var showAverageTotals = $scope.isAvgMode(); var totalsDelta = 0; if (angular.isNumber(newValue) && !isNaN(newValue)) var totalsDelta = newValue; if (showAverageTotals) { var monthIndexInMonthes = $scope.InternalData.Header.Weeks[weekIndex].ParentIndex; var weekCountInThisMonth = $scope.InternalData.Header.Months[monthIndexInMonthes].Childs.length; totalsDelta = totalsDelta / weekCountInThisMonth; } originalRow.Cells[monthIndex] = roundService.roundQuantity(originalRow.Cells[monthIndex] + totalsDelta - originalRow.Cells[weekIndex]); originalRow.Cells[weekIndex] = angular.isNumber(newValue) ? roundService.roundQuantity(newValue) : newValue; }; function updateResourceCell(originalRow, weekIndex, monthIndex, newValues) { if (!originalRow || !originalRow.Cells) return; var monthIndexInMonthes = $scope.InternalData.Header.Weeks[weekIndex].ParentIndex; var weekCountInThisMonth = $scope.InternalData.Header.Months[monthIndexInMonthes].Childs.length; var totalWeeks = $scope.InternalData.Header.TotalWeeks; if (originalRow.Cells[monthIndex] == undefined) originalRow.Cells[monthIndex] = { Value1: 0, Value2: 0, Allocated: 0, Needed: 0, Capacity: 0, NonProjectTime: 0, SuperECAllocations: 0 }; if (originalRow.Cells[weekIndex] == undefined) originalRow.Cells[weekIndex] = { Value1: 0, Value2: 0, Allocated: 0, Needed: 0, Capacity: 0, NonProjectTime: 0, SuperECAllocations: 0 }; // we should to increase/decrease month total value by delta between new and old values var totalsDelta = {}; totalsDelta.Value1 = newValues.Value1 - originalRow.Cells[weekIndex].Value1; totalsDelta.Value2 = newValues.Value2 - originalRow.Cells[weekIndex].Value2; totalsDelta.Allocated = newValues.Allocated - originalRow.Cells[weekIndex].Allocated; totalsDelta.Needed = newValues.Needed - originalRow.Cells[weekIndex].Needed; totalsDelta.Capacity = newValues.Capacity - originalRow.Cells[weekIndex].Capacity; totalsDelta.NonProjectTime = newValues.NonProjectTime - originalRow.Cells[weekIndex].NonProjectTime; totalsDelta.SuperECAllocations = newValues.SuperECAllocations - originalRow.Cells[weekIndex].SuperECAllocations; var showAverageTotals = $scope.isAvgMode(); originalRow.Total.Value1 = roundService.roundQuantity(originalRow.Total.Value1 + (showAverageTotals ? (totalsDelta.Value1 / totalWeeks): totalsDelta.Value1)); originalRow.Total.Value2 = roundService.roundQuantity(originalRow.Total.Value2 + (showAverageTotals ? (totalsDelta.Value2 / totalWeeks) : totalsDelta.Value2)); originalRow.Total.Allocated = roundService.roundQuantity(originalRow.Total.Allocated + (showAverageTotals ? (totalsDelta.Allocated / totalWeeks) : totalsDelta.Allocated)); originalRow.Total.Needed = roundService.roundQuantity(originalRow.Total.Needed + (showAverageTotals ? (totalsDelta.Needed / totalWeeks) : totalsDelta.Needed)); originalRow.Total.Capacity = roundService.roundQuantity(originalRow.Total.Capacity + (showAverageTotals ? (totalsDelta.Capacity / totalWeeks) : totalsDelta.Capacity)); originalRow.Total.NonProjectTime = roundService.roundQuantity(originalRow.Total.NonProjectTime + (showAverageTotals ? (totalsDelta.NonProjectTime / totalWeeks): totalsDelta.NonProjectTime)); originalRow.Total.SuperECAllocations = roundService.roundQuantity(originalRow.Total.SuperECAllocations + (showAverageTotals ? (totalsDelta.SuperECAllocations / totalWeeks): totalsDelta.SuperECAllocations)); originalRow.Cells[monthIndex].Value1 = roundService.roundQuantity(originalRow.Cells[monthIndex].Value1 + (showAverageTotals ? (totalsDelta.Value1 / weekCountInThisMonth) : totalsDelta.Value1)); originalRow.Cells[monthIndex].Value2 = roundService.roundQuantity(originalRow.Cells[monthIndex].Value2 + (showAverageTotals ? (totalsDelta.Value2 / weekCountInThisMonth): totalsDelta.Value2)); originalRow.Cells[monthIndex].Allocated = roundService.roundQuantity(originalRow.Cells[monthIndex].Allocated + (showAverageTotals ? (totalsDelta.Allocated / weekCountInThisMonth): totalsDelta.Allocated)); originalRow.Cells[monthIndex].Needed = roundService.roundQuantity(originalRow.Cells[monthIndex].Needed + (showAverageTotals ? (totalsDelta.Needed / weekCountInThisMonth) : totalsDelta.Needed)); originalRow.Cells[monthIndex].Capacity = roundService.roundQuantity(originalRow.Cells[monthIndex].Capacity + (showAverageTotals ? (totalsDelta.Capacity / weekCountInThisMonth) : totalsDelta.Capacity)); originalRow.Cells[monthIndex].NonProjectTime = roundService.roundQuantity(originalRow.Cells[monthIndex].NonProjectTime + (showAverageTotals ? (totalsDelta.NonProjectTime / weekCountInThisMonth): totalsDelta.NonProjectTime)); originalRow.Cells[monthIndex].SuperECAllocations = roundService.roundQuantity(originalRow.Cells[monthIndex].SuperECAllocations + (showAverageTotals ? (totalsDelta.SuperECAllocations / weekCountInThisMonth) : totalsDelta.SuperECAllocations)); originalRow.Cells[weekIndex] = newValues; updateWeeklyCellCssClass(originalRow, weekIndex); }; function updateResourcesCell(originalRow, weekIndex, monthIndex, resources, milliseconds) { if (!originalRow || !originalRow.Resources) return; for (var resIndex = 0; resIndex < originalRow.Resources.length; resIndex++) { var resRow = originalRow.Resources[resIndex]; var newValues = { Value1: 0, Value2: 0, Allocated: 0, Needed: 0, Capacity: 0, NonProjectTime: 0, SuperECAllocations: 0 }; for (var i = 0; i < resources.length; i++) { if (resRow.Id == resources[i].Id) { var resource = resources[i]; if (!resourceHasTeam(resource, milliseconds)) continue; var values = getResourceCellValue(resource, milliseconds, originalRow); newValues = concatValues(newValues, values); } } updateResourceCell(resRow, weekIndex, monthIndex, newValues); } }; function updateTotalRowCell(originalRow, weekIndex, monthIndex, newValues) { if (!originalRow || !originalRow.Cells) return; if (originalRow.Cells[monthIndex] == undefined) { if ((originalRow.Type == $scope.RowType.ExpenditureCategory) && !originalRow.AllowResourceAssignment) originalRow.Cells[monthIndex] = { Value1: '-', Value2: '-', Allocated: 0, Needed: 0, Capacity: 0, NonProjectTime: 0, SuperECAllocations: 0 }; else originalRow.Cells[monthIndex] = { Value1: 0, Value2: 0, Allocated: 0, Needed: 0, Capacity: 0, NonProjectTime: 0, SuperECAllocations: 0 }; } if (originalRow.Cells[weekIndex] == undefined) { if ((originalRow.Type == $scope.RowType.ExpenditureCategory) && !originalRow.AllowResourceAssignment) originalRow.Cells[weekIndex] = { Value1: '-', Value2: '-', Allocated: 0, Needed: 0, Capacity: 0, NonProjectTime: 0, SuperECAllocations: 0 }; else originalRow.Cells[weekIndex] = { Value1: 0, Value2: 0, Allocated: 0, Needed: 0, Capacity: 0, NonProjectTime: 0, SuperECAllocations: 0 }; } var monthIndexInMonthes = $scope.InternalData.Header.Weeks[weekIndex].ParentIndex; var weekCountInThisMonth = $scope.InternalData.Header.Months[monthIndexInMonthes].Childs.length; var totalWeeks = $scope.InternalData.Header.TotalWeeks; var showAverageTotals = $scope.isAvgMode(); var totalsDelta = { Value1: 0, Value2: 0, Allocated: 0, Needed: 0, Capacity: 0, NonProjectTime: 0, SuperECAllocations: 0 }; var newValue1Analyzed = angular.isNumber(newValues.Value1) ? newValues.Value1 : 0; var newValue2Analyzed = angular.isNumber(newValues.Value2) ? newValues.Value2 : 0; var oldValue1Analyzed = angular.isNumber(originalRow.Cells[weekIndex].Value1) ? originalRow.Cells[weekIndex].Value1 : 0; var oldValue2Analyzed = angular.isNumber(originalRow.Cells[weekIndex].Value2) ? originalRow.Cells[weekIndex].Value2 : 0; totalsDelta.Value1 = newValue1Analyzed - oldValue1Analyzed; totalsDelta.Value2 = newValue2Analyzed - oldValue2Analyzed; totalsDelta.Allocated = (newValues.Allocated || 0) - (originalRow.Cells[weekIndex].Allocated || 0); totalsDelta.Needed = (newValues.Needed || 0) - (originalRow.Cells[weekIndex].Needed || 0); totalsDelta.Capacity = (newValues.Capacity || 0) - (originalRow.Cells[weekIndex].Capacity || 0); totalsDelta.NonProjectTime = (newValues.NonProjectTime || 0) - (originalRow.Cells[weekIndex].NonProjectTime || 0); totalsDelta.SuperECAllocations = (newValues.SuperECAllocations || 0) - (originalRow.Cells[weekIndex].SuperECAllocations || 0); if (angular.isNumber(newValues.Value1)) { if (angular.isNumber(originalRow.Cells[monthIndex].Value1)) originalRow.Cells[monthIndex].Value1 += (showAverageTotals ? (totalsDelta.Value1 / weekCountInThisMonth) : totalsDelta.Value1); else originalRow.Cells[monthIndex].Value1 = (showAverageTotals ? (totalsDelta.Value1 / weekCountInThisMonth) : totalsDelta.Value1); if (angular.isNumber(originalRow.Total.Value1)) originalRow.Total.Value1 += (showAverageTotals ? (totalsDelta.Value1 / totalWeeks) : totalsDelta.Value1); else originalRow.Total.Value1 = (showAverageTotals ? (totalsDelta.Value1 / totalWeeks) : totalsDelta.Value1); } if (angular.isNumber(newValues.Value2)) { if (angular.isNumber(originalRow.Cells[monthIndex].Value2)) originalRow.Cells[monthIndex].Value2 += (showAverageTotals ? (totalsDelta.Value2 / weekCountInThisMonth) : totalsDelta.Value2); else originalRow.Cells[monthIndex].Value2 = (showAverageTotals ? (totalsDelta.Value2 / weekCountInThisMonth) : totalsDelta.Value2); if (angular.isNumber(originalRow.Total.Value2)) originalRow.Total.Value2 += (showAverageTotals ? (totalsDelta.Value2 / totalWeeks) : totalsDelta.Value2); else originalRow.Total.Value2 = (showAverageTotals ? (totalsDelta.Value2 / totalWeeks) : totalsDelta.Value2); } originalRow.Cells[monthIndex].Allocated += (showAverageTotals ? (totalsDelta.Allocated / weekCountInThisMonth) : totalsDelta.Allocated); originalRow.Cells[monthIndex].Needed += (showAverageTotals ? (totalsDelta.Needed / weekCountInThisMonth) : totalsDelta.Needed); originalRow.Cells[monthIndex].Capacity += (showAverageTotals ? (totalsDelta.Capacity / weekCountInThisMonth) : totalsDelta.Capacity); originalRow.Cells[monthIndex].NonProjectTime += (showAverageTotals ? (totalsDelta.NonProjectTime / weekCountInThisMonth) : totalsDelta.NonProjectTime); originalRow.Cells[monthIndex].SuperECAllocations += (showAverageTotals ? (totalsDelta.SuperECAllocations / weekCountInThisMonth) : totalsDelta.SuperECAllocations); originalRow.Total.Allocated += (showAverageTotals ? (totalsDelta.Allocated / totalWeeks) : totalsDelta.Allocated); originalRow.Total.Needed += (showAverageTotals ? (totalsDelta.Needed / totalWeeks) : totalsDelta.Needed); originalRow.Total.Capacity += (showAverageTotals ? (totalsDelta.Capacity / totalWeeks) : totalsDelta.Capacity); originalRow.Total.NonProjectTime += (showAverageTotals ? (totalsDelta.NonProjectTime / totalWeeks) : totalsDelta.NonProjectTime); originalRow.Total.SuperECAllocations += (showAverageTotals ? (totalsDelta.SuperECAllocations / totalWeeks) : totalsDelta.SuperECAllocations); originalRow.Cells[weekIndex] = newValues; updateWeeklyCellCssClass(originalRow, weekIndex); }; function updateWeeklyCellCssClass(row, weekIndex) { // Set cells highlighting if ((row.Type == $scope.RowType.ExpenditureCategory) && !row.AllowResourceAssignment) { updateProjectRoleCellCssClass(row, weekIndex); } else { switch (parseInt($scope.DisplayMode.TotalsAs)) { case 4: //Remaining/Capacity updateRemainingCapacityCssClass(row, weekIndex); break; default: cellHighlightingService.setCellCssClasses(row, weekIndex); break; } } var monthIndexInMonthes = $scope.InternalData.Header.Weeks[weekIndex].ParentIndex; var month = $scope.InternalData.Header.Months[monthIndexInMonthes]; updateMonthlyCellCssClass(row, month); }; function updateMonthlyCellCssClass(row, month) { if (!row.CSSClass) return; var isOverAllocated = false, isUnderAllocated = false, isEquals = true; var monthWeeks = month.Childs; var monthIndex = month.SelfIndexInWeeks; for (var i in monthWeeks) { var cssClasses = row.CSSClass[monthWeeks[i]]; if (cssClasses == cellHighlightingService.cellOverClass) { isOverAllocated = true; isEquals = false; } else if (cssClasses == cellHighlightingService.cellLessClass) { isUnderAllocated = true; isEquals = false; } else if (cssClasses != cellHighlightingService.cellEqualClass && cssClasses != '') { isEquals = false; } } if (isOverAllocated) row.CSSClass[monthIndex] = cellHighlightingService.cellOverClass; else if (isUnderAllocated) row.CSSClass[monthIndex] = cellHighlightingService.cellLessClass; else if (isEquals) row.CSSClass[monthIndex] = cellHighlightingService.cellEqualClass; else row.CSSClass[monthIndex] = ''; }; function updateRemainingCapacityCssClass(row, weekIndex) { if (!row || isNaN(weekIndex)) return; row.CSSClass[weekIndex] = cellHighlightingService.removeHighlightClasses(row.CSSClass[weekIndex]); var compareResult = null; if (row.Cells[weekIndex].Value1 < 0) compareResult = 1; else if (row.Cells[weekIndex].Value1 > 0) compareResult = -1; else if (row.Cells[weekIndex].Value1 == 0) if (row.Cells[weekIndex].Capacity == 0) compareResult = null; // do not validate zeros else compareResult = 0; var compareResultClass = cellHighlightingService.getCssClass(compareResult); var resultCellClasses = cellHighlightingService.addCssClass(row.CSSClass[weekIndex], compareResultClass); row.CSSClass[weekIndex] = resultCellClasses; }; function updateProjectRoleCellCssClass(row, weekIndex) { if (!row || isNaN(weekIndex)) return; row.CSSClass[weekIndex] = cellHighlightingService.removeHighlightClasses(row.CSSClass[weekIndex]); var compareResult = null; var cell = row.Cells[weekIndex]; var need = angular.isNumber(cell.Needed) && !isNaN(cell.Needed) ? Number(cell.Needed) : 0; var allocated = angular.isNumber(cell.Allocated) && !isNaN(cell.Allocated) ? Number(cell.Allocated) : 0; if ((allocated == 0) && (need == 0)) // Do not highlight cells with no values return; if (allocated > need) compareResult = 1; if (allocated < need) compareResult = -1; if (allocated == need) compareResult = 0; var highlightingClassToAdd = cellHighlightingService.getCssClass(compareResult); var resultCellClasses = cellHighlightingService.addCssClass(row.CSSClass[weekIndex], highlightingClassToAdd); row.CSSClass[weekIndex] = resultCellClasses; }; function getExpCatCellValue(expCatId, teamIds, we, withResources) { if (!expCatId || !we) return null; var result = { Value1: '-', Value2: '-', Allocated: 0, Needed: 0, Capacity: 0, NonProjectTime: 0, SuperECAllocations: 0 }; // Get teams, the expenditure belongs to (from incoming array or from service) var teamsToBrowse = getExpenditureTeams(expCatId, teamIds); if (!teamsToBrowse || !angular.isArray(teamsToBrowse) || !teamsToBrowse.length) { // No teams for expenditure found return result; } // Get expenditure type: ordinary EC or project role var randomExpenditureTeamId = teamsToBrowse[0]; var expCatItem = teamInfoService.getExpenditureCategoryInTeam(randomExpenditureTeamId, expCatId); if (!expCatItem) return result; // Get agregated data for expenditure by specified team list var result = expCatItem.AllowResourceAssignment ? getOrdinaryExpCatCellValue(expCatId, teamsToBrowse, we) : getProjectRoleCellValue(expCatId, teamsToBrowse, we); if (!result) return result; if (withResources) { // Get resources for expenditure result.Resources = []; for (var tIndex = 0; tIndex < teamsToBrowse.length; tIndex++) { var teamId = teamsToBrowse[tIndex]; var resourceModels = teamInfoService.getResourcesByTeam(teamId, expCatId); if (resourceModels) { for (var resourceId in resourceModels) { result.Resources.push(resourceModels[resourceId]); } } } } return result; }; function getOrdinaryExpCatCellValue(expCatId, teamIds, we) { // Return value (2 items) for ordinary EC cell. Converted to Resources/hours according to current display settings if (!expCatId) throw "getOrdinaryExpCatCellValue: 'expCatId' parameter is empty"; if (!we) throw "getOrdinaryExpCatCellValue: 'we' parameter is empty"; var result = { Value1: 0, Value2: 0, Allocated: 0, Needed: 0, Capacity: 0, NonProjectTime: 0, SuperECAllocations: 0, IsProjectRole: false }; var val1 = 0; var val2 = 0; var allocated = 0; var needed = 0; var capacity = 0; var npts = 0; var superECAllocs = 0; // Get teams to loop through var teamsToBrowse = getExpenditureTeams(expCatId, teamIds); if (!teamsToBrowse || !angular.isArray(teamsToBrowse) || !teamsToBrowse.length) return result; for (var tIndex = 0; tIndex < teamsToBrowse.length; tIndex++) { var teamId = teamsToBrowse[tIndex]; var expCat = teamInfoService.getExpenditureCategoryInTeam(teamId, expCatId, false); if (expCat) { capacity += ($scope.DisplayMode.CapacityView ? (expCat.ActualCapacityValues[we] || 0) : (capacity = expCat.PlannedCapacityValues[we] || 0)); allocated += (expCat.AllocatedCapacity[we] || 0); needed += expCat.NeedCapacity[we] || 0; if (expCat.Resources) { // Get NPT allocations and time, allocated to project roles for (var resourceId in expCat.Resources) { var resourceInExpCat = expCat.Resources[resourceId]; var resourceModel = dataSources.getResourceById(resourceId); var resourceNpt = teamInfoService.getResourceSummaryNptAllocation(resourceInExpCat, we) || 0; var resourceSuperECAllocs = teamInfoService.getSuperEcsTotalAllocatedHrs(resourceId, we) || 0; if (!$scope.DisplayMode.IsUOMHours) { // Instant convertion of values to resources - get resource's own expenditure npts += hoursResourcesConverter.convertToResources(resourceModel.ExpenditureCategoryId, resourceNpt); superECAllocs += hoursResourcesConverter.convertToResources(resourceModel.ExpenditureCategoryId, resourceSuperECAllocs); } else { npts += resourceNpt; superECAllocs += resourceSuperECAllocs; } } } } } if (!$scope.DisplayMode.IsUOMHours) { // Instant convertion of values to resources allocated = hoursResourcesConverter.convertToResources(expCatId, allocated); needed = hoursResourcesConverter.convertToResources(expCatId, needed); capacity = hoursResourcesConverter.convertToResources(expCatId, capacity); } switch (parseInt($scope.DisplayMode.TotalsAs)) { case 1: // Allocated/Capacity mode val1 += (allocated + npts + superECAllocs); val2 += capacity; break; case 2: // Need/Capacity mode val1 = needed; val2 = capacity; break; case 3: // Allocated/Need mode val1 = allocated + npts + superECAllocs; val2 = needed; break; case 4: // Remaining/Capacity mode val1 = capacity - needed - superECAllocs; val2 = capacity; break; default: throw "Display mode TotalAs (" + $scope.DisplayMode.TotalsAs + ") not supported"; } result = { Value1: roundService.roundQuantity(val1), Value2: roundService.roundQuantity(val2), Allocated: roundService.roundQuantity(allocated), Needed: roundService.roundQuantity(needed), Capacity: roundService.roundQuantity(capacity), NonProjectTime: roundService.roundQuantity(npts), SuperECAllocations: roundService.roundQuantity(superECAllocs), IsProjectRole: false }; return result; }; function getProjectRoleCellValue(expCatId, teamIds, we) { if (!expCatId) throw "getProjectRoleCellValue: expCatId parameter is empty"; if (!we) throw "getProjectRoleCellValue: we parameter is empty"; var result = { Value1: 0, Value2: 0, Allocated: 0, Needed: 0, RemainingNeed: 0, IsProjectRole: true }; var val1 = 0; var val2 = 0; var teamsAllocated = 0; // var resourcesAssigned = 0; var needed = 0; var remainingNeed = 0; // Get teams to loop through var teamsToBrowse = getExpenditureTeams(expCatId, teamIds); if (!teamsToBrowse || !angular.isArray(teamsToBrowse) || !teamsToBrowse.length) return result; var weekendings = [we]; var expCatNeed = teamInfoService.getExpenditureTotalNeed(expCatId, weekendings); var expCatTeamAllocations = teamInfoService.getTeamAllocationsTotalByExpCat(expCatId, teamsToBrowse, weekendings); //var expCatResourceAssignments = teamInfoService.getResourceTotalAllocatedHrs(expCatId, teamsToBrowse, null, weekendings); needed = expCatNeed && (we in expCatNeed) ? expCatNeed[we] || 0 : 0; teamsAllocated = expCatTeamAllocations && (we in expCatTeamAllocations) ? expCatTeamAllocations[we] || 0 : 0; // resourcesAssigned = expCatResourceAssignments && (we in expCatResourceAssignments) ? expCatResourceAssignments[we] || 0 : 0; if (!$scope.DisplayMode.IsUOMHours) { // Instant convertion of values to resources teamsAllocated = hoursResourcesConverter.convertToResources(expCatId, teamsAllocated); needed = hoursResourcesConverter.convertToResources(expCatId, needed); } remainingNeed = needed - teamsAllocated; // The same calculations for Planned and Actuals display mode // For Super EC Allocated = Need always. So, in order to get Need summary value, we calculate Allocated summary switch (parseInt($scope.DisplayMode.TotalsAs)) { case 1: // Allocated/Capacity // Project role has no display values in this mode val1 = '-'; val2 = '-'; break; case 2: // Need/Capacity val1 = remainingNeed; val2 = '-'; break; case 3: // Allocated/Need val1 = teamsAllocated; val2 = remainingNeed; break; case 4: // Remaining/Capacity val1 = -remainingNeed; val2 = '-'; break; default: throw "Display mode TotalAs (" + $scope.DisplayMode.TotalsAs + ") not supported"; } var result = { Value1: angular.isNumber(val1) ? roundService.roundQuantity(val1) : val1, Value2: angular.isNumber(val2) ? roundService.roundQuantity(val2) : val2, Allocated: roundService.roundQuantity(teamsAllocated), Needed: roundService.roundQuantity(needed), RemainingNeed: roundService.roundQuantity(remainingNeed), IsProjectRole: true }; return result; }; // returns an object with values to display in Resource row (bottom part) function getResourceCellValue(resource, we, expCatRow) { var values = { Value1: 0, Value2: 0, Allocated: 0, Capacity: 0, NonProjectTime: 0, SuperECAllocations: 0 }; var summaryAllocated = teamInfoService.getResourceSummaryAllocatedHrs(resource.Id, we); values.SuperECAllocations = teamInfoService.getSuperEcsTotalAllocatedHrs(resource.Id, we); values.Capacity = resource.TotalCapacity[we] || 0; switch (parseInt($scope.DisplayMode.TotalsAs)) { case 4: // Remaining/Capacity values.Value1 = (resource.TotalCapacity[we] || 0) - summaryAllocated; if (expCatRow.AllowResourceAssignment) { values.NonProjectTime += teamInfoService.getResourceSummaryNptAllocation(resource, we) || 0; } values.Value1 -= values.NonProjectTime; values.Value2 = values.Capacity; break; default: // Allocated/Capacity values.Value1 = summaryAllocated || 0; if (expCatRow.AllowResourceAssignment) { values.NonProjectTime += teamInfoService.getResourceSummaryNptAllocation(resource, we) || 0; } values.Value1 += values.NonProjectTime; values.Value2 = values.Capacity; break; }; values.Allocated = summaryAllocated - values.SuperECAllocations; if (!$scope.DisplayMode.IsUOMHours) { if (angular.isNumber(values.Value1) && !isNaN(values.Value1)) { values.Value1 = hoursResourcesConverter.convertToResources(resource.OwnExpenditureCategoryId, values.Value1); } if (angular.isNumber(values.Value2) && !isNaN(values.Value2)) { values.Value2 = hoursResourcesConverter.convertToResources(resource.OwnExpenditureCategoryId, values.Value2); } values.Allocated = hoursResourcesConverter.convertToResources(resource.OwnExpenditureCategoryId, values.Allocated); values.Capacity = hoursResourcesConverter.convertToResources(resource.OwnExpenditureCategoryId, values.Capacity); values.NonProjectTime = hoursResourcesConverter.convertToResources(resource.OwnExpenditureCategoryId, values.NonProjectTime); values.SuperECAllocations = hoursResourcesConverter.convertToResources(resource.OwnExpenditureCategoryId, values.SuperECAllocations); } values.Value1 = roundService.roundQuantity(values.Value1); values.Value2 = roundService.roundQuantity(values.Value2); values.Allocated = roundService.roundQuantity(values.Allocated); values.Capacity = roundService.roundQuantity(values.Capacity); values.NonProjectTime = roundService.roundQuantity(values.NonProjectTime); values.SuperECAllocations = roundService.roundQuantity(values.SuperECAllocations); return values; }; //==========End Convert data model to UI data model methods=============// function resourceValueChanged(teamId, expenditureCategoryId, resourceId, weekEnding) { if (!teamId || !expenditureCategoryId || !resourceId || !(new Date(weekEnding).getTime())) return; var weekIndex = getWeekIndex(weekEnding); if (weekIndex < 0) return; var team = teamInfoService.getById(teamId); if (!team) return; var week = $scope.InternalData.Header.Weeks[weekIndex]; if (!week) return; var month = $scope.InternalData.Header.Months[week.ParentIndex]; if (!month) return; var topLevelItemId = undefined; switch ($scope.DisplayMode.GroupBy) { case groupByEnum.Team: topLevelItemId = team.Id; break; case groupByEnum.Company: topLevelItemId = team.CompanyId; break; case groupByEnum.CostCenter: // Get Cost Center for EC var expCat = dataSources.getExpenditureById(expenditureCategoryId); if (!expCat || !expCat.CreditId) { throw "Expenditure Category " +expenditureCategoryId + "not found in dataSources"; } topLevelItemId = expCat.CreditId; break; } // update exactly changed EC (general or Super EC) var ecKey = getECKey(expenditureCategoryId, team.Id, topLevelItemId); var originalRow = $scope.DisplayData[ecKey]; if (originalRow) { fillExpCatCell(weekIndex, month.SelfIndexInWeeks, originalRow); if (topLevelExists()) { var topLevelItem = getTopLevelItem(topLevelItemId); if (topLevelItem && $scope.DisplayData[topLevelItem.Key]) { switch (topLevelItem.Type) { case $scope.RowType.Team: fillTeamCell(weekIndex, month.SelfIndexInWeeks, $scope.DisplayData[topLevelItem.Key]); break; case $scope.RowType.Company: fillCompanyCell(weekIndex, month.SelfIndexInWeeks, $scope.DisplayData[topLevelItem.Key]); break; case $scope.RowType.CostCenter: fillCostCenterCell(weekIndex, month.SelfIndexInWeeks, $scope.DisplayData[topLevelItem.Key]); break; } } } } var expCategoryInTeam = team.ExpCategories[expenditureCategoryId]; if (!expCategoryInTeam) { return; } // update general EC and resource under it if changed EC is Project role if (!expCategoryInTeam.AllowResourceAssignment) { if (expCategoryInTeam.Resources) { var resource = expCategoryInTeam.Resources[resourceId]; if (resource) { ecKey = getECKey(resource.OwnExpenditureCategoryId, team.Id, topLevelItemId); originalRow = $scope.DisplayData[ecKey]; } } } if (!originalRow) { return; } fillExpCatCell(weekIndex, month.SelfIndexInWeeks, originalRow); }; function teamValueChanged(teamId, expenditureCategoryId, weekEnding) { if (!teamId || !expenditureCategoryId || !(new Date(weekEnding).getTime())) return; var weekIndex = getWeekIndex(weekEnding); if (weekIndex < 0) return; var team = teamInfoService.getById(teamId); if (!team) return; var week = $scope.InternalData.Header.Weeks[weekIndex]; if (!week) return; var month = $scope.InternalData.Header.Months[week.ParentIndex]; if (!month) return; var topLevelItemId = undefined; switch ($scope.DisplayMode.GroupBy) { case groupByEnum.Team: topLevelItemId = team.Id; break; case groupByEnum.Company: topLevelItemId = team.CompanyId; break; case groupByEnum.CostCenter: // Get Cost Center for EC var expCat = dataSources.getExpenditureById(expenditureCategoryId); if (!expCat || !expCat.CreditId) { throw "Expenditure Category " +expenditureCategoryId + "not found in dataSources"; } topLevelItemId = expCat.CreditId; break; } // update exactly changed EC (general or Super EC) var ecKey = getECKey(expenditureCategoryId, team.Id, topLevelItemId); var originalRow = $scope.DisplayData[ecKey]; if (originalRow) { fillExpCatCell(weekIndex, month.SelfIndexInWeeks, originalRow); if (topLevelExists()) { var topLevelItem = getTopLevelItem(topLevelItemId); if (topLevelItem && $scope.DisplayData[topLevelItem.Key]) { switch (topLevelItem.Type) { case $scope.RowType.Team: fillTeamCell(weekIndex, month.SelfIndexInWeeks, $scope.DisplayData[topLevelItem.Key]); break; case $scope.RowType.Company: fillCompanyCell(weekIndex, month.SelfIndexInWeeks, $scope.DisplayData[topLevelItem.Key]); break; case $scope.RowType.CostCenter: fillCostCenterCell(weekIndex, month.SelfIndexInWeeks, $scope.DisplayData[topLevelItem.Key]); break; } } } } }; function getWeekIndex(weekEnding) { var weekIndex = -1; for (var i = 0; i < $scope.InternalData.Header.Weeks.length; i++) { var week = $scope.InternalData.Header.Weeks[i]; if (!!week && week.Milliseconds == weekEnding && week.DataType == Header.DataType.Week) { weekIndex = i; break; } } return weekIndex; }; function isExpenditureVisible(expCat) { // Returns TRUE, if EC must be displayed. For ordinary ECs always returns TRUE. // For Project Role returns true, if it has at least one resource with non-zero allocations if (!expCat) return false; var result = false; var expCatId = expCat.Id; var ACTUALS = 1; var PLANNED = 2; var TEAM = 4; if (expCat.AllowResourceAssignment) { // Ordinary EC. Check it fits current Capcity display mode result = (!$scope.DisplayMode.CapacityView && (((expCat.ECScenario & PLANNED) > 0) || ((expCat.ECScenario & TEAM) > 0))) || ($scope.DisplayMode.CapacityView && (((expCat.ECScenario & ACTUALS) > 0) || ((expCat.ECScenario & TEAM) > 0))); if (result) { // Displayed only, if has planned capacity or actual capacity for calendar period (ticket 2409) result = expCatHasPlannedCapacityForPeriod(expCat, $scope.Weekendings) || expCatHasActualCapacityForPeriod(expCat, $scope.Weekendings); } } else { // Super EC. Always display it to show remaining need result = true; } return result; }; function resourceHasTeam(resourceModel, we) { if (!resourceModel || !we || !resourceModel.Teams || !resourceModel.Teams.length) return false; var result = false; for (var index = 0; index < resourceModel.Teams.length; index++) { var teamMembership = resourceModel.Teams[index]; result = teamMembership.StartDate && (teamMembership.StartDate < we) && (!teamMembership.EndDate || teamMembership.EndDate >= we); if (result) { break; } } return result; }; function getTooltipContent(cell, rowType) { var tooltip = ''; var units = ' hours'; if (!$scope.DisplayMode.IsUOMHours) units = ' resources'; var displayAllocated, displayNeeded, displayNpt, displayCapacity, displaySuper; if (rowType == 'resource') { displayAllocated = displayNpt = displayCapacity = displaySuper = true; } else { switch (parseInt($scope.DisplayMode.TotalsAs)) { case 1: //Allocated/Capacity displayAllocated = true; displayCapacity = displayNpt = displaySuper = (rowType != 'expenditure') || !cell.IsProjectRole; displayNeeded = (rowType == 'expenditure') && cell.IsProjectRole; break; case 2: // Need/Capacity displayNeeded = true; displayCapacity = (rowType != 'expenditure') || !cell.IsProjectRole; displayAllocated = (rowType == 'expenditure') && cell.IsProjectRole; break; case 3: //Allocated/Need displayAllocated = displayNeeded = true; displayNpt = displaySuper = (rowType != 'expenditure') || !cell.IsProjectRole; break; case 4: //Remaining/Capacity displayNeeded = true; displayCapacity = displaySuper = (rowType != 'expenditure') || !cell.IsProjectRole; displayAllocated = (rowType == 'expenditure') && cell.IsProjectRole; break; } } if (displayAllocated) tooltip += 'Allocated: ' + cell.Allocated + units; if (displayNeeded && cell.Needed) tooltip += (tooltip.length > 0 ? '
' : '') + 'Need: ' + cell.Needed + units; if (displayNpt && cell.NonProjectTime) tooltip += (tooltip.length > 0 ? '
' : '') + 'Non-Project Time: ' + cell.NonProjectTime + units; if (displaySuper && cell.SuperECAllocations) tooltip += (tooltip.length > 0 ? '
' : '') + 'Project Roles: ' + cell.SuperECAllocations + units; if (displayCapacity && cell.Capacity) tooltip += (tooltip.length > 0 ? '
' : '') + 'Capacity: ' + cell.Capacity + units; return tooltip; }; function hideRedundantTooltips(exceptObj) { $('.tooltip').not(exceptObj).removeClass('in').hide(); }; function setupJSEvents() { var scrollTimeout = 0; var obj = window; $(obj).off('scroll.tt').on('scroll.tt', function () { if (scrollTimeout > 0) { if ($(obj).data('scroll.tt.timeout')) { clearTimeout($(obj).data('scroll.tt.timeout')); } $(obj).data('scroll.tt.timeout', setTimeout(hideRedundantTooltips, scrollTimeout)); } else hideRedundantTooltips(); }); $(document).off('click.tt').on('click.tt', function (e) { var parents = $(e.target).parents(":data('bs.tooltip')"); var target; if (parents && parents.length && parents.length > 0) { target = $(parents[0]).next(); } hideRedundantTooltips(target); }); }; function concatValues(item1, item2) { var result = angular.extend({}, item1); if (angular.isNumber(result.Value1)) { if (angular.isNumber(item2.Value1)) { result.Value1 += item2.Value1; } } else { result.Value1 = item2.Value1; } if (angular.isNumber(result.Value2)) { if (angular.isNumber(item2.Value2)) { result.Value2 += item2.Value2; } } else { result.Value2 = item2.Value2; } result.Allocated += item2.Allocated || 0; result.Needed += item2.Needed || 0; result.Capacity += item2.Capacity || 0; result.NonProjectTime += item2.NonProjectTime || 0; result.SuperECAllocations += item2.SuperECAllocations || 0; return result; }; function topLevelExists() { return $scope.DisplayMode.GroupBy === groupByEnum.Team || $scope.DisplayMode.GroupBy === groupByEnum.Company || $scope.DisplayMode.GroupBy === groupByEnum.CostCenter; }; function getTopLevelItem(itemId) { if ($scope.DisplayMode.GroupBy === groupByEnum.Team) { var team = teamInfoService.getById(itemId); var item = { Key: itemId, Name: team.Name, Type: $scope.RowType.Team }; return item; } if ($scope.DisplayMode.GroupBy === groupByEnum.Company) { var company = dataSources.getCompanyById(itemId); var item = { Key: itemId, Name: company.Name, Type: $scope.RowType.Company }; return item; } if ($scope.DisplayMode.GroupBy === groupByEnum.CostCenter) { var costCenter = dataSources.getCostCenterById(itemId); var item = { Key: itemId, Name: costCenter.Name, Type: $scope.RowType.CostCenter }; return item; } return null; }; function getECKey(expenditureCategoryId, teamId, rootItemId) { if (!topLevelExists()) { return expenditureCategoryId; } var topLevelItemId = undefined; switch ($scope.DisplayMode.GroupBy) { case groupByEnum.Team: topLevelItemId = teamId; break; case groupByEnum.Company: topLevelItemId = rootItemId; // contains companyId break; case groupByEnum.CostCenter: topLevelItemId = rootItemId; // contains costCenterId break; } var topLevelItem = getTopLevelItem(topLevelItemId); var key = topLevelItem.Key + '-' + expenditureCategoryId; return key; }; function getWeekendingFromHeader(header) { if (!header || !header.Header) return; var result = []; if (header.Header.Weeks) { var weeksCount = header.Header.Weeks.length; for (var index = 0; index < weeksCount; index++) { var week = header.Header.Weeks[index]; if ((week.DataType == Header.DataType.Week) && week.Milliseconds) { result.push(week.Milliseconds); } } } return result; }; function expCatHasPlannedCapacityForPeriod(expCatItem, weeks) { // Return TRUE, if category has at least one non-zero value for planned capacity in period if (!expCatItem || !weeks || !angular.isArray(weeks)) return false; var result = false; if (!expCatItem.PlannedCapacityValues) return result; var weeksCount = weeks.length; for (var index = 0; index < weeksCount; index++) { var we = weeks[index]; var value = expCatItem.PlannedCapacityValues[we]; result = angular.isNumber(value) && (Number(value) > 0); if (result) break; } return result; }; function expCatHasActualCapacityForPeriod(expCatItem, weeks) { // Return TRUE, if category has at least one non-zero value for actual capacity in period if (!expCatItem || !weeks || !angular.isArray(weeks)) return false; var result = false; if (!expCatItem.ActualCapacityValues) return result; var weeksCount = weeks.length; for (var index = 0; index < weeksCount; index++) { var we = weeks[index]; var value = expCatItem.ActualCapacityValues[we]; result = angular.isNumber(value) && (Number(value) > 0); if (result) break; } return result; }; function getExpenditureTeams(expCatId, teamIds) { // Returns team IDs, the expenditure exists in. If teams array is specified, doesn't perform any // checks, and returns this array back var result = null; if (!expCatId) return result; if (teamIds && angular.isArray(teamIds)) { result = teamIds; } else { // Get teams to loop through var teamsForExpCat = teamInfoService.getTeamsByExpenditureCategoryId(expCatId, false); if (!teamsForExpCat) return result; result = Object.keys(teamsForExpCat); } return result; }; }]);