'use strict'; uiDirectives.factory('activityCalendarUIService', ['activityCalendarService', 'dataSources', 'scenarioDetailsService', 'teamInfoService', 'hoursResourcesConverter', 'roundService', 'calculateDistributionService', 'cellHighlightingService', '$filter', '$timeout', '$q', '$http', '$templateCache', function (activityCalendarService, dataSources, scenarioDetailsService, teamInfoService, hoursResourcesConverter, roundService, calculateDistributionService, cellHighlightingService, $filter, $timeout, $q, $http, $templateCache) { var resolveRange = function (header, startDate, endDate) { if (!header || !header.getFirstWeek || !header.getLastWeek || !startDate || !endDate) { return; } var firstWeek = header.getFirstWeek(); var lastWeek = header.getLastWeek(); if (!firstWeek || !lastWeek) { return; } var range = { startDate: Math.max(startDate, firstWeek.Milliseconds), endDate: Math.min(endDate, lastWeek.Milliseconds) }; return range; }; var checkAnyTeamEditable = function (teams) { if (!teams || !teams.length) { return false; } for (var i = 0; i < teams.length; i++) { if (!teams[i].ReadOnly) { return true; } } return false; }; var checkAllTeamsEditable = function (teams) { if (!teams || !teams.length) { return false; } for (var i = 0; i < teams.length; i++) { if (teams[i].ReadOnly === true) { return false; } } return true; }; var service = { allocationMode: { teamAllocation: 1, // Team Allocations needAllocation: 2, // Project Need (ScenarioDetail) remainingNeedAllocation: 3, // Project Need minus Team Allocations unassignedNeedAllocation: 4, // Project Need minus Resource Allocations remainingTeamAllocation: 5, // Team Allocations minus Resource Allocations defaultHoursCollectionName: 'QuantityHoursValues', defaultResourcesCollectionName: 'QuantityResourcesValues', defaultHoursTotalName: 'TotalHoursValue', defaultResourcesTotalName: 'TotalResourcesValue', }, filterEntityType: { team: 1, view: 2, company: 3, resource: 4, report: 5, }, viewRowTemplates: { projectRowTemplate: '/Content/templates/ActivityCalendar/projectRow.html', projectRowNumbersTemplate: '/Content/templates/ActivityCalendar/projectNumbersRow.html', expCatRowTemplate: '/Content/templates/ActivityCalendar/expCatRow.html', expCatRowNumbersTemplate: '/Content/templates/ActivityCalendar/expCatNumbersRow.html', resourceRowTemplate: '/Content/templates/ActivityCalendar/resourceRow.html', resourceRowNumbersTemplate: '/Content/templates/ActivityCalendar/resourceNumbersRow.html', expCatUnallocatedRowTemplate: '/Content/templates/ActivityCalendar/expCatUnallocatedRow.html', expCatUnallocatedRowNumbersTemplate: '/Content/templates/ActivityCalendar/expCatUnallocatedNumbersRow.html', teamRowTemplate: '/Content/templates/ActivityCalendar/teamRow.html', teamRowNumbersTemplate: '/Content/templates/ActivityCalendar/teamNumbersRow.html', teamUnallocatedRowTemplate: '/Content/templates/ActivityCalendar/teamUnallocatedRow.html', teamUnallocatedRowNumbersTemplate: '/Content/templates/ActivityCalendar/teamUnallocatedNumbersRow.html', resourceGbrRowTemplate: '/Content/templates/ActivityCalendar/resourceGbrRow.html', resourceGbrRowNumbersTemplate: '/Content/templates/ActivityCalendar/resourceGbrNumbersRow.html', projectGbrRowTemplate: '/Content/templates/ActivityCalendar/projectGbrRow.html', projectGbrRowNumbersTemplate: '/Content/templates/ActivityCalendar/projectGbrNumbersRow.html', projectRcRowTemplate: '/Content/templates/ActivityCalendar/projectRcRow.html', projectRcRowNumbersTemplate: '/Content/templates/ActivityCalendar/projectRcNumbersRow.html', projectActualsRowTemplate: '/Content/templates/ActivityCalendar/projectActualsRow.html', projectActualsRowNumbersTemplate: '/Content/templates/ActivityCalendar/projectActualsNumbersRow.html', projectSupplyDemandReportRowTemplate: '/Content/templates/SupplyDemandReport/projectRow.html', projectSupplyDemandReportRowNumbersTemplate: '/Content/templates/SupplyDemandReport/projectNumbersRow.html', expCatSupplyDemandReportRowTemplate: '/Content/templates/SupplyDemandReport/expCatRow.html', expCatSupplyDemandReportRowNumbersTemplate: '/Content/templates/SupplyDemandReport/expCatNumbersRow.html', }, cacheTemplates: function () { var promises = []; angular.forEach(this.viewRowTemplates, function (templateUrl, key) { console.debug("cacheTemplates "+ templateUrl + " query"); // DEBUG console.time("cacheTemplates " + templateUrl); // DEBUG promises.push(dataSources.loadStaticFile(templateUrl + '?v=' + new Date().getTime()) .then(function (response) { console.timeEnd("cacheTemplates " + templateUrl); // DEBUG $templateCache.put(templateUrl, response.data); }, function (response) { throw 'An error occurred while loading template ' + templateUrl; })); }); return promises; }, getUtcNow: function () { var nowDate = new Date(); var year = nowDate.getUTCFullYear(); var month = nowDate.getUTCMonth(); var day = nowDate.getUTCDate(); var hours = nowDate.getUTCHours(); var minutes = nowDate.getUTCMinutes(); var utcNowMs = Date.UTC(year, month, day, hours, minutes); return utcNowMs; }, getAllocationMode: function (viewModelRow, customAllocationMode) { // Calculates allocation mode for Row // If no values are set in the Row as well as no custom value specified, returns default value (team allocations) var result = viewModelRow && viewModelRow.AllocationMode && angular.isNumber(viewModelRow.AllocationMode) ? Number(viewModelRow.AllocationMode) : (angular.isNumber(customAllocationMode) ? Number(customAllocationMode) : this.allocationMode.teamAllocation); return result; }, getCollectionNames4AllocationMode: function (allocationMode) { switch (allocationMode) { case this.allocationMode.needAllocation: return { hoursCollectionName: 'NeedHoursValues', hoursTotal: 'TotalNeedHoursValue', resourcesCollectionName: 'NeedResourcesValues', resourceTotal: 'TotalNeedResourcesValue' }; case this.allocationMode.remainingNeedAllocation: return { hoursCollectionName: 'RemainingNeedHoursValues', hoursTotal: 'TotalRemainingNeedHoursValue', resourcesCollectionName: 'RemainingNeedResourcesValues', resourceTotal: 'TotalRemainingNeedResourcesValue' }; case this.allocationMode.unassignedNeedAllocation: return { hoursCollectionName: 'UnassignedNeedHoursValues', hoursTotal: 'TotalUnassignedNeedHoursValue', resourcesCollectionName: 'UnassignedNeedResourcesValues', resourceTotal: 'TotalUnassignedNeedResourcesValue' }; case this.allocationMode.remainingTeamAllocation: return { hoursCollectionName: 'RemainingTAHoursValues', hoursTotal: 'TotalRemainingTAHoursValue', resourcesCollectionName: 'RemainingTAResourcesValues', resourceTotal: 'TotalRemainingTAResourcesValue' }; default: return { hoursCollectionName: this.allocationMode.defaultHoursCollectionName, hoursTotal: this.allocationMode.defaultHoursTotalName, resourcesCollectionName: this.allocationMode.defaultResourcesCollectionName, resourceTotal: this.allocationMode.defaultResourcesTotalName }; } }, getTeamAllocations4Display: function (filter, displayMode, header, calendarDate, projectId, expCatId, teamId) { var result = []; if (!filter || !displayMode || !header || !calendarDate || !projectId || !expCatId) return result; var project = activityCalendarService.getProjectById(filter, projectId); if (!project || !project.ActiveScenario || !project.ActiveScenario.Id || !project.Teams) return result; var scenarioId = project.ActiveScenario.Id; var teamAllocations = {}; var teams = []; var weekendings = []; if (calendarDate.DataType == Header.DataType.Week) { // Get allocations for a single weekending weekendings.push(calendarDate.Milliseconds); } if (calendarDate.DataType == Header.DataType.Month) { // Get summary allocations for month weekendings = header.getWeeksInMonth(calendarDate.ParentIndex); } if (!weekendings || (weekendings.length < 1)) return result; if (teamId) { // Get data for single team teams.push(teamId); } else { // Get data for all project teams teams = Object.keys(project.Teams); } var valuesCount = weekendings.length; for (var tIndex = 0; tIndex < teams.length; tIndex++) { var currentTeamId = teams[tIndex]; var currentTeam = teamInfoService.getById(currentTeamId); var needValue = undefined; for (var wIndex = 0; wIndex < weekendings.length; wIndex++) { var weekending = weekendings[wIndex]; var allocatedHours = activityCalendarService.getTeamAllocations4Scenario4Week(filter, scenarioId, [currentTeamId], expCatId, weekending); if (angular.isNumber(allocatedHours)) { needValue = (needValue || 0) + allocatedHours; } } if (angular.isNumber(needValue)) { if (displayMode.ShowAvgTotals && !displayMode.IsUOMHours && (valuesCount > 1)) { needValue = needValue / valuesCount; } if (displayMode.IsUOMHours) { needValue = roundService.roundQuantity(needValue); } else { needValue = hoursResourcesConverter.convertToResources(expCatId, needValue); needValue = roundService.roundQuantity(needValue); } var teamInfo = { Id: currentTeamId, Name: currentTeam.Name, Need: needValue }; teamAllocations[currentTeamId] = teamInfo; } } var keys = Object.keys(teamAllocations); if (keys.length > 0) { result = $filter('sortObjectsBy')(teamAllocations, 'Name', false); } return result; }, getExpCatUnassignedNeed: function (filter, displayMode, header, calendarDate, projectId, scenarioId, expCatIds, withResourceAllocations) { if (!filter || !displayMode || !header || !calendarDate || !projectId || !scenarioId) { return null; } var weekendings = []; var result = { Need: 0, AllocatedToTeamsInView: 0, AllocatedToTeamsOutOfView: 0, ResourceAllocations: 0, Underallocation: 0 }; if (withResourceAllocations) { result.ResourceAllocations = 0; } if (calendarDate.DataType == Header.DataType.Week) { // Get allocations for a single weekending weekendings.push(calendarDate.Milliseconds); } if (calendarDate.DataType == Header.DataType.Month) { // Get summary allocations for month weekendings = header.getWeeksInMonth(calendarDate.ParentIndex); } var weeksCount = weekendings.length; var useAverageValue = (calendarDate.DataType == Header.DataType.Month) && displayMode.ShowAvgTotals && !displayMode.IsUOMHours && (weeksCount > 0); var needAllocations = activityCalendarService.getNeedAllocations4Scenario(filter, scenarioId); var unassignedNeedAllocations = activityCalendarService.getUnassignedNeedAllocations4Scenario(filter, scenarioId); var restTeamAllocations = activityCalendarService.getRestTeamAllocations4Scenario(filter, scenarioId); if (!needAllocations) return result; var expCats = expCats = (expCatIds && angular.isArray(expCatIds)) ? expCatIds : (needAllocations ? Object.keys(needAllocations) : []); // Project need calculation (and resource allocations) for (var ecIndex = 0; ecIndex < expCats.length; ecIndex++) { var expCatId = expCats[ecIndex]; var expCatNeed = 0; var unassignedNeed = 0; if (expCatId in needAllocations) { var expCatTeamAllocations = this.getTeamAllocations4Display(filter, displayMode, header, calendarDate, projectId, expCatId); var expCatNeedData = needAllocations[expCatId]; if (expCatNeedData && expCatNeedData.Allocations) { for (var wIndex = 0; wIndex < weekendings.length; wIndex++) { var we = weekendings[wIndex]; if ((we in expCatNeedData.Allocations) && angular.isNumber(expCatNeedData.Allocations[we])) { if (displayMode.IsUOMHours) { expCatNeed += expCatNeedData.Allocations[we]; } else { expCatNeed += hoursResourcesConverter.convertToResources(expCatId, expCatNeedData.Allocations[we]); } if (withResourceAllocations && unassignedNeedAllocations && (expCatId in unassignedNeedAllocations) && unassignedNeedAllocations[expCatId].Allocations) { // Calculate resource allocations sum as Project Need minus Unassigned need, because some resource // allocations may be out of current view, and not present in data model var unassignedData = unassignedNeedAllocations[expCatId].Allocations; if ((we in unassignedData) && angular.isNumber(unassignedData[we])) { if (displayMode.IsUOMHours) { unassignedNeed += unassignedData[we]; } else { unassignedNeed += hoursResourcesConverter.convertToResources(expCatId, unassignedData[we]); } } } } } } if (useAverageValue) { expCatNeed = expCatNeed / weeksCount; if (withResourceAllocations) { unassignedNeed = unassignedNeed / weeksCount; } } } result.Need += roundService.roundQuantity(expCatNeed); if (withResourceAllocations) { result.ResourceAllocations += roundService.roundQuantity(expCatNeed >= unassignedNeed ? expCatNeed - unassignedNeed : 0); } // Team Allocations in current view calculation if (expCatTeamAllocations && (expCatTeamAllocations.length > 0)) { // Allocated to team in current view for (var index = 0; index < expCatTeamAllocations.length; index++) { var teamInfo = expCatTeamAllocations[index]; result.AllocatedToTeamsInView += roundService.roundQuantity(teamInfo.Need); } } // Rest team Allocations calculation (for teams out of current view) if (restTeamAllocations && (expCatId in restTeamAllocations)) { var teamAllocationsOutOfView = 0; var expCatTeamsAllocated = restTeamAllocations[expCatId]; if (expCatTeamsAllocated && expCatTeamsAllocated.Allocations) { for (var wIndex = 0; wIndex < weekendings.length; wIndex++) { // Allocations to teams out of current view var we = weekendings[wIndex]; if ((we in expCatTeamsAllocated.Allocations) && angular.isNumber(expCatTeamsAllocated.Allocations[we])) { if (displayMode.IsUOMHours) { teamAllocationsOutOfView += expCatTeamsAllocated.Allocations[we]; } else { teamAllocationsOutOfView += hoursResourcesConverter.convertToResources(expCatId, expCatTeamsAllocated.Allocations[we]); } } } if (useAverageValue) { // Get average value teamAllocationsOutOfView = teamAllocationsOutOfView / weeksCount; } result.AllocatedToTeamsOutOfView += roundService.roundQuantity(teamAllocationsOutOfView); } } } var allocatedToTeamsSummary = result.AllocatedToTeamsInView + result.AllocatedToTeamsOutOfView; result.Underallocation = roundService.roundQuantity(result.Need - allocatedToTeamsSummary); result.Need = roundService.roundQuantity(result.Need); result.AllocatedToTeamsInView = roundService.roundQuantity(result.AllocatedToTeamsInView); result.AllocatedToTeamsOutOfView = roundService.roundQuantity(result.AllocatedToTeamsOutOfView); if (withResourceAllocations) { result.ResourceAllocations = roundService.roundQuantity(result.ResourceAllocations); } return result; }, getExpCatRemainingTeamAllocations: function (filter, displayMode, header, calendarDate, projectId, allocationMode, expCatIds, teamIds) { if (!filter || !displayMode || !header || !calendarDate || !projectId || !allocationMode) { return null; } var projectRawData = activityCalendarService.getProjectById(filter, projectId); if (!projectRawData || !projectRawData.ActiveScenario || !projectRawData.ActiveScenario.Id) return null; var resourceAllocations = 0; var weekendings = []; var result = { TeamAllocations: 0, ResourceAllocations: 0 }; if (calendarDate.DataType == Header.DataType.Week) { // Get allocations for a single weekending weekendings.push(calendarDate.Milliseconds); } if (calendarDate.DataType == Header.DataType.Month) { // Get summary allocations for month weekendings = header.getWeeksInMonth(calendarDate.ParentIndex); } var weeksCount = weekendings.length; var useAverageValue = (calendarDate.DataType == Header.DataType.Month) && displayMode.ShowAvgTotals && !displayMode.IsUOMHours && (weeksCount > 0); // Filtering of ECs by teams is not performed for No Team block (even if filer for AC is set to Team or View) var isFilteredByTeamOrView = this.isFilteredByTeams(filter) && (allocationMode != this.allocationMode.remainingNeedAllocation); var expCats = expCatIds && angular.isArray(expCatIds) ? expCatIds : null; var teams = teamIds && angular.isArray(teamIds) ? teamIds : null; var projectExpCatsData = this.getExpenditures4Project(projectRawData, teams, expCats, header, filter, false, isFilteredByTeamOrView, true); // Calculate resource allocations sum if (projectExpCatsData) { var expCats = Object.keys(projectExpCatsData); if (!expCats || !angular.isArray(expCats) || !expCats.length) { return result; } for (var ecIndex = 0; ecIndex < expCats.length; ecIndex++) { var expCatId = expCats[ecIndex]; if (projectExpCatsData[expCatId] && projectExpCatsData[expCatId].Teams) { var teams = projectExpCatsData[expCatId].Teams; for (var teamId in teams) { var team = teams[teamId]; var expCatResourceAllocations = 0; var expCatTeamAllocations = 0; for (var wIndex = 0; wIndex < weekendings.length; wIndex++) { var we = weekendings[wIndex]; if (team.Allocations && (we in team.Allocations) && angular.isNumber(team.Allocations[we])) { if (displayMode.IsUOMHours) { expCatTeamAllocations += team.Allocations[we]; } else { expCatTeamAllocations += hoursResourcesConverter.convertToResources(expCatId, team.Allocations[we]); } } if (team.Resources) { for (var resourceId in team.Resources) { var resource = team.Resources[resourceId]; if (resource && resource.Allocations) { if ((we in resource.Allocations) && angular.isNumber(resource.Allocations[we])) { if (displayMode.IsUOMHours) { expCatResourceAllocations += resource.Allocations[we]; } else { expCatResourceAllocations += hoursResourcesConverter.convertToResources(expCatId, resource.Allocations[we]); } } } } } } if (useAverageValue) { expCatTeamAllocations = expCatTeamAllocations / weeksCount; expCatResourceAllocations = expCatResourceAllocations / weeksCount; } result.TeamAllocations += roundService.roundQuantity(expCatTeamAllocations); result.ResourceAllocations += roundService.roundQuantity(expCatResourceAllocations); } } } } result.TeamAllocations = roundService.roundQuantity(result.TeamAllocations); result.ResourceAllocations = roundService.roundQuantity(result.ResourceAllocations); return result; }, toggleRow: function (uiRow, dataModel, newValue) { newValue = newValue || !uiRow.Collapsed; uiRow.Collapsed = dataModel.Collapsed = newValue; if (uiRow.Children) { var that = this; angular.forEach(uiRow.Children, function (child) { that.showRow(child, !uiRow.Collapsed); }); } if (uiRow.NonProjectTime) { this.showRow(uiRow.NonProjectTime, !uiRow.Collapsed); } }, showRow: function (uiRow, isDisplay) { uiRow.Show = isDisplay; uiRow.Initialized = uiRow.Initialized || uiRow.Show; if (uiRow.Children) { var that = this; angular.forEach(uiRow.Children, function (child) { that.showRow(child, !uiRow.Collapsed && isDisplay); }); } }, toggleGridSource: function (rows, isUOMHours, allocationMode) { if (!rows || !rows.length) { return; } for (var i = 0; i < rows.length; i++) { var collectionNames = this.getCollectionNames4AllocationMode(rows[i].AllocationMode || allocationMode); if (isUOMHours) { rows[i].TotalValue = (collectionNames.hoursTotal in rows[i] ? rows[i][collectionNames.hoursTotal] : rows[i][this.allocationMode.defaultHoursTotalName]); rows[i].Cells = angular.copy(collectionNames.hoursCollectionName in rows[i] ? rows[i][collectionNames.hoursCollectionName] : rows[i][this.allocationMode.defaultHoursCollectionName]); } else { rows[i].TotalValue = (collectionNames.resourceTotal in rows[i] ? rows[i][collectionNames.resourceTotal] : rows[i][this.allocationMode.defaultResourcesTotalName]); rows[i].Cells = angular.copy(collectionNames.resourcesCollectionName in rows[i] ? rows[i][collectionNames.resourcesCollectionName] : rows[i][this.allocationMode.defaultResourcesCollectionName]); } if (rows[i].Children && rows[i].Children.length) { this.toggleGridSource(rows[i].Children, isUOMHours, allocationMode); } } }, toggleBarsValuesMode: function (rows, isBarMode) { if (!rows || !rows.length) { return; } for (var i = 0; i < rows.length; i++) { var row = rows[i]; if (row && row.Cells && row.CSSStyle && row.CSSClass) { for (var j = 0; j < row.Cells.length; j++) { var cell = row.Cells[j]; if (cell === undefined) { continue; } row.CSSClass[j] = (row.CSSClass[j] || '').replace(/ac-colors-mode-cell/g, ''); if (isBarMode && row.Color) { row.CSSClass[j] += " ac-colors-mode-cell"; row.CSSStyle[j] = "border-bottom: 1px solid " + row.Color + " !important;border-right-color: " + row.Color + " !important;background-color: " + row.Color + ";"; } else { row.CSSStyle[j] = null; } } } } }, toggleSortBy: function (rows, sortBy, sortOrderAsc) { if (!rows || !rows.length) { return rows; } // sort project parts the same way as top level projects var that = this; angular.forEach(rows, function (row) { if (row.RowType == 'Project' && row.Children && row.Children.length > 0) row.Children = that.toggleSortBy(row.Children, sortBy, sortOrderAsc); }); return $filter('orderBy')(rows, sortBy, !sortOrderAsc); }, toggleParts: function (rows, showParts, masterProjectsLevel, customMasterProjectTemplate) { if (!rows || !rows.length) { return rows; } var result = []; var masterProjects = {}; var that = this; var projectsRootLevel = masterProjectsLevel && angular.isNumber(masterProjectsLevel) && !isNaN(masterProjectsLevel) ? masterProjectsLevel : 1; // arrange rows according to showParts argument's value angular.forEach(rows, function (item) { if (showParts) // do not group by master project { if (item.IsMaster) { // if this is a master project then we should expand all child rows if (item.Children) for (var i = 0; i < item.Children.length; i++) { var child = item.Children[i]; child.Name = child.getFullName(); result.push(child); } } else { result.push(item); item.Name = item.getFullName(); } } else { if (item.IsMaster) result.push(item); else { if (item.ParentProjectId && item.ParentProjectId != item.Id) { var parent = masterProjects[item.ParentProjectId]; if (!parent) { parent = that.createViewModel4Project({ ParentProjectId: item.ParentProjectId, ProjectId: item.ParentProjectId, Name: item.ParentName, ReadOnly: true, Color: item.Color, ActiveScenario: {}, IsMaster: true }); parent.Level = projectsRootLevel; if (customMasterProjectTemplate && parent.Templates) { if (customMasterProjectTemplate.Main) { parent.Templates.Main = customMasterProjectTemplate.Main; } if (customMasterProjectTemplate.Numbers) { parent.Templates.Numbers = customMasterProjectTemplate.Numbers; } } masterProjects[item.ParentProjectId] = parent; result.push(parent); } parent.addPart(item); if (item.AllocationMode && angular.isNumber(item.AllocationMode)) { // Copy allocation mode from part to it's parent project parent.AllocationMode = item.AllocationMode; } } else result.push(item); } } }); // collapse all top level rows angular.forEach(result, function (item) { that.setLevel(item, projectsRootLevel); if (projectsRootLevel == 1) { item.Initialized = true; item.Show = true; that.toggleRow(item, {}, true); } }); return result; }, setLevel: function (row, level) { row.Level = level; if (row.Children) for (var i = 0; i < row.Children.length; i++) this.setLevel(row.Children[i], level + 1); }, toggleStatus: function (btn, projectId, scenario) { var $deferrer = $q.defer(); var dateRange = dataSources.getDateRange(); $(btn).scenarioStatusToggle({ scenario: { scenarioId: scenario.Id, projectId: projectId, startDate: scenario.StartDate ? DateTimeConverter.msFormatAsUtcString(scenario.StartDate) : null, endDate: scenario.EndDate ? DateTimeConverter.msFormatAsUtcString(scenario.EndDate) : null, }, minStartDate: dateRange.MinDate, maxEndDate: dateRange.MaxDate, runType: 'OnDemand', success: function (args) { // emulate document click to hide menus and popovers $(document).trigger('click.pm'); args[0].resolve(true); }, cancel: function (args) { args[0].resolve(false); }, error: function (args) { args[0].reject(); }, }); $(btn).scenarioStatusToggle('toggleStatus', [$deferrer]); return $deferrer.promise; }, openCreateScenarioWz: function (projectId, scenarioWizContainerId, scenarioWizModalId) { scenarioWizContainerId = scenarioWizContainerId || 'reloadForm'; scenarioWizModalId = scenarioWizModalId || 'createScenario'; var url = "/Scenarios/LoadScenario?Id=" + projectId; $('#' + scenarioWizContainerId).load(url, function () { if (typeof initScenario === 'function') initScenario(); $('#' + scenarioWizModalId).modal('show'); }); }, applyAvgMode: function (rows, header) { if (!rows || !header) { return; } if (!header.Months || !header.Weeks) { return; } for (var i = 0; i < rows.length; i++) { for (var j = 0; j < header.Months.length; j++) { var month = header.Months[j]; var monthLength = month.Childs.length; if (monthLength <= 0) { continue; } if (!rows[i].Cells[month.SelfIndexInWeeks]) { continue; } rows[i].Cells[month.SelfIndexInWeeks] = roundService.roundQuantity(rows[i].Cells[month.SelfIndexInWeeks] / monthLength); } rows[i].TotalValue = roundService.roundQuantity(rows[i].TotalValue / rows[i].VisibleCellsCount); if (rows[i].Children) { this.applyAvgMode(rows[i].Children, header); } } }, createViewModel4Team: function (teamData, filter) { if (!teamData) { return null; } var projectsViewModel = this.createViewModel4Projects(teamData.Projects, filter); if (projectsViewModel && angular.isArray(projectsViewModel) && projectsViewModel.length) { for (var index = 0; index < projectsViewModel.length; index++) { projectsViewModel[index].ParentTeamId = teamData.Id; } } var model = { Id: teamData.Id, Name: teamData.Name || 'empty', VisibleCellsCount: 0, // equals to the number of weeks in the calendar TotalHoursValue: 0, // total value in hours TotalResourcesValue: 0, // total value in resources TotalValue: 0, // total value for view, it depends on selected display mode (hours, resources) QuantityHoursValues: [], // week/month values in hours QuantityResourcesValues: [], // week/month values in resources Cells: [], // week/month values for view, it depends on selected display mode (hours, resources) Children: projectsViewModel }; return model; }, createViewModel4Company: function (companyData, filter) { if (!companyData) { return null; } var addECTeams = filter.EntityType == this.filterEntityType.view; var projectsViewModel = this.createViewModel4Projects(companyData.Projects, filter, addECTeams); var model = { Id: companyData.Id, Name: companyData.Name || 'empty', VisibleCellsCount: 0, // equals to the number of weeks in the calendar TotalHoursValue: 0, // total value in hours TotalResourcesValue: 0, // total value in resources TotalValue: 0, // total value for view, it depends on selected display mode (hours, resources) QuantityHoursValues: [], // week/month values in hours QuantityResourcesValues: [], // week/month values in resources NeedHoursValues: [], // week/month need values in hours NeedResourcesValues: [], // week/month need values in resources TotalNeedHoursValue: 0, // total value in hours TotalNeedResourcesValue: 0, // total value in resources Cells: [], // week/month values for view, it depends on selected display mode (hours, resources) Children: projectsViewModel }; return model; }, createViewModel4Client: function (clientData, filter) { if (!clientData) { return null; } var addECTeams = filter.EntityType == this.filterEntityType.view; var projectsViewModel = this.createViewModel4Projects(clientData.Projects, filter, addECTeams); var model = { Id: clientData.Id, Name: clientData.Name || 'empty', VisibleCellsCount: 0, // equals to the number of weeks in the calendar TotalHoursValue: 0, // total value in hours TotalResourcesValue: 0, // total value in resources TotalValue: 0, // total value for view, it depends on selected display mode (hours, resources) QuantityHoursValues: [], // week/month values in hours QuantityResourcesValues: [], // week/month values in resources NeedHoursValues: [], // week/month need values in hours NeedResourcesValues: [], // week/month need values in resources TotalNeedHoursValue: 0, // total value in hours TotalNeedResourcesValue: 0, // total value in resources Cells: [], // week/month values for view, it depends on selected display mode (hours, resources) Children: projectsViewModel }; return model; }, createViewModel4CostCenter: function (costCenterData, filter) { if (!costCenterData) { return null; } var addECTeams = filter.EntityType == this.filterEntityType.view; var projectsViewModel = this.createViewModel4Projects(costCenterData.Projects, filter, addECTeams); if (projectsViewModel && angular.isArray(projectsViewModel)) { for (var index = 0; index < projectsViewModel.length; index++) { if (projectsViewModel[index]) { projectsViewModel[index].ParentCostCenterId = costCenterData.Id; } } } var model = { Id: costCenterData.Id, Name: costCenterData.Name || 'empty', VisibleCellsCount: 0, // equals to the number of weeks in the calendar TotalHoursValue: 0, // total value in hours TotalResourcesValue: 0, // total value in resources TotalValue: 0, // total value for view, it depends on selected display mode (hours, resources) QuantityHoursValues: [], // week/month values in hours QuantityResourcesValues: [], // week/month values in resources NeedHoursValues: [], // week/month need values in hours NeedResourcesValues: [], // week/month need values in resources TotalNeedHoursValue: 0, // total value in hours TotalNeedResourcesValue: 0, // total value in resources Cells: [], // week/month values for view, it depends on selected display mode (hours, resources) Children: projectsViewModel }; return model; }, createViewModel4Projects: function (projects, filter, addECTeams, customRowTemplates) { if (!projects) { return null; } var result = []; for (var projectId in projects) { var projectItem = projects[projectId]; if (projectItem.Project && projectItem.Expenditures) { var projectViewModel = this.createViewModel4Project(projectItem.Project, projectItem.Expenditures, filter, addECTeams, customRowTemplates); if (projectViewModel) { result.push(projectViewModel); } } } return result; }, createViewModel4Project: function (project, expenditures, filter, addECTeams, customRowTemplates) {//todo: test all usages // project - project data from data layer // expenditures - data from data layer for each expenditure of the project. Data is grouped by team if addECTeams is true, // otherwise data is sum for all project team's data of each category row // filter - represents filtering info entered by user // addECTeams - indicates whether to add team rows under EC rows or not if (!project || !project.ActiveScenario) { return null; } var expendituresModel = this.createViewModel4ExpCats(expenditures, addECTeams, customRowTemplates); var editUrl; if (project.ParentProjectId) { editUrl = '/Project/Edit/' + project.ParentProjectId; if (project.PartId) editUrl += '?partId=' + project.PartId; } var templates = angular.extend({}, this.viewRowTemplates, customRowTemplates || {}); var projectViewModel = { Id: project.ProjectId, ParentProjectId: project.ParentProjectId, ParentName: project.ParentName, IsMaster: project.IsMaster, PartName: project.Name || 'empty', Name: project.Name || 'empty', AllocationMode: project.AllocationMode || this.allocationMode.teamAllocation, Color: project.Color, Type: project.TypeName, Date: project.DeadlineMs, Priority: project.Priority, ReadOnly: project.ReadOnly, DisableResourceEdit: project.ReadOnly || this.isFilteredByCompany(filter), VisibleCellsCount: 0, // equals to the scenario duration in the selected period, number of scenario cells filled with values VisibleCellWeekendings: {}, // represents array of weekendings in ms for single project or part and union of part's weeks for master project ActiveScenario: { Id: project.ActiveScenario.Id, Name: project.ActiveScenario.Name, IsBottomUp: project.ActiveScenario.IsBottomUp, StartDate: project.ActiveScenario.StartDate, EndDate: project.ActiveScenario.EndDate }, InactiveScenarios: project.InactiveScenarios, RowType: 'Project', Templates: { Main: templates.projectRowTemplate, Numbers: templates.projectRowNumbersTemplate }, EditUrl: editUrl, Collapsed: true, TotalHoursValue: 0, // total value in hours TotalResourcesValue: 0, // total value in resources TotalValue: 0, // total value for view, it depends on selected display mode (hours, resources) QuantityHoursValues: [], // week/month values in hours QuantityResourcesValues: [], // week/month values in resources NeedHoursValues: [], // week/month need values in hours NeedResourcesValues: [], // week/month need values in resources TotalNeedHoursValue: 0, // total value in hours TotalNeedResourcesValue: 0, // total value in resources RemainingNeedHoursValues: [], // week/month need values in hours RemainingNeedResourcesValues: [], // week/month need values in resources RemainingTAHoursValues: [], // week/month remaining team allocation values in hours RemainingTAResourcesValues: [], // week/month remaining team allocation values in resources TotalRemainingNeedHoursValue: 0, // total value in hours TotalRemainingNeedResourcesValue: 0, // total value in resources TotalRemainingTAHoursValue: 0, // total value in hours TotalRemainingTAResourcesValue: 0, // total value in resources UnassignedNeedHoursValues: [], // week/month need values in hours UnassignedNeedResourcesValues: [], // week/month need values in resources TotalUnassignedNeedHoursValue: 0, // total value in hours TotalUnassignedNeedResourcesValue: 0, // total value in resources Cells: [], // week/month values for view, it depends on selected display mode (hours, resources) CSSStyle: [], CSSClass: [], Children: expendituresModel, // children expenditure rows for the project calculateResourceTotalOnRange: function (weeks) { return calculateDistributionService.calculateSumOnRange(this.Cells, weeks); }, addPart: function (part) { part.Name = part.getSelfName(); this.TotalHoursValue = roundService.roundQuantity(this.TotalHoursValue + part.TotalHoursValue); this.TotalResourcesValue = roundService.roundQuantity(this.TotalResourcesValue + part.TotalResourcesValue); this.TotalValue = roundService.roundQuantity(this.TotalValue + part.TotalValue); this.QuantityHoursValues = calculateDistributionService.mergeAllocations(this.QuantityHoursValues, part.QuantityHoursValues); this.QuantityResourcesValues = calculateDistributionService.mergeAllocations(this.QuantityResourcesValues, part.QuantityResourcesValues); this.NeedHoursValues = calculateDistributionService.mergeAllocations(this.NeedHoursValues, part.NeedHoursValues); this.NeedResourcesValues = calculateDistributionService.mergeAllocations(this.NeedResourcesValues, part.NeedResourcesValues); this.TotalNeedHoursValue = roundService.roundQuantity(this.TotalNeedHoursValue + part.TotalNeedHoursValue); this.TotalNeedResourcesValue = roundService.roundQuantity(this.TotalNeedResourcesValue + part.TotalNeedResourcesValue); this.RemainingNeedHoursValues = calculateDistributionService.mergeAllocations(this.RemainingNeedHoursValues, part.RemainingNeedHoursValues); this.RemainingNeedResourcesValues = calculateDistributionService.mergeAllocations(this.RemainingNeedResourcesValues, part.RemainingNeedResourcesValues); this.RemainingTAHoursValues = calculateDistributionService.mergeAllocations(this.RemainingTAHoursValues, part.RemainingTAHoursValues); this.RemainingTAResourcesValues = calculateDistributionService.mergeAllocations(this.RemainingTAResourcesValues, part.RemainingTAResourcesValues); this.TotalRemainingNeedHoursValue = roundService.roundQuantity(this.TotalRemainingNeedHoursValue + part.TotalRemainingNeedHoursValue); this.TotalRemainingNeedResourcesValue = roundService.roundQuantity(this.TotalRemainingNeedResourcesValue + part.TotalRemainingNeedResourcesValue); this.TotalRemainingTAHoursValue = roundService.roundQuantity(this.TotalRemainingTAHoursValue + part.TotalRemainingTAHoursValue); this.TotalRemainingTAResourcesValue = roundService.roundQuantity(this.TotalRemainingTAResourcesValue + part.TotalRemainingTAResourcesValue); this.UnassignedNeedHoursValues = calculateDistributionService.mergeAllocations(this.UnassignedNeedHoursValues, part.UnassignedNeedHoursValues); this.UnassignedNeedResourcesValues = calculateDistributionService.mergeAllocations(this.UnassignedNeedResourcesValues, part.UnassignedNeedResourcesValues); this.TotalUnassignedNeedHoursValue = roundService.roundQuantity(this.TotalUnassignedNeedHoursValue + part.TotalUnassignedNeedHoursValue); this.TotalUnassignedNeedResourcesValue = roundService.roundQuantity(this.TotalUnassignedNeedResourcesValue + part.TotalUnassignedNeedResourcesValue); this.VisibleCellWeekendings = calculateDistributionService.mergeAllocations(this.VisibleCellWeekendings, part.VisibleCellWeekendings); this.VisibleCellsCount = Object.keys(this.VisibleCellWeekendings).length; if ((this.Level !== undefined) && angular.isNumber(this.Level) && !isNaN(this.Level)) { part.Level = this.Level + 1; } if (!this.Children) this.Children = []; this.Children.push(part); }, getSelfName: function () { return this.PartName; }, getFullName: function () { var displayName; if (this.PartName) displayName = this.PartName; if (this.ParentName) { if (displayName.length > 0) displayName += ': '; displayName += this.ParentName; } return displayName; } }; return projectViewModel; }, createViewModel4ExpCats: function (expenditures, addECTeams, customRowTemplates) {//todo: test all usages if (!expenditures || Object.keys(expenditures).length <= 0) { return null; } var rows = []; for (var categoryId in expenditures) { var category = expenditures[categoryId]; var itemViewModel = this.createViewModel4ExpCat(category, addECTeams, customRowTemplates); if (itemViewModel) { rows.push(itemViewModel); } }; return rows; }, createViewModel4ExpCat: function (exp, addECTeams, customRowTemplates) {//todo: test all usages if (!exp) { return null; } var resources = addECTeams ? null : this.createViewModel4Resources(exp.Resources); var availableResources = addECTeams ? null : this.createViewModel4AvailableResources(exp.AvailableResources); var ecTeams = null; var templates = angular.extend({}, this.viewRowTemplates, customRowTemplates || {}); if (addECTeams) { ecTeams = this.createViewModel4ECTeams(exp.Teams); for (var teamId in ecTeams) { ecTeams[teamId].Templates.Main = templates.teamRowTemplate; ecTeams[teamId].Templates.Numbers = templates.teamRowNumbersTemplate; } } var model = { Id: exp.Id, Name: exp.Name || 'empty', AllocationMode: exp.AllocationMode || this.allocationMode.teamAllocation, RowType: 'ExpCat', Templates: { Main: templates.expCatRowTemplate, Numbers: templates.expCatRowNumbersTemplate }, AllowResourceAssignment: exp.AllowResourceAssignment, Collapsed: exp.Collapsed || true, VisibleCellsCount: 0, // equals to the scenario duration in the selected period, number of scenario cells filled with values TotalHoursValue: 0, // total value in hours TotalResourcesValue: 0, // total value in resources TotalValue: 0, // total value for view, it depends on selected display mode (hours, resources) QuantityHoursValues: [], // week/month values in hours QuantityResourcesValues: [], // week/month values in resources NeedHoursValues: [], // week/month need values in hours NeedResourcesValues: [], // week/month need values in resources TotalNeedHoursValue: 0, // total value in hours TotalNeedResourcesValue: 0, // total value in resources RemainingNeedHoursValues: [], // week/month need values in hours RemainingNeedResourcesValues: [], // week/month need values in resources TotalRemainingNeedHoursValue: 0, // total value in hours TotalRemainingNeedResourcesValue: 0, // total value in resources RemainingTAHoursValues: [], // week/month need values in hours RemainingTAResourcesValues: [], // week/month need values in resources TotalRemainingTAHoursValue: 0, // total value in hours TotalRemainingTAResourcesValue: 0, // total value in resources UnassignedNeedHoursValues: [], // week/month need values in hours UnassignedNeedResourcesValues: [], // week/month need values in resources TotalUnassignedNeedHoursValue: 0, // total value in hours TotalUnassignedNeedResourcesValue: 0, // total value in resources RemainingCapacityValues: [], // team allocation - resource allocation in hours Cells: [], // week/month values for view, it depends on selected display mode (hours, resources) CSSClass: [], Children: addECTeams ? ecTeams : resources, // children resource rows for the expenditure AvailableResources: addECTeams ? null : availableResources }; return model; }, createViewModel4ECTeams: function (teams) { if (!teams || Object.keys(teams).length <= 0) { return null; } var rows = []; for (var teamId in teams) { var team = teams[teamId]; var itemViewModel = this.createViewModel4ECTeam(team); if (itemViewModel) { rows.push(itemViewModel); } }; return rows; }, createViewModel4ECTeam: function (ecTeamData) { if (!ecTeamData) { return null; } var resources = this.createViewModel4Resources(ecTeamData.Resources); var availableResources = this.createViewModel4AvailableResources(ecTeamData.AvailableResources); var basicModel = { Id: ecTeamData.Id, Name: ecTeamData.Name || 'empty', AllocationMode: ecTeamData.AllocationMode || this.allocationMode.teamAllocation, ExpenditureCategoryId: ecTeamData.ExpenditureCategoryId, Templates: { Main: this.viewRowTemplates.teamUnallocatedRowTemplate, Numbers: this.viewRowTemplates.teamUnallocatedRowNumbersTemplate }, Initialized: ecTeamData.Initialized, Show: ecTeamData.Show, Level: ecTeamData.Level, RowType: 'Team', Collapsed: true, VisibleCellsCount: 0, // equals to the scenario duration in the selected period, number of scenario cells filled with values TotalHoursValue: 0, // total value in hours TotalResourcesValue: 0, // total value in resources TotalValue: 0, // total value for view, it depends on selected display mode (hours, resources) QuantityHoursValues: [], // week/month values in hours QuantityResourcesValues: [], // week/month values in resources NeedHoursValues: [], // week/month need values in hours NeedResourcesValues: [], // week/month need values in resources TotalNeedHoursValue: 0, // total value in hours TotalNeedResourcesValue: 0, // total value in resources RemainingNeedHoursValues: [], // week/month need values in hours RemainingNeedResourcesValues: [], // week/month need values in resources TotalRemainingNeedHoursValue: 0, // total value in hours TotalRemainingNeedResourcesValue: 0, // total value in resources RemainingTAHoursValues: [], // week/month need values in hours RemainingTAResourcesValues: [], // week/month need values in resources TotalRemainingTAHoursValue: 0, // total value in hours TotalRemainingTAResourcesValue: 0, // total value in resources UnassignedNeedHoursValues: [], // week/month need values in hours UnassignedNeedResourcesValues: [], // week/month need values in resources TotalUnassignedNeedHoursValue: 0, // total value in hours TotalUnassignedNeedResourcesValue: 0, // total value in resources RemainingCapacityValues: [], // team allocation - resource allocation in hours Cells: [], // week/month values for view, it depends on selected display mode (hours, resources) CSSClass: [], Children: resources, // children resource rows for the expenditure AvailableResources: availableResources, calculateResourceTotalOnRange: function (weeks) { return calculateDistributionService.calculateSumOnRange(this.Cells, weeks); }, }; var basicEditableModel = this.createViewModelEditableBasic(); var model = angular.extend({}, basicModel, basicEditableModel); return model; }, createViewModel4Resources: function (resources) { if (!resources || !Object.keys(resources).length) { return null; } var rows = []; for (var resourceId in resources) { var resource = resources[resourceId]; var itemViewModel = this.createViewModel4Resource(resource); if (itemViewModel) { rows.push(itemViewModel); } }; return rows; }, createViewModel4ResourceBasic: function (resource) { if (!resource) { return; } var model = { Id: resource.Id, Name: resource.Name || 'empty', Templates: { Main: this.viewRowTemplates.resourceRowTemplate, Numbers: this.viewRowTemplates.resourceRowNumbersTemplate }, Collapsed: true, VisibleCellsCount: 0, // equals to the scenario duration in the selected period, number of scenario cells filled with values VisibleCellWeekendings: {}, // represents an array of weekendings in ms for the row TotalHoursValue: 0, // total value in hours TotalResourcesValue: 0, // total value in resources TotalValue: 0, // total value for view, it depends on selected display mode (hours, resources) QuantityHoursValues: [], // week/month values in hours QuantityResourcesValues: [], // week/month values in resources Cells: [], // week/month values for view, it depends on selected display mode (hours, resources) CSSClass: [], calculateResourceTotalOnRange: function (weeks) { return calculateDistributionService.calculateSumOnRange(this.Cells, weeks); }, }; return model; }, createViewModelEditableBasic: function () { var model = { IsEditable: false, CanBeRemoved: true, EditableForecastWeeks: [], // array with editable weeks for resource forecast values sortWeeks: function (weeks) { var result = { Editable: [], ReadOnly: [], }; if (!weeks || !weeks.length) { return result; } if (!this.EditableForecastWeeks || !this.EditableForecastWeeks.length) { return result; } for (var i = 0; i < weeks.length; i++) { if (this.EditableForecastWeeks[weeks[i]]) { result.Editable.push(weeks[i]); } else { result.ReadOnly.push(weeks[i]); } } return result; }, }; return model; }, createViewModel4Resource: function (resource) { if (!resource) { return null; } var basicModel = this.createViewModel4ResourceBasic(resource); var basicEditableModel = this.createViewModelEditableBasic(); var model = angular.extend({}, basicModel, basicEditableModel, { Teams: [], RowType: 'Resource' }); return model; }, createViewModel4AvailableResources: function (resources) { if (!resources || !Object.keys(resources).length) { return null; } var items = {}; for (var resourceId in resources) { var resource = resources[resourceId]; var model = this.createViewModel4AvailableResource(resource); if (model) { items[resourceId] = model; } }; return items; }, createViewModel4AvailableResource: function (resource) { if (!resource) { return null; } var model = { id: resource.Id, name: resource.Name, teams: resource.Teams, startDate: resource.StartDate, endDate: resource.EndDate, minAvailability: resource.MinAvailability, maxAvailability: resource.MaxAvailability, avgAvailability: resource.AvgAvailability, }; return model; }, createViewModel4TotalRow: function (name) { var model = { Name: name, Collapsed: false, VisibleCellsCount: 0, TotalHoursValue: 0, // total value in hours TotalResourcesValue: 0, // total value in resources TotalValue: 0, // total value for view, it depends on selected display mode (hours, resources) QuantityHoursValues: [], // week/month values in hours QuantityResourcesValues: [], // week/month values in resources Cells: [], // week/month values for view, it depends on selected display mode (hours, resources) CSSStyle: [], CSSClass: [], Children: [], }; return model; }, createViewModel4NptTotalRow: function (name, data) { var model = { Name: name, RowType: "NPT", Collapsed: true, VisibleCellsCount: 0, TotalHoursValue: 0, // total value in hours TotalResourcesValue: 0, // total value in resources TotalValue: 0, // total value for view, it depends on selected display mode (hours, resources) QuantityHoursValues: [], // week/month values in hours QuantityResourcesValues: [], // week/month values in resources Cells: [], // week/month values for view, it depends on selected display mode (hours, resources) CSSStyle: [], CSSClass: [] }; var nptCategoryModels = this.createViewModel4NptCategories(data); if (nptCategoryModels && (nptCategoryModels.length > 0)) model.Children = nptCategoryModels; return model; }, createViewModel4NptCategories: function (nptCategoriesData) { if (!nptCategoriesData) { return null; } var result = []; var nptCategoriesDataSorted = $filter('sortObjectsBy')(nptCategoriesData, 'Name', false); for (var index = 0; index < nptCategoriesDataSorted.length; index++) { var nptCategoryItem = nptCategoriesDataSorted[index]; if (nptCategoryItem.NonProjectTime) { var nptCategoryViewModel = this.createViewModel4NptCategory(nptCategoryItem); if (nptCategoryViewModel) { result.push(nptCategoryViewModel); } } } return result; }, createViewModel4NptCategory: function (nptCategoryData) { if (!nptCategoryData || !nptCategoryData.NonProjectTime || (nptCategoryData.NonProjectTime.length < 1)) return null; var categoryViewModel = { Id: nptCategoryData.Id, Name: nptCategoryData.Name || 'empty', VisibleCellsCount: 0, RowType: 'NonProjectTimeCategory', Collapsed: nptCategoryData.Collapsed || true, TotalHoursValue: 0, // total value in hours TotalResourcesValue: 0, // total value in resources TotalValue: 0, // total value for view, it depends on selected display mode (hours, resources) QuantityHoursValues: [], // week/month values in hours QuantityResourcesValues: [], // week/month values in resources Cells: [], // week/month values for view, it depends on selected display mode (hours, resources) CSSStyle: [], CSSClass: [] }; categoryViewModel.Children = this.createViewModel4NptItems(nptCategoryData.NonProjectTime); return categoryViewModel; }, createViewModel4NptItems: function (nptItemsData) { if (!nptItemsData) { return null; } var result = []; var nptItemsDataSorted = $filter('sortObjectsBy')(nptItemsData, 'Name', false); for (var index = 0; index < nptItemsDataSorted.length; index++) { var nptItem = nptItemsDataSorted[index]; if (nptItem.Resources) { var nptItemViewModel = this.createViewModel4NptItem(nptItem); if (nptItemViewModel) { result.push(nptItemViewModel); } } } return result; }, createViewModel4NptItem: function (nptItemData) { if (!nptItemData || !nptItemData.Resources || (nptItemData.Resources.length < 1)) { return null; } var nptViewModel = { Id: nptItemData.Id, Name: nptItemData.isTeamWide ? (nptItemData.Name + ' (team-wide)') : nptItemData.Name, IsTeamWide: nptItemData.isTeamWide, IsReadOnly: false, VisibleCellsCount: 0, RowType: 'NonProjectTime', Collapsed: nptItemData.Collapsed || true, TotalHoursValue: 0, // total value in hours TotalResourcesValue: 0, // total value in resources TotalValue: 0, // total value for view, it depends on selected display mode (hours, resources) QuantityHoursValues: [], // week/month values in hours QuantityResourcesValues: [], // week/month values in resources EditableNptWeeks: [], // week/month values are editable Resources: [], // Resources with ECs in team-wide NPT. For resource-level NPT is empty Cells: [], // week/month values for view, it depends on selected display mode (hours, resources) CSSStyle: [], CSSClass: [], sortWeeks: function (weeks) { var result = { Editable: [], ReadOnly: [], }; if (!weeks || !weeks.length) { return result; } if (!this.EditableNptWeeks || !this.EditableNptWeeks.length) { return result; } for (var i = 0; i < weeks.length; i++) { if (this.EditableNptWeeks[weeks[i]]) { result.Editable.push(weeks[i]); } else { result.ReadOnly.push(weeks[i]); } } return result; }, calculateResourceTotalOnRange: function (weeks) { return calculateDistributionService.calculateSumOnRange(this.Cells, weeks); }, }; if (!nptItemData.isTeamWide) { // Resource-level NPT drops down to it's resources list nptViewModel.Children = this.createViewModel4NptResources(nptItemData.Resources); } else { // We store resources in team-wide NPT for (var resourceId in nptItemData.Resources) { var resourceInfo = { Id: resourceId, ExpenditureCategoryId: nptItemData.Resources[resourceId].ExpenditureCategoryId } nptViewModel.Resources.push(resourceInfo); } } return nptViewModel; }, createViewModel4NptResources: function (nptResourcesData) { if (!nptResourcesData) { return null; } var result = []; var nptResourcesDataSorted = $filter('sortObjectsBy')(nptResourcesData, 'SortingKey', false); for (var index = 0; index < nptResourcesDataSorted.length; index++) { var resourceData = nptResourcesDataSorted[index]; var resourceViewModel = this.createViewModel4NptResource(resourceData); if (resourceViewModel) { result.push(resourceViewModel); } } return result; }, createViewModel4NptResource: function (resourceData) { if (!resourceData) { return null; } var resourceViewModel = { Id: resourceData.Id, Name: resourceData.Name || 'empty', ExpenditureCategoryId: resourceData.ExpenditureCategoryId, VisibleCellsCount: 0, IsReadOnly: false, RowType: 'NonProjectTimeResource', Collapsed: resourceData.Collapsed || true, TotalHoursValue: 0, // total value in hours TotalResourcesValue: 0, // total value in resources TotalValue: 0, // total value for view, it depends on selected display mode (hours, resources) QuantityHoursValues: [], // week/month values in hours QuantityResourcesValues: [], // week/month values in resources EditableNptWeeks: [], // // week/month values are editable Cells: [], // week/month values for view, it depends on selected display mode (hours, resources) CSSStyle: [], CSSClass: [], sortWeeks: function (weeks) { var result = { Editable: [], ReadOnly: [], }; if (!weeks || !weeks.length) { return result; } if (!this.EditableNptWeeks || !this.EditableNptWeeks.length) { return result; } for (var i = 0; i < weeks.length; i++) { if (this.EditableNptWeeks[weeks[i]]) { result.Editable.push(weeks[i]); } else { result.ReadOnly.push(weeks[i]); } } return result; }, calculateResourceTotalOnRange: function (weeks) { return calculateDistributionService.calculateSumOnRange(this.Cells, weeks); }, }; return resourceViewModel; }, createViewModel4AvailableTeams: function (availableTeams) { var availableTeamsViewModel = []; if (!availableTeams || !Object.keys(availableTeams).length) { return availableTeamsViewModel; } for (var teamId in availableTeams) { var availableTeamViewModel = this.createViewModel4AvailableTeam(availableTeams[teamId]); if (availableTeamViewModel) { availableTeamsViewModel.push(availableTeamViewModel); } } return availableTeamsViewModel; }, createViewModel4AvailableTeam: function (availableTeam) { if (!availableTeam) { return null; } var availableTeamViewModel = { Id: availableTeam.Id, Name: availableTeam.Name, }; return availableTeamViewModel; }, fillProjectRowWithData: function (rollupRow, projectRow, expenditures, header, filter, addECTeams) { if (!projectRow || !projectRow.ActiveScenario) { return; } if (!header || !header.Months || !header.Weeks || !filter) { return; } var weeksCount = 0; if (!projectRow.Children || !expenditures || !Object.keys(expenditures).length) { this.fillExpenditureCategoryRowWithData(header, rollupRow, projectRow, null, null, projectRow.ActiveScenario.StartDate, projectRow.ActiveScenario.EndDate); } else { for (var ecIndex = 0; ecIndex < projectRow.Children.length; ecIndex++) { var ecRow = projectRow.Children[ecIndex]; var category = expenditures[ecRow.Id]; if (category) { this.fillExpenditureCategoryRowWithData(header, rollupRow, projectRow, ecRow, category, projectRow.ActiveScenario.StartDate, projectRow.ActiveScenario.EndDate); if (addECTeams) { if (ecRow.Children && category.Teams) { for (var teamKey in ecRow.Children) { var teamRow = ecRow.Children[teamKey]; var team = category.Teams[teamRow.Id]; this.fillECTeamRowWithData(header, teamRow, ecRow, team, projectRow.Id, projectRow.ActiveScenario.StartDate, projectRow.ActiveScenario.EndDate, projectRow.ReadOnly); if (teamRow.Children && team.Resources) { for (var resourceIndex = 0; resourceIndex < teamRow.Children.length; resourceIndex++) { var resourceRow = teamRow.Children[resourceIndex]; var resource = team.Resources[resourceRow.Id]; this.fillResourceRowWithData(header, teamRow, teamRow.ExpenditureCategoryId, resourceRow.Id, resourceRow, resource, null, projectRow.ActiveScenario.StartDate, projectRow.ActiveScenario.EndDate, false, projectRow.DisableResourceEdit); }; } }; } } else { if (ecRow.Children && category.Resources) { for (var resourceIndex = 0; resourceIndex < ecRow.Children.length; resourceIndex++) { var resourceRow = ecRow.Children[resourceIndex]; var resource = category.Resources[resourceRow.Id]; this.fillResourceRowWithData(header, ecRow, ecRow.Id, resourceRow.Id, resourceRow, resource, null, projectRow.ActiveScenario.StartDate, projectRow.ActiveScenario.EndDate, false, projectRow.DisableResourceEdit); }; } } } } } if (rollupRow) { rollupRow.VisibleCellsCount = header.TotalWeeks; } }, // rollupRow - team (for group by team mode) or company (for group by company mode) row fillExpenditureCategoryRowWithData: function (header, rollupRow, projectRow, expenditureCategoryRow, categoryRowData, startDate, endDate) { if (!header) { return; } if (!rollupRow && !projectRow && !expenditureCategoryRow) { return; } var projectWeeksCount = 0; var projectWeeks = {}; for (var monthIndex = 0; monthIndex < header.Months.length; monthIndex++) { var month = header.Months[monthIndex]; for (var mcIndex = 0; mcIndex < month.Childs.length; mcIndex++) { var weekIndex = month.Childs[mcIndex]; var week = header.Weeks[weekIndex]; if ((!!startDate && week.Milliseconds < startDate) || (!!endDate && week.Milliseconds > endDate)) { continue; } projectWeeksCount += 1; projectWeeks[week.Milliseconds] = 0; var hoursValue = (categoryRowData && categoryRowData.Allocations) ? categoryRowData.Allocations[week.Milliseconds] || 0 : 0; var resourcesValue = hoursValue === 0 ? 0 : hoursResourcesConverter.convertToResources(categoryRowData.Id, hoursValue); var needHoursValue = (categoryRowData && categoryRowData.NeedAllocations) ? categoryRowData.NeedAllocations[week.Milliseconds] || 0 : 0; var needResourcesValue = needHoursValue === 0 ? 0 : hoursResourcesConverter.convertToResources(categoryRowData.Id, needHoursValue); var remainingNeedHoursValue = (categoryRowData && categoryRowData.RemainingNeed) ? Math.max(categoryRowData.RemainingNeed[week.Milliseconds] || 0, 0) : 0; var remainingNeedResourcesValue = remainingNeedHoursValue === 0 ? 0 : hoursResourcesConverter.convertToResources(categoryRowData.Id, remainingNeedHoursValue); var remainingTAHoursValue = (categoryRowData && categoryRowData.RemainingTeamAllocations) ? Math.max(categoryRowData.RemainingTeamAllocations[week.Milliseconds] || 0, 0) : 0; var remainingTAResourcesValue = remainingTAHoursValue === 0 ? 0 : hoursResourcesConverter.convertToResources(categoryRowData.Id, remainingTAHoursValue); var unassignedNeedHoursValue = (categoryRowData && categoryRowData.UnassignedNeed) ? categoryRowData.UnassignedNeed[week.Milliseconds] || 0 : 0; var unassignedNeedResourcesValue = unassignedNeedHoursValue === 0 ? 0 : hoursResourcesConverter.convertToResources(categoryRowData.Id, unassignedNeedHoursValue); if (expenditureCategoryRow) { // week values expenditureCategoryRow.QuantityHoursValues[weekIndex] = hoursValue; expenditureCategoryRow.RemainingCapacityValues[weekIndex] = hoursValue; expenditureCategoryRow.QuantityResourcesValues[weekIndex] = resourcesValue; expenditureCategoryRow.NeedHoursValues[weekIndex] = needHoursValue; expenditureCategoryRow.NeedResourcesValues[weekIndex] = needResourcesValue; expenditureCategoryRow.RemainingNeedHoursValues[weekIndex] = remainingNeedHoursValue; expenditureCategoryRow.RemainingNeedResourcesValues[weekIndex] = remainingNeedResourcesValue; expenditureCategoryRow.RemainingTAHoursValues[weekIndex] = remainingTAHoursValue; expenditureCategoryRow.RemainingTAResourcesValues[weekIndex] = remainingTAResourcesValue; expenditureCategoryRow.UnassignedNeedHoursValues[weekIndex] = unassignedNeedHoursValue; expenditureCategoryRow.UnassignedNeedResourcesValues[weekIndex] = unassignedNeedResourcesValue; // month values expenditureCategoryRow.QuantityHoursValues[month.SelfIndexInWeeks] = (expenditureCategoryRow.QuantityHoursValues[month.SelfIndexInWeeks] || 0) + hoursValue; expenditureCategoryRow.RemainingCapacityValues[month.SelfIndexInWeeks] = (expenditureCategoryRow.RemainingCapacityValues[month.SelfIndexInWeeks] || 0) + hoursValue; expenditureCategoryRow.QuantityResourcesValues[month.SelfIndexInWeeks] = (expenditureCategoryRow.QuantityResourcesValues[month.SelfIndexInWeeks] || 0) + resourcesValue; expenditureCategoryRow.NeedHoursValues[month.SelfIndexInWeeks] = (expenditureCategoryRow.NeedHoursValues[month.SelfIndexInWeeks] || 0) + needHoursValue; expenditureCategoryRow.NeedResourcesValues[month.SelfIndexInWeeks] = (expenditureCategoryRow.NeedResourcesValues[month.SelfIndexInWeeks] || 0) + needResourcesValue; expenditureCategoryRow.RemainingNeedHoursValues[month.SelfIndexInWeeks] = (expenditureCategoryRow.RemainingNeedHoursValues[month.SelfIndexInWeeks] || 0) + remainingNeedHoursValue; expenditureCategoryRow.RemainingNeedResourcesValues[month.SelfIndexInWeeks] = (expenditureCategoryRow.RemainingNeedResourcesValues[month.SelfIndexInWeeks] || 0) + remainingNeedResourcesValue; expenditureCategoryRow.RemainingTAHoursValues[month.SelfIndexInWeeks] = (expenditureCategoryRow.RemainingTAHoursValues[month.SelfIndexInWeeks] || 0) + remainingTAHoursValue; expenditureCategoryRow.RemainingTAResourcesValues[month.SelfIndexInWeeks] = (expenditureCategoryRow.RemainingTAResourcesValues[month.SelfIndexInWeeks] || 0) + remainingTAResourcesValue; expenditureCategoryRow.UnassignedNeedHoursValues[month.SelfIndexInWeeks] = (expenditureCategoryRow.UnassignedNeedHoursValues[month.SelfIndexInWeeks] || 0) + unassignedNeedHoursValue; expenditureCategoryRow.UnassignedNeedResourcesValues[month.SelfIndexInWeeks] = (expenditureCategoryRow.UnassignedNeedResourcesValues[month.SelfIndexInWeeks] || 0) + unassignedNeedResourcesValue; // grand total values expenditureCategoryRow.TotalHoursValue += hoursValue; expenditureCategoryRow.TotalResourcesValue += resourcesValue; expenditureCategoryRow.TotalNeedHoursValue += needHoursValue; expenditureCategoryRow.TotalNeedResourcesValue += needResourcesValue; expenditureCategoryRow.TotalRemainingNeedHoursValue += remainingNeedHoursValue; expenditureCategoryRow.TotalRemainingNeedResourcesValue += remainingNeedResourcesValue; expenditureCategoryRow.TotalRemainingTAHoursValue += remainingTAHoursValue; expenditureCategoryRow.TotalRemainingTAResourcesValue += remainingTAResourcesValue; expenditureCategoryRow.TotalUnassignedNeedHoursValue += unassignedNeedHoursValue; expenditureCategoryRow.TotalUnassignedNeedResourcesValue += unassignedNeedResourcesValue; } if (projectRow) { // week values projectRow.QuantityHoursValues[weekIndex] = (projectRow.QuantityHoursValues[weekIndex] || 0) + hoursValue; projectRow.QuantityResourcesValues[weekIndex] = (projectRow.QuantityResourcesValues[weekIndex] || 0) + resourcesValue; projectRow.NeedHoursValues[weekIndex] = (projectRow.NeedHoursValues[weekIndex] || 0) + needHoursValue; projectRow.NeedResourcesValues[weekIndex] = (projectRow.NeedResourcesValues[weekIndex] || 0) + needResourcesValue; projectRow.RemainingNeedHoursValues[weekIndex] = (projectRow.RemainingNeedHoursValues[weekIndex] || 0) + remainingNeedHoursValue; projectRow.RemainingNeedResourcesValues[weekIndex] = (projectRow.RemainingNeedResourcesValues[weekIndex] || 0) + remainingNeedResourcesValue; projectRow.RemainingTAHoursValues[weekIndex] = (projectRow.RemainingTAHoursValues[weekIndex] || 0) + remainingTAHoursValue; projectRow.RemainingTAResourcesValues[weekIndex] = (projectRow.RemainingTAResourcesValues[weekIndex] || 0) + remainingTAResourcesValue; projectRow.UnassignedNeedHoursValues[weekIndex] = (projectRow.UnassignedNeedHoursValues[weekIndex] || 0) + unassignedNeedHoursValue; projectRow.UnassignedNeedResourcesValues[weekIndex] = (projectRow.UnassignedNeedResourcesValues[weekIndex] || 0) + unassignedNeedResourcesValue; // month values projectRow.QuantityHoursValues[month.SelfIndexInWeeks] = (projectRow.QuantityHoursValues[month.SelfIndexInWeeks] || 0) + hoursValue; projectRow.QuantityResourcesValues[month.SelfIndexInWeeks] = (projectRow.QuantityResourcesValues[month.SelfIndexInWeeks] || 0) + resourcesValue; projectRow.NeedHoursValues[month.SelfIndexInWeeks] = (projectRow.NeedHoursValues[month.SelfIndexInWeeks] || 0) + needHoursValue; projectRow.NeedResourcesValues[month.SelfIndexInWeeks] = (projectRow.NeedResourcesValues[month.SelfIndexInWeeks] || 0) + needResourcesValue; projectRow.RemainingNeedHoursValues[month.SelfIndexInWeeks] = (projectRow.RemainingNeedHoursValues[month.SelfIndexInWeeks] || 0) + remainingNeedHoursValue; projectRow.RemainingNeedResourcesValues[month.SelfIndexInWeeks] = (projectRow.RemainingNeedResourcesValues[month.SelfIndexInWeeks] || 0) + remainingNeedResourcesValue; projectRow.RemainingTAHoursValues[month.SelfIndexInWeeks] = (projectRow.RemainingTAHoursValues[month.SelfIndexInWeeks] || 0) + remainingTAHoursValue; projectRow.RemainingTAResourcesValues[month.SelfIndexInWeeks] = (projectRow.RemainingTAResourcesValues[month.SelfIndexInWeeks] || 0) + remainingTAResourcesValue; projectRow.UnassignedNeedHoursValues[month.SelfIndexInWeeks] = (projectRow.UnassignedNeedHoursValues[month.SelfIndexInWeeks] || 0) + unassignedNeedHoursValue; projectRow.UnassignedNeedResourcesValues[month.SelfIndexInWeeks] = (projectRow.UnassignedNeedResourcesValues[month.SelfIndexInWeeks] || 0) + unassignedNeedResourcesValue; // grand total values projectRow.TotalHoursValue += hoursValue; projectRow.TotalResourcesValue += resourcesValue; projectRow.TotalNeedHoursValue += needHoursValue; projectRow.TotalNeedResourcesValue += needResourcesValue; projectRow.TotalRemainingNeedHoursValue += remainingNeedHoursValue; projectRow.TotalRemainingNeedResourcesValue += remainingNeedResourcesValue; projectRow.TotalRemainingTAHoursValue += remainingTAHoursValue; projectRow.TotalRemainingTAResourcesValue += remainingTAResourcesValue; projectRow.TotalUnassignedNeedHoursValue += unassignedNeedHoursValue; projectRow.TotalUnassignedNeedResourcesValue += unassignedNeedResourcesValue; } if (rollupRow) { rollupRow.QuantityHoursValues[weekIndex] = (rollupRow.QuantityHoursValues[weekIndex] || 0) + hoursValue; rollupRow.QuantityResourcesValues[weekIndex] = (rollupRow.QuantityResourcesValues[weekIndex] || 0) + resourcesValue; rollupRow.QuantityHoursValues[month.SelfIndexInWeeks] = (rollupRow.QuantityHoursValues[month.SelfIndexInWeeks] || 0) + hoursValue; rollupRow.QuantityResourcesValues[month.SelfIndexInWeeks] = (rollupRow.QuantityResourcesValues[month.SelfIndexInWeeks] || 0) + resourcesValue; rollupRow.TotalHoursValue += hoursValue; rollupRow.TotalResourcesValue += resourcesValue; // for project and company we have these collections, but for team we do not if (rollupRow.NeedHoursValues) { rollupRow.NeedHoursValues[weekIndex] = (rollupRow.NeedHoursValues[weekIndex] || 0) + needHoursValue; rollupRow.NeedHoursValues[month.SelfIndexInWeeks] = (rollupRow.NeedHoursValues[month.SelfIndexInWeeks] || 0) + needHoursValue; rollupRow.TotalNeedHoursValue += needHoursValue; } if (rollupRow.NeedResourcesValues) { rollupRow.NeedResourcesValues[weekIndex] = (rollupRow.NeedResourcesValues[weekIndex] || 0) + needResourcesValue; rollupRow.NeedResourcesValues[month.SelfIndexInWeeks] = (rollupRow.NeedResourcesValues[month.SelfIndexInWeeks] || 0) + needResourcesValue; rollupRow.TotalNeedResourcesValue += needResourcesValue; } } } } if (expenditureCategoryRow) { expenditureCategoryRow.VisibleCellsCount = projectWeeksCount; } if (projectRow) { projectRow.VisibleCellsCount = projectWeeksCount; projectRow.VisibleCellWeekendings = projectWeeks; } }, // To-Do: ENV-2254. review ability to join fillECTeamRowWithData and fillExpenditureCategoryRowWithData as they fill almost the same data // 1) improve rollup rows handling // 2) move resource filling out of method // 3) keep collections filling and rounding from fillECTeamRowWithData method fillECTeamRowWithData: function (header, row, expCatRow, rowData, projectId, startDate, endDate, projectReadOnly) { if (!header) { return; } if (!row) throw 'row argument of fillECTeamRowWithData function is required'; if (!row.ExpenditureCategoryId) throw 'ExpenditureCategoryId is not set for the EC\'s team row'; if (!row.Id) throw 'TeamRow.Id is not set for the EC\'s team row'; // If we must set weeks in the past read-only, we get the first week, from which the values start to be editable var rowIsEditable = false; var projectWeeksCount = 0; var ecId = row.ExpenditureCategoryId; var teamId = row.Id; for (var monthIndex = 0; monthIndex < header.Months.length; monthIndex++) { var month = header.Months[monthIndex]; var monthEditable = true; for (var mcIndex = 0; mcIndex < month.Childs.length; mcIndex++) { var weekIndex = month.Childs[mcIndex]; var week = header.Weeks[weekIndex]; var weekEditable = true; if ((!!startDate && week.Milliseconds < startDate) || (!!endDate && week.Milliseconds > endDate)) { // if scenario does not cover each week of the month we shouldn't give ability to edit this month monthEditable = false; continue; } projectWeeksCount += 1; var hoursValue = (rowData && rowData.Allocations) ? rowData.Allocations[week.Milliseconds] || 0 : 0; var resourcesValue = hoursValue === 0 ? 0 : hoursResourcesConverter.convertToResources(ecId, hoursValue); var needHoursValue = (rowData && rowData.NeedAllocations) ? rowData.NeedAllocations[week.Milliseconds] || 0 : 0; var needResourcesValue = needHoursValue === 0 ? 0 : hoursResourcesConverter.convertToResources(ecId, needHoursValue); var remainingNeedHoursValue = (rowData && rowData.RemainingNeed) ? Math.max(rowData.RemainingNeed[week.Milliseconds] || 0, 0) : 0; var remainingNeedResourcesValue = remainingNeedHoursValue === 0 ? 0 : hoursResourcesConverter.convertToResources(ecId, remainingNeedHoursValue); var remainingTAHoursValue = (rowData && rowData.RemainingTeamAllocations) ? Math.max(rowData.RemainingTeamAllocations[week.Milliseconds] || 0, 0) : 0; var remainingTAResourcesValue = remainingTAHoursValue === 0 ? 0 : hoursResourcesConverter.convertToResources(ecId, remainingTAHoursValue); if (row) { // week values row.QuantityHoursValues[weekIndex] = roundService.roundQuantity(hoursValue); row.QuantityResourcesValues[weekIndex] = roundService.roundQuantity(resourcesValue); row.RemainingCapacityValues[weekIndex] = roundService.roundQuantity(hoursValue); // To-Do: remove me, take remaining/all should deal with DAL method(s) row.NeedHoursValues[weekIndex] = roundService.roundQuantity(needHoursValue); row.NeedResourcesValues[weekIndex] = roundService.roundQuantity(needResourcesValue); row.RemainingNeedHoursValues[weekIndex] = roundService.roundQuantity(remainingNeedHoursValue); row.RemainingNeedResourcesValues[weekIndex] = roundService.roundQuantity(remainingNeedResourcesValue); row.RemainingTAHoursValues[weekIndex] = roundService.roundQuantity(remainingTAHoursValue); row.RemainingTAResourcesValues[weekIndex] = roundService.roundQuantity(remainingTAResourcesValue); // month values row.QuantityHoursValues[month.SelfIndexInWeeks] = (row.QuantityHoursValues[month.SelfIndexInWeeks] || 0) + hoursValue; row.RemainingCapacityValues[month.SelfIndexInWeeks] = (row.RemainingCapacityValues[month.SelfIndexInWeeks] || 0) + hoursValue; row.NeedHoursValues[month.SelfIndexInWeeks] = (row.NeedHoursValues[month.SelfIndexInWeeks] || 0) + needHoursValue; row.RemainingNeedHoursValues[month.SelfIndexInWeeks] = (row.RemainingNeedHoursValues[month.SelfIndexInWeeks] || 0) + remainingNeedHoursValue; row.RemainingTAHoursValues[month.SelfIndexInWeeks] = (row.RemainingTAHoursValues[month.SelfIndexInWeeks] || 0) + remainingTAHoursValue; // grand total values row.TotalHoursValue += hoursValue; row.TotalNeedHoursValue += needHoursValue; row.TotalRemainingNeedHoursValue += remainingNeedHoursValue; row.TotalRemainingTAHoursValue += remainingTAHoursValue; } // if user has only read permissions on project we should set all cells to non-editable state if (projectReadOnly === true) { weekEditable = false; } // if team is not editable for any week of the month we shouldn't give ability to edit this month monthEditable = monthEditable && weekEditable; // if team is editable for any week of the scenario we should give ability to edit grand total cell for this row rowIsEditable = rowIsEditable || weekEditable; row.EditableForecastWeeks[weekIndex] = weekEditable; // To-Do: ENV-2249. implement set week styles method for teams or reuse any of existing methods //this.updateResourceWeekStyles(teamId, row, week.Milliseconds, weekIndex); } // round calculated month values to avoid rounding issues if (!isNaN(row.QuantityHoursValues[month.SelfIndexInWeeks])) { row.QuantityHoursValues[month.SelfIndexInWeeks] = roundService.roundQuantity(row.QuantityHoursValues[month.SelfIndexInWeeks] || 0); row.QuantityResourcesValues[month.SelfIndexInWeeks] = roundService.roundQuantity(hoursResourcesConverter.convertToResources(ecId, row.QuantityHoursValues[month.SelfIndexInWeeks] || 0)); } if (!isNaN(row.RemainingCapacityValues[month.SelfIndexInWeeks])) row.RemainingCapacityValues[month.SelfIndexInWeeks] = roundService.roundQuantity(row.RemainingCapacityValues[month.SelfIndexInWeeks] || 0); if (!isNaN(row.NeedHoursValues[month.SelfIndexInWeeks])) { row.NeedHoursValues[month.SelfIndexInWeeks] = roundService.roundQuantity(row.NeedHoursValues[month.SelfIndexInWeeks] || 0); row.NeedResourcesValues[month.SelfIndexInWeeks] = roundService.roundQuantity(hoursResourcesConverter.convertToResources(ecId, row.NeedHoursValues[month.SelfIndexInWeeks] || 0)); } if (!isNaN(row.RemainingNeedHoursValues[month.SelfIndexInWeeks])) { row.RemainingNeedHoursValues[month.SelfIndexInWeeks] = roundService.roundQuantity(row.RemainingNeedHoursValues[month.SelfIndexInWeeks] || 0); row.RemainingNeedResourcesValues[month.SelfIndexInWeeks] = roundService.roundQuantity(hoursResourcesConverter.convertToResources(ecId, row.RemainingNeedHoursValues[month.SelfIndexInWeeks] || 0)); } if (!isNaN(row.RemainingTAHoursValues[month.SelfIndexInWeeks])) { row.RemainingTAHoursValues[month.SelfIndexInWeeks] = roundService.roundQuantity(row.RemainingTAHoursValues[month.SelfIndexInWeeks] || 0); row.RemainingTAResourcesValues[month.SelfIndexInWeeks] = roundService.roundQuantity(hoursResourcesConverter.convertToResources(ecId, row.RemainingTAHoursValues[month.SelfIndexInWeeks] || 0)); } row.EditableForecastWeeks[month.SelfIndexInWeeks] = monthEditable; this.updateMonthStyles(row, month.SelfIndexInWeeks, month.Childs); } // round calculated grand total values to avoid rounding issues row.TotalHoursValue = roundService.roundQuantity(row.TotalHoursValue); row.TotalResourcesValue = roundService.roundQuantity(hoursResourcesConverter.convertToResources(ecId, row.TotalHoursValue)); row.TotalNeedHoursValue = roundService.roundQuantity(row.TotalNeedHoursValue); row.TotalNeedResourcesValue = roundService.roundQuantity(hoursResourcesConverter.convertToResources(ecId, row.TotalNeedHoursValue)); row.TotalRemainingNeedHoursValue = roundService.roundQuantity(row.TotalRemainingNeedHoursValue); row.TotalRemainingNeedResourcesValue = roundService.roundQuantity(hoursResourcesConverter.convertToResources(ecId, row.TotalRemainingNeedHoursValue)); row.TotalRemainingTAHoursValue = roundService.roundQuantity(row.TotalRemainingTAHoursValue); row.TotalRemainingTAResourcesValue = roundService.roundQuantity(hoursResourcesConverter.convertToResources(ecId, row.TotalRemainingTAHoursValue)); row.VisibleCellsCount = projectWeeksCount; row.CanBeRemoved = !projectReadOnly; row.IsEditable = rowIsEditable; }, fillResourceRowWithData: function (header, parentRow, expCatId, resourceId, resourceRow, resourceRowData, rollupToRows, startDate, endDate, readonlyToCurrentDate, disableResourceEdit) { if (!header || !resourceId || !resourceRow) { return; } // If we must set weeks in the past read-only, we get the first week, from which the values start to be editable var firstEditableWeek = readonlyToCurrentDate ? header.getNextWeekByDate(this.getUtcNow()) : null; var rowIsEditable = false; var performRollup = rollupToRows && rollupToRows.length; resourceRow.Teams = resourceRowData ? resourceRowData.Teams || [] : []; for (var monthIndex = 0; monthIndex < header.Months.length; monthIndex++) { var month = header.Months[monthIndex]; var monthEditable = true; for (var mcIndex = 0; mcIndex < month.Childs.length; mcIndex++) { var weekIndex = month.Childs[mcIndex]; var week = header.Weeks[weekIndex]; var weekEditable = true; if ((!!startDate && week.Milliseconds < startDate) || (!!endDate && week.Milliseconds > endDate)) { // if scenario does not cover each week of the month we shouldn't give ability to edit this month monthEditable = false; continue; } var resHoursValue = (resourceRowData && resourceRowData.Allocations) ? resourceRowData.Allocations[week.Milliseconds] || 0 : 0; var resResourcesValue = resHoursValue === 0 ? 0 : hoursResourcesConverter.convertToResources(expCatId, resHoursValue); resourceRow.VisibleCellsCount += 1; resourceRow.VisibleCellWeekendings[week.Milliseconds] = 0; resourceRow.QuantityHoursValues[weekIndex] = roundService.roundQuantity(resHoursValue); resourceRow.QuantityResourcesValues[weekIndex] = roundService.roundQuantity(resResourcesValue); resourceRow.QuantityHoursValues[month.SelfIndexInWeeks] = (resourceRow.QuantityHoursValues[month.SelfIndexInWeeks] || 0) + resHoursValue; resourceRow.QuantityResourcesValues[month.SelfIndexInWeeks] = (resourceRow.QuantityResourcesValues[month.SelfIndexInWeeks] || 0) + resResourcesValue; resourceRow.TotalHoursValue += resHoursValue; resourceRow.TotalResourcesValue += resResourcesValue; // we need to roll up current resource values upto some total rows if (performRollup) { for (var rollupRowIndex = 0; rollupRowIndex < rollupToRows.length; rollupRowIndex++) { var rollupRow = rollupToRows[rollupRowIndex]; if (rollupRow) { rollupRow.QuantityHoursValues[weekIndex] = (rollupRow.QuantityHoursValues[weekIndex] || 0) + resHoursValue; rollupRow.QuantityResourcesValues[weekIndex] = (rollupRow.QuantityResourcesValues[weekIndex] || 0) + resResourcesValue; rollupRow.QuantityHoursValues[month.SelfIndexInWeeks] = (rollupRow.QuantityHoursValues[month.SelfIndexInWeeks] || 0) + resHoursValue; rollupRow.QuantityResourcesValues[month.SelfIndexInWeeks] = (rollupRow.QuantityResourcesValues[month.SelfIndexInWeeks] || 0) + resResourcesValue; rollupRow.TotalHoursValue += resHoursValue; rollupRow.TotalResourcesValue += resResourcesValue; } } } // to-do: remove me, validation should use activityCalendarService to get data for validation if (parentRow && parentRow.RemainingCapacityValues) { parentRow.RemainingCapacityValues[weekIndex] = roundService.roundQuantity((parentRow.RemainingCapacityValues[weekIndex] || 0) - resHoursValue); parentRow.RemainingCapacityValues[month.SelfIndexInWeeks] = roundService.roundQuantity((parentRow.RemainingCapacityValues[month.SelfIndexInWeeks] || 0) - resHoursValue); } // Check if current resource is a member of any team in the list var availableTeam = this.resolveResourceAvailableTeam(week.Milliseconds, resourceRow.Teams); if (!availableTeam || availableTeam.ReadOnly) { // Resource doesn't have available team at this week or team is read only. Cell should be read-only weekEditable = false; } if (weekEditable && readonlyToCurrentDate && firstEditableWeek) { // Set cells read-only for cells in the past weekEditable = (header.compareWeeks(week, firstEditableWeek) > 0); } // if user has only read permissions on project we should set all cells to non-editable state if (disableResourceEdit === true) { weekEditable = false; } // if resource is not available for any week of the month we shouldn't give ability to edit this month monthEditable = monthEditable && weekEditable; // if resource is available for any week of the scenario we should give ability to edit grand total cell for this row rowIsEditable = rowIsEditable || weekEditable; resourceRow.EditableForecastWeeks[weekIndex] = weekEditable; this.updateResourceWeekStyles(resourceId, resourceRow, week.Milliseconds, weekIndex); } resourceRow.EditableForecastWeeks[month.SelfIndexInWeeks] = monthEditable; if (resourceRow.QuantityHoursValues[month.SelfIndexInWeeks]) { resourceRow.QuantityHoursValues[month.SelfIndexInWeeks] = roundService.roundQuantity(resourceRow.QuantityHoursValues[month.SelfIndexInWeeks]); } if (resourceRow.QuantityResourcesValues[month.SelfIndexInWeeks]) { resourceRow.QuantityResourcesValues[month.SelfIndexInWeeks] = roundService.roundQuantity(resourceRow.QuantityResourcesValues[month.SelfIndexInWeeks]); } this.updateMonthStyles(resourceRow, month.SelfIndexInWeeks, month.Childs); } resourceRow.CanBeRemoved = !disableResourceEdit && checkAllTeamsEditable(resourceRow.Teams); resourceRow.IsEditable = rowIsEditable; resourceRow.TotalHoursValue = roundService.roundQuantity(resourceRow.TotalHoursValue); resourceRow.TotalResourcesValue = roundService.roundQuantity(resourceRow.TotalResourcesValue); }, fillResourceActualsRowWithData: function (header, actualsRow, actualsData, startDate, endDate, disableResourceEdit) { if (!header || !actualsRow || !actualsRow.ExpenditureCategory || !actualsRow.ExpenditureCategory.Id) { return; } // If we must set weeks in the past read-only, we get the first week, from which the values start to be editable var firstReadOnlyWeek = header.getNextWeekByDate(this.getUtcNow()); var rowIsEditable = false; actualsRow.Teams = actualsData ? actualsData.Teams || [] : []; for (var monthIndex = 0; monthIndex < header.Months.length; monthIndex++) { var month = header.Months[monthIndex]; var monthEditable = true; for (var mcIndex = 0; mcIndex < month.Childs.length; mcIndex++) { var weekIndex = month.Childs[mcIndex]; var week = header.Weeks[weekIndex]; var weekEditable = true; if ((!!startDate && week.Milliseconds < startDate) || (!!endDate && week.Milliseconds > endDate)) { // if scenario does not cover each week of the month we shouldn't give ability to edit this month monthEditable = false; continue; } var resHoursValue = (actualsData && actualsData.Actuals) ? actualsData.Actuals[week.Milliseconds] || 0 : 0; var resResourcesValue = resHoursValue === 0 ? 0 : hoursResourcesConverter.convertToResources(actualsRow.ExpenditureCategory.Id, resHoursValue); actualsRow.VisibleCellsCount += 1; actualsRow.QuantityHoursValues[weekIndex] = roundService.roundQuantity(resHoursValue); actualsRow.QuantityResourcesValues[weekIndex] = roundService.roundQuantity(resResourcesValue); actualsRow.QuantityHoursValues[month.SelfIndexInWeeks] = (actualsRow.QuantityHoursValues[month.SelfIndexInWeeks] || 0) + resHoursValue; actualsRow.QuantityResourcesValues[month.SelfIndexInWeeks] = (actualsRow.QuantityResourcesValues[month.SelfIndexInWeeks] || 0) + resResourcesValue; actualsRow.TotalHoursValue += resHoursValue; actualsRow.TotalResourcesValue += resResourcesValue; // check if current week is editable var availableTeam = this.resolveResourceAvailableTeam(week.Milliseconds, actualsRow.Teams); if (!availableTeam || availableTeam.ReadOnly) { // Resource doesn't has available team at this week. Cell should be read-only weekEditable = false; } if (weekEditable && firstReadOnlyWeek) { // Set cells read-only for cells in the past weekEditable = (header.compareWeeks(week, firstReadOnlyWeek) <= 0); } if (disableResourceEdit === true) { weekEditable = false; } // if resource is not available for any week of the month we shouldn't give ability to edit this month monthEditable = monthEditable && weekEditable; // if resource is available for any week of the scenario we should give ability to edit grand total cell for this row rowIsEditable = rowIsEditable || weekEditable; actualsRow.EditableForecastWeeks[weekIndex] = weekEditable; } actualsRow.EditableForecastWeeks[month.SelfIndexInWeeks] = monthEditable; if (actualsRow.QuantityHoursValues[month.SelfIndexInWeeks]) { actualsRow.QuantityHoursValues[month.SelfIndexInWeeks] = roundService.roundQuantity(actualsRow.QuantityHoursValues[month.SelfIndexInWeeks]); } if (actualsRow.QuantityResourcesValues[month.SelfIndexInWeeks]) { actualsRow.QuantityResourcesValues[month.SelfIndexInWeeks] = roundService.roundQuantity(actualsRow.QuantityResourcesValues[month.SelfIndexInWeeks]); } } actualsRow.IsEditable = rowIsEditable; actualsRow.TotalHoursValue = roundService.roundQuantity(actualsRow.TotalHoursValue); actualsRow.TotalResourcesValue = roundService.roundQuantity(actualsRow.TotalResourcesValue); }, fillTotalRowData: function (projectRows, totalRow, header, allocationMode/*to-do: remove allocationMode argument*/) { if (!totalRow || !projectRows) { return; } if (!header || !header.Months || !header.Weeks) { return; } totalRow.TotalHoursValue = 0; totalRow.TotalResourcesValue = 0; totalRow.VisibleCellsCount = header.TotalWeeks; for (var monthIndex = 0; monthIndex < header.Months.length; monthIndex++) { var month = header.Months[monthIndex]; totalRow.QuantityHoursValues[month.SelfIndexInWeeks] = 0; totalRow.QuantityResourcesValues[month.SelfIndexInWeeks] = 0; for (var mcIndex = 0; mcIndex < month.Childs.length; mcIndex++) { var weekIndex = month.Childs[mcIndex]; var week = header.Weeks[weekIndex]; totalRow.QuantityHoursValues[weekIndex] = 0; totalRow.QuantityResourcesValues[weekIndex] = 0; for (var childIndex = 0; childIndex < projectRows.length; childIndex++) { var projectRow = projectRows[childIndex]; var collectionNames = this.getCollectionNames4AllocationMode(projectRow.AllocationMode || allocationMode); var hoursValue = (collectionNames.hoursCollectionName in projectRow) ? (projectRow[collectionNames.hoursCollectionName][weekIndex] || 0) : (projectRow[this.allocationMode.defaultHoursCollectionName][weekIndex] || 0); var resourcesValue = (collectionNames.resourcesCollectionName in projectRow) ? (projectRow[collectionNames.resourcesCollectionName][weekIndex] || 0) : (projectRow[this.allocationMode.defaultResourcesCollectionName][weekIndex] || 0); totalRow.QuantityHoursValues[weekIndex] = (totalRow.QuantityHoursValues[weekIndex] || 0) + hoursValue; totalRow.QuantityResourcesValues[weekIndex] = (totalRow.QuantityResourcesValues[weekIndex] || 0) + resourcesValue; totalRow.QuantityHoursValues[month.SelfIndexInWeeks] = (totalRow.QuantityHoursValues[month.SelfIndexInWeeks] || 0) + hoursValue; totalRow.QuantityResourcesValues[month.SelfIndexInWeeks] = (totalRow.QuantityResourcesValues[month.SelfIndexInWeeks] || 0) + resourcesValue; totalRow.TotalHoursValue += hoursValue; totalRow.TotalResourcesValue += resourcesValue; } totalRow.QuantityHoursValues[weekIndex] = roundService.roundQuantity(totalRow.QuantityHoursValues[weekIndex]); totalRow.QuantityResourcesValues[weekIndex] = roundService.roundQuantity(totalRow.QuantityResourcesValues[weekIndex]); } totalRow.QuantityHoursValues[month.SelfIndexInWeeks] = roundService.roundQuantity(totalRow.QuantityHoursValues[month.SelfIndexInWeeks]); totalRow.QuantityResourcesValues[month.SelfIndexInWeeks] = roundService.roundQuantity(totalRow.QuantityResourcesValues[month.SelfIndexInWeeks]); } totalRow.TotalHoursValue = roundService.roundQuantity(totalRow.TotalHoursValue); totalRow.TotalResourcesValue = roundService.roundQuantity(totalRow.TotalResourcesValue); }, fillRemainingCapacityRowData: function (totalRow, capacityRow, header, isCapacityTypeActuals) { if (!totalRow || !capacityRow) { return; } if (!header || !header.Months || !header.Weeks) { return; } capacityRow.TotalHoursValue = 0; capacityRow.TotalResourcesValue = 0; capacityRow.VisibleCellsCount = header.TotalWeeks; for (var monthIndex = 0; monthIndex < header.Months.length; monthIndex++) { var month = header.Months[monthIndex]; capacityRow.QuantityHoursValues[month.SelfIndexInWeeks] = 0; capacityRow.QuantityResourcesValues[month.SelfIndexInWeeks] = 0; for (var mcIndex = 0; mcIndex < month.Childs.length; mcIndex++) { var weekIndex = month.Childs[mcIndex]; var week = header.Weeks[weekIndex]; capacityRow.QuantityHoursValues[weekIndex] = 0; capacityRow.QuantityResourcesValues[weekIndex] = 0; var hoursTotalValue = totalRow.QuantityHoursValues[weekIndex] || 0; var resourcesTotalValue = totalRow.QuantityResourcesValues[weekIndex] || 0; var hoursCapacityValue = 0; var resourcesCapacityValue = 0; angular.forEach(teamInfoService.getAll(), function (team, teamKey) { for (var catKey in team.ExpCategories) { var teamExpCat = team.ExpCategories[catKey]; var ecCapHours = 0; var ecCapResources = 0; if (teamExpCat.AllowResourceAssignment) { // Calculations for ordinary EC var ecCapHours = isCapacityTypeActuals ? teamExpCat.ActualCapacityValues[week.Milliseconds] || 0 : teamExpCat.PlannedCapacityValues[week.Milliseconds] || 0; if (!angular.isNumber(ecCapHours) || isNaN(ecCapHours)) { ecCapHours = 0; } ecCapResources = hoursResourcesConverter.convertToResources(catKey, ecCapHours); } else { // Super EC does not increase team capacity } hoursCapacityValue += ecCapHours; resourcesCapacityValue += ecCapResources; } }); var hoursToAdd = hoursCapacityValue - hoursTotalValue; var resourcesToAdd = resourcesCapacityValue - resourcesTotalValue; capacityRow.QuantityHoursValues[weekIndex] = roundService.roundQuantity(capacityRow.QuantityHoursValues[weekIndex] + hoursToAdd); capacityRow.QuantityResourcesValues[weekIndex] = roundService.roundQuantity(capacityRow.QuantityResourcesValues[weekIndex] + resourcesToAdd); capacityRow.QuantityHoursValues[month.SelfIndexInWeeks] = capacityRow.QuantityHoursValues[month.SelfIndexInWeeks] + hoursToAdd; capacityRow.QuantityResourcesValues[month.SelfIndexInWeeks] = capacityRow.QuantityResourcesValues[month.SelfIndexInWeeks] + resourcesToAdd; capacityRow.TotalHoursValue += hoursToAdd; capacityRow.TotalResourcesValue += resourcesToAdd; } capacityRow.QuantityHoursValues[month.SelfIndexInWeeks] = roundService.roundQuantity(capacityRow.QuantityHoursValues[month.SelfIndexInWeeks]); capacityRow.QuantityResourcesValues[month.SelfIndexInWeeks] = roundService.roundQuantity(capacityRow.QuantityResourcesValues[month.SelfIndexInWeeks]); } capacityRow.TotalHoursValue = roundService.roundQuantity(capacityRow.TotalHoursValue); capacityRow.TotalResourcesValue = roundService.roundQuantity(capacityRow.TotalResourcesValue); }, fillResourceRemainingCapacityRowData: function (totalRow, capacityRow, resourceId, header) { if (!totalRow || !capacityRow || !resourceId) { return; } if (!header || !header.Months || !header.Weeks) { return; } var teams = teamInfoService.getAll(); var teamKeys = Object.keys(teams); capacityRow.TotalHoursValue = 0; capacityRow.TotalResourcesValue = 0; capacityRow.VisibleCellsCount = header.TotalWeeks; for (var monthIndex = 0; monthIndex < header.Months.length; monthIndex++) { var month = header.Months[monthIndex]; capacityRow.QuantityHoursValues[month.SelfIndexInWeeks] = 0; capacityRow.QuantityResourcesValues[month.SelfIndexInWeeks] = 0; for (var mcIndex = 0; mcIndex < month.Childs.length; mcIndex++) { var weekIndex = month.Childs[mcIndex]; var week = header.Weeks[weekIndex]; capacityRow.QuantityHoursValues[weekIndex] = 0; capacityRow.QuantityResourcesValues[weekIndex] = 0; var hoursTotalValue = totalRow.QuantityHoursValues[weekIndex] || 0; var resourcesTotalValue = totalRow.QuantityResourcesValues[weekIndex] || 0; var hoursCapacityValue = 0; var resourcesCapacityValue = 0; for (var tIndex = 0; tIndex < teamKeys.length; tIndex++) { var team = teams[teamKeys[tIndex]]; for (var catKey in team.ExpCategories) { var teamExpCat = team.ExpCategories[catKey]; if (teamExpCat.Resources && (resourceId in teamExpCat.Resources)) { var resource = teamExpCat.Resources[resourceId]; if (resource && teamExpCat.AllowResourceAssignment) { var resCapHours = 0; var resCapResources = 0; // Calculations for ordinary EC if (resource.TotalCapacity && resource.TotalCapacity[week.Milliseconds]) { var resCapHours = resource.TotalCapacity[week.Milliseconds]; if (!angular.isNumber(resCapHours) || isNaN(resCapResources)) { resCapHours = 0; } resCapResources = hoursResourcesConverter.convertToResources(catKey, resCapHours); } hoursCapacityValue += resCapHours; resourcesCapacityValue += resCapResources; } } } }; var hoursToAdd = hoursCapacityValue - hoursTotalValue; var resourcesToAdd = resourcesCapacityValue - resourcesTotalValue; capacityRow.QuantityHoursValues[weekIndex] = roundService.roundQuantity(capacityRow.QuantityHoursValues[weekIndex] + hoursToAdd); capacityRow.QuantityResourcesValues[weekIndex] = roundService.roundQuantity(capacityRow.QuantityResourcesValues[weekIndex] + resourcesToAdd); capacityRow.QuantityHoursValues[month.SelfIndexInWeeks] = capacityRow.QuantityHoursValues[month.SelfIndexInWeeks] + hoursToAdd; capacityRow.QuantityResourcesValues[month.SelfIndexInWeeks] = capacityRow.QuantityResourcesValues[month.SelfIndexInWeeks] + resourcesToAdd; capacityRow.TotalHoursValue += hoursToAdd; capacityRow.TotalResourcesValue += resourcesToAdd; } capacityRow.QuantityHoursValues[month.SelfIndexInWeeks] = roundService.roundQuantity(capacityRow.QuantityHoursValues[month.SelfIndexInWeeks]); capacityRow.QuantityResourcesValues[month.SelfIndexInWeeks] = roundService.roundQuantity(capacityRow.QuantityResourcesValues[month.SelfIndexInWeeks]); } capacityRow.TotalHoursValue = roundService.roundQuantity(capacityRow.TotalHoursValue); capacityRow.TotalResourcesValue = roundService.roundQuantity(capacityRow.TotalResourcesValue); }, fillTotalNptRowData: function (totalNptRow, nptDataModel, header, rollupToRows) { if (!totalNptRow) { return; } if (!header || !header.Months || !header.Weeks) { return; } totalNptRow.TotalHoursValue = 0; totalNptRow.TotalResourcesValue = 0; totalNptRow.VisibleCellsCount = header.TotalWeeks; if (totalNptRow.Children && nptDataModel) { // Fill NPT total row data during filling of NPT categories rows for (var index = 0; index < totalNptRow.Children.length; index++) { var nptCatRow = totalNptRow.Children[index]; var nptCatData = nptDataModel[nptCatRow.Id]; this.fillNptCategoryRowData(nptCatRow, totalNptRow, nptCatData, header, rollupToRows); }; totalNptRow.TotalHoursValue = roundService.roundQuantity(totalNptRow.TotalHoursValue); totalNptRow.TotalResourcesValue = roundService.roundQuantity(totalNptRow.TotalResourcesValue); } else { // Fill NPT total row with zero values, as NPT data is empty for (var monthIndex = 0; monthIndex < header.Months.length; monthIndex++) { var month = header.Months[monthIndex]; totalNptRow.QuantityHoursValues[month.SelfIndexInWeeks] = 0; totalNptRow.QuantityResourcesValues[month.SelfIndexInWeeks] = 0; for (var mcIndex = 0; mcIndex < month.Childs.length; mcIndex++) { var weekIndex = month.Childs[mcIndex]; var week = header.Weeks[weekIndex]; totalNptRow.QuantityHoursValues[weekIndex] = 0; totalNptRow.QuantityResourcesValues[weekIndex] = 0; } } } }, fillNptCategoryRowData: function (nptCatRow, totalNptRow, nptCatData, header, rollupToRows) { if (!nptCatRow || !totalNptRow) { return; } if (!header || !header.Months || !header.Weeks) { return; } nptCatRow.VisibleCellsCount = header.TotalWeeks; var readonlyToDate = this.getUtcNow(); if (nptCatRow.Children && nptCatData) { for (var index = 0; index < nptCatRow.Children.length; index++) { var nptItemRow = nptCatRow.Children[index]; var nptItemData = nptCatData.NonProjectTime[nptItemRow.Id]; this.fillNptItemRowData(nptItemRow, nptCatRow, totalNptRow, nptItemData, readonlyToDate, header, rollupToRows); }; } }, fillNptItemRowData: function (nptItemRow, nptCatRow, totalNptRow, nptItemData, readonlyToDate, header, rollupToRows) { if (!nptItemRow || !nptCatRow || !totalNptRow) { return; } if (!header || !header.Months || !header.Weeks) { return; } nptItemRow.VisibleCellsCount = header.TotalWeeks; if (nptItemData && nptItemData.Resources) { if (!nptItemData.isTeamWide) { // Fill rows with data for resource-level NPT if (nptItemRow.Children && nptItemRow.Children.length) { for (var index = 0; index < nptItemRow.Children.length; index++) { var nptResourceRow = nptItemRow.Children[index]; var nptResourceData = nptItemData.Resources[nptResourceRow.Id]; this.fillNptResourceRowData(nptResourceRow, nptItemRow, nptCatRow, totalNptRow, nptItemData, nptResourceData, readonlyToDate, header, rollupToRows); }; } } else { // Fill with data for team-level NPT for (var resourceId in nptItemData.Resources) { var nptResourceData = nptItemData.Resources[resourceId]; this.fillNptResourceRowData(null, nptItemRow, nptCatRow, totalNptRow, nptItemData, nptResourceData, readonlyToDate, header, rollupToRows); }; } } }, fillNptResourceRowData: function (nptResourceRow, nptItemRow, nptCatRow, totalNptRow, nptItemData, nptResourceData, readonlyToDate, header, rollupToRows) { if (!header) { return; } if (!nptItemRow || !nptCatRow || !totalNptRow || !nptItemData || !nptResourceData) { return; } var performRollup = rollupToRows && (rollupToRows.length > 0); if (nptResourceRow) { nptResourceRow.VisibleCellsCount = header.TotalWeeks; } for (var monthIndex = 0; monthIndex < header.Months.length; monthIndex++) { var month = header.Months[monthIndex]; if (nptResourceRow) { nptResourceRow.EditableNptWeeks[month.SelfIndexInWeeks] = true; } if (nptItemRow && nptItemRow.IsTeamWide) { nptItemRow.EditableNptWeeks[month.SelfIndexInWeeks] = true; } for (var mcIndex = 0; mcIndex < month.Childs.length; mcIndex++) { var weekIndex = month.Childs[mcIndex]; var week = header.Weeks[weekIndex]; var resourceExpCatId = nptResourceData.ExpenditureCategoryId; var hoursValue = (nptResourceData && nptResourceData.Allocations && nptResourceData.Allocations[week.Milliseconds]) ? nptResourceData.Allocations[week.Milliseconds] || 0 : 0; var resourcesValue = (resourceExpCatId && hoursValue != 0) ? hoursResourcesConverter.convertToResources(resourceExpCatId, hoursValue) : 0; var isWeekEditable = (hoursValue != 0) && (week.Milliseconds > readonlyToDate); if (nptResourceRow) { nptResourceRow.QuantityHoursValues[weekIndex] = hoursValue; nptResourceRow.QuantityResourcesValues[weekIndex] = resourcesValue; nptResourceRow.QuantityHoursValues[month.SelfIndexInWeeks] = (nptResourceRow.QuantityHoursValues[month.SelfIndexInWeeks] || 0) + hoursValue; nptResourceRow.QuantityResourcesValues[month.SelfIndexInWeeks] = (nptResourceRow.QuantityResourcesValues[month.SelfIndexInWeeks] || 0) + resourcesValue; nptResourceRow.TotalHoursValue += hoursValue; nptResourceRow.TotalResourcesValue += resourcesValue; // Set week / month cells to be editable or read-only nptResourceRow.EditableNptWeeks[weekIndex] = isWeekEditable && !nptResourceData.isReadOnly; nptResourceRow.EditableNptWeeks[month.SelfIndexInWeeks] = nptResourceRow.EditableNptWeeks[month.SelfIndexInWeeks] && nptResourceRow.EditableNptWeeks[weekIndex]; } if (nptItemRow) { nptItemRow.QuantityHoursValues[weekIndex] = (nptItemRow.QuantityHoursValues[weekIndex] || 0) + hoursValue; nptItemRow.QuantityResourcesValues[weekIndex] = (nptItemRow.QuantityResourcesValues[weekIndex] || 0) + resourcesValue; nptItemRow.QuantityHoursValues[month.SelfIndexInWeeks] = (nptItemRow.QuantityHoursValues[month.SelfIndexInWeeks] || 0) + hoursValue; nptItemRow.QuantityResourcesValues[month.SelfIndexInWeeks] = (nptItemRow.QuantityResourcesValues[month.SelfIndexInWeeks] || 0) + resourcesValue; nptItemRow.TotalHoursValue += hoursValue; nptItemRow.TotalResourcesValue += resourcesValue; // Set week / month cells to be editable or read-only // nptItemRow.EditableNptWeeks[weekIndex] = isWeekEditable && nptItemRow.IsTeamWide && !nptItemData.isReadOnly; nptItemRow.EditableNptWeeks[weekIndex] = isWeekEditable && !nptItemData.isReadOnly; nptItemRow.EditableNptWeeks[month.SelfIndexInWeeks] = nptItemRow.EditableNptWeeks[month.SelfIndexInWeeks] && nptItemRow.EditableNptWeeks[weekIndex]; } if (nptCatRow) { nptCatRow.QuantityHoursValues[weekIndex] = (nptCatRow.QuantityHoursValues[weekIndex] || 0) + hoursValue; nptCatRow.QuantityResourcesValues[weekIndex] = (nptCatRow.QuantityResourcesValues[weekIndex] || 0) + resourcesValue; nptCatRow.QuantityHoursValues[month.SelfIndexInWeeks] = (nptCatRow.QuantityHoursValues[month.SelfIndexInWeeks] || 0) + hoursValue; nptCatRow.QuantityResourcesValues[month.SelfIndexInWeeks] = (nptCatRow.QuantityResourcesValues[month.SelfIndexInWeeks] || 0) + resourcesValue; nptCatRow.TotalHoursValue += hoursValue; nptCatRow.TotalResourcesValue += resourcesValue; } if (totalNptRow) { totalNptRow.QuantityHoursValues[weekIndex] = (totalNptRow.QuantityHoursValues[weekIndex] || 0) + hoursValue; totalNptRow.QuantityResourcesValues[weekIndex] = (totalNptRow.QuantityResourcesValues[weekIndex] || 0) + resourcesValue; totalNptRow.QuantityHoursValues[month.SelfIndexInWeeks] = (totalNptRow.QuantityHoursValues[month.SelfIndexInWeeks] || 0) + hoursValue; totalNptRow.QuantityResourcesValues[month.SelfIndexInWeeks] = (totalNptRow.QuantityResourcesValues[month.SelfIndexInWeeks] || 0) + resourcesValue; totalNptRow.TotalHoursValue += hoursValue; totalNptRow.TotalResourcesValue += resourcesValue; } if (performRollup) { for (var rollupRowIndex = 0; rollupRowIndex < rollupToRows.length; rollupRowIndex++) { var rollupRow = rollupToRows[rollupRowIndex]; if (rollupRow) { rollupRow.QuantityHoursValues[weekIndex] = (rollupRow.QuantityHoursValues[weekIndex] || 0) + hoursValue; rollupRow.QuantityResourcesValues[weekIndex] = (rollupRow.QuantityResourcesValues[weekIndex] || 0) + resourcesValue; rollupRow.QuantityHoursValues[month.SelfIndexInWeeks] = (rollupRow.QuantityHoursValues[month.SelfIndexInWeeks] || 0) + hoursValue; rollupRow.QuantityResourcesValues[month.SelfIndexInWeeks] = (rollupRow.QuantityResourcesValues[month.SelfIndexInWeeks] || 0) + resourcesValue; rollupRow.TotalHoursValue += hoursValue; rollupRow.TotalResourcesValue += resourcesValue; } } } } } }, groupNptDataByCategory: function (resourceKey, resourceData, targetData) { if (!resourceData || !resourceData.NonProjectTime) return targetData; for (var nptId in resourceData.NonProjectTime) { var sourceNpt = resourceData.NonProjectTime[nptId]; if (!sourceNpt) continue; var targetCat = targetData[sourceNpt.CategoryId]; if (!targetCat) targetCat = {}; var targetNpt = targetCat[nptId]; if (!targetNpt) targetNpt = { Id: sourceNpt.Id, Name: sourceNpt.Name, isTeamWide: sourceNpt.isTeamWide, isReadOnly: sourceNpt.isReadOnly, CategoryId: sourceNpt.CategoryId }; if (!targetNpt.Resources) targetNpt.Resources = {}; if (!targetNpt.Resources[resourceKey]) targetNpt.Resources[resourceKey] = { Id: resourceKey, Name: resourceData.Name, isReadOnly: sourceNpt.isReadOnly, SortingKey: resourceData.SortingKey, ExpenditureCategoryId: resourceData.OwnExpenditureCategoryId, Allocations: {} }; if (!sourceNpt.Allocations) continue for (var week in sourceNpt.Allocations) { targetNpt.Resources[resourceKey].Allocations[week] = (targetNpt.Resources[resourceKey].Allocations[week] || 0) + (sourceNpt.Allocations[week] || 0); } targetCat[nptId] = targetNpt; targetData[sourceNpt.CategoryId] = targetCat; } return targetData; }, getNonProjectTimeDataModel: function (nonProjectTimeCategoryCache, groupByMode) { if (!nonProjectTimeCategoryCache || (groupByMode === undefined) || (groupByMode == "")) return null; if (!angular.isNumber(Number(groupByMode))) return; var groupMode = Number(groupByMode); var teams = teamInfoService.getAll(); if (!teams) return null; var nonProjectTimeCategories = nonProjectTimeCategoryCache; var nonProjectTime = {}; var result = {}; var nptCatKeys = Object.keys(nonProjectTimeCategories); if (nptCatKeys.length < 1) // NPT data found in data source dataModel return result; // gather all npt allocations from resources and group them by NPT Category for (var teamKey in teams) { var teamData = teams[teamKey]; if (!teamData || !teamData.ExpCategories) continue; for (var expCatKey in teamData.ExpCategories) { var expCatData = teamData.ExpCategories[expCatKey]; if (!expCatData || !expCatData.Resources || !expCatData.AllowResourceAssignment) continue; for (var resKey in expCatData.Resources) { var resData = teamInfoService.extendResourceModelWithAttributes(expCatData.Resources[resKey]); if (!resData || !resData.NonProjectTime || (Object.keys(resData.NonProjectTime).length < 1)) continue; var groupingKey = null; switch (groupMode) { case 0: groupingKey = resKey; break; // Single group NPTs by current resource case 1: groupingKey = teamData.Id; break; // Group NPTs by Teams case 2: groupingKey = C_EMPTY_GUID; break; // Single virtual group on Guid.Empty key case 3: groupingKey = resKey; break; // Group NPTs by Resources case 4: groupingKey = teamData.CompanyId; break; // Group NPTs by Companies case 5: groupingKey = C_EMPTY_GUID; break; // Single virtual group by Guid.Empty key for group by client mode case 6: groupingKey = C_EMPTY_GUID; break; // Single virtual group by Guid.Empty key for group by cost center mode } if (groupingKey) { if (!nonProjectTime[groupingKey]) nonProjectTime[groupingKey] = {}; nonProjectTime[groupingKey] = this.groupNptDataByCategory(resKey, resData, nonProjectTime[groupingKey]); } } } } // build UI dataModel rows for (var groupId in nonProjectTime) { var nonProjectTimeForGroup = nonProjectTime[groupId]; for (var nptCatId in nonProjectTimeCategories) { var nptCatName = nonProjectTimeCategories[nptCatId]; var nptCatResultModel = { Id: nptCatId, Name: nptCatName, NonProjectTime: {} } // Get NPT-items in current category var nptResourcesAsArray = {}; var nptItems = nonProjectTimeForGroup[nptCatId]; if (!nptItems) continue; for (var nptItemId in nptItems) { var nptItem = angular.copy(nptItems[nptItemId]); nptCatResultModel.NonProjectTime[nptItemId] = nptItem; } if (!result[groupId]) result[groupId] = {}; result[groupId][nptCatId] = nptCatResultModel; } } return result; }, getNonProjectTimeItemsByCategory: function (nonProjectTime, categoryId) { if (!categoryId || !nonProjectTime) return null; var result = {}; for (var nptId in nonProjectTime) { var nptItem = nonProjectTime[nptId]; if (nptItem.CategoryId && (nptItem.CategoryId == categoryId)) result[nptId] = nptItem; } return result; }, getExpenditures4Project: function (project, teamIds, ecs, header, filter, includeProjectNeed, filterExpendituresByTeams, groupDataByTeams, includeWithoutAllocation) {//todo: test all usage // groupDataByTeams - indicates whether to put team rows under EC rows or not var expenditures = {}; if (!project || !project.ActiveScenario || !filter) { return expenditures; } var scenarioId = project.ActiveScenario.Id; var startDate = project.ActiveScenario.StartDate; var endDate = project.ActiveScenario.StartDate; var allocationMode = project.AllocationMode || this.allocationMode.teamAllocation; if (!scenarioId || !startDate || !endDate) { return expenditures; } if (!header || !header.getWeekEndingsRange) { return expenditures; } var projectTeams = Object.keys(project.Teams || {}); // which teams are here, only AC or all project? Answ: AC displayed only teamIds = (teamIds && teamIds.length) ? teamIds : projectTeams; var ta4Project = activityCalendarService.getTeamAllocations4Scenario(filter, teamIds, scenarioId) || {}; var na4Project = includeProjectNeed ? activityCalendarService.getNeedAllocations4Scenario(filter, scenarioId) || {} : {}; // remaining need should always be calculated according to all project teams that are available in the calendar var rna4Project = includeProjectNeed ? activityCalendarService.getRemainingNeedAllocations4Scenario(filter, projectTeams, scenarioId) || {} : {}; var un4Project = includeProjectNeed ? activityCalendarService.getUnassignedNeedAllocations4Scenario(filter, scenarioId) || {} : {}; var rta4Project = includeProjectNeed ? activityCalendarService.getRemainingTeamAllocations4Scenario(filter, teamIds, project.ProjectId, scenarioId) || {} : {}; // TODO: when will remove redundant data based on allocationMode verify that list of ecs is not missing any category because of absence of related data var requiredECs = ecs || union(Object.keys(ta4Project), Object.keys(na4Project)); var filteredExpCats = requiredECs; if (filterExpendituresByTeams) { // Apply filtering of Expenditures by teams and existance of team allocations filteredExpCats = activityCalendarService.filterCategoriesVsTeams(requiredECs, teamIds); var alwaysVisibleExpCats = activityCalendarService.getAlwaysVisibleProjectRolesByClientSideFilter(); filteredExpCats = this.getCategoriesWithNonZeroTeamAllocations(filteredExpCats, ta4Project, alwaysVisibleExpCats, includeWithoutAllocation); } for (var ecIndex = 0; ecIndex < filteredExpCats.length; ecIndex++) { var categoryId = filteredExpCats[ecIndex]; if (categoryId in expenditures) { // just additional check for case when requiredECs is not a distinct array for some reasons continue; } var sourceCategory = dataSources.getExpenditureById(categoryId); if (!sourceCategory) { throw 'Expenditure category with id = ' + categoryId + ' does not exist'; } var categoryTeams = (categoryId in ta4Project) ? angular.copy(ta4Project[categoryId].Teams || null) : null; if (groupDataByTeams) { // allocation mode on team row cannot be project need or unassigned project need var teamAllocationMode = (allocationMode == this.allocationMode.needAllocation || allocationMode == this.allocationMode.remainingNeedAllocation) ? this.allocationMode.teamAllocation : allocationMode; if (categoryTeams) for (var teamId in categoryTeams) { var currentTeam = teamInfoService.getById(teamId); categoryTeams[teamId].AllocationMode = teamAllocationMode; categoryTeams[teamId].Resources = this.getAssignedResources4Project(filter, project, categoryId, [teamId]); categoryTeams[teamId].AvailableResources = this.getAvailableResources4Project(filter, header, project, categoryId, [teamId]); categoryTeams[teamId].Name = currentTeam.Name; categoryTeams[teamId].Id = currentTeam.Id; categoryTeams[teamId].ExpenditureCategoryId = categoryId; if (rta4Project && (categoryId in rta4Project) && rta4Project[categoryId].Teams && (teamId in rta4Project[categoryId].Teams)) categoryTeams[teamId].RemainingTeamAllocations = angular.copy(rta4Project[categoryId].Teams[teamId].Allocations || {}); else categoryTeams[teamId].RemainingTeamAllocations = {}; } } expenditures[categoryId] = { Id: categoryId, Name: sourceCategory.ExpenditureCategoryName, AllocationMode: allocationMode, AllowResourceAssignment: sourceCategory.AllowResourceAssignment, Allocations: (categoryId in ta4Project) ? angular.copy(ta4Project[categoryId].Allocations || {}) : {}, NeedAllocations: (categoryId in na4Project) ? angular.copy(na4Project[categoryId].Allocations || {}) : {}, RemainingNeed: (categoryId in rna4Project) ? angular.copy(rna4Project[categoryId].Allocations || {}) : {}, UnassignedNeed: (categoryId in un4Project) ? angular.copy(un4Project && un4Project[categoryId] && un4Project[categoryId].Allocations ? un4Project[categoryId].Allocations : {}) : {}, RemainingTeamAllocations: (categoryId in rta4Project) ? angular.copy(rta4Project[categoryId].Allocations || {}) : {}, Teams: categoryTeams }; } return expenditures; }, getExpendituresWithResources4Project: function (project, teamIds, ecs, header, filter, includeProjectNeed, filterCategories, groupDataByTeams, includeWithoutAllocation) {//todo: test all usage //console.log(groupDataByTeams,includeWithoutAllocation); // groupDataByTeams - indicates whether to put team rows under EC rows or not var expenditures = this.getExpenditures4Project(project, teamIds, ecs, header, filter, includeProjectNeed, filterCategories, groupDataByTeams, includeWithoutAllocation) || {}; if (!groupDataByTeams) // do not put resources under EC row, because there will be team rows { for (var categoryId in expenditures) { expenditures[categoryId].Resources = this.getAssignedResources4Project(filter, project, categoryId, teamIds); expenditures[categoryId].AvailableResources = this.getAvailableResources4Project(filter, header, project, categoryId, teamIds); } } return expenditures; }, getAssignedResources4Project: function (filter, project, categoryId, teamIds, resourceId) { if (!filter || !project || !project.ActiveScenario || !project.Teams || !categoryId) { return null; } var result = {}; var targetTeamIds = (teamIds && teamIds.length) ? teamIds : Object.keys(project.Teams); if (targetTeamIds.length <= 0) { return null; } var isSuperEC = dataSources.isSuperEC(categoryId); var requestedResourceIds = resourceId ? [resourceId] : (activityCalendarService.getResourcesByProject(filter, project.ProjectId, categoryId, targetTeamIds) || []); for (var teamIndex = 0; teamIndex < targetTeamIds.length; teamIndex++) { var teamId = targetTeamIds[teamIndex]; // if requested category is super one we need to get all resources that exist in the team var resources = teamInfoService.getResourcesByTeam(teamId, (isSuperEC ? null : categoryId)); if (!resources || !Object.keys(resources).length) { continue; } for (var resourceIndex = 0; resourceIndex < requestedResourceIds.length; resourceIndex++) { var processingResourceId = requestedResourceIds[resourceIndex]; var isResourceAssigned = activityCalendarService.checkResourceIsAssignedToProject(filter, project.ProjectId, categoryId, targetTeamIds, processingResourceId); if (!isResourceAssigned) { continue; } var resource = resources[processingResourceId]; if (resource) { if (!result[resource.Id]) { result[resource.Id] = { Id: resource.Id, Name: resource.Name, Teams: [], Allocations: {}, }; } var savedResource = result[resource.Id]; for (var mIndex = 0; mIndex < resource.Teams.length; mIndex++) { var membershipInfo = resource.Teams[mIndex]; if (membershipInfo.TeamId == teamId) { savedResource.Teams.push({ TeamId: teamId, ReadOnly: membershipInfo.ReadOnly, StartDate: membershipInfo.StartDate, EndDate: membershipInfo.EndDate }); savedResource.StartDate = Math.min(savedResource.StartDate || membershipInfo.StartDate, membershipInfo.StartDate); savedResource.EndDate = membershipInfo.EndDate ? Math.max(savedResource.EndDate || membershipInfo.EndDate, membershipInfo.EndDate) : null; } } var resourceAllocations = activityCalendarService.getResourceAllocations4Scenario(filter, resource.Id, project.ActiveScenario.Id, teamId, categoryId); if (resourceAllocations) { angular.forEach(resourceAllocations, function (item, key) { savedResource.Allocations[key] = item; }); } } } } return result; }, getAvailableResources4Project: function (filter, header, project, categoryId, teamIds, resourceId) { if (!filter || !header || !project || !project.ActiveScenario || !project.Teams || !categoryId) { return null; } var availableResources = {}; var result = {}; var targetTeamIds = (teamIds && teamIds.length) ? teamIds : Object.keys(project.Teams); if (targetTeamIds.length <= 0) { return null; } var isSuperEC = dataSources.isSuperEC(categoryId); var scenarioRange = header.getWeekEndingsRange(project.ActiveScenario.StartDate, project.ActiveScenario.EndDate); for (var teamIndex = 0; teamIndex < targetTeamIds.length; teamIndex++) { var teamId = targetTeamIds[teamIndex]; // if requested category is super one we need to get all resources that exist in the team var resources = teamInfoService.getResourcesWithAllocationsByTeam(teamId, (isSuperEC ? null : categoryId), (resourceId ? [resourceId] : null)); if (!resources || !Object.keys(resources).length) { continue; } var requestedResourceIds = resourceId ? [resourceId] : Object.keys(resources); for (var resourceIndex = 0; resourceIndex < requestedResourceIds.length; resourceIndex++) { var processingResourceId = requestedResourceIds[resourceIndex]; var isResourceAssigned = activityCalendarService.checkResourceIsAssignedToProject(filter, project.ProjectId, categoryId, targetTeamIds, processingResourceId); if (isResourceAssigned) { continue; } var resource = resources[processingResourceId]; if (resource && checkAnyTeamEditable(resource.Teams)) { if (!availableResources[resource.Id]) { availableResources[resource.Id] = { Id: resource.Id, Name: resource.Name, OwnExpenditureCategoryId: resource.OwnExpenditureCategoryId, Teams: [], AllocatedCapacity: {}, TotalCapacity: {} }; } var savedResource = availableResources[resource.Id]; for (var mIndex = 0; mIndex < resource.Teams.length; mIndex++) { var teamMembership = resource.Teams[mIndex]; if (teamMembership && (teamMembership.TeamId == teamId)) { savedResource.Teams.push({ TeamId: teamId, ReadOnly: teamMembership.ReadOnly, StartDate: teamMembership.StartDate, EndDate: teamMembership.EndDate }); savedResource.StartDate = Math.min(savedResource.StartDate || teamMembership.StartDate, teamMembership.StartDate); savedResource.EndDate = teamMembership.EndDate ? Math.max(savedResource.EndDate || teamMembership.EndDate, teamMembership.EndDate) : null; } } if (resource.AllocatedCapacity) { angular.forEach(resource.AllocatedCapacity, function (item, key) { savedResource.AllocatedCapacity[key] = item; }); } if (resource.TotalCapacity) { angular.forEach(resource.TotalCapacity, function (item, key) { savedResource.TotalCapacity[key] = item; }); } if (resource.NonProjectTime) { savedResource.NonProjectTime = resource.NonProjectTime; } } } } if (Object.keys(availableResources).length) { for (var resourceId in availableResources) { var resource = availableResources[resourceId]; if (resource) { var sourceCategory = dataSources.getExpenditureById(resource.OwnExpenditureCategoryId); scenarioDetailsService.recalculateResourceAvailability(resource, scenarioRange); if (resource.IsVisible) { result[resourceId] = { Id: resourceId, Name: resource.Name, Teams: resource.Teams, StartDate: resource.StartDate, EndDate: resource.EndDate, MinAvailability: resource.MinAvailability, MaxAvailability: resource.MaxAvailability, AvgAvailability: resource.AvgAvailability, }; } } } } return result; }, getTeamRowsInProjectUnassigned: function (projectRow, teamId) { if (!projectRow || !projectRow.Children || !teamId) return null; var foundTeamRows = {}; for (var expCatIndex = 0; expCatIndex < projectRow.Children.length; expCatIndex++) { var expCatRow = projectRow.Children[expCatIndex]; if (expCatRow && expCatRow.Children && expCatRow.Children.length) { for (var teamIndex = 0; teamIndex < expCatRow.Children.length; teamIndex++) { var teamRow = expCatRow.Children[teamIndex]; if ((teamRow.Id == teamId) && expCatRow.Id) { foundTeamRows[expCatRow.Id] = { ExpCatRow: expCatRow, TeamRow: teamRow, TeamIndex: teamIndex }; } } } } return foundTeamRows; }, formatPeopleResourceOption: function (element) { if (!element) { return null; } var $optionScope = angular.element(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); } return null; }, assignResource: function (filter, header, projectId, sourceExpCatRow, resourceId, rowToAddAssignmentRowTo) { if (!filter || !resourceId) { return; } if (!sourceExpCatRow || !rowToAddAssignmentRowTo || !rowToAddAssignmentRowTo.AvailableResources || !(resourceId in rowToAddAssignmentRowTo.AvailableResources)) { return; } var resource = rowToAddAssignmentRowTo.AvailableResources[resourceId]; if (!resource || !resource.teams || !resource.teams.length) { return; } var project = activityCalendarService.getProjectById(filter, projectId); if (!project || !project.ActiveScenario) { return; } var range = resolveRange(header, project.ActiveScenario.StartDate, project.ActiveScenario.EndDate); if (!range || !range.startDate || !range.endDate) { return; } // assign resource from all teams that fit to the visible scenario period var assignedTeams = []; for (var i = 0; i < resource.teams.length; i++) { var team = resource.teams[i]; if (team.StartDate <= range.endDate && (!team.EndDate || team.EndDate >= range.startDate)) { activityCalendarService.assignResource(filter, project.ProjectId, sourceExpCatRow.Id, team.TeamId, resource.id); assignedTeams.push(team.TeamId); } } // init resource values with zero var scenarioRange = header.getWeekEndingsRange(project.ActiveScenario.StartDate, project.ActiveScenario.EndDate); for (var i = 0; i < scenarioRange.length; i++) { var availableTeam = this.resolveResourceAvailableTeam(scenarioRange[i], resource.teams); if (availableTeam) { activityCalendarService.changeResourceValue(filter, project.ProjectId, sourceExpCatRow.Id, availableTeam.TeamId, resource.id, scenarioRange[i], 0, false); } } // Drop off pre-filtered data cache due to recent data changes activityCalendarService.invalidatePreFilteredCache(filter); var rowForAssignmentCreated = false; var assignedResources = this.getAssignedResources4Project(filter, project, sourceExpCatRow.Id, assignedTeams, resource.id); if (assignedResources && (resource.id in assignedResources)) { // Create viewModel as Resource Row and fill it with data to display var assignedResource = assignedResources[resource.id]; var assignedResourceModel = this.createViewModel4Resource(assignedResource); if (assignedResourceModel) { this.fillResourceRowWithData(header, rowToAddAssignmentRowTo, sourceExpCatRow.Id, resource.id, assignedResourceModel, assignedResource, null, range.startDate, range.endDate); // for new resource it is not need to pass real hours/resources mode, because it values are filled with 0 this.toggleGridSource([assignedResourceModel], null); assignedResourceModel.Initialized = true; assignedResourceModel.Show = true; assignedResourceModel.Level = rowToAddAssignmentRowTo.Level + 1; if (!rowToAddAssignmentRowTo.Children) { rowToAddAssignmentRowTo.Children = []; } rowToAddAssignmentRowTo.Children.push(assignedResourceModel); rowForAssignmentCreated = true; } } if (rowForAssignmentCreated) { // Remove assigned resource from available resources list at the source EC row rowToAddAssignmentRowTo.ResourceToAssignId = null; delete rowToAddAssignmentRowTo.AvailableResources[resource.id]; // set collection to null if it is empty to hide row with control for resource assignment if (!Object.keys(rowToAddAssignmentRowTo.AvailableResources).length) { rowToAddAssignmentRowTo.AvailableResources = null; } } }, assignTeam: function (filter, header, projectId, teamId, extendedModel) { if (!filter || !header || !projectId || !teamId) { return; } var project = activityCalendarService.getProjectById(filter, projectId); if (!project || !project.ActiveScenario) { return; } var weekEndings = header.getWeekEndingsRange(project.ActiveScenario.StartDate, project.ActiveScenario.EndDate); if (!weekEndings || !weekEndings.length) { return; } activityCalendarService.assignTeam(filter, projectId, teamId, extendedModel); activityCalendarService.createTeamAllocations(filter, projectId, teamId, weekEndings); // Drop off pre-filtered data cache due to recent data changes activityCalendarService.invalidatePreFilteredCache(filter); }, checkResourceValue: function (filter, header, projectId, parentRow, resourceId, resRow, isUOMHours, isAvgMode, $index, $data, isActuals) { if (!filter || !header || !projectId || !parentRow || !resourceId || !resRow || !resRow.Cells) { return 'Error has occurred. Try again later.'; } var newValue = roundService.roundQuantity($data); if (isNaN(newValue)) newValue = 0; if (newValue < 0) { return "Value should not be less than zero"; } if (newValue == resRow.Cells[$index]) return false; var week = header.Weeks[$index]; if (!week) { console.error('Incorrect week index: ' + $index); return false; } var changedCells = []; if (week.DataType == Header.DataType.Month) { changedCells = this.changeResourceMonthValue(filter, header, resourceId, resRow, projectId, parentRow, isUOMHours, isAvgMode, week.ParentIndex, newValue, isActuals); } else { var changedCell = this.changeResourceWeekValue(filter, header, resourceId, resRow, projectId, parentRow, isUOMHours, isAvgMode, $index, newValue, isActuals); if (changedCell) { changedCells.push(changedCell); } } // Drop off pre-filtered data cache due to recent data changes activityCalendarService.invalidatePreFilteredCache(filter); return changedCells; }, checkTeamValue: function (filter, header, projectId, expCatRow, teamId, teamRow, isUOMHours, isAvgMode, $index, $data) { if (!filter || !header || !projectId || !expCatRow || !teamId || !teamRow || !teamRow.Cells) { return 'Error has occurred. Try again later.'; } var newValue = roundService.roundQuantity($data); if (isNaN(newValue)) newValue = 0; if (newValue < 0) { return "Value should not be less than zero"; } if (newValue == teamRow.Cells[$index]) return false; var week = header.Weeks[$index]; if (!week) { console.error('Incorrect week index: ' + $index); return false; } var changedCells = []; if (week.DataType == Header.DataType.Month) { changedCells = this.changeTeamMonthValue(filter, header, teamId, teamRow, projectId, expCatRow, isUOMHours, isAvgMode, week.ParentIndex, newValue); } else { var changedCell = this.changeTeamWeekValue(filter, header, teamId, teamRow, projectId, expCatRow, isUOMHours, isAvgMode, $index, newValue); if (changedCell) { changedCells.push(changedCell); } } // Drop off pre-filtered data cache due to recent data changes activityCalendarService.invalidatePreFilteredCache(filter); return changedCells; }, checkResourceGrandTotalValue: function (filter, header, projectId, startDate, endDate, parentRow, resourceId, resRow, isUOMHours, isAvgMode, $data, isActuals) { if (!filter || !header || !projectId || !startDate || !endDate || !parentRow || !resourceId || !resRow) { return 'Error has occurred. Try again later.'; } var newValue = roundService.roundQuantity($data); if (isNaN(newValue)) newValue = 0; if (newValue < 0) { return "Value should not be less than zero"; } if (newValue == resRow.TotalValue) return false; var result = this.changeResourceGrandTotalValue(filter, header, resourceId, resRow, projectId, startDate, endDate, parentRow, isUOMHours, isAvgMode, newValue, isActuals); // Drop off pre-filtered data cache due to recent data changes activityCalendarService.invalidatePreFilteredCache(filter); return result; }, checkTeamGrandTotalValue: function (filter, header, projectId, startDate, endDate, expCatRow, teamId, teamRow, isUOMHours, isAvgMode, $data) { if (!filter || !header || !projectId || !startDate || !endDate || !expCatRow || !teamId || !teamRow) { return 'Error has occurred. Try again later.'; } var newValue = roundService.roundQuantity($data); if (isNaN(newValue)) newValue = 0; if (newValue < 0) { return "Value should not be less than zero"; } if (newValue == teamRow.TotalValue) return false; var result = this.changeTeamGrandTotalValue(filter, header, teamId, teamRow, projectId, startDate, endDate, expCatRow, isUOMHours, isAvgMode, newValue); // Drop off pre-filtered data cache due to recent data changes activityCalendarService.invalidatePreFilteredCache(filter); return result; }, checkNptResourceValue: function (filter, header, remCapacityRow, rollupToRows, nptCatRow, nptItemRow, nptResourceRow, isUOMHours, isAvgMode, $index, $data) { if (!filter || !header || !nptCatRow || !nptItemRow || !nptResourceRow || !nptResourceRow.Cells) { return 'Error has occurred. Try again later.'; } var newValue = roundService.roundQuantity($data); if (isNaN(newValue)) newValue = 0; if (newValue < 0) { return "Value should not be less than zero"; } if (newValue == nptResourceRow.Cells[$index]) return false; var week = header.Weeks[$index]; if (!week) { console.error('Incorrect week index: ' + $index); return false; } var changedCells = []; if (week.DataType == Header.DataType.Month) { changedCells = this.changeNptResourceMonthValue(filter, header, remCapacityRow, rollupToRows, nptCatRow, nptItemRow, nptResourceRow, isUOMHours, isAvgMode, week.ParentIndex, newValue); } else { var changedCell = this.changeNptResourceWeekValue(filter, header, remCapacityRow, rollupToRows, nptCatRow, nptItemRow, nptResourceRow, isUOMHours, isAvgMode, $index, newValue); if (changedCell) { changedCells.push(changedCell); } } // Drop off pre-filtered data cache due to recent data changes activityCalendarService.invalidatePreFilteredCache(filter); return changedCells; }, checkTeamWideNptValue: function (filter, header, remCapacityRow, rollupToRows, nptCatRow, nptItemRow, isUOMHours, isAvgMode, $index, $data) { if (!filter || !header || !nptCatRow || !nptItemRow || !nptItemRow.Cells) { return 'Error has occurred. Try again later.'; } var newValue = roundService.roundQuantity($data); if (isNaN(newValue)) newValue = 0; if (newValue < 0) { return "Value should not be less than zero"; } if (newValue == nptItemRow.Cells[$index]) return false; var week = header.Weeks[$index]; if (!week) { console.error('Incorrect week index: ' + $index); return false; } var changedCells = []; if (week.DataType == Header.DataType.Month) { changedCells = this.changeTeamWideNptMonthValue(filter, header, remCapacityRow, rollupToRows, nptCatRow, nptItemRow, isUOMHours, isAvgMode, week.ParentIndex, newValue); } else { var changedCell = this.changeTeamWideNptWeekValue(filter, header, remCapacityRow, rollupToRows, nptCatRow, nptItemRow, isUOMHours, isAvgMode, $index, newValue); if (changedCell) { changedCells.push(changedCell); } } // Drop off pre-filtered data cache due to recent data changes activityCalendarService.invalidatePreFilteredCache(filter); return changedCells; }, takeRemainingCapacity: function (filter, header, projectId, startDate, endDate, parentRow, resourceId, resRow, isUOMHours, isAvgMode) { if (!filter || !header || !projectId || !startDate || !endDate || !parentRow || !resourceId || !resRow) { return; } var result = this.takeCapacity(filter, header, projectId, startDate, endDate, parentRow, resourceId, resRow, isUOMHours, isAvgMode, true); return result; }, takeFullCapacity: function (filter, header, projectId, startDate, endDate, parentRow, resourceId, resRow, isUOMHours, isAvgMode) { if (!filter || !header || !projectId || !startDate || !endDate || !parentRow || !resourceId || !resRow) { return; } var result = this.takeCapacity(filter, header, projectId, startDate, endDate, parentRow, resourceId, resRow, isUOMHours, isAvgMode, false); return result; }, takeCapacity: function (filter, header, projectId, startDate, endDate, parentRow, resourceId, resRow, isUOMHours, isAvgMode, isRemaining) { if (!filter || !header || !projectId || !startDate || !endDate || !parentRow || !resourceId || !resRow || !resRow.Teams) { return; } var weeks = header.getWeeks(startDate, endDate); if (!weeks || !Object.keys(weeks).length) { return; } //to-do: replace parentRow argument with expCatId and get remaining capacity directly from envisageService var expCatId = parentRow.ExpenditureCategoryId || parentRow.Id; var resourceTeams = resRow.Teams.map(function (team) { return team.TeamId }); var resourceSummary = teamInfoService.getResourceSummary(resourceTeams, expCatId, resourceId); if (!resourceSummary) { return; } var changedCells = []; for (var weekIndexKey in weeks) { var weekIndex = parseInt(weekIndexKey); var restCapacity = parentRow.RemainingCapacityValues[weekIndex] || 0; var assignedCapacity = resRow.QuantityHoursValues[weekIndex] || 0; // we need to consider resource allocation in the current scenario var availableCapacity = (resourceSummary.TotalCapacity ? roundService.roundQuantity(resourceSummary.TotalCapacity[weeks[weekIndex]] || 0) : 0) - assignedCapacity; if (isRemaining) { var allocatedCapacityInOtherScenarios = (resourceSummary.AllocatedCapacity ? roundService.roundQuantity(resourceSummary.AllocatedCapacity[weeks[weekIndex]] || 0) : 0) - assignedCapacity; var assignedNpt = roundService.roundQuantity(teamInfoService.getResourceSummaryNptAllocation(resourceSummary, weeks[weekIndex])); availableCapacity = roundService.roundQuantity(availableCapacity - allocatedCapacityInOtherScenarios - assignedNpt); } var capacityToAssign = Math.min(Math.max(restCapacity, 0), Math.max(availableCapacity, 0)); var newValue = capacityToAssign + assignedCapacity; if (!isUOMHours) { newValue = hoursResourcesConverter.convertToResources(expCatId, newValue); } var changedCell = this.changeResourceWeekValue(filter, header, resourceId, resRow, projectId, parentRow, isUOMHours, isAvgMode, weekIndex, newValue, false); if (changedCell) { changedCells.push(changedCell); } } // Drop off pre-filtered data cache due to recent data changes activityCalendarService.invalidatePreFilteredCache(filter); return changedCells; }, takeTeamFullCapacity: function (filter, header, projectId, startDate, endDate, expCatRow, teamRow, isUOMHours, isAvgMode) { if (!filter || !header || !projectId || !startDate || !endDate || !expCatRow || !teamRow) { return; } var result = this.takeTeamCapacity(filter, header, projectId, startDate, endDate, expCatRow, teamRow, isUOMHours, isAvgMode, false); return result; }, takeTeamRemainingCapacity: function (filter, header, projectId, startDate, endDate, expCatRow, teamRow, isUOMHours, isAvgMode) { if (!filter || !header || !projectId || !startDate || !endDate || !expCatRow || !teamRow) { return; } return this.takeTeamCapacity(filter, header, projectId, startDate, endDate, expCatRow, teamRow, isUOMHours, isAvgMode, true); }, takeTeamCapacity: function (filter, header, projectId, startDate, endDate, expCatRow, teamRow, isUOMHours, isAvgMode, isRemaining) { if (!filter || !header || !projectId || !startDate || !endDate || !expCatRow || !teamRow) { return; } var project = activityCalendarService.getProjectById(filter, projectId); if (!project || !project.ActiveScenario || !project.ActiveScenario.Id) { return; } var scenarioId = project.ActiveScenario.Id; var weeks = header.getWeeks(startDate, endDate); if (!weeks || !Object.keys(weeks).length) { return; } var weekendingsMs =[]; for (var wKeyAsIndex in weeks) { weekendingsMs.push(weeks[parseInt(wKeyAsIndex)]); } var teamId = teamRow.Id; var originalExpCatId = expCatRow.Id; // Check the EC is a project role var expCatInfo = dataSources.getExpenditureById(originalExpCatId); if (!expCatInfo) return; var isProjectRole = !expCatInfo.AllowResourceAssignment; var expCatInTeam = null; if (!isProjectRole) { // For ordinary EC we need to get it's info in current team context to figure up it's capacity expCatInTeam = teamInfoService.getExpenditureCategoryInTeam(teamId, originalExpCatId, false); if (!expCatInTeam) { return; } } // Get team allocations for current category (if it is an ordinary category) or for all categories (if it is a project role). // Do it for all scenarios var expCatId4Allocations = !isProjectRole ? originalExpCatId : undefined; var expCatTeamAllocations = activityCalendarService.getTeamAllocationsSummary4Week(filter, [teamId], expCatId4Allocations, weekendingsMs, true); var teamNpt = {}; if (isRemaining) { teamNpt = teamInfoService.getTeamSummaryNptAllocations(teamId, expCatId4Allocations); } var nowUtc = DateTimeConverter.getUtcNowMs(); var changedCells = []; for (var weekIndexKey in weeks) { var weekIndex = parseInt(weekIndexKey); var weekendingMs = weeks[weekIndex]; // Get remaining need for current category (we should try to assign it by maximum) var capacityForWe = 0; var restCapacity = expCatRow.RemainingCapacityValues[weekIndex] || 0; // Get total current team allocations for current scenario for current category plus all project roles var allocatedInCurrentScenarioTotal = activityCalendarService.getTeamAllocations4Scenario4Week(filter, scenarioId, [teamId], expCatId4Allocations, weekendingMs, true); // Get total current team allocations for current scenario for current category only var allocatedInScenarioToOriginalExpCat = activityCalendarService.getTeamAllocations4Scenario4Week(filter, scenarioId, [teamId], originalExpCatId, weekendingMs, false); if (isProjectRole) { // For dates in the past we take actuals capacity values, for future - planned if (weekendingMs < nowUtc) capacityForWe = teamInfoService.getTeamTotalCapacity(teamId, false, weekendingMs); else capacityForWe = teamInfoService.getTeamTotalCapacity(teamId, true, weekendingMs); } else { if (weekendingMs < nowUtc) capacityForWe = expCatInTeam.ActualCapacityValues && (weekendingMs in expCatInTeam.ActualCapacityValues) && angular.isNumber(expCatInTeam.ActualCapacityValues[weekendingMs]) ? (expCatInTeam.ActualCapacityValues[weekendingMs] || 0) : 0; else capacityForWe = expCatInTeam.PlannedCapacityValues && (weekendingMs in expCatInTeam.PlannedCapacityValues) && angular.isNumber(expCatInTeam.PlannedCapacityValues[weekendingMs]) ? (expCatInTeam.PlannedCapacityValues[weekendingMs] || 0) : 0; } var availableCapacity = capacityForWe - allocatedInCurrentScenarioTotal; if (isRemaining) { var allocationsInAllScenarios = 0; if (expCatTeamAllocations && expCatTeamAllocations[teamId] && (weekendingMs in expCatTeamAllocations[teamId]) && angular.isNumber(expCatTeamAllocations[teamId][weekendingMs])) { allocationsInAllScenarios = Number(expCatTeamAllocations[teamId][weekendingMs]); } var allocatedCapacityInOtherScenarios = allocationsInAllScenarios - allocatedInCurrentScenarioTotal; var assignedNpt = teamNpt && teamNpt[weeks[weekIndex]] ? teamNpt[weekendingMs] : 0; availableCapacity = availableCapacity - allocatedCapacityInOtherScenarios - assignedNpt; } var capacityToAssign = Math.min(Math.max(restCapacity, 0), Math.max(availableCapacity, 0)); var newValue = capacityToAssign + allocatedInScenarioToOriginalExpCat; if (!isUOMHours) { newValue = hoursResourcesConverter.convertToResources(originalExpCatId, newValue); } newValue = roundService.roundQuantity(newValue); var changedCell = this.changeTeamWeekValue(filter, header, teamId, teamRow, projectId, expCatRow, isUOMHours, isAvgMode, weekIndex, newValue, false); if (changedCell) { changedCells.push(changedCell); } } // Drop off pre-filtered data cache due to recent data changes activityCalendarService.invalidatePreFilteredCache(filter); return changedCells; }, zeroTeam: function (filter, header, projectId, startDate, endDate, expCatRow, teamRow, isUOMHours, isAvgMode) { if (!filter || !header || !projectId || !startDate || !endDate || !expCatRow || !teamRow) { return 'Error has occurred. Try again later.'; } var result = this.changeTeamGrandTotalValue(filter, header, teamRow.Id, teamRow, projectId, startDate, endDate, expCatRow, isUOMHours, isAvgMode, 0); // Drop off pre-filtered data cache due to recent data changes activityCalendarService.invalidatePreFilteredCache(filter); return result; }, zeroResource: function (filter, header, projectId, startDate, endDate, parentRow, resourceId, resRow, isUOMHours, isAvgMode) { if (!filter || !header || !projectId || !startDate || !endDate || !parentRow || !resourceId || !resRow) { return 'Error has occurred. Try again later.'; } var result = this.changeResourceGrandTotalValue(filter, header, resourceId, resRow, projectId, startDate, endDate, parentRow, isUOMHours, isAvgMode, 0, false); // Drop off pre-filtered data cache due to recent data changes activityCalendarService.invalidatePreFilteredCache(filter); return result; }, cleanAndRemoveTeam: function (filter, header, projectRow, teamId, startDate, endDate, isUOMHours, isAvgMode) { if (!filter || !header || !projectRow || !projectRow.Children || !teamId || !startDate || !endDate) { return; } // Get team rows inside project row var changedCells = {}; var foundTeamRows = this.getTeamRowsInProjectUnassigned(projectRow, teamId); for (var expCatId in foundTeamRows) { var currentTeamRow = foundTeamRows[expCatId].TeamRow; var currentExpCatRow = foundTeamRows[expCatId].ExpCatRow; var project = activityCalendarService.getProjectById(filter, projectRow.Id); var changedCells4ExpCat = []; if (project && project.Teams && project.Teams[teamId] && ((project.Teams[teamId].Type || activityCalendarService.TeamType.Saved) !== activityCalendarService.TeamType.SavedPending)) { changedCells4ExpCat = this.changeTeamGrandTotalValue(filter, header, currentTeamRow.Id, currentTeamRow, projectRow.Id, startDate, endDate, currentExpCatRow, isUOMHours, isAvgMode, 0); } else { var originalAllocations = activityCalendarService.getTeamAllocationsBackup(filter, project.ActiveScenario.Id, teamId, expCatId); if (originalAllocations) { var weeks = header.getWeeks(startDate, endDate); for (var weekIndex in weeks) { if (weeks[weekIndex] in originalAllocations) { var originalValue = originalAllocations[weeks[weekIndex]] || 0; var cellValue = isUOMHours ? originalValue : hoursResourcesConverter.convertToResources(expCatId, originalValue); var changedCell = this.changeTeamWeekValue(filter, header, teamId, currentTeamRow, projectRow.Id, currentExpCatRow, isUOMHours, isAvgMode, weekIndex, cellValue || 0); if (changedCell) { changedCells4ExpCat.push(changedCell); } } } } } changedCells[expCatId] = { ExpCatRow: currentExpCatRow, ChangedCells: changedCells4ExpCat }; } this.removeTeam(filter, header, projectRow, teamId); return changedCells; }, removeTeam: function (filter, header, projectRow, teamId) { if (!filter || !header || !projectRow || !teamId) { return; } var foundTeamRows = this.getTeamRowsInProjectUnassigned(projectRow, teamId); for (var expCatId in foundTeamRows) { var teamRow = foundTeamRows[expCatId].TeamRow; var expCatRow = foundTeamRows[expCatId].ExpCatRow; var teamIndexInExpCatRow = foundTeamRows[expCatId].TeamIndex; if (teamRow.Children && teamRow.Children.length) { // Team row has some child assigned resources. Remove them // Make a copy for ECrow, because removeResource method changes it var expCatRowCopy = angular.copy(expCatRow); for (var resourceIndex = teamRow.Children.length - 1; resourceIndex >= 0; resourceIndex--) { var resourceRow = teamRow.Children[resourceIndex]; this.removeResource(filter, header, projectRow.Id, expCatId, expCatRowCopy, resourceRow.Id, resourceRow, resourceIndex); } teamRow.Children = []; } if (expCatRow.Children && (expCatRow.Children.length > teamIndexInExpCatRow)) { expCatRow.Children.splice(teamIndexInExpCatRow, 1); } } activityCalendarService.removeTeam(filter, projectRow.Id, teamId) // Drop off pre-filtered data cache due to recent data changes activityCalendarService.invalidatePreFilteredCache(filter); }, cleanAndRemoveResource: function (filter, header, projectId, startDate, endDate, expCatId, parentRow, resourceId, resRow, isUOMHours, isAvgMode, $index) { if (!filter || !header || !projectId || !startDate || !endDate || !expCatId || !parentRow || !resourceId || !resRow) { return; } var changedCells = this.changeResourceGrandTotalValue(filter, header, resourceId, resRow, projectId, startDate, endDate, parentRow, isUOMHours, isAvgMode, 0, false); this.removeResource(filter, header, projectId, expCatId, parentRow, resourceId, resRow, $index); return changedCells; }, removeResource: function (filter, header, projectId, expCatId, parentRow, resourceId, resRow, $index) { if (!filter || !projectId || !expCatId || !parentRow || !resourceId) { return; } if (!resRow || !resRow.Teams || !resRow.Teams.length) { return; } var project = activityCalendarService.getProjectById(filter, projectId); if (!project || !project.ActiveScenario) { return; } var range = resolveRange(header, project.ActiveScenario.StartDate, project.ActiveScenario.EndDate); if (!range || !range.startDate || !range.endDate) { return; } // remove resource from all teams (we do not need to analyze period where team fits) var assignedTeams = []; for (var i = 0; i < resRow.Teams.length; i++) { var team = resRow.Teams[i]; if (team && team.TeamId) { activityCalendarService.removeResource(filter, project.ProjectId, expCatId, team.TeamId, resourceId); assignedTeams.push(team.TeamId); } } // Drop off pre-filtered data cache due to recent data changes activityCalendarService.invalidatePreFilteredCache(filter); var availableResources = this.getAvailableResources4Project(filter, header, project, expCatId, assignedTeams, resourceId); if (availableResources && (resourceId in availableResources)) { var availableResourceModel = this.createViewModel4AvailableResource(availableResources[resourceId]); if (availableResourceModel) { if (!parentRow.AvailableResources) { parentRow.AvailableResources = {}; } parentRow.AvailableResources[availableResourceModel.id] = availableResourceModel; } } if (parentRow.Children && parentRow.Children.length) { parentRow.Children.splice($index, 1); } }, //todo: replace expCatRow with expCatId changeResourceWeekValue: function (filter, header, resourceId, resRow, projectId, parentRow, isUOMHours, isAvgMode, $index, newValue, isActuals) { if (!filter || !header || !resourceId || !resRow || !projectId || !parentRow) { return null; } var week = header.Weeks[$index]; if (!week || week.DataType != Header.DataType.Week) { return null; } if (isNaN(parseFloat(newValue))) { newValue = 0; } var team = this.resolveResourceAvailableTeam(week.Milliseconds, resRow.Teams); if (!team || !team.TeamId) { return null; } var expCatId = parentRow.ExpenditureCategoryId || parentRow.Id; var hoursValue = roundService.roundQuantity(isUOMHours ? newValue : hoursResourcesConverter.convertToHours(expCatId, newValue)); var deltaHoursValue = roundService.roundQuantity(hoursValue - (resRow.QuantityHoursValues[$index] || 0)); var changedCell = { TeamId: team.TeamId, ExpenditureCategoryId: expCatId, ResourceId: resourceId, WeekEnding: week.Milliseconds, WeekIndex: $index, MonthIndex: week.ParentIndex, DeltaHoursValue: deltaHoursValue, }; this.updateResourceValueInViewModel(header, resRow, parentRow, isUOMHours, isAvgMode, deltaHoursValue, $index); activityCalendarService.changeResourceValue(filter, projectId, changedCell.ExpenditureCategoryId, changedCell.TeamId, changedCell.ResourceId, changedCell.WeekEnding, hoursValue, isActuals); return changedCell; }, changeTeamWeekValue: function (filter, header, teamId, teamRow, projectId, expCatRow, isUOMHours, isAvgMode, $index, newValue) { if (!filter || !header || !teamId || !teamRow || !projectId || !expCatRow) { return null; } var week = header.Weeks[$index]; if (!week || week.DataType != Header.DataType.Week) { return null; } if (isNaN(parseFloat(newValue))) { newValue = 0; } // Get categories count in team (before changes) to determine, if category was added to team var expCatsInTeamCountPrev = teamInfoService.getExpenditureCategoriesInTeamCount(teamId, true); var hoursValue = roundService.roundQuantity(isUOMHours ? newValue : hoursResourcesConverter.convertToHours(expCatRow.Id, newValue)); var oldValue = (teamRow.QuantityHoursValues[$index] || 0); var deltaHoursValue = roundService.roundQuantity(hoursValue - (teamRow.QuantityHoursValues[$index] || 0)); var changedCell = { TeamId: teamId, ExpenditureCategoryId: expCatRow.Id, WeekEnding: week.Milliseconds, WeekIndex: $index, MonthIndex: week.ParentIndex, DeltaHoursValue: deltaHoursValue, OriginalValue: oldValue }; this.updateTeamValueInViewModel(header, teamRow, expCatRow, isUOMHours, isAvgMode, deltaHoursValue, $index); activityCalendarService.changeTeamValue(filter, projectId, changedCell.ExpenditureCategoryId, changedCell.TeamId, changedCell.WeekEnding, hoursValue); // Get categories count in team (after changes) var expCatsInTeamCountAfter = teamInfoService.getExpenditureCategoriesInTeamCount(teamId, true); if (expCatsInTeamCountPrev != expCatsInTeamCountAfter) { changedCell.Added = true; } return changedCell; }, changeResourceMonthValue: function (filter, header, resourceId, resRow, projectId, parentRow, isUOMHours, isAvgMode, monthIndex, newValue, isActuals) { if (!filter || !header || !resourceId || !resRow || !projectId || !parentRow) { return null; } var month = header.Months[monthIndex]; if (!month || !month.Childs) { return null; } newValue = parseFloat(newValue) || 0; if (isAvgMode) { newValue *= month.Childs.length; } return this.alignTotalValue(filter, header, resourceId, resRow, projectId, parentRow, isUOMHours, isAvgMode, month.Childs, newValue, isActuals, this.changeResourceWeekValue); }, changeTeamMonthValue: function (filter, header, teamId, teamRow, projectId, expCatRow, isUOMHours, isAvgMode, monthIndex, newValue) { if (!filter || !header || !teamId || !teamRow || !projectId || !expCatRow) { return null; } var month = header.Months[monthIndex]; if (!month || !month.Childs) { return null; } newValue = parseFloat(newValue) || 0; if (isAvgMode) { newValue *= month.Childs.length; } return this.alignTotalValue(filter, header, teamId, teamRow, projectId, expCatRow, isUOMHours, isAvgMode, month.Childs, newValue, null, this.changeTeamWeekValue); }, changeNptResourceWeekValue: function (filter, header, remCapacityRow, rollupToRows, nptCatRow, nptItemRow, nptResourceRow, isUOMHours, isAvgMode, $index, newValue) { if (!filter || !header || !nptCatRow || !nptItemRow || !nptResourceRow) { return null; } var week = header.Weeks[$index]; if (!week || week.DataType != Header.DataType.Week) { return null; } if (isNaN(parseFloat(newValue))) { newValue = 0; } var hoursValue = roundService.roundQuantity(isUOMHours ? newValue : hoursResourcesConverter.convertToHours(nptResourceRow.ExpenditureCategoryId, newValue)); var deltaHoursValue = roundService.roundQuantity(hoursValue - (nptResourceRow.QuantityHoursValues[$index] || 0)); var changedCell = { NonProjectTimeCategoryId: nptCatRow.Id, NonProjectTimeId: nptItemRow.Id, ResourceId: nptResourceRow.Id, WeekEnding: week.Milliseconds, }; activityCalendarService.changeNptResourceValue(filter, nptItemRow.Id, nptResourceRow.Id, nptResourceRow.ExpenditureCategoryId, week.Milliseconds, hoursValue); this.updateNptResourceValueInViewModel(header, nptResourceRow, rollupToRows, remCapacityRow, isUOMHours, isAvgMode, deltaHoursValue, $index); return changedCell; }, changeNptResourceMonthValue: function (filter, header, remCapacityRow, rollupToRows, nptCatRow, nptItemRow, nptResourceRow, isUOMHours, isAvgMode, monthIndex, newValue) { if (!filter || !header || !nptCatRow || !nptItemRow || !nptResourceRow) { return null; } var month = header.Months[monthIndex]; if (!month || !month.Childs) { return null; } newValue = parseFloat(newValue) || 0; if (isAvgMode) { newValue *= month.Childs.length; } return this.alignNptResourceTotalValue(filter, header, remCapacityRow, rollupToRows, nptCatRow, nptItemRow, nptResourceRow, isUOMHours, isAvgMode, month.Childs, newValue); }, changeTeamWideNptWeekValue: function (filter, header, remCapacityRow, rollupToRows, nptCatRow, nptItemRow, isUOMHours, isAvgMode, $index, newValue) { if (!filter || !header || !nptCatRow || !nptItemRow || !nptItemRow.Resources || (nptItemRow.Resources.length < 1)) { return null; } var week = header.Weeks[$index]; if (!week || week.DataType != Header.DataType.Week) { return null; } if (isNaN(parseFloat(newValue))) { newValue = 0; } // Get current allocations for NPT-resources and calculate total delta in hours and resources var currentAllocations = activityCalendarService.getNonProjectTimeResourceAllocations(nptItemRow.Id, week.Milliseconds); var requiredResources = nptItemRow.Resources.map(function (e) { return e.Id; }); var resourcesMissed = !Object.keysInObject(requiredResources, currentAllocations); if (resourcesMissed) { throw "Unable to update team-wide non-project time allocations: resource not found"; } var keys = Object.keys(currentAllocations); var values = Object.values(currentAllocations); var alignedValues = calculateDistributionService.alignValues(values, null, newValue); var deltaHoursTotal = 0; var deltaResourcesTotal = 0; for (var index = 0; index < nptItemRow.Resources.length; index++) { var resource = nptItemRow.Resources[index]; var resourceValueIndex = keys.indexOf(resource.Id); var resourceValue = alignedValues[resourceValueIndex]; var oldValue = currentAllocations[resource.Id] || 0; var hoursValue = roundService.roundQuantity(isUOMHours ? resourceValue : hoursResourcesConverter.convertToHours(resource.ExpenditureCategoryId, resourceValue)); var deltaHours = roundService.roundQuantity(hoursValue - oldValue); var deltaResources = roundService.roundQuantity(hoursResourcesConverter.convertToResources(resource.ExpenditureCategoryId, deltaHours)); deltaHoursTotal += deltaHours; deltaResourcesTotal += deltaResources; activityCalendarService.changeTeamWideNptValue(filter, nptItemRow.Id, resource.Id, resource.ExpenditureCategoryId, week.Milliseconds, hoursValue); } // final rounding of total values deltaHoursTotal = roundService.roundQuantity(deltaHoursTotal); deltaResourcesTotal = roundService.roundQuantity(deltaResourcesTotal); this.updateTeamWideNptValuesInViewModel(header, remCapacityRow, rollupToRows, deltaHoursTotal, deltaResourcesTotal, isUOMHours, isAvgMode, $index); var changedCell = { NonProjectTimeCategoryId: nptCatRow.Id, NonProjectTimeId: nptItemRow.Id, WeekEnding: week.Milliseconds, }; return changedCell; }, changeTeamWideNptMonthValue: function (filter, header, remCapacityRow, rollupToRows, nptCatRow, nptItemRow, isUOMHours, isAvgMode, monthIndex, newValue) { if (!filter || !header || !nptCatRow || !nptItemRow) { return null; } var month = header.Months[monthIndex]; if (!month || !month.Childs) { return null; } newValue = parseFloat(newValue) || 0; if (isAvgMode) { newValue *= month.Childs.length; } return this.alignTeamWideNptTotalValue(filter, header, remCapacityRow, rollupToRows, nptCatRow, nptItemRow, isUOMHours, isAvgMode, month.Childs, newValue); }, // todo: replace expCatRow with expCatId and do not update RemainingCapacity in expCatRow because it should be calculated on the fly from DAL changeResourceGrandTotalValue: function (filter, header, resourceId, resRow, projectId, startDate, endDate, parentRow, isUOMHours, isAvgMode, newValue, isActuals) { if (!filter || !header || !resourceId || !resRow || !projectId || !parentRow) { return; } var weeks = header.getWeeks(startDate, endDate); if (!weeks) { return; } var weekIndexes = Object.keys(weeks); newValue = parseFloat(newValue) || 0; if (isAvgMode) { newValue *= weekIndexes.length; } return this.alignTotalValue(filter, header, resourceId, resRow, projectId, parentRow, isUOMHours, isAvgMode, weekIndexes, newValue, isActuals, this.changeResourceWeekValue); }, changeTeamGrandTotalValue: function (filter, header, teamId, teamRow, projectId, startDate, endDate, expCatRow, isUOMHours, isAvgMode, newValue) { if (!filter || !header || !teamId || !teamRow || !projectId || !expCatRow) { return; } var weeks = header.getWeeks(startDate, endDate); if (!weeks) { return; } var weekIndexes = Object.keys(weeks); newValue = parseFloat(newValue) || 0; if (isAvgMode) { newValue *= weekIndexes.length; } return this.alignTotalValue(filter, header, teamId, teamRow, projectId, expCatRow, isUOMHours, isAvgMode, weekIndexes, newValue, null, this.changeTeamWeekValue); }, /// changeWeekHandler - service method to handle weekly change (changeResourceWeekValue or changeTeamWeekValue) alignResourceTotalValue: function (filter, header, resourceId, resRow, projectId, expCatRow, isUOMHours, isAvgMode, weeks, newValue, isActuals) { if (!filter || !header || !resourceId || !resRow || !projectId || !expCatRow || !weeks || !weeks.length) { return null; } var sortedWeeks = resRow.sortWeeks(weeks); if (!sortedWeeks || !sortedWeeks.Editable || !sortedWeeks.Editable.length) { return null; } var readOnlyTotal = resRow.calculateResourceTotalOnRange(sortedWeeks.ReadOnly); var newTotal = roundService.roundQuantity(Math.max((parseFloat(newValue) || 0) - readOnlyTotal, 0)); var changedCells = []; var alignedValues = calculateDistributionService.alignValues(resRow.Cells, sortedWeeks.Editable, newTotal); if (alignedValues) { for (var key in alignedValues) { var weekIndex = parseInt(key); var weekValue = alignedValues[weekIndex] || 0; // to-do: change expCatRow to expCatId var changedCell = this.changeResourceWeekValue(filter, header, resourceId, resRow, projectId, expCatRow, isUOMHours, isAvgMode, weekIndex, weekValue, isActuals); if (changedCell) { changedCells.push(changedCell); } } } return changedCells; }, alignTotalValue: function (filter, header, rowId, row, projectId, parentRow, isUOMHours, isAvgMode, weeks, newValue, isActuals, changeWeekHandler) { if (!filter || !header || !rowId || !row || !projectId || !parentRow || !weeks || !weeks.length || 'function' !== typeof (changeWeekHandler)) { return null; } var sortedWeeks = row.sortWeeks(weeks); if (!sortedWeeks || !sortedWeeks.Editable || !sortedWeeks.Editable.length) { return null; } var readOnlyTotal = row.calculateResourceTotalOnRange(sortedWeeks.ReadOnly); var newTotal = roundService.roundQuantity(Math.max((parseFloat(newValue) || 0) - readOnlyTotal, 0)); var changedCells = []; var alignedValues = calculateDistributionService.alignValues(row.Cells, sortedWeeks.Editable, newTotal); if (alignedValues) { for (var key in alignedValues) { var weekIndex = parseInt(key); var weekValue = alignedValues[weekIndex] || 0; var changedCell = changeWeekHandler.apply(this, [filter, header, rowId, row, projectId, parentRow, isUOMHours, isAvgMode, weekIndex, weekValue, isActuals]); if (changedCell) { changedCells.push(changedCell); } } } return changedCells; }, alignNptResourceTotalValue: function (filter, header, remCapacityRow, rollupToRows, nptCatRow, nptItemRow, nptResourceRow, isUOMHours, isAvgMode, weeks, newValue) { if (!filter || !header || !nptCatRow || !nptItemRow || !nptResourceRow || !weeks || !weeks.length) { return null; } var sortedWeeks = nptResourceRow.sortWeeks(weeks); if (!sortedWeeks || !sortedWeeks.Editable || !sortedWeeks.Editable.length) { return null; } var readOnlyTotal = nptResourceRow.calculateResourceTotalOnRange(sortedWeeks.ReadOnly); var newTotal = roundService.roundQuantity(Math.max((parseFloat(newValue) || 0) - readOnlyTotal, 0)); var changedCells = []; var alignedValues = calculateDistributionService.alignValues(nptResourceRow.Cells, sortedWeeks.Editable, newTotal); if (alignedValues) { for (var key in alignedValues) { var weekIndex = parseInt(key); var weekValue = alignedValues[weekIndex] || 0; var changedCell = this.changeNptResourceWeekValue(filter, header, remCapacityRow, rollupToRows, nptCatRow, nptItemRow, nptResourceRow, isUOMHours, isAvgMode, weekIndex, weekValue); if (changedCell) { changedCells.push(changedCell); } } } return changedCells; }, alignTeamWideNptTotalValue: function (filter, header, remCapacityRow, rollupToRows, nptCatRow, nptItemRow, isUOMHours, isAvgMode, weeks, newValue) { if (!filter || !header || !nptCatRow || !nptItemRow || !weeks || !weeks.length) { return null; } var sortedWeeks = nptItemRow.sortWeeks(weeks); if (!sortedWeeks || !sortedWeeks.Editable || !sortedWeeks.Editable.length) { return null; } var readOnlyTotal = nptItemRow.calculateResourceTotalOnRange(sortedWeeks.ReadOnly); var newTotal = roundService.roundQuantity(Math.max((parseFloat(newValue) || 0) - readOnlyTotal, 0)); var changedCells = []; var alignedValues = calculateDistributionService.alignValues(nptItemRow.Cells, sortedWeeks.Editable, newTotal); if (alignedValues) { for (var key in alignedValues) { var weekIndex = parseInt(key); var weekValue = alignedValues[weekIndex] || 0; var changedCell = this.changeTeamWideNptWeekValue(filter, header, remCapacityRow, rollupToRows, nptCatRow, nptItemRow, isUOMHours, isAvgMode, weekIndex, weekValue); if (changedCell) { changedCells.push(changedCell); } } } return changedCells; }, // todo: replace expCatRow with expCatId updateResourceValueInViewModel: function (header, resRow, parentRow, isUOMHours, isAvgMode, deltaHours, colIndex) { if (!header || !resRow || !parentRow) { return; } if (isNaN(colIndex = parseInt(colIndex)) || colIndex < 0) { return; } var week = header.Weeks[colIndex], month = header.Months[week.ParentIndex], monthIndex = month.SelfIndexInWeeks, monthWeeksCount = month.Childs.length; // Caution: we should round result of operation because of known javascript math problem: http://www.webdeveloper.com/forum/showthread.php?92612-Basic-(-)-Javascript-math-precision-problem // Example: 4.615385 + 6.923077 + 6.923077 + 6.923077 + 4.615384 = 29.999999999999996, but should be 30 var expCatId = parentRow.ExpenditureCategoryId || parentRow.Id; resRow.QuantityHoursValues[colIndex] = roundService.roundQuantity(resRow.QuantityHoursValues[colIndex] + (deltaHours || 0)); resRow.QuantityHoursValues[monthIndex] = roundService.roundQuantity(resRow.QuantityHoursValues[monthIndex] + (deltaHours || 0)); resRow.TotalHoursValue = roundService.roundQuantity(resRow.TotalHoursValue + (deltaHours || 0)); resRow.QuantityResourcesValues[colIndex] = roundService.roundQuantity(hoursResourcesConverter.convertToResources(expCatId, resRow.QuantityHoursValues[colIndex])); resRow.QuantityResourcesValues[monthIndex] = roundService.roundQuantity(hoursResourcesConverter.convertToResources(expCatId, resRow.QuantityHoursValues[monthIndex])); resRow.TotalResourcesValue = roundService.roundQuantity(hoursResourcesConverter.convertToResources(expCatId, resRow.TotalHoursValue)); if (parentRow.RemainingCapacityValues) { parentRow.RemainingCapacityValues[colIndex] = roundService.roundQuantity(parentRow.RemainingCapacityValues[colIndex] - (deltaHours || 0)); parentRow.RemainingCapacityValues[monthIndex] = roundService.roundQuantity(parentRow.RemainingCapacityValues[monthIndex] - (deltaHours || 0)); } if (isUOMHours) { resRow.Cells[colIndex] = resRow.QuantityHoursValues[colIndex]; resRow.Cells[monthIndex] = resRow.QuantityHoursValues[monthIndex]; resRow.TotalValue = resRow.TotalHoursValue; } else { resRow.Cells[colIndex] = resRow.QuantityResourcesValues[colIndex]; resRow.Cells[monthIndex] = isAvgMode ? roundService.roundQuantity(resRow.QuantityResourcesValues[monthIndex] / monthWeeksCount) : resRow.QuantityResourcesValues[monthIndex]; resRow.TotalValue = isAvgMode ? roundService.roundQuantity(resRow.TotalResourcesValue / resRow.VisibleCellsCount) : resRow.TotalResourcesValue; } }, updateTeamValueInViewModel: function (header, teamRow, expCatRow, isUOMHours, isAvgMode, deltaHours, colIndex) { if (!header || !teamRow || !expCatRow) { return; } if (isNaN(colIndex = parseInt(colIndex)) || colIndex < 0) { return; } var week = header.Weeks[colIndex], month = header.Months[week.ParentIndex], monthIndex = month.SelfIndexInWeeks, monthWeeksCount = month.Childs.length; teamRow.QuantityHoursValues[colIndex] = roundService.roundQuantity(teamRow.QuantityHoursValues[colIndex] + (deltaHours || 0)); teamRow.QuantityHoursValues[monthIndex] = roundService.roundQuantity(teamRow.QuantityHoursValues[monthIndex] + (deltaHours || 0)); teamRow.TotalHoursValue = roundService.roundQuantity(teamRow.TotalHoursValue + (deltaHours || 0)); teamRow.QuantityResourcesValues[colIndex] = roundService.roundQuantity(hoursResourcesConverter.convertToResources(expCatRow.Id, teamRow.QuantityHoursValues[colIndex])); teamRow.QuantityResourcesValues[monthIndex] = roundService.roundQuantity(hoursResourcesConverter.convertToResources(expCatRow.Id, teamRow.QuantityHoursValues[monthIndex])); teamRow.TotalResourcesValue = roundService.roundQuantity(hoursResourcesConverter.convertToResources(expCatRow.Id, teamRow.TotalHoursValue)); if (expCatRow.RemainingCapacityValues) { expCatRow.RemainingCapacityValues[colIndex] = roundService.roundQuantity(expCatRow.RemainingCapacityValues[colIndex] - (deltaHours || 0)); expCatRow.RemainingCapacityValues[monthIndex] = roundService.roundQuantity(expCatRow.RemainingCapacityValues[monthIndex] - (deltaHours || 0)); } if (isUOMHours) { teamRow.Cells[colIndex] = teamRow.QuantityHoursValues[colIndex]; teamRow.Cells[monthIndex] = teamRow.QuantityHoursValues[monthIndex]; teamRow.TotalValue = teamRow.TotalHoursValue; } else { teamRow.Cells[colIndex] = teamRow.QuantityResourcesValues[colIndex]; teamRow.Cells[monthIndex] = isAvgMode ? roundService.roundQuantity(teamRow.QuantityResourcesValues[monthIndex] / monthWeeksCount) : teamRow.QuantityResourcesValues[monthIndex]; teamRow.TotalValue = isAvgMode ? roundService.roundQuantity(teamRow.TotalResourcesValue / teamRow.VisibleCellsCount) : teamRow.TotalResourcesValue; } }, updateNptResourceValueInViewModel: function (header, nptResourceRow, rollupToRows, remCapacityRow, isUOMHours, isAvgMode, deltaHours, colIndex) { if (!header || !nptResourceRow) { return; } if (isNaN(colIndex = parseInt(colIndex)) || colIndex < 0) { return; } var deltaResources = hoursResourcesConverter.convertToResources(nptResourceRow.ExpenditureCategoryId, deltaHours) || 0; var week = header.Weeks[colIndex], month = header.Months[week.ParentIndex], monthIndex = month.SelfIndexInWeeks, monthWeeksCount = month.Childs.length; // Caution: we should round result of operation because of known javascript math problem: http://www.webdeveloper.com/forum/showthread.php?92612-Basic-(-)-Javascript-math-precision-problem // Example: 4.615385 + 6.923077 + 6.923077 + 6.923077 + 4.615384 = 29.999999999999996, but should be 30 // Update of NPT resource row nptResourceRow.QuantityHoursValues[colIndex] = roundService.roundQuantity(nptResourceRow.QuantityHoursValues[colIndex] + (deltaHours || 0)); nptResourceRow.QuantityHoursValues[monthIndex] = roundService.roundQuantity(nptResourceRow.QuantityHoursValues[monthIndex] + (deltaHours || 0)); nptResourceRow.TotalHoursValue = roundService.roundQuantity(nptResourceRow.TotalHoursValue + (deltaHours || 0)); nptResourceRow.QuantityResourcesValues[colIndex] = roundService.roundQuantity(nptResourceRow.QuantityResourcesValues[colIndex] + deltaResources); nptResourceRow.QuantityResourcesValues[monthIndex] = roundService.roundQuantity(nptResourceRow.QuantityResourcesValues[monthIndex] + deltaResources); nptResourceRow.TotalResourcesValue = roundService.roundQuantity(nptResourceRow.TotalResourcesValue + deltaResources); if (rollupToRows && rollupToRows.length) { for (var index = 0; index < rollupToRows.length; index++) { var rollupRow = rollupToRows[index]; rollupRow.QuantityHoursValues[colIndex] = roundService.roundQuantity(rollupRow.QuantityHoursValues[colIndex] + (deltaHours || 0)); rollupRow.QuantityHoursValues[monthIndex] = roundService.roundQuantity(rollupRow.QuantityHoursValues[monthIndex] + (deltaHours || 0)); rollupRow.TotalHoursValue = roundService.roundQuantity(rollupRow.TotalHoursValue + (deltaHours || 0)); rollupRow.QuantityResourcesValues[colIndex] = roundService.roundQuantity(rollupRow.QuantityResourcesValues[colIndex] + deltaResources); rollupRow.QuantityResourcesValues[monthIndex] = roundService.roundQuantity(rollupRow.QuantityResourcesValues[monthIndex] + deltaResources); rollupRow.TotalResourcesValue = roundService.roundQuantity(rollupRow.TotalResourcesValue + deltaResources); } } if (remCapacityRow) { // Update of Remaining Capacity row remCapacityRow.QuantityHoursValues[colIndex] = roundService.roundQuantity(remCapacityRow.QuantityHoursValues[colIndex] - (deltaHours || 0)); remCapacityRow.QuantityHoursValues[monthIndex] = roundService.roundQuantity(remCapacityRow.QuantityHoursValues[monthIndex] - (deltaHours || 0)); remCapacityRow.TotalHoursValue = roundService.roundQuantity(remCapacityRow.TotalHoursValue - (deltaHours || 0)); remCapacityRow.QuantityResourcesValues[colIndex] = roundService.roundQuantity(remCapacityRow.QuantityResourcesValues[colIndex] - deltaResources); remCapacityRow.QuantityResourcesValues[monthIndex] = roundService.roundQuantity(remCapacityRow.QuantityResourcesValues[monthIndex] - deltaResources); remCapacityRow.TotalResourcesValue = roundService.roundQuantity(remCapacityRow.TotalResourcesValue - deltaResources); } if (isUOMHours) { nptResourceRow.Cells[colIndex] = nptResourceRow.QuantityHoursValues[colIndex]; nptResourceRow.Cells[monthIndex] = nptResourceRow.QuantityHoursValues[monthIndex]; nptResourceRow.TotalValue = nptResourceRow.TotalHoursValue; if (rollupToRows && rollupToRows.length) { for (var index = 0; index < rollupToRows.length; index++) { var rollupRow = rollupToRows[index]; rollupRow.Cells[colIndex] = rollupRow.QuantityHoursValues[colIndex]; rollupRow.Cells[monthIndex] = rollupRow.QuantityHoursValues[monthIndex]; rollupRow.TotalValue = rollupRow.TotalHoursValue; } } if (remCapacityRow) { remCapacityRow.Cells[colIndex] = remCapacityRow.QuantityHoursValues[colIndex]; remCapacityRow.Cells[monthIndex] = remCapacityRow.QuantityHoursValues[monthIndex]; remCapacityRow.TotalValue = remCapacityRow.TotalHoursValue; } } else { nptResourceRow.Cells[colIndex] = nptResourceRow.QuantityResourcesValues[colIndex]; nptResourceRow.Cells[monthIndex] = isAvgMode ? roundService.roundQuantity(nptResourceRow.QuantityResourcesValues[monthIndex] / monthWeeksCount) : nptResourceRow.QuantityResourcesValues[monthIndex]; nptResourceRow.TotalValue = isAvgMode ? roundService.roundQuantity(nptResourceRow.TotalResourcesValue / nptResourceRow.VisibleCellsCount) : nptResourceRow.TotalResourcesValue; if (rollupToRows && rollupToRows.length) { for (var index = 0; index < rollupToRows.length; index++) { var rollupRow = rollupToRows[index]; rollupRow.Cells[colIndex] = rollupRow.QuantityResourcesValues[colIndex]; rollupRow.Cells[monthIndex] = isAvgMode ? roundService.roundQuantity(rollupRow.QuantityResourcesValues[monthIndex] / monthWeeksCount) : rollupRow.QuantityResourcesValues[monthIndex]; rollupRow.TotalValue = isAvgMode ? roundService.roundQuantity(rollupRow.TotalResourcesValue / rollupRow.VisibleCellsCount) : rollupRow.TotalResourcesValue; } } if (remCapacityRow) { remCapacityRow.Cells[colIndex] = remCapacityRow.QuantityResourcesValues[colIndex]; remCapacityRow.Cells[monthIndex] = isAvgMode ? roundService.roundQuantity(remCapacityRow.QuantityResourcesValues[monthIndex] / monthWeeksCount) : remCapacityRow.QuantityResourcesValues[monthIndex]; remCapacityRow.TotalValue = isAvgMode ? roundService.roundQuantity(remCapacityRow.TotalResourcesValue / remCapacityRow.VisibleCellsCount) : remCapacityRow.TotalResourcesValue; } } }, updateTeamWideNptValuesInViewModel: function (header, remCapacityRow, rowsToUpdate, deltaHours, deltaResources, isUOMHours, isAvgMode, colIndex) { if (!header || !rowsToUpdate || !rowsToUpdate.length) { return; } if (isNaN(colIndex = parseInt(colIndex)) || colIndex < 0) { return; } var week = header.Weeks[colIndex], month = header.Months[week.ParentIndex], monthIndex = month.SelfIndexInWeeks, monthWeeksCount = month.Childs.length; // Caution: we should round result of operation because of known javascript math problem: http://www.webdeveloper.com/forum/showthread.php?92612-Basic-(-)-Javascript-math-precision-problem // Example: 4.615385 + 6.923077 + 6.923077 + 6.923077 + 4.615384 = 29.999999999999996, but should be 30 for (var index = 0; index < rowsToUpdate.length; index++) { var row = rowsToUpdate[index]; row.QuantityHoursValues[colIndex] = roundService.roundQuantity(row.QuantityHoursValues[colIndex] + (deltaHours || 0)); row.QuantityHoursValues[monthIndex] = roundService.roundQuantity(row.QuantityHoursValues[monthIndex] + (deltaHours || 0)); row.TotalHoursValue = roundService.roundQuantity(row.TotalHoursValue + (deltaHours || 0)); row.QuantityResourcesValues[colIndex] = roundService.roundQuantity(row.QuantityResourcesValues[colIndex] + deltaResources); row.QuantityResourcesValues[monthIndex] = roundService.roundQuantity(row.QuantityResourcesValues[monthIndex] + deltaResources); row.TotalResourcesValue = roundService.roundQuantity(row.TotalResourcesValue + deltaResources); } if (remCapacityRow) { // Update of Remaining Capacity row remCapacityRow.QuantityHoursValues[colIndex] = roundService.roundQuantity(remCapacityRow.QuantityHoursValues[colIndex] - (deltaHours || 0)); remCapacityRow.QuantityHoursValues[monthIndex] = roundService.roundQuantity(remCapacityRow.QuantityHoursValues[monthIndex] - (deltaHours || 0)); remCapacityRow.TotalHoursValue = roundService.roundQuantity(remCapacityRow.TotalHoursValue - (deltaHours || 0)); remCapacityRow.QuantityResourcesValues[colIndex] = roundService.roundQuantity(remCapacityRow.QuantityResourcesValues[colIndex] - deltaResources); remCapacityRow.QuantityResourcesValues[monthIndex] = roundService.roundQuantity(remCapacityRow.QuantityResourcesValues[monthIndex] - deltaResources); remCapacityRow.TotalResourcesValue = roundService.roundQuantity(remCapacityRow.TotalResourcesValue - deltaResources); } if (isUOMHours) { for (var index = 0; index < rowsToUpdate.length; index++) { var row = rowsToUpdate[index]; row.Cells[colIndex] = row.QuantityHoursValues[colIndex]; row.Cells[monthIndex] = row.QuantityHoursValues[monthIndex]; row.TotalValue = row.TotalHoursValue; } if (remCapacityRow) { remCapacityRow.Cells[colIndex] = remCapacityRow.QuantityHoursValues[colIndex]; remCapacityRow.Cells[monthIndex] = remCapacityRow.QuantityHoursValues[monthIndex]; remCapacityRow.TotalValue = remCapacityRow.TotalHoursValue; } } else { for (var index = 0; index < rowsToUpdate.length; index++) { var row = rowsToUpdate[index]; row.Cells[colIndex] = row.QuantityResourcesValues[colIndex]; row.Cells[monthIndex] = isAvgMode ? roundService.roundQuantity(row.QuantityResourcesValues[monthIndex] / monthWeeksCount) : row.QuantityResourcesValues[monthIndex]; row.TotalValue = isAvgMode ? roundService.roundQuantity(row.TotalResourcesValue / row.VisibleCellsCount) : row.TotalResourcesValue; } if (remCapacityRow) { remCapacityRow.Cells[colIndex] = remCapacityRow.QuantityResourcesValues[colIndex]; remCapacityRow.Cells[monthIndex] = isAvgMode ? roundService.roundQuantity(remCapacityRow.QuantityResourcesValues[monthIndex] / monthWeeksCount) : remCapacityRow.QuantityResourcesValues[monthIndex]; remCapacityRow.TotalValue = isAvgMode ? roundService.roundQuantity(remCapacityRow.TotalResourcesValue / remCapacityRow.VisibleCellsCount) : remCapacityRow.TotalResourcesValue; } } }, recalculateResourceAvailability: function (filter, header, projectRows, resourceId) { if (!filter || !header || !projectRows || !projectRows.length || !resourceId) { return; } for (var projectIndex = 0; projectIndex < projectRows.length; projectIndex++) { var projectRow = projectRows[projectIndex]; if (projectRow.Children && projectRow.Children.length) { for (var ecIndex = 0; ecIndex < projectRow.Children.length; ecIndex++) { var ecRow = projectRow.Children[ecIndex]; if (ecRow.AvailableResources && ecRow.AvailableResources[resourceId]) { var project = activityCalendarService.getProjectById(filter, projectRow.Id); if (project && project.ActiveScenario) { var availableResourceRow = ecRow.AvailableResources[resourceId]; var availableResourceTeams = availableResourceRow.teams ? availableResourceRow.teams.map(function (team) { return team.TeamId; }) : []; var availableResourceData = this.getAvailableResources4Project(filter, header, project, ecRow.Id, availableResourceTeams, resourceId); if (availableResourceData && availableResourceData[resourceId]) { var resource = availableResourceData[resourceId]; availableResourceRow.minAvailability = resource.MinAvailability; availableResourceRow.maxAvailability = resource.MaxAvailability; availableResourceRow.avgAvailability = resource.AvgAvailability; } } } } } } }, /* Highlighting methods */ updateResourceWeekStyles: function (resourceId, resourceRow, weekEndingMs, weekIndex) { if (!resourceId || !resourceRow || !resourceRow.CSSClass || !weekEndingMs) { return; } resourceRow.CSSClass[weekIndex] = cellHighlightingService.removeHighlightClasses(resourceRow.CSSClass[weekIndex]); var resourceIsOverAllocated = activityCalendarService.isResourceOverAllocated(resourceId, weekEndingMs); // set overallocated class only if current cell contains a positive value if (resourceIsOverAllocated === true && resourceRow.QuantityHoursValues && (resourceRow.QuantityHoursValues[weekIndex] || 0) > 0) { resourceRow.CSSClass[weekIndex] = resourceRow.CSSClass[weekIndex] + ' ' + cellHighlightingService.cellOverClass; } }, updateResourceStyles: function (header, resourceId, resourceRows, data) { if (!header || !resourceId || !resourceRows || !resourceRows.length || typeof data !== 'object' || !angular.isArray(data) || !data.length) { return; } for (var rIndex = 0; rIndex < resourceRows.length; rIndex++) { var resourceRow = resourceRows[rIndex]; var monthsToUpdate = []; for (var i = 0; i < data.length; i++) { var monthIndex = data[i].MonthIndex; var weekIndex = data[i].WeekIndex; var weekEndingMs = data[i].WeekEnding; this.updateResourceWeekStyles(resourceId, resourceRow, weekEndingMs, weekIndex); if (monthsToUpdate.indexOf(monthIndex) < 0) { monthsToUpdate.push(monthIndex); } } for (var mIndex = 0; mIndex < monthsToUpdate.length; mIndex++) { var month = header.Months[monthsToUpdate[mIndex]]; this.updateMonthStyles(resourceRow, month.SelfIndexInWeeks, month.Childs); } } }, updateResourceStylesOnEntireRange: function (header, resourceId, resourceRow) { if (!header || !resourceId || !resourceRow || !resourceRow.QuantityHoursValues) { return; } for (var monthIndex = 0; monthIndex < header.Months.length; monthIndex++) { var month = header.Months[monthIndex]; for (var mcIndex = 0; mcIndex < month.Childs.length; mcIndex++) { var weekIndex = month.Childs[mcIndex]; var week = header.Weeks[weekIndex]; if (resourceRow.QuantityHoursValues[weekIndex] == undefined) { continue; } this.updateResourceWeekStyles(resourceId, resourceRow, week.Milliseconds, weekIndex); } this.updateMonthStyles(resourceRow, month.SelfIndexInWeeks, month.Childs); } }, getProjectWeekendingRange: function (header, projectRow) { if (!header || !projectRow) return null; var result = { Weeks: {}, Months: {} }; for (var monthIndex = 0; monthIndex < header.Months.length; monthIndex++) { var month = header.Months[monthIndex]; var monthWeekIncluded = false; for (var mcIndex = 0; mcIndex < month.Childs.length; mcIndex++) { var weekIndex = month.Childs[mcIndex]; var week = header.Weeks[weekIndex]; var weekInScenarioRange = false; if (projectRow.IsMaster) { if (projectRow.Children && angular.isArray(projectRow.Children)) { for (var pIndex = 0; pIndex < projectRow.Children.length; pIndex++) { var partRow = projectRow.Children[pIndex]; if (partRow && partRow.ActiveScenario && !!partRow.ActiveScenario.StartDate && !!partRow.ActiveScenario.EndDate) { if ((week.Milliseconds >= partRow.ActiveScenario.StartDate) && (week.Milliseconds <= partRow.ActiveScenario.EndDate)) { // Week is inside scenario range weekInScenarioRange = true; } } } } } else { if (!!projectRow.ActiveScenario.StartDate && !!projectRow.ActiveScenario.EndDate) { if ((week.Milliseconds >= projectRow.ActiveScenario.StartDate) && (week.Milliseconds <= projectRow.ActiveScenario.EndDate)) { // Week is inside scenario range weekInScenarioRange = true; } } } if (weekInScenarioRange) { result.Weeks[week.Milliseconds] = weekIndex; monthWeekIncluded = true; // usage: this.updateProjectWeekStyles(projectRow, week.Milliseconds, weekIndex); } } if (monthWeekIncluded) { result.Months[month.SelfIndexInWeeks] = month.Childs; // usage: this.updateMonthStyles(projectRow, month.SelfIndexInWeeks, month.Childs); } } return result; }, getChangesDataWeekendingRange: function (header, data) { if (!header || (typeof data !== 'object') || !angular.isArray(data) || !data.length) { return null; } var result = { Weeks: {}, Months: {} }; var prevMonthIndex = data[0].MonthIndex; for (var i = 0; i < data.length; i++) { var monthIndex = data[i].MonthIndex; var weekIndex = data[i].WeekIndex; var weekEndingMs = data[i].WeekEnding; result.Weeks[weekEndingMs] = weekIndex; if (i === (data.length - 1) || prevMonthIndex !== monthIndex) { var month = header.Months[prevMonthIndex]; result.Months[month.SelfIndexInWeeks] = month.Childs; prevMonthIndex = monthIndex; } } return result; }, getAgregates4HighlightingNeedVsTa: function (projectsData, weekendings, includeRestTeamAllocations, agregateToMasterProject) { // Agregates to validate Project Need vs Team Allocations if (!projectsData || !weekendings || !angular.isArray(weekendings)) return null; var result = {}; var multipleProjects = Object.keys(projectsData).length > 1; for (var wIndex = 0; wIndex < weekendings.length; wIndex++) { var we = weekendings[wIndex]; var masterProjectResult = { Level: 'Master', TargetValue: 0, CalculatedValue: 0, Children: {} }; for (var projectId in projectsData) { var projectData = projectsData[projectId]; var prjResult = { Level: 'Project', TargetValue: 0, CalculatedValue: 0, Children: {} }; if (agregateToMasterProject || multipleProjects) { // Need to get agregated data to master project level masterProjectResult.Children[projectId] = prjResult; } for (var expCatId in projectData.TeamsInView) { var expCat = projectData.TeamsInView[expCatId]; if (expCat && expCat.NeedAllocations) { var expCatResult = { Level: 'Category', TargetValue: roundService.roundQuantity(expCat.NeedAllocations[we] || 0), CalculatedValue: 0 }; if (includeRestTeamAllocations && projectData.TeamsOutOfView && (expCatId in projectData.TeamsOutOfView)) { var expCatOutOfViewData = projectData.TeamsOutOfView[expCatId]; if (expCatOutOfViewData && expCatOutOfViewData.Allocations && (we in expCatOutOfViewData.Allocations)) { // For No Team block (Group by Team mode) it's necessary to take rest teams allocations in calculation also expCatResult.CalculatedValue = angular.isNumber(expCatOutOfViewData.Allocations[we]) ? Number(expCatOutOfViewData.Allocations[we]) : 0; } } prjResult.Children[expCatId] = expCatResult; prjResult.TargetValue += expCatResult.TargetValue; if (expCat.Teams) { for (var teamId in expCat.Teams) { var team = expCat.Teams[teamId]; if (team && team.Allocations) { expCatResult.CalculatedValue += team.Allocations[we] || 0; } } } expCatResult.CalculatedValue = roundService.roundQuantity(expCatResult.CalculatedValue); prjResult.CalculatedValue += expCatResult.CalculatedValue; } } prjResult.TargetValue = roundService.roundQuantity(prjResult.TargetValue); prjResult.CalculatedValue = roundService.roundQuantity(prjResult.CalculatedValue); if (agregateToMasterProject || multipleProjects) { // Need to get agregated data to master project level masterProjectResult.TargetValue += prjResult.TargetValue; masterProjectResult.CalculatedValue += prjResult.CalculatedValue; masterProjectResult.Children[projectId] = prjResult; } else { // No master project level is needed. Simple project well be on the top of agregated data in result result[we] = prjResult; } } if (agregateToMasterProject || multipleProjects) { // Need to get agregated data to master project level. Master project will be on the top of agregated data result masterProjectResult.TargetValue = roundService.roundQuantity(masterProjectResult.TargetValue); masterProjectResult.CalculatedValue = roundService.roundQuantity(masterProjectResult.CalculatedValue); result[we] = masterProjectResult; } } return result; }, getAgregates4HighlightingTaVsRa: function (projectsData, weekendings, agregateToMasterProject) { // Agregates to validate Team allocations vs Resource allocations if (!projectsData || !weekendings || !angular.isArray(weekendings)) return null; var result = {}; var multipleProjects = Object.keys(projectsData).length > 1; for (var wIndex = 0; wIndex < weekendings.length; wIndex++) { var we = weekendings[wIndex]; var masterProjectResult = { Level: 'Master', TargetValue: 0, CalculatedValue: 0, Children: {} }; for (var projectId in projectsData) { var projectData = projectsData[projectId]; var prjResult = { Level: 'Project', TargetValue: 0, CalculatedValue: 0, Children: {} }; if (agregateToMasterProject || multipleProjects) { // Need to get agregated data to master project level masterProjectResult.Children[projectId] = prjResult; } for (var expCatId in projectData.TeamsInView) { var expCat = projectData.TeamsInView[expCatId]; if (expCat && expCat.Allocations) { var expCatResult = { Level: 'Category', TargetValue: 0, CalculatedValue: 0, Children: {} }; prjResult.Children[expCatId] = expCatResult; if (expCat.Teams) { for (var teamId in expCat.Teams) { var team = expCat.Teams[teamId]; if (team && team.Allocations) { // for team allocations display mode var teamResult = { Level: 'Team', TargetValue: team.Allocations[we] || 0, CalculatedValue: 0 }; expCatResult.Children[teamId] = teamResult; if (team.Resources) { for (var resourceId in team.Resources) { var resource = team.Resources[resourceId]; if (resource && resource.Allocations) { teamResult.CalculatedValue += resource.Allocations[we] || 0; }; } teamResult.CalculatedValue = roundService.roundQuantity(teamResult.CalculatedValue); } expCatResult.CalculatedValue += teamResult.CalculatedValue; expCatResult.TargetValue += teamResult.TargetValue; } } } expCatResult.CalculatedValue = roundService.roundQuantity(expCatResult.CalculatedValue); expCatResult.TargetValue = roundService.roundQuantity(expCatResult.TargetValue); prjResult.CalculatedValue += expCatResult.CalculatedValue; prjResult.TargetValue += expCatResult.TargetValue; } } prjResult.TargetValue = roundService.roundQuantity(prjResult.TargetValue); prjResult.CalculatedValue = roundService.roundQuantity(prjResult.CalculatedValue); if (agregateToMasterProject || multipleProjects) { // Need to get agregated data to master project level masterProjectResult.TargetValue += prjResult.TargetValue; masterProjectResult.CalculatedValue += prjResult.CalculatedValue; masterProjectResult.Children[projectId] = prjResult; } else { // No master project level is needed. Simple project well be on the top of agregated data in result result[we] = prjResult; } } if (agregateToMasterProject || multipleProjects) { // Need to get agregated data to master project level. Master project will be on the top of agregated data result masterProjectResult.TargetValue = roundService.roundQuantity(masterProjectResult.TargetValue); masterProjectResult.CalculatedValue = roundService.roundQuantity(masterProjectResult.CalculatedValue); result[we] = masterProjectResult; } } return result; }, getAgregates4HighlightingUneedVsRa: function (projectsData, weekendings, agregateToMasterProject) { // Agregates to validate Unassigned need vs resource allocations // These agregates are necessary for Group By Business Unit & Filter by Business Unit. // Precalculated Unassigned need is in expCat.RemainingNeed collections. Take it as is. // Resources can not be assigned in group by Business Unit mode, so resource allocations are always 0 if (!projectsData || !weekendings || !angular.isArray(weekendings)) return null; var result = {}; var multipleProjects = Object.keys(projectsData).length > 1; for (var wIndex = 0; wIndex < weekendings.length; wIndex++) { var we = weekendings[wIndex]; var masterProjectResult = { Level: 'Master', TargetValue: 0, CalculatedValue: 0, Children: {} }; for (var projectId in projectsData) { var projectData = projectsData[projectId]; var prjResult = { Level: 'Project', TargetValue: 0, CalculatedValue: 0, Children: {} }; if (agregateToMasterProject || multipleProjects) { // Need to get agregated data to master project level masterProjectResult.Children[projectId] = prjResult; } for (var expCatId in projectData.TeamsInView) { var expCat = projectData.TeamsInView[expCatId]; if (expCat && expCat.RemainingNeed) { var expCatResult = { Level: 'Category', TargetValue: roundService.roundQuantity(expCat.UnassignedNeed[we] || 0), CalculatedValue: 0 }; prjResult.Children[expCatId] = expCatResult; prjResult.TargetValue += expCatResult.TargetValue; } } prjResult.TargetValue = roundService.roundQuantity(prjResult.TargetValue); if (agregateToMasterProject || multipleProjects) { // Need to get agregated data to master project level masterProjectResult.TargetValue += prjResult.TargetValue; masterProjectResult.CalculatedValue += prjResult.CalculatedValue; masterProjectResult.Children[projectId] = prjResult; } else { // No master project level is needed. Simple project well be on the top of agregated data in result result[we] = prjResult; } } if (agregateToMasterProject || multipleProjects) { // Need to get agregated data to master project level. Master project will be on the top of agregated data result masterProjectResult.TargetValue = roundService.roundQuantity(masterProjectResult.TargetValue); masterProjectResult.CalculatedValue = roundService.roundQuantity(masterProjectResult.CalculatedValue); result[we] = masterProjectResult; } } return result; }, getProjectDataForHighlighting: function (header, filter, projectIds, allocationMode, expCatsFilter, teamsFilter) { if (!header || !filter || !projectIds || !angular.isArray(projectIds) || !allocationMode) return null; var result = {}; // Filtering of ECs by teams is not performed for No Team block (even if filer for AC is set to Team or View) var isFilteredByTeamOrView = this.isFilteredByTeams(filter) && (allocationMode != this.allocationMode.remainingNeedAllocation); var includeProjectNeed = (allocationMode == this.allocationMode.needAllocation) || (allocationMode == this.allocationMode.remainingNeedAllocation) || (allocationMode == this.allocationMode.unassignedNeedAllocation); var excludeResourceAllocations = !includeProjectNeed; for (var pIndex = 0; pIndex < projectIds.length; pIndex++) { var projectId = projectIds[pIndex]; var projectRawData = activityCalendarService.getProjectById(filter, projectId); if (!projectRawData || !projectRawData.ActiveScenario || !projectRawData.ActiveScenario.Id) return null; var projectData = { TeamsInView: null }; var projectExpCatsData = this.getExpenditures4Project(projectRawData, null, null, header, filter, includeProjectNeed, isFilteredByTeamOrView, excludeResourceAllocations); if ((!expCatsFilter || !angular.isArray(expCatsFilter) || !expCatsFilter.length) && (!teamsFilter || !angular.isArray(teamsFilter) || !teamsFilter.length)) { // No filtering applied projectData.TeamsInView = projectExpCatsData; } else { // Apply filtering by ECs and teams var filteringResult = {}; for (var expCatId in projectExpCatsData) { var currentExpCatData = angular.copy(projectExpCatsData[expCatId]); if (expCatsFilter && angular.isArray(expCatsFilter) && expCatsFilter.length) { if (expCatsFilter.indexOf(expCatId) < 0) { // EC not passed through filter currentExpCatData = null; } } if (currentExpCatData && currentExpCatData.Teams && teamsFilter && angular.isArray(teamsFilter) && teamsFilter.length) { var filteredTeams4ExpCat = {}; for (var teamId in currentExpCatData.Teams) { if (teamsFilter.indexOf(teamId) >= 0) { // Team passed through filter filteredTeams4ExpCat[teamId] = currentExpCatData.Teams[teamId]; } } if (Object.keys(filteredTeams4ExpCat).length) { currentExpCatData.Teams = filteredTeams4ExpCat; } else { // Contains no teams currentExpCatData = null; } } if (currentExpCatData) { filteringResult[expCatId] = currentExpCatData; } } if (Object.keys(filteringResult).length) { projectData.TeamsInView = filteringResult; } } if (allocationMode == this.allocationMode.remainingNeedAllocation) { // Get also team allocations for not visible in currect calendar view teams projectData.TeamsOutOfView = activityCalendarService.getRestTeamAllocations4Scenario(filter, projectRawData.ActiveScenario.Id); } result[projectId] = projectData; } return result; }, getNonHighlightableProjectRows: function (allocationMode) { // Row is not highlightable withing project highlighting engine, because not present in // a particular view, or is editable and is highlighted via edit validation engine var result = {}; if (!allocationMode) return result; switch (allocationMode) { case this.allocationMode.needAllocation: case this.allocationMode.remainingNeedAllocation: result['Team'] = true; break; case this.allocationMode.teamAllocation: case this.allocationMode.remainingTeamAllocation: result['Resource'] = true; break; } // this.allocationMode.unassignedNeedAllocation ?? return result; }, updateProjectStyles: function (header, filter, projectRows, data, expendituresFilter, teamsFilter) { // Call this method with empty param to update styles on entire project range if (!header || !filter || !projectRows || !angular.isArray(projectRows) || !projectRows.length) { return; } var isMasterProject = false; for (var pIndex = 0; pIndex < projectRows.length; pIndex++) { var projectRow = projectRows[pIndex]; // Get weeks and months to update styles for var calendarDatesToUpdate = (typeof data === 'object' && angular.isArray(data) && data.length) ? this.getChangesDataWeekendingRange(header, data) : // Queue style updates to changed weeks only this.getProjectWeekendingRange(header, projectRow); // Queue style updates for entire project range var weekendings = Object.keys(calendarDatesToUpdate.Weeks); // Get project Ids (part ids list for multipart project) var projectIds = []; if (projectRow.IsMaster) { for (var partIndex = 0; partIndex < projectRow.Children.length; partIndex++) { var partRow = projectRow.Children[partIndex]; if (partRow && partRow.Id) { projectIds.push(partRow.Id); } } isMasterProject = true; } else { // Simple project (non-multipart) projectIds.push(projectRow.Id); isMasterProject = false; } // Get allocation mode and rowtypes, which don't participate in highlighting var allocationModeCalculated = this.getAllocationMode(projectRow); var unhighlightableRowTypes = this.getNonHighlightableProjectRows(allocationModeCalculated); var projectsData = this.getProjectDataForHighlighting(header, filter, projectIds, allocationModeCalculated, expendituresFilter, teamsFilter); var projectsDataAgregated = null; switch (allocationModeCalculated) { case this.allocationMode.needAllocation: case this.allocationMode.remainingNeedAllocation: { var includeRestTeamAllocations = (allocationModeCalculated == this.allocationMode.remainingNeedAllocation); projectsDataAgregated = this.getAgregates4HighlightingNeedVsTa(projectsData, weekendings, includeRestTeamAllocations, isMasterProject); break; } case this.allocationMode.teamAllocation: { projectsDataAgregated = this.getAgregates4HighlightingTaVsRa(projectsData, weekendings, isMasterProject); break; } case this.allocationMode.remainingTeamAllocation: { projectsDataAgregated = this.getAgregates4HighlightingTaVsRa(projectsData, weekendings, isMasterProject); break; } case this.allocationMode.unassignedNeedAllocation: { // For AC filtering by Business Unit validation. Unassigned block. Validation of Unassigned project need vs Resource allocations projectsDataAgregated = this.getAgregates4HighlightingUneedVsRa(projectsData, weekendings, isMasterProject); break; } default: { console.log("activityCalendarUIService.updateProjectStyles: Unknown Allocation mode found"); } } if (projectsDataAgregated) { // Update highlighting for week cells for (var weekEndingMs in calendarDatesToUpdate.Weeks) { var weekIndex = calendarDatesToUpdate.Weeks[weekEndingMs]; var projectAgregatesWe = projectsDataAgregated[weekEndingMs]; this.updateProjectWeekStyles(projectRow, weekIndex, projectAgregatesWe, unhighlightableRowTypes); } for (var monthIndex in calendarDatesToUpdate.Months) { var weekIndexes = calendarDatesToUpdate.Months[monthIndex]; this.updateMonthStyles(projectRow, monthIndex, weekIndexes, unhighlightableRowTypes); } } } }, updateProjectWeekStyles: function (row, weekIndex, data, unhighlightableRowTypes) { if (!row || !row.CSSClass || !data) { return; } var filterByRowTypes = unhighlightableRowTypes && (Object.keys(unhighlightableRowTypes).length > 0); if (!filterByRowTypes || (row.RowType && !(row.RowType in unhighlightableRowTypes))) { row.CSSClass[weekIndex] = cellHighlightingService.getWithValidationCssClass(row.CSSClass[weekIndex], data.CalculatedValue || 0, data.TargetValue || 0); } if (row.Children && row.Children.length && data.Children) { for (var index = 0; index < row.Children.length; index++) { var childRow = row.Children[index]; if (childRow.Id) { var childData = data.Children[childRow.Id]; if (childData) { if (!filterByRowTypes || (row.RowType && !(row.RowType in unhighlightableRowTypes))) { this.updateProjectWeekStyles(childRow, weekIndex, childData, unhighlightableRowTypes); } } } } } }, updateMonthStyles: function (row, monthIndexInWeeks, weekIndexes, unhighlightableRowTypes) { if (!row || !row.CSSClass || !weekIndexes || !weekIndexes.length) { return; } var filterByRowTypes = unhighlightableRowTypes && (Object.keys(unhighlightableRowTypes).length > 0); row.CSSClass[monthIndexInWeeks] = cellHighlightingService.removeHighlightClasses(row.CSSClass[monthIndexInWeeks]) || ''; var hasOverAllocated = false; var hasUnderAllocated = false; var cellEqualCount = 0; var monthExistingCells = 0; if (!filterByRowTypes || (row.RowType && !(row.RowType in unhighlightableRowTypes))) { for (var i = 0; i < weekIndexes.length; i++) { if ((row.CSSClass[weekIndexes[i]] || '').indexOf(cellHighlightingService.cellOverClass) >= 0) { hasOverAllocated = true; } else if ((row.CSSClass[weekIndexes[i]] || '').indexOf(cellHighlightingService.cellLessClass) >= 0) { hasUnderAllocated = true; } else if ((row.CSSClass[weekIndexes[i]] || '').indexOf(cellHighlightingService.cellEqualClass) >= 0 || row.QuantityHoursValues[weekIndexes[i]] == 0) // identify zero cell as equal to mark monthly cell equal even if some(not all) of cells are zero { cellEqualCount++; } if (Object.keys(row.CSSClass).indexOf(weekIndexes[i].toString()) >= 0) monthExistingCells++; } if (hasOverAllocated) { // if any cell is colored with red we should color month cell with red as well row.CSSClass[monthIndexInWeeks] = (row.CSSClass[monthIndexInWeeks] || '') + ' ' + cellHighlightingService.cellOverClass; } else if (hasUnderAllocated) {// if there is no cell colored with red and exist at least one cell that is colored with yellow we should color month cell with yellow as well row.CSSClass[monthIndexInWeeks] = (row.CSSClass[monthIndexInWeeks] || '') + ' ' + cellHighlightingService.cellLessClass; } else if (cellEqualCount === monthExistingCells && row.QuantityHoursValues[monthIndexInWeeks] > 0) { // in some cases weekly cells can be without highlighting and we must color month cell with green only when all cells are colored with green or zero // and monthly value is greater than zero row.CSSClass[monthIndexInWeeks] = (row.CSSClass[monthIndexInWeeks] || '') + ' ' + cellHighlightingService.cellEqualClass; } } if (row.Children && row.Children.length) { for (var cIndex = 0; cIndex < row.Children.length; cIndex++) { var childRow = row.Children[cIndex]; if (!filterByRowTypes || (childRow.RowType && !(childRow.RowType in unhighlightableRowTypes))) { this.updateMonthStyles(childRow, monthIndexInWeeks, weekIndexes, unhighlightableRowTypes); } } } }, setTeamWideNptItemsReadOnly: function (nonProjectTimeDataCache) { if (!nonProjectTimeDataCache) return; for (var groupKey in nonProjectTimeDataCache) { var nptGroup = nonProjectTimeDataCache[groupKey]; if (nptGroup) { for (var nptCatId in nptGroup) { var nptCatItem = nptGroup[nptCatId]; if (nptCatItem && nptCatItem.NonProjectTime) { for (var nptItemId in nptCatItem.NonProjectTime) { var nptItem = nptCatItem.NonProjectTime[nptItemId]; if (nptItem && nptItem.isTeamWide) nptItem.isReadOnly = true; } } } } } }, castDisplayModeIntoTeamInfoMode: function (displayMode) { if (!displayMode) { return null; } var castGroupBy = function () { if (!displayMode.hasOwnProperty('GroupBy')) { return 'Category'; } switch (displayMode.GroupBy.Value) { case '1': // Group by Team AC mode case '5': // Supply and Demand report return 'Team'; case '4': return 'Business Unit'; case '6': return 'Cost Center'; default: return 'Category'; } }; var tiDisplayMode = {}; tiDisplayMode.IsUOMHours = displayMode.IsUOMHours; tiDisplayMode.IsAvgMode = displayMode.ShowAvgTotals; tiDisplayMode.GroupBy = castGroupBy(); if (displayMode.hasOwnProperty('CapacityMode') >= 0) tiDisplayMode.TotalsAs = displayMode.CapacityMode.Value; if (displayMode.hasOwnProperty('IsCapacityModeActuals') >= 0) tiDisplayMode.CapacityView = displayMode.IsCapacityModeActuals.Value; if (displayMode.hasOwnProperty('ShowLower') >= 0) tiDisplayMode.ShowResources = displayMode.ShowLower.Value; return tiDisplayMode; }, 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.main-table').prevAll(":has(.editable:visible):first").find(".editable:visible:last"); t.$form.$submit(); $timeout(function () { tab2Cell.click(); }, 0); } 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.main-table').nextAll(":has(.editable:visible):first").find(".editable:visible:first"); t.$form.$submit(); $timeout(function () { tab2Cell.click(); }, 0); } } }); }, onTxtBlur: function (t) { t.$form.$submit(); }, rollupRemainingRow: function (rowObject, deltaHoursValue, deltaResourcesValue, allowNegative, weekCellIndex, monthCellIndex) { if (deltaHoursValue == 0) return [0, 0]; var row = rowObject.Row; var deltaHours = (deltaHoursValue || 0); var deltaResources = (deltaResourcesValue || 0); // rollup delta value to all nested rows and override incoming delta to avoid negative numbers if necessary if (rowObject.Children && rowObject.Children.length > 0) { deltaHours = 0; deltaResources = 0; for (var i = 0; i < rowObject.Children.length; i++) { var res = this.rollupRemainingRow(rowObject.Children[i], (deltaHoursValue || 0), (deltaResourcesValue || 0), allowNegative, weekCellIndex, monthCellIndex); deltaHours += res[0]; deltaResources += res[1]; } } var collectionNames = this.getRowCollectionNames(rowObject); // if negative numbers are not allowed then we should reset value to zero if (!allowNegative && ((row[collectionNames.hoursCollectionName][weekCellIndex] || 0) + deltaHours) < 0) { deltaHours = -(row[collectionNames.hoursCollectionName][weekCellIndex] || 0); deltaResources = -(row[collectionNames.resourcesCollectionName][weekCellIndex] || 0); } // add delta value to values from all collections row[collectionNames.hoursCollectionName][weekCellIndex] = roundService.roundQuantity((row[collectionNames.hoursCollectionName][weekCellIndex] || 0) + deltaHours); row[collectionNames.hoursCollectionName][monthCellIndex] = roundService.roundQuantity((row[collectionNames.hoursCollectionName][monthCellIndex] || 0) + deltaHours); row[collectionNames.hoursTotal] = roundService.roundQuantity((row[collectionNames.hoursTotal] || 0) + deltaHours); row[collectionNames.resourcesCollectionName][weekCellIndex] = roundService.roundQuantity((row[collectionNames.resourcesCollectionName][weekCellIndex] || 0) + deltaResources); row[collectionNames.resourcesCollectionName][monthCellIndex] = roundService.roundQuantity((row[collectionNames.resourcesCollectionName][monthCellIndex] || 0) + deltaResources); row[collectionNames.resourceTotal] = roundService.roundQuantity((row[collectionNames.resourceTotal] || 0) + deltaResources); // return adjusted delta values return [deltaHours, deltaResources]; }, getRowCollectionNames: function (rowObject) {//todo: check all calls alloc mode source changed var row = rowObject.Row; var collectionNames = this.getCollectionNames4AllocationMode(row.AllocationMode); var names = { hoursCollectionName: (collectionNames.hoursCollectionName in row ? collectionNames.hoursCollectionName : this.allocationMode.defaultHoursCollectionName), hoursTotal: (collectionNames.hoursTotal in row ? collectionNames.hoursTotal : this.allocationMode.defaultHoursTotalName), resourcesCollectionName: (collectionNames.resourcesCollectionName in row ? collectionNames.resourcesCollectionName : this.allocationMode.defaultResourcesCollectionName), resourceTotal: (collectionNames.resourceTotal in row ? collectionNames.resourceTotal : this.allocationMode.defaultResourcesTotalName) }; return names; }, // returns true if AC filtered ty team or view, otherwise returns false isFilteredByTeams: function (filter) { var result = (filter.EntityType == this.filterEntityType.team) || (filter.EntityType == this.filterEntityType.view); return result; }, // returns true if AC filtered ty company, otherwise returns false isFilteredByCompany: function (filter) { if (!filter) return false; var result = filter.EntityType == this.filterEntityType.company; return result; }, getCategoriesWithNonZeroTeamAllocations: function (expCats, projectTeamAllocations, alwaysVisibleExpCats, includeWithoutAllocation) { if (!expCats || !expCats.length) return expCats; if (!projectTeamAllocations) return []; var result = []; for (var index = 0; index < expCats.length; index++) { var expCatId = expCats[index]; var isProjectRole = dataSources.isSuperEC(expCatId); if (!isProjectRole || (isProjectRole && (!alwaysVisibleExpCats || !angular.isArray(alwaysVisibleExpCats) || (alwaysVisibleExpCats.indexOf(expCatId) < 0)))) { // Always perform filtering for ordinary ECs. // Perform filtering for a project role, only if it is not in Always Visible list if (expCatId in projectTeamAllocations) { var expCatData = projectTeamAllocations[expCatId]; if (includeWithoutAllocation || (expCatData && expCatData.Allocations && !calculateDistributionService.isEmptyAllocations(expCatData.Allocations, false))) { result.push(expCatId); } } } else { // Project Roles is in Always Visible list result.push(expCatId); } } return result; }, // sets AllocationMode property to provided value for the specified row and all children levels setAllocationMode: function (row, allocationMode, isOverwrite) { // row - an object which allocation mode should be set // allocationMode - value that will be set for the specified row and all it's descendants // isOverwrite - indicates whether to overwrite row.AllocationMode if it is already set or not if (!row || !allocationMode) { return; } row.AllocationMode = isOverwrite ? allocationMode : row.AllocationMode || allocationMode; if (row.Children && row.Children.length) { var that = this; angular.forEach(row.Children, function (child) { that.setAllocationMode(child, allocationMode); }) } }, getProjectRows: function (collection, projectId) { if (!projectId || !collection || !angular.isArray(collection)) return null; var result = { masterProjectRow: null, projectRow: null }; for (var mIndex = 0; mIndex < collection.length; mIndex++) { var row = collection[mIndex]; if (row) { if (row.IsMaster) { if (row.Children) { for (var pIndex = 0; pIndex < row.Children.length; pIndex++) { var projectRow = row.Children[pIndex]; if (projectRow && projectRow.Id && (projectRow.Id == projectId)) { result.masterProjectRow = row; result.projectRow = projectRow; break; } } } } else { if (row.Id && (row.Id == projectId)) { result.projectRow = row; } } } if (result.projectRow) { break; } } return result; }, getResourceRows: function (collection, resourceId) { if (!resourceId || !collection || !angular.isArray(collection)) return null; var result = []; for (var index = 0; index < collection.length; index++) { var row = collection[index]; if (row) { if (row.RowType && (row.RowType == 'Resource') && (row.Id == resourceId)) { result.push(row); break; } if ((!row.RowType || (row.RowType != 'Resource')) && row.Children) { var foundRows = this.getResourceRows(row.Children, resourceId); if (foundRows && foundRows.length) { result = result.concat(foundRows); } } } } return result; }, findRowInCollection: function (collection, id, filter) { if (!id || !collection || !angular.isArray(collection) || !collection.length) return null; var result = null; for (var index = 0; index < collection.length; index++) { var row = collection[index]; if (row && row.Id && row.Id == id) { var fullMatch = true; if (filter) { var properties = Object.keys(filter); for (var propIndex = 0; propIndex < properties.length; propIndex++) { var propName = properties[propIndex]; if (row[propName] !== filter[propName]) { fullMatch = false; break; } } } if (fullMatch) { result = row; break; } } } return result; }, resolveResourceAvailableTeam: function (weekEndingMs, teams) { if (!weekEndingMs || !teams || !teams.length) return null; for (var i = 0; i < teams.length; i++) { var team = teams[i]; if ((!!team.StartDate && team.StartDate <= weekEndingMs) && (!team.EndDate || team.EndDate >= weekEndingMs)) { return team; } } return null; }, // ----------------- No Team section methods ------------------------- // removes empty rows from projectExpCats collection and children // in other words: let's hide it if there is nothing to assign removeEmptyUnassignedExpenditures: function (projectId, projectExpCats, filter) { if (!projectExpCats) return; var result = {}; var ec2TeamsExisting = {}; var expCatsInPendingTeams = activityCalendarService.getExpendituresExistInPendingTeams(filter, projectId); for (var expCatId in projectExpCats) { // check if there is any team that contains category if (ec2TeamsExisting[expCatId] == undefined) { ec2TeamsExisting[expCatId] = teamInfoService.teamsWithExpenditureCategoryExist(expCatId); } var expCatItem = projectExpCats[expCatId]; if (expCatItem && ec2TeamsExisting[expCatId] && ((expCatsInPendingTeams && (expCatId in expCatsInPendingTeams) && expCatsInPendingTeams[expCatId]) || !calculateDistributionService.isEmptyAllocations(expCatItem.RemainingNeed))) { // Display EC, if it has realated teams and (it has recently assigned teams or has unallocated need) result[expCatId] = expCatItem; } } if (Object.keys(result).length < 1) { // No EC with Remaining Need was found result = undefined; } return result; }, getTooltipEcUnassignedContent: function (opts, displayMode, filter, header, noTeamRows) { var tooltip = 'No data available'; var units = displayMode.IsUOMHours ? ' hours' : ' resources'; if (!opts || !opts.header || !opts.projectId || !opts.expCatId) return tooltip; var headerText = opts.header; var projectId = opts.projectId; var expCatId = opts.expCatId; var rows = this.findNoTeamRows(noTeamRows, projectId, expCatId); if (!rows || !rows.projectRow || !rows.expCatRow) return tooltip; var projectRow = rows.projectRow; var expCatRow = rows.expCatRow; var scenarioId = projectRow.ActiveScenario ? projectRow.ActiveScenario.Id : null; if (!projectId || !scenarioId) { return tooltip; } var expCatInfo = this.getExpCatUnassignedNeed(filter, displayMode, header, headerText, projectId, scenarioId, [expCatRow.Id], false); if (expCatInfo) { tooltip = 'Category Need: ' + String(expCatInfo.Need || 0) + units + '
' + 'Allocated to Teams in View: ' + String(expCatInfo.AllocatedToTeamsInView || 0) + units + '
' + 'Allocated to Teams out of View: ' + String(expCatInfo.AllocatedToTeamsOutOfView || 0) + units; if (expCatInfo.Underallocation) { if (expCatInfo.Underallocation > 0) { tooltip += ('
Underallocation: ' + String(expCatInfo.Underallocation) + units); } else { if (expCatInfo.Underallocation < 0) { tooltip += ('
Overallocation: ' + String(-expCatInfo.Underallocation) + units); } } } } return tooltip; }, getTooltipProjectUnassignedContent: function (opts, displayMode, filter, header, noTeamRows) { var tooltip = 'No data available'; var units = displayMode.IsUOMHours ? ' hours' : ' resources'; if (!opts || !opts.header || !opts.projectId) return tooltip; var headerText = opts.header; var projectId = opts.projectId; var projectNeed = 0; var projectAllocatedInView = 0; var projectAllocatedOutOfView = 0; var projectRowsInfo = this.getProjectRows(noTeamRows, projectId); var projectRow = projectRowsInfo && projectRowsInfo.projectRow ? projectRowsInfo.projectRow : null; if (!projectRow) return tooltip; var scenarioId = projectRow.ActiveScenario ? projectRow.ActiveScenario.Id : null; if (!scenarioId) { return tooltip; } if (projectRow.Children && projectRow.Children.length) { for (var ecIndex = 0; ecIndex < projectRow.Children.length; ecIndex++) { var expCatRow = projectRow.Children[ecIndex]; if (expCatRow) { var expCatSummaryInfo = this.getExpCatUnassignedNeed(filter, displayMode, header, headerText, projectId, scenarioId, [expCatRow.Id], false); if (expCatSummaryInfo) { projectNeed += (expCatSummaryInfo.Need || 0); projectAllocatedInView += (expCatSummaryInfo.AllocatedToTeamsInView || 0); projectAllocatedOutOfView += (expCatSummaryInfo.AllocatedToTeamsOutOfView || 0); } } } } projectNeed = Math.roundQuantity(projectNeed); projectAllocatedInView = Math.roundQuantity(projectAllocatedInView); projectAllocatedOutOfView = Math.roundQuantity(projectAllocatedOutOfView); tooltip = 'Project Need: ' + String(projectNeed) + units + '
' + 'Allocated to Teams in View: ' + String(projectAllocatedInView) + units + '
' + 'Allocated to Teams out of View: ' + String(projectAllocatedOutOfView) + units; return tooltip; }, findNoTeamRows: function (rows, projectId, expCatId) { if (!projectId || !expCatId || !rows || !rows.length) { return null; } var result = { masterProjectRow: null, projectRow: null, expCatRow: null }; // Find project rows (master & part) var projectRowsInfo = this.getProjectRows(rows, projectId); result.masterProjectRow = projectRowsInfo && projectRowsInfo.masterProjectRow ? projectRowsInfo.masterProjectRow : null; result.projectRow = projectRowsInfo && projectRowsInfo.projectRow ? projectRowsInfo.projectRow : null; if (result.projectRow && result.projectRow.Children) { result.expCatRow = this.findRowInCollection(result.projectRow.Children, expCatId); } return result; }, getNoTeamRowsToUpdate: function (noTeamRow, projectId, expCatId) { if (!projectId || !expCatId || !noTeamRow || !noTeamRow.Children) return []; // Get corresponding row for unassigned project var result = []; var foundRowsToRollup = this.findNoTeamRows(noTeamRow.Children, projectId, expCatId); var result = []; var masterProjectRow = null; if (foundRowsToRollup) { if (foundRowsToRollup.expCatRow) { var expCatRow = { Row: foundRowsToRollup.expCatRow, Children: [], }; if (foundRowsToRollup.masterProjectRow) { masterProjectRow = { Row: foundRowsToRollup.masterProjectRow, Children: [], }; } if (foundRowsToRollup.projectRow) { var projectRow = { Row: foundRowsToRollup.projectRow, Children: [], }; var unassignedRow = { Row: noTeamRow, Children: [] }; projectRow.Children.push(expCatRow); if (masterProjectRow) { masterProjectRow.Children.push(projectRow) unassignedRow.Children.push(masterProjectRow); } else { unassignedRow.Children.push(projectRow); } result.push(unassignedRow); } } } return result; }, getAvailableTeams: function (filter, projectId, expenditureCategoryId) { if (!projectId || !expenditureCategoryId) { return null; } var availableTeams = activityCalendarService.getTeamsAvailable4Assign(filter, projectId, expenditureCategoryId); var availableTeamsViewModel = this.createViewModel4AvailableTeams(availableTeams); if (!availableTeamsViewModel || !Object.keys(availableTeamsViewModel).length) { return null; } return $filter('sortObjectsBy')(availableTeamsViewModel, 'Name', false); }, refreshProjectAvailableTeams: function (filter, projectRow) { // we need to refresh list with available teams for each expenditure category under certain project if (projectRow.Children) { for (var ecIndex = 0; ecIndex < projectRow.Children.length; ecIndex++) { var ecRow = projectRow.Children[ecIndex]; if (ecRow) { ecRow.AvailableTeams = this.getAvailableTeams(filter, projectRow.Id, ecRow.Id); } } } }, setNoTeamRowTemplates: function (expCatRow) { if (!expCatRow) return; expCatRow.Templates = { Main: this.viewRowTemplates.expCatUnallocatedRowTemplate, Numbers: this.viewRowTemplates.expCatUnallocatedRowNumbersTemplate }; if (expCatRow.Children) { for (var tIndex = 0; tIndex < expCatRow.Children.length; tIndex) { var teamRow = expCatRow.Children[tIndex]; teamRow.Templates = { Main: this.viewRowTemplates.teamUnallocatedRowTemplate, Numbers: this.viewRowTemplates.teamUnallocatedRowNumbersTemplate }; } } }, createAndFillNewEcTeamRow: function (projectRow, expCatRow, teamId, teamInfo, header, displayMode, filter) { // create a UI row model and fill it with data var ecTeamData = this.getDataModel4NewTeam(teamId, projectRow.Id, expCatRow.Id, filter, header); ecTeamData = angular.extend({}, ecTeamData, { Id: teamInfo.Id, ExpenditureCategoryId: ecTeamData.Id, Name: teamInfo.Name, Initialized: expCatRow.Initialized, Show: expCatRow.Show, Level: expCatRow.Level + 1 }); var newTeamRow = this.createViewModel4ECTeam(ecTeamData); this.fillECTeamRowWithData(header, newTeamRow, expCatRow, ecTeamData, projectRow.Id, projectRow.ActiveScenario.StartDate, projectRow.ActiveScenario.EndDate, projectRow.ReadOnly); this.toggleGridSource([newTeamRow], displayMode.IsUOMHours); if (displayMode.IsAvgMode()) { this.applyAvgMode([newTeamRow], header); } // Remove highlighting classes for new team row (they are automatically set in toggleGridSource if (newTeamRow && newTeamRow.CSSClass && angular.isArray(newTeamRow.CSSClass)) { for (var index = 0; index < newTeamRow.CSSClass.length; index++) { newTeamRow.CSSClass[index] = ''; } } // add new team row to EC row's children and resort it if (!expCatRow.Children) expCatRow.Children = []; expCatRow.Children.push(newTeamRow); expCatRow.Children = this.toggleSortBy(expCatRow.Children, displayMode.SortBy, displayMode.SortOrder); }, getDataModel4NewTeam: function (teamId, projectId, expCatId, filter, header) { if (!teamId || !projectId || !expCatId) { return null; } var project = activityCalendarService.getProjectById(filter, projectId); if (!project || !project.Teams || !(teamId in project.Teams)) { return null; } var expCats = this.getExpendituresWithResources4Project(project, [teamId], [expCatId], header, filter, true, false); if (expCats && expCats[expCatId]) return expCats[expCatId]; else return null; }, refreshNoTeamProjectStyles: function (filter, header, noTeamRows, projectRow, data) { if (!projectRow || typeof data !== 'object' || !angular.isArray(data)) { return; } var rowToUpdateStylesIn = projectRow; if (projectRow.Level && (projectRow.Level > 1)) { // For master project perform style updates for entire master project var projectRowsInfo = this.getProjectRows(noTeamRows, projectRow.Id); if (projectRowsInfo && projectRowsInfo.masterProjectRow) { rowToUpdateStylesIn = projectRowsInfo.masterProjectRow; } } this.updateProjectStyles(header, filter, [rowToUpdateStylesIn], data); }, updateTotalRows: function (changedCells, teamRow, expCatRow, projectId, noTeamRow, assignedRows, header, filter, displayMode) { if (typeof changedCells !== 'object' || !angular.isArray(changedCells) || !expCatRow || !projectId || !teamRow) { return; } var expenditureCategoryId = expCatRow.Id; // gather rows to rollup changes var noTeamRowsToUpdate = this.getNoTeamRowsToUpdate(noTeamRow, projectId, expenditureCategoryId) ||[]; var assignedRowsToUpdate = this.getAssignedRowsToUpdate(assignedRows, teamRow.Id, projectId, expenditureCategoryId) || []; if ((assignedRowsToUpdate.length <= 0) && (noTeamRowsToUpdate.length <= 0)) { return; } var header = header; var isUOMHours = displayMode.IsUOMHours; var isAvgMode = displayMode.IsAvgMode(); var project = activityCalendarService.getProjectById(filter, projectId); var weekEndings = changedCells.map(function (cell) { return cell.WeekEnding; }); var remainingNeed = activityCalendarService.getRemainingNeedAllocations4Scenario(filter, Object.keys(project.Teams), project.ActiveScenario.Id, [expenditureCategoryId], weekEndings) || {}; var expCatCollectionNames = this.getCollectionNames4AllocationMode(expCatRow.AllocationMode); for (var i = 0; i < changedCells.length; i++) { var cell = changedCells[i]; var month = header.Months[cell.MonthIndex]; var monthWeeksCount = month.Childs.length; // decrease values in related rows by provided delta value var oldRemainingNeedValue = expCatRow[expCatCollectionNames.hoursCollectionName][cell.WeekIndex] || 0; // get delta value for exp cat row var newRemainingNeedValue = ((remainingNeed[expenditureCategoryId] || {}).Allocations || {})[cell.WeekEnding] || 0; var rollupRemainingNeedDeltaHours = oldRemainingNeedValue - newRemainingNeedValue; var rollupRemainingNeedDeltaResources = hoursResourcesConverter.convertToResources(expenditureCategoryId, rollupRemainingNeedDeltaHours); // team allocations delta values var rollupTeamAllocationDeltaHours = cell.DeltaHoursValue || 0; var rollupTeamAllocationDeltaResources = hoursResourcesConverter.convertToResources(expenditureCategoryId, rollupTeamAllocationDeltaHours); // rollup rows under no team section for (var rowIndex = 0; rowIndex < noTeamRowsToUpdate.length; rowIndex++) { var row = noTeamRowsToUpdate[rowIndex]; this.rollupRemainingRow(row, -(rollupRemainingNeedDeltaHours || 0), -(rollupRemainingNeedDeltaResources || 0), false, cell.WeekIndex, month.SelfIndexInWeeks); this.setCellsValues4Row(row, cell.WeekIndex, month.SelfIndexInWeeks, isUOMHours, isAvgMode, monthWeeksCount); } // rollup already assigned rows in the top part for (var rowIndex = 0; rowIndex < assignedRowsToUpdate.length; rowIndex++) { var rollupValues = this.rollupRemainingRow(assignedRowsToUpdate[rowIndex], (rollupTeamAllocationDeltaHours || 0), (rollupTeamAllocationDeltaResources || 0), false, cell.WeekIndex, month.SelfIndexInWeeks); this.setCellsValues4Row(assignedRowsToUpdate[rowIndex], cell.WeekIndex, month.SelfIndexInWeeks, isUOMHours, isAvgMode, monthWeeksCount); var assignedProjectItems = assignedRowsToUpdate[rowIndex].Children; if (assignedProjectItems && angular.isArray(assignedProjectItems)) { for (var projectIndex = 0; projectIndex < assignedProjectItems.length; projectIndex++) { // refresh original project styles var assignedProjectItem = assignedProjectItems[projectIndex]; var assignedProjectRow = assignedProjectItem.Row; if (assignedProjectItem.updateCellStyles) { this.refreshProjectStyles(assignedRows, header, filter, assignedProjectRow, changedCells); } // change remaining capacity value for original category in the top part if (assignedProjectItem.Children && angular.isArray(assignedProjectItem.Children)) { for (var ecIndex = 0; ecIndex < assignedProjectItem.Children.length; ecIndex++) { var assignedECRow = assignedProjectItem.Children[ecIndex].Row; if (assignedECRow.RemainingCapacityValues) { assignedECRow.RemainingCapacityValues[cell.WeekIndex] += rollupValues[0]; assignedECRow.RemainingCapacityValues[month.SelfIndexInWeeks] += rollupValues[0]; } } } } } } } }, getAssignedRowsToUpdate: function (assignedRows, teamId, projectId, expCatId) { if (!teamId || !projectId || !expCatId || !assignedRows || !assignedRows.length) { return []; } var foundRowsToRollup = this.findAssignedRows(assignedRows, teamId, projectId, expCatId); var result = []; var masterProjectRow = null; if (foundRowsToRollup) { if (foundRowsToRollup.expCatRow) { var expCatRow = { Row: foundRowsToRollup.expCatRow, Children: [], }; if (foundRowsToRollup.masterProjectRow) { masterProjectRow = { updateCellStyles: false, // No cell highlighting update needed (master project rows have no highlighting) Row: foundRowsToRollup.masterProjectRow, Children: [], }; } if (foundRowsToRollup.projectRow) { var projectRow = { updateCellStyles: true, // Cell highlighting update needed Row: foundRowsToRollup.projectRow, Children: [], }; if (foundRowsToRollup.teamRow) { var teamRow = { Row: foundRowsToRollup.teamRow, Children: [], }; projectRow.Children.push(expCatRow); if (masterProjectRow) { masterProjectRow.Children.push(projectRow) teamRow.Children.push(masterProjectRow); } else { teamRow.Children.push(projectRow); } result.push(teamRow); } } } } return result; }, findAssignedRows: function (assignedRows, teamId, projectId, expCatId) { if (!teamId || !projectId || !expCatId || !assignedRows || !assignedRows.length) return null; var result = { teamRow: null, masterProjectRow: null, projectRow: null, expCatRow: null }; if (assignedRows && angular.isArray(assignedRows)) { // Get project rows (master & part) var projectRowsInfo = this.getProjectRows(assignedRows, projectId); result.masterProjectRow = projectRowsInfo && projectRowsInfo.masterProjectRow ? projectRowsInfo.masterProjectRow : null; result.projectRow = projectRowsInfo && projectRowsInfo.projectRow ? projectRowsInfo.projectRow : null; if (result.projectRow && result.projectRow.Children) { result.expCatRow = this.findRowInCollection(result.projectRow.Children, expCatId); } } return result; }, // set Cells values based on new values and apply AVG mode if necessary setCellsValues4Row: function (rowObject, weekCellIndex, monthCellIndex, isUOMHours, isAvgMode, monthWeeksCount) { var row = rowObject.Row; var collectionNames = this.getRowCollectionNames(rowObject); // recursively set Cells properties for al nested rows if (rowObject.Children && rowObject.Children.length > 0) { for (var i = 0; i < rowObject.Children.length; i++) { this.setCellsValues4Row(rowObject.Children[i], weekCellIndex, monthCellIndex, isUOMHours, isAvgMode, monthWeeksCount); } } if (isUOMHours) { row.Cells[weekCellIndex] = row[collectionNames.hoursCollectionName][weekCellIndex]; row.Cells[monthCellIndex] = row[collectionNames.hoursCollectionName][monthCellIndex]; row.TotalValue = row[collectionNames.hoursTotal]; } else { row.Cells[weekCellIndex] = row[collectionNames.resourcesCollectionName][weekCellIndex]; row.Cells[monthCellIndex] = isAvgMode ? monthWeeksCount == 0 ? 0 : Math.roundQuantity(row[collectionNames.resourcesCollectionName][monthCellIndex] / monthWeeksCount) : row[collectionNames.resourcesCollectionName][monthCellIndex]; row.TotalValue = isAvgMode ? row.VisibleCellsCount == 0 ? 0 : Math.roundQuantity(row[collectionNames.resourceTotal] / row.VisibleCellsCount) : row[collectionNames.resourceTotal]; } }, refreshProjectStyles: function (rows, header, filter, projectRow, data) { if (!projectRow || typeof data !== 'object' || !angular.isArray(data)) { return; } var rowToUpdateStylesIn = projectRow; if (projectRow.Level && (projectRow.Level > 1)) { // For master project perform style updates for entire master project var projectRowsInfo = this.getProjectRows(rows, projectRow.Id); if (projectRowsInfo && projectRowsInfo.masterProjectRow) { rowToUpdateStylesIn = projectRowsInfo.masterProjectRow; } } this.updateProjectStyles(header, filter, [rowToUpdateStylesIn], data); }, categoryRowsWereAdded: function(changedCellsData) { if (!changedCellsData || !angular.isArray(changedCellsData)) return false; var result = false; for (var index = 0; index < changedCellsData.length; index++) { if (changedCellsData[index] && changedCellsData[index].Added) { result = true; break; } } return result; } } return service; }]); uiDirectives.directive("gridRow", ['$templateCache', '$compile', function ($templateCache, $compile) { var rowCounter = 0; var linkedCounter = 0; var directiveDefinitionObject = { scope: { 'InitRow': '=gridRowInit' // indicates whether to render the row or not }, link: function (scope, element, attrs, ctrl, transclude) { //console.log('scope.InitRow: ' + scope.InitRow + '|' + attrs.gridRowInit); // function which gets template content, compiles it and attaches it to the DOM var buildNodes = function (value) { if (value) { linkedCounter = linkedCounter + 1; // console.log('gridRow linked: ' + linkedCounter); var tmpl = angular.element($templateCache.get(attrs.templateurl)); $newNodes = $compile(tmpl)(scope.$parent); //element.replaceWith(element, $newNodes); element.after($newNodes); } }; // function which destroys the DOM created by directive var cleanup = function () { angular.element($newNodes).remove(); }; var $newNodes; // postpone building the DOM until gridRowInit=true if gridRowInit attribute is set if (attrs.gridRowInit) { scope.$watch('InitRow', function (newValue, oldValue) { //console.log('watch fired with value: ' + newValue); if ($newNodes === undefined && newValue === true) buildNodes(true); // compile and attach DOM only if gridRowInit attribute expression is set to true }); } else { // else (gridRowInit attribute is not set) buildNodes(true); } // hide original tr element.addClass('ng-hide'); scope.$on('$destroy', function (event) { cleanup(); }); rowCounter = rowCounter + 1; // console.log('gridRow link ' + rowCounter); //console.log(scope.$parent); } }; return directiveDefinitionObject; }]);