'use strict'; app .controller('activityCalendarByResController', ['$scope', '$q', '$filter', '$timeout', 'activityCalendarService', 'teamInfoService', 'activityCalendarUIService', 'dataSources', 'hoursResourcesConverter', 'roundService', 'calculateDistributionService', function ($scope, $q, $filter, $timeout, activityCalendarService, teamInfoService, activityCalendarUIService, dataSources, hoursResourcesConverter, roundService, calculateDistributionService) { //console.log('activityCalendarByResController started'); var commonErrorMessage = 'An error occurred while processing your request. Please, try again later.'; var calendarDataUrl = '/CapacityManagement/GetActivityCalendar'; var projectExpendituresCache = {}; // cache for aggregated ECs by project for "group by resources" mode var unassignedProjectsCache = {}; // cache for projects for unassigned section var nonProjectTimeCache = {}; // cache for non-project time data var nonProjectTimeCategoryCache = {}; // cache for non-project time categories data $scope.ViewModel = { DisplayMode: null, Header: null, Rows: null, UnassignedRow: null, TotalRow: null, RemainingCapacityRow: null, NonProjectTimeTotalRows: null, DataLoaded: false }; $scope.Filter = null; /* Event listeners */ $scope.$on('ac.rebuild-calendar', rebuildCalendarHandler); $scope.$on('ac.options-changed', optionsChangedHandler); $scope.$on('teamBoard.plannedCapacityChanged', function (eventData) { // move it to bottom part and handle it there console.log('teamBoard.plannedCapacityChanged event raised'); }); $scope.$on('resourceEdited', function (eventData) { rebuildCalendarHandler(eventData, $scope.Filter, $scope.ViewModel.DisplayMode, true); }); /* Methods for interaction with view */ $scope.init = function (filter, displayMode) { rebuildCalendarHandler(null, filter, displayMode); }; $scope.toggleMonth = function (monthIndex) { $scope.ViewModel.Header.toggleMonth(monthIndex); }; $scope.toggleRow = function (uiRow, $event) { if ($event && ($($event.target).parents('.menuGroup').length > 0)) return; // to-do: we should keep collapsed state in data-model items between possible future postbacks var dataModel = activityCalendarService.dataItemStub || {}; activityCalendarUIService.toggleRow(uiRow, dataModel); }; $scope.ToggleStatus = function ($event, projectId, scenario) { var btn = $($event.target).closest('.popover-warning'); if (btn.length == 1) { activityCalendarUIService.toggleStatus(btn, projectId, scenario) .then(function (result) { if (!!result) rebuildCalendarHandler($event, $scope.Filter, $scope.ViewModel.DisplayMode, true); }) .then(null, function () { showErrorModal(null, 'An error occurred while toggling scenario status', '000009'); }); } }; $scope.AddScenario = function ($event, projectId) { activityCalendarUIService.openCreateScenarioWz(projectId); }; $scope.formatPeopleResourceOption = function (result, container, query, escapeMarkup) { return activityCalendarUIService.formatPeopleResourceOption(result.element); }; $scope.assignResource = function (projectId, expCatRow, resourceId, teamId) { if (!projectId || !expCatRow || !resourceId) { return; } var rowToAddResource = expCatRow; if (teamId) { for (var teamKey in expCatRow.Children) { if (teamId != expCatRow.Children[teamKey].Id) continue; rowToAddResource = expCatRow.Children[teamKey]; } } activityCalendarUIService.assignResource($scope.Filter, $scope.ViewModel.Header, projectId, expCatRow, resourceId, rowToAddResource); rowToAddResource.ResourceToAssignId = null; dataChanged(); }; $scope.checkResourceValueTopPart = function (projectRow, resourceRow, $index, $data) { if (!projectRow || !projectRow.ExpenditureCategoryId || !projectRow.ExpenditureCategoryId.length || projectRow.IsMultipleECs || !resourceRow) { return; } var filter = $scope.Filter, header = $scope.ViewModel.Header, isUOMHours = $scope.ViewModel.DisplayMode.IsUOMHours, isAvgMode = $scope.ViewModel.DisplayMode.IsAvgMode(), startDate = projectRow.ActiveScenario.StartDate, endDate = projectRow.ActiveScenario.EndDate; var result = activityCalendarUIService.checkResourceValue(filter, header, projectRow.Id, { Id: projectRow.ExpenditureCategoryId[0] }, resourceRow.Id, projectRow, isUOMHours, isAvgMode, $index, $data); onChangeResourceData(filter, header, projectRow.Id, projectRow.ExpenditureCategoryId[0], null, resourceRow.Id, startDate, endDate, isUOMHours, isAvgMode, result, false); return false; }; $scope.checkResourceValue = function (projectRow, expCatRow, resRow, $index, $data, teamRow) { if (!projectRow || !expCatRow || !resRow) { return; } var filter = $scope.Filter, header = $scope.ViewModel.Header, isUOMHours = $scope.ViewModel.DisplayMode.IsUOMHours, isAvgMode = $scope.ViewModel.DisplayMode.IsAvgMode(), startDate = projectRow.ActiveScenario.StartDate, endDate = projectRow.ActiveScenario.EndDate, parentRow = teamRow || expCatRow || {}, teamId = teamRow && teamRow.Id; var result = activityCalendarUIService.checkResourceValue(filter, header, projectRow.Id, parentRow, resRow.Id, resRow, isUOMHours, isAvgMode, $index, $data, false); onChangeResourceData(filter, header, projectRow.Id, expCatRow.Id, teamId, resRow.Id, startDate, endDate, isUOMHours, isAvgMode, result, true); return false; }; $scope.checkResourceGrandTotalValueTopPart = function (row, resource, $data) { if (!row || !row.ExpenditureCategoryId || !row.ExpenditureCategoryId.length || row.IsMultipleECs || !row.ActiveScenario || !resource) { return; } var filter = $scope.Filter, header = $scope.ViewModel.Header, isUOMHours = $scope.ViewModel.DisplayMode.IsUOMHours, isAvgMode = $scope.ViewModel.DisplayMode.IsAvgMode(), startDate = row.ActiveScenario.StartDate, endDate = row.ActiveScenario.EndDate; var result = activityCalendarUIService.checkResourceGrandTotalValue(filter, header, row.Id, startDate, endDate, { Id: row.ExpenditureCategoryId[0] }, resource.Id, row, isUOMHours, isAvgMode, $data, false); onChangeResourceData(filter, header, row.Id, row.ExpenditureCategoryId[0], null, resource.Id, startDate, endDate, isUOMHours, isAvgMode, result, false); return false; }; $scope.checkResourceGrandTotalValue = function (projectRow, expCatRow, resRow, $data, teamRow) { if (!projectRow || !projectRow || !resRow) { return; } var filter = $scope.Filter, header = $scope.ViewModel.Header, isUOMHours = $scope.ViewModel.DisplayMode.IsUOMHours, isAvgMode = $scope.ViewModel.DisplayMode.IsAvgMode(), startDate = projectRow.ActiveScenario.StartDate, endDate = projectRow.ActiveScenario.EndDate, parentRow = teamRow || expCatRow || {}, teamId = teamRow && teamRow.Id; var result = activityCalendarUIService.checkResourceGrandTotalValue(filter, header, projectRow.Id, startDate, endDate, parentRow, resRow.Id, resRow, isUOMHours, isAvgMode, $data, false); onChangeResourceData(filter, header, projectRow.Id, expCatRow.Id, teamId, resRow.Id, startDate, endDate, isUOMHours, isAvgMode, result, true); return false; }; $scope.takeRemaining = function (projectRow, expCatRow, resRow, teamRow) { if (!projectRow || !projectRow.ActiveScenario || !expCatRow || !resRow) { return; } var filter = $scope.Filter, header = $scope.ViewModel.Header, isUOMHours = $scope.ViewModel.DisplayMode.IsUOMHours, isAvgMode = $scope.ViewModel.DisplayMode.IsAvgMode(), startDate = projectRow.ActiveScenario.StartDate, endDate = projectRow.ActiveScenario.EndDate, parentRow = teamRow || expCatRow || {}, teamId = teamRow && teamRow.Id; var result = activityCalendarUIService.takeRemainingCapacity(filter, header, projectRow.Id, startDate, endDate, parentRow, resRow.Id, resRow, isUOMHours, isAvgMode); onChangeResourceData(filter, header, projectRow.Id, expCatRow.Id, teamId, resRow.Id, startDate, endDate, isUOMHours, isAvgMode, result, true); }; $scope.takeAll = function (projectRow, expCatRow, resRow, teamRow) { if (!projectRow || !projectRow.ActiveScenario || !expCatRow || !resRow) return; var filter = $scope.Filter, header = $scope.ViewModel.Header, isUOMHours = $scope.ViewModel.DisplayMode.IsUOMHours, isAvgMode = $scope.ViewModel.DisplayMode.IsAvgMode(), startDate = projectRow.ActiveScenario.StartDate, endDate = projectRow.ActiveScenario.EndDate, parentRow = teamRow || expCatRow || {}, teamId = teamRow && teamRow.Id; var result = activityCalendarUIService.takeFullCapacity(filter, header, projectRow.Id, startDate, endDate, parentRow, resRow.Id, resRow, isUOMHours, isAvgMode); onChangeResourceData(filter, header, projectRow.Id, expCatRow.Id, teamId, resRow.Id, startDate, endDate, isUOMHours, isAvgMode, result, true); }; $scope.zeroResource = function (projectRow, expCatRow, resRow, teamRow) { if (!projectRow || !projectRow.ActiveScenario || !resRow) { return; } bootbox.confirm({ message: "Are you sure you want to fill this resource quantities with 0s?", callback: function (result) { $scope.$apply(function () { if (result) { var filter = $scope.Filter, header = $scope.ViewModel.Header, isUOMHours = $scope.ViewModel.DisplayMode.IsUOMHours, isAvgMode = $scope.ViewModel.DisplayMode.IsAvgMode(), projectId = projectRow.Id, startDate = projectRow.ActiveScenario.StartDate, endDate = projectRow.ActiveScenario.EndDate, expCatRowParam = teamRow || expCatRow || {}, teamId = teamRow && teamRow.Id; // if this is a project row from top part if (typeof projectRow.ExpenditureCategoryId === 'object' && angular.isArray(projectRow.ExpenditureCategoryId)) { // if there are any expenditures for the selected resource then let's update data for those expenditures if (projectRow.ExpenditureCategoryId.length > 0) { for (var i = 0; i < projectRow.ExpenditureCategoryId.length; i++) { expCatRowParam = { Id: projectRow.ExpenditureCategoryId[i] }; var changedCells = activityCalendarUIService.zeroResource(filter, header, projectId, startDate, endDate, expCatRowParam, resRow.Id, projectRow, isUOMHours, isAvgMode); onChangeResourceData(filter, header, projectId, projectRow.ExpenditureCategoryId[0], teamId, resRow.Id, startDate, endDate, isUOMHours, isAvgMode, changedCells, false); } } } else { // if this is a resource row from unassigned part var changedCells = activityCalendarUIService.zeroResource(filter, header, projectId, startDate, endDate, expCatRowParam, resRow.Id, resRow, isUOMHours, isAvgMode); onChangeResourceData(filter, header, projectId, expCatRow.Id, teamId, resRow.Id, startDate, endDate, isUOMHours, isAvgMode, changedCells, true); } } }); }, className: "bootbox-sm" }); }; $scope.removeResource = function (prjRow, expCatRow, teamRow, resRow, $index) { if (!prjRow || !prjRow.ActiveScenario || !resRow) { return; } bootbox.confirm({ message: "Are you sure you want to remove this resource?", callback: function (result) { $scope.$apply(function () { if (result) { var filter = $scope.Filter, header = $scope.ViewModel.Header, isUOMHours = $scope.ViewModel.DisplayMode.IsUOMHours, isAvgMode = $scope.ViewModel.DisplayMode.IsAvgMode(), projectId = prjRow.Id, startDate = prjRow.ActiveScenario.StartDate, endDate = prjRow.ActiveScenario.EndDate, parentRow = teamRow || expCatRow || {}, expCatId = expCatRow && expCatRow.Id, cellsToUpdate = [], teamId = teamRow && teamRow.Id // if this is a project row from top part if (typeof prjRow.ExpenditureCategoryId === 'object' && angular.isArray(prjRow.ExpenditureCategoryId)) { // if there are any expenditures for the selected resource then let's update data for those expenditures if (prjRow.ExpenditureCategoryId.length > 0) { var projUnassignedRow = null, unassignedResIndex = -1; if ($scope.ViewModel.UnassignedRow && $scope.ViewModel.UnassignedRow.Children) { for (var index = 0; index < $scope.ViewModel.UnassignedRow.Children.length; index++) { var pRow = $scope.ViewModel.UnassignedRow.Children[index]; if (pRow && pRow.Id && pRow.Id == projectId) { projUnassignedRow = pRow; break; } } } for (var i = 0; i < prjRow.ExpenditureCategoryId.length; i++) { parentRow = {}; expCatId = prjRow.ExpenditureCategoryId[i]; // Get corresponding row for EC in Unassigned block if (projUnassignedRow && projUnassignedRow.Children && projUnassignedRow.Children.length) { for (var ecIndex = 0; ecIndex < projUnassignedRow.Children.length; ecIndex++) { var unassignedEcRow = projUnassignedRow.Children[ecIndex]; if (unassignedEcRow && unassignedEcRow.Id && unassignedEcRow.Id == prjRow.ExpenditureCategoryId[i]) { parentRow = unassignedEcRow; break; } } } // remove resource from project and recalculate available resources for exp cat row from related EC row in Unassigned block // pass parentRow.Children.length as $index to avoid removing of unexisting resources from unassigned EC row var changedCells = activityCalendarUIService.cleanAndRemoveResource(filter, header, projectId, startDate, endDate, expCatId, parentRow, resRow.Id, prjRow, isUOMHours, isAvgMode, (parentRow.Children || []).length); if (typeof changedCells === 'object' && angular.isArray(changedCells) && changedCells.length > 0) { cellsToUpdate = cellsToUpdate.concat(changedCells); } } for (var idx = 0; idx < resRow.Children.length; idx++) { var child = resRow.Children[idx]; if (child.Id == prjRow.Id) { resRow.Children.splice(idx, 1); break; } if (child.Children && child.Children.length) { for (var idx2 = 0; idx2 < child.Children.length; idx2++) { if (child.Children[idx2].Id == prjRow.Id) { child.Children.splice(idx2, 1); break; } } // if master project does not have parts any more then remove master project as well if (child.Children.length == 0) resRow.Children.splice(idx, 1); } } var expenditureCategoryId = (!prjRow.IsMultipleECs && prjRow.ExpenditureCategoryId && prjRow.ExpenditureCategoryId.length) ? prjRow.ExpenditureCategoryId[0] : resRow.OwnExpenditureCategoryId; onChangeResourceData(filter, header, projectId, expenditureCategoryId, teamId, resRow.Id, startDate, endDate, isUOMHours, isAvgMode, cellsToUpdate, false); } } else { // if this is a resource row from unassigned part var changedCells = activityCalendarUIService.cleanAndRemoveResource(filter, header, projectId, startDate, endDate, expCatId, parentRow, resRow.Id, resRow, isUOMHours, isAvgMode, $index); if (typeof changedCells === 'object' && angular.isArray(changedCells) && changedCells.length > 0) { cellsToUpdate = cellsToUpdate.concat(changedCells); } onChangeResourceData(filter, header, projectId, expCatRow.Id, teamId, resRow.Id, startDate, endDate, isUOMHours, isAvgMode, cellsToUpdate, true); } } }); }, className: "bootbox-sm" }); }; $scope.checkNptResourceValue = function (topResourceRow, nptTotalRow, nptCatRow, nptItemRow, nptResourceRow, $index, $data) { if (!topResourceRow || !nptTotalRow || !nptCatRow || !nptItemRow || !nptResourceRow) return; var filter = $scope.Filter, header = $scope.ViewModel.Header, isUOMHours = $scope.ViewModel.DisplayMode.IsUOMHours, isAvgMode = $scope.ViewModel.DisplayMode.IsAvgMode(); var rollupRows = []; rollupRows.push(topResourceRow); rollupRows.push(nptTotalRow); rollupRows.push(nptCatRow); rollupRows.push(nptItemRow); rollupRows.push($scope.ViewModel.TotalRow); var result = activityCalendarUIService.checkNptResourceValue(filter, header, $scope.ViewModel.RemainingCapacityRow, rollupRows, nptCatRow, nptItemRow, nptResourceRow, isUOMHours, isAvgMode, $index, $data); // if result is not false it is incorrect one and we do not need to recalculate availability in this case if (typeof result === 'object' && angular.isArray(result) && result.length > 0) { triggerNptResourceChanged(result); dataChanged(); return false; } return result; }; $scope.watchKeyInput = function (t) { activityCalendarUIService.watchKeyInput(t); }; $scope.onTxtBlur = function (t) { activityCalendarUIService.onTxtBlur(t); }; /* Event handlers */ function rebuildCalendarHandler(event, filter, displayMode, forceDataReload, successCallback) { // if filter is changed we need to invalidate cache if ($scope.Filter && !activityCalendarService.cacheKeysEqual($scope.Filter, filter)) { activityCalendarService.invalidateCache($scope.Filter); } $scope.ViewModel.DataLoaded = false; $scope.ViewModel.DisplayMode = angular.copy(displayMode || {}); $scope.Filter = angular.copy(filter || {}); blockUI(); hoursResourcesConverter.load(forceDataReload).then(function () { var loadCalendarTask = activityCalendarService.getActivityCalendar(calendarDataUrl, filter, forceDataReload); var nptCategories = dataSources.getNonProjectTimeCategories(); var cachedTemplates = activityCalendarUIService.cacheTemplates(); var dateRangeTask = dataSources.loadAvailableDateRange(); var allTasks = [loadCalendarTask, nptCategories, dateRangeTask].concat(cachedTemplates); $q.all(allTasks) .then(function (asyncResults) { var calendar = asyncResults[0]; if (calendar.Teams) { var teamIds = Object.keys(calendar.Teams); var promise = dataSources.loadResourcesByTeamIds(teamIds) .then(function (result) { return asyncResults; }); return promise; } else { return asyncResults; } }) .then(function (asyncResults) { var calendar = asyncResults[0]; nonProjectTimeCategoryCache = asyncResults[1]; createViewModel(calendar); fillViewModelWithData(calendar); initBottomPart(); toggleParts(); toggleSortBy(); toggleHoursResourcesMode(); toggleBarsValuesMode(); toggleMonths(); $scope.$emit('ac.workflowactionsloaded'); unblockUI(); if (typeof successCallback === 'function') { successCallback(calendar); } if (angular.isObject(calendar)) $scope.ViewModel.DataLoaded = true; }) .then(null, function () { unblockUI(); showErrorModal('Oops!', commonErrorMessage); }); }).then(null, function () { unblockUI(); showErrorModal('Oops!', commonErrorMessage); }); }; function optionsChangedHandler(event, displayMode, key, newValue) { //console.log(key); $scope.ViewModel.DisplayMode = displayMode || {}; switch (key) { case 'uomMode': toggleHoursResourcesMode(); $scope.$broadcast('changeUOMMode', newValue); break; case 'capBarMode': toggleBarsValuesMode(); break; case 'defaultView': toggleMonths(); break; case 'sortBy': case 'sortOrder': toggleSortBy(); break; case 'showParts': toggleParts(); toggleSortBy(); toggleHoursResourcesMode(); break; case 'capacityView': toggleCapacityType(); $scope.$broadcast('capacityViewChanged', newValue); break; default: break; }; }; function dataChanged() { $scope.$emit('ac.calendarDataChanged'); }; /* Methods for preparing data */ function removeEmptyUnassignedExpenditures(projectExpCatsCache, allocationMode) { if (!projectExpCatsCache) return; var result = {}; for (var expCatId in projectExpCatsCache) { var expCatItem = projectExpCatsCache[expCatId]; var allocationData = expCatItem.UnassignedNeed; if (allocationMode == activityCalendarUIService.allocationMode.remainingTeamAllocation) allocationData = expCatItem.RemainingTeamAllocations; if (expCatItem && !calculateDistributionService.isEmptyAllocations(allocationData, true)) { result[expCatId] = expCatItem; } } if (result && !Object.keys(result).length) { // No EC with Unassigned Need was found result = undefined; } // Remove empty teams (with no available resources to assign) var teamsFilteringResult = {}; for (var expId in result) { var exp = result[expId]; exp.Resources = {}; // do not show resources already assigned to the team because they are in the top part switch ($scope.Filter.EntityType) { case activityCalendarUIService.filterEntityType.team: // AC displayed for a single Team if (exp.AvailableResources && Object.keys(exp.AvailableResources).length > 0) { teamsFilteringResult[expId] = exp; } break; case activityCalendarUIService.filterEntityType.view: // AC displayed for view View if (exp.Teams && Object.keys(exp.Teams).length) { var availableForAssignTeams = {}; for (var teamId in exp.Teams) { var team = exp.Teams[teamId]; team.Resources = {}; // do not show resources already assigned to the team because they are in the top part if (team.AvailableResources && Object.keys(team.AvailableResources).length > 0) availableForAssignTeams[teamId] = team; } exp.Teams = availableForAssignTeams; if (exp.Teams && Object.keys(exp.Teams).length) { teamsFilteringResult[expId] = exp; } } break; default: teamsFilteringResult[expId] = exp; break; } } result = teamsFilteringResult; if (result && !Object.keys(result).length) { // No ECs in project after filtering result = undefined; } return result; }; function getDataModel4UnassignedProjects(filteredByView) { var result = {}; var projects = activityCalendarService.getProjects($scope.Filter); if (!projects) { return result; } var allocationMode = $scope.Filter.EntityType == activityCalendarUIService.filterEntityType.company ? activityCalendarUIService.allocationMode.unassignedNeedAllocation : activityCalendarUIService.allocationMode.remainingTeamAllocation; for (var projectId in projects) { var extendedProjectModel = angular.extend({}, projects[projectId], { AllocationMode: allocationMode }); var projectModel = getDataModel4SingleUnassignedProject(extendedProjectModel, filteredByView); if (projectModel) { result[projectId] = projectModel; } } return result; }; function getDataModel4SingleUnassignedProject(project, filteredByView) { if (!project) return null; var result = null; var projectUnassignedExpenditures = activityCalendarUIService.getExpendituresWithResources4Project(project, null, null, $scope.ViewModel.Header, $scope.Filter, true, false/*will be filtered in controller*/, filteredByView); var projectUnassignedExpendituresNonEmpty = removeEmptyUnassignedExpenditures(projectUnassignedExpenditures, project.AllocationMode); if (projectUnassignedExpendituresNonEmpty && (Object.keys(projectUnassignedExpendituresNonEmpty).length > 0)) { result = { Project: project, Expenditures: projectUnassignedExpendituresNonEmpty }; } return result; }; /* Methods for creating view models */ function createViewModel(model) { var startDate = new Date().getTime(); createHeaderViewModel(model.WeekEndings); createRowsViewModel(); var endDate = new Date().getTime(); //console.log('creating view model: ' + (endDate - startDate) + ' ms'); }; function createHeaderViewModel(weekEndings) { $scope.ViewModel.Header = (new GridHeader(weekEndings || {}).create()); }; function createRowsViewModel() { $scope.ViewModel.Rows = []; $scope.ViewModel.NonProjectTimeTotalRows = []; var filteredByView = $scope.Filter.EntityType == activityCalendarUIService.filterEntityType.view; projectExpendituresCache = activityCalendarService.getResourcesWithAssignedProjects($scope.Filter); unassignedProjectsCache = getDataModel4UnassignedProjects(filteredByView); nonProjectTimeCache = activityCalendarUIService.getNonProjectTimeDataModel(nonProjectTimeCategoryCache, $scope.DisplayMode.GroupBy.Value); // Set Team-wide npts read-only for current view, because they are displayed partially activityCalendarUIService.setTeamWideNptItemsReadOnly(nonProjectTimeCache); if (projectExpendituresCache && (Object.keys(projectExpendituresCache).length > 0)) { var sortedResources = $filter('sortObjectsBy')(projectExpendituresCache, 'Name', false); for (var resourceIndex = 0; resourceIndex < sortedResources.length; resourceIndex++) { var resource = sortedResources[resourceIndex]; if (resource) { var resourceModel = activityCalendarUIService.createViewModel4ResourceBasic(resource); if (resource.Projects) { resourceModel.OwnExpenditureCategoryId = resource.OwnExpenditureCategoryId; resourceModel.VisibleCellsCount = $scope.ViewModel.Header.TotalWeeks; resourceModel.Children = []; for (var projectId in resource.Projects) { var project = resource.Projects[projectId]; var projectModel = activityCalendarUIService.createViewModel4Project(project, null, $scope.Filter); var projectEditableModel = angular.extend({}, projectModel, activityCalendarUIService.createViewModelEditableBasic()); if (projectEditableModel) { projectEditableModel.IsMultipleECs = project.IsMultipleECs; projectEditableModel.ExpenditureCategoryId = project.ExpenditureCategoryId; projectEditableModel.Teams = []; resourceModel.Children.push(projectEditableModel); } } } var resourceNptData = (nonProjectTimeCache && (resource.Id in nonProjectTimeCache)) ? nonProjectTimeCache[resource.Id] : null; var resourceNptModel = activityCalendarUIService.createViewModel4NptTotalRow('Non-Project Time', resourceNptData); resourceModel.NonProjectTime = resourceNptModel; $scope.ViewModel.NonProjectTimeTotalRows.push(resourceNptModel); setTopPartCustomRowTemplates(resourceModel); setTooltips4ResourceRowTop(resourceModel); $scope.ViewModel.Rows.push(resourceModel); } } } $scope.ViewModel.UnassignedRow = activityCalendarUIService.createViewModel4TotalRow('Unassigned'); if (unassignedProjectsCache && Object.keys(unassignedProjectsCache).length > 0) { $scope.ViewModel.UnassignedRow.Children = activityCalendarUIService.createViewModel4Projects(unassignedProjectsCache, $scope.Filter, filteredByView); setTooltips4UnassignedRows($scope.ViewModel.UnassignedRow.Children); } // Create total rows createTotalRowModels(); }; function setTopPartCustomRowTemplates(resourceModel) { if (!resourceModel) return; resourceModel.Templates = { Main: activityCalendarUIService.viewRowTemplates.resourceGbrRowTemplate, Numbers: activityCalendarUIService.viewRowTemplates.resourceGbrRowNumbersTemplate }; if (resourceModel.Children) { for (var index = 0; index < resourceModel.Children.length; index++) { var projectRow = resourceModel.Children[index]; projectRow.Templates = { Main: activityCalendarUIService.viewRowTemplates.projectGbrRowTemplate, Numbers: activityCalendarUIService.viewRowTemplates.projectGbrRowNumbersTemplate }; } } }; function setTooltips4ResourceRowTop(resourceRow) { if (!resourceRow) return; resourceRow.getTooltipContent = $scope.getResourceTopTooltipContent; }; function setTooltips4UnassignedRows(rows) { if (!rows || !angular.isArray(rows)) return; for (var index = 0; index < rows.length; index++) { var row = rows[index]; if (row) { if (row.RowType && (row.RowType == 'Project') && row.IsMaster) { // Tooltips for parts of the master project setTooltips4UnassignedRows(row.Children) } else { // Tooltips for simple project row (without parts), EC and Team rows setTooltips4UnassignedRow(row); } } } }; function setTooltips4UnassignedRow(row) { if (!row) return; if (row.RowType) { switch (row.RowType) { case 'Project': case 'ExpCat': case 'Team': row.getTooltipContent = $scope.getTooltipUnassignedContent; break; } } if (row.Children && angular.isArray(row.Children)) { setTooltips4UnassignedRows(row.Children); } }; function createTotalRowModels() { $scope.ViewModel.TotalRow = activityCalendarUIService.createViewModel4TotalRow('Total'); $scope.ViewModel.RemainingCapacityRow = activityCalendarUIService.createViewModel4TotalRow('Remaining Capacity'); }; function getTotalRows() { var rows = []; if ($scope.ViewModel.TotalRow) { rows.push($scope.ViewModel.TotalRow); } if ($scope.ViewModel.RemainingCapacityRow) { rows.push($scope.ViewModel.RemainingCapacityRow); } return rows; }; function getResourceRowById(resourceId) { if (!resourceId || !$scope.ViewModel.Rows || !$scope.ViewModel.Rows.length) return null; var foundRow = null; for (var index = 0; index < $scope.ViewModel.Rows.length; index++) { var row = $scope.ViewModel.Rows[index]; if (row && row.Id && (row.Id == resourceId)) { foundRow = row; break; } } return foundRow; } /* Root methods for creating view models and filling them with data */ function fillViewModelWithData(model) { var startDate = new Date().getTime(); if (!$scope.ViewModel.Rows) { return; } for (var i = 0; i < $scope.ViewModel.Rows.length; i++) { var resourceRow = $scope.ViewModel.Rows[i]; if (resourceRow && resourceRow.Children) { var resourceData = projectExpendituresCache[resourceRow.Id]; for (var projectIndex = 0; projectIndex < resourceRow.Children.length; projectIndex++) { var projectRow = resourceRow.Children[projectIndex]; var resourceRowData = resourceData.Projects[projectRow.Id]; var expenditureCategoryId = (!projectRow.IsMultipleECs && projectRow.ExpenditureCategoryId && projectRow.ExpenditureCategoryId.length) ? projectRow.ExpenditureCategoryId[0] : resourceRow.OwnExpenditureCategoryId; activityCalendarUIService.fillResourceRowWithData($scope.ViewModel.Header, null, expenditureCategoryId, resourceRow.Id, projectRow, resourceRowData, [resourceRow], projectRow.ActiveScenario.StartDate, projectRow.ActiveScenario.EndDate, false, projectRow.DisableResourceEdit); } var resourceNptData = (resourceRow.NonProjectTime && (resourceRow.Id in nonProjectTimeCache)) ? nonProjectTimeCache[resourceRow.Id] : null; activityCalendarUIService.fillTotalNptRowData(resourceRow.NonProjectTime, resourceNptData, $scope.ViewModel.Header, [resourceRow]); } activityCalendarUIService.updateResourceStylesOnEntireRange($scope.ViewModel.Header, resourceRow.Id, resourceRow); } var filteredByView = $scope.Filter.EntityType == activityCalendarUIService.filterEntityType.view; if ($scope.ViewModel.UnassignedRow && $scope.ViewModel.UnassignedRow.Children && $scope.ViewModel.UnassignedRow.Children.length) { for (var j = 0; j < $scope.ViewModel.UnassignedRow.Children.length; j++) { var project = $scope.ViewModel.UnassignedRow.Children[j]; var projectData = (unassignedProjectsCache || {})[project.Id] || {}; activityCalendarUIService.fillProjectRowWithData(null, project, projectData.Expenditures, $scope.ViewModel.Header, $scope.Filter, filteredByView); if (project.Children) { for (var k = 0; k < project.Children.length; k++) { var ecRow = project.Children[k]; ecRow.RemainingCapacityValues = angular.extend([], ecRow.RemainingTAHoursValues); if (ecRow.Children) { for (var l = 0; l < ecRow.Children.length; l++) { if (ecRow.Children[l].RowType == 'Team') { var teamRow = ecRow.Children[l]; teamRow.RemainingCapacityValues = angular.extend([], teamRow.RemainingTAHoursValues); } } } } } } activityCalendarUIService.fillTotalRowData($scope.ViewModel.UnassignedRow.Children, $scope.ViewModel.UnassignedRow, $scope.ViewModel.Header); } // Fill NPT-rows with data and clear NPT caches. Caches not need more nonProjectTimeCache = {}; nonProjectTimeCategoryCache = {}; // Fill total rows with data fillTotalRowsWithData(); var endDate = new Date().getTime(); //console.log('filling model with data: ' + (endDate - startDate) + ' ms'); }; function fillTotalRowsWithData() { var startDate = new Date().getTime(); var rowsForTotal = ($scope.ViewModel.UnassignedRow && $scope.ViewModel.UnassignedRow.Children) ? angular.copy($scope.ViewModel.UnassignedRow.Children) : []; if ($scope.ViewModel.Rows && ($scope.ViewModel.Rows.length > 0)) { rowsForTotal = rowsForTotal.concat(angular.copy($scope.ViewModel.Rows)); } activityCalendarUIService.fillTotalRowData(rowsForTotal, $scope.ViewModel.TotalRow, $scope.ViewModel.Header); activityCalendarUIService.fillRemainingCapacityRowData($scope.ViewModel.TotalRow, $scope.ViewModel.RemainingCapacityRow, $scope.ViewModel.Header, $scope.ViewModel.DisplayMode.IsCapacityModeActuals.Value); var endDate = new Date().getTime(); console.log('filling total rows with data: ' + (endDate - startDate) + ' ms'); }; /* Methods for toggling display modes */ function toggleHoursResourcesMode() { var totalRows = getTotalRows(); activityCalendarUIService.toggleGridSource($scope.ViewModel.NonProjectTimeTotalRows, $scope.ViewModel.DisplayMode.IsUOMHours); activityCalendarUIService.toggleGridSource($scope.ViewModel.Rows, $scope.ViewModel.DisplayMode.IsUOMHours); if ($scope.ViewModel.UnassignedRow) { activityCalendarUIService.toggleGridSource([$scope.ViewModel.UnassignedRow], $scope.ViewModel.DisplayMode.IsUOMHours); } activityCalendarUIService.toggleGridSource(totalRows, $scope.ViewModel.DisplayMode.IsUOMHours); if ($scope.ViewModel.DisplayMode.IsAvgMode()) { activityCalendarUIService.applyAvgMode($scope.ViewModel.NonProjectTimeTotalRows, $scope.ViewModel.Header); activityCalendarUIService.applyAvgMode($scope.ViewModel.Rows, $scope.ViewModel.Header); if ($scope.ViewModel.UnassignedRow) { activityCalendarUIService.applyAvgMode([$scope.ViewModel.UnassignedRow], $scope.ViewModel.Header); } activityCalendarUIService.applyAvgMode(totalRows, $scope.ViewModel.Header); } }; function toggleBarsValuesMode() { if (!$scope.ViewModel.Rows || !$scope.ViewModel.Rows.length) { return; } for (var i = 0; i < $scope.ViewModel.Rows.length; i++) { var projects = $scope.ViewModel.Rows[i].Children; if (projects) { activityCalendarUIService.toggleBarsValuesMode(projects, $scope.ViewModel.DisplayMode.IsBarMode); } } if ($scope.ViewModel.UnassignedRow && $scope.ViewModel.UnassignedRow.Children) { activityCalendarUIService.toggleBarsValuesMode($scope.ViewModel.UnassignedRow.Children, $scope.ViewModel.DisplayMode.IsBarMode); } }; function toggleMonths() { if ($scope.ViewModel.DisplayMode.IsViewModeMonth) { $scope.ViewModel.Header.collapseMonthes(); } else { $scope.ViewModel.Header.expandMonthes(); } }; function toggleParts() { if (!$scope.ViewModel || !$scope.ViewModel.Rows) return; var customProjectTemplates = { Main: activityCalendarUIService.viewRowTemplates.projectGbrRowTemplate, Numbers: activityCalendarUIService.viewRowTemplates.projectGbrRowNumbersTemplate }; for (var i = 0; i < $scope.ViewModel.Rows.length; i++) { var resource = $scope.ViewModel.Rows[i]; if (resource.Children && resource.Children.length > 0) { resource.Children = activityCalendarUIService.toggleParts(resource.Children, $scope.ViewModel.DisplayMode.ShowParts.Value, 2, customProjectTemplates); activityCalendarUIService.toggleRow(resource, {}, true); } } if ($scope.ViewModel.UnassignedRow && $scope.ViewModel.UnassignedRow.Children && $scope.ViewModel.UnassignedRow.Children.length) { $scope.ViewModel.UnassignedRow.Children = activityCalendarUIService.toggleParts($scope.ViewModel.UnassignedRow.Children, $scope.ViewModel.DisplayMode.ShowParts.Value); activityCalendarUIService.updateProjectStyles($scope.ViewModel.Header, $scope.Filter, $scope.ViewModel.UnassignedRow.Children); } }; function toggleSortBy() { if (!$scope.ViewModel || !$scope.ViewModel.Rows) { return; } for (var i = 0; i < $scope.ViewModel.Rows.length; i++) { var resource = $scope.ViewModel.Rows[i]; if (resource.Children && resource.Children.length > 0) { resource.Children = activityCalendarUIService.toggleSortBy(resource.Children, $scope.ViewModel.DisplayMode.SortBy, $scope.ViewModel.DisplayMode.SortOrder); } } if ($scope.ViewModel.UnassignedRow && $scope.ViewModel.UnassignedRow.Children && $scope.ViewModel.UnassignedRow.Children.length) { $scope.ViewModel.UnassignedRow.Children = activityCalendarUIService.toggleSortBy($scope.ViewModel.UnassignedRow.Children, $scope.ViewModel.DisplayMode.SortBy, $scope.ViewModel.DisplayMode.SortOrder); } }; function toggleCapacityType() { // reset total row values createTotalRowModels(); // recalculate total row UI data fillTotalRowsWithData(); toggleHoursResourcesMode(); }; function initBottomPart() { // need to pass new objects for preventing using single instances of objects inside current and other controllers $scope.$broadcast('rebindTeamInfo', { Header: $scope.ViewModel.Header, DisplayMode: activityCalendarUIService.castDisplayModeIntoTeamInfoMode($scope.ViewModel.DisplayMode), }); }; function onChangeResourceData(filter, header, projectId, expCatId, teamId, resourceId, startDate, endDate, isUOMHours, isAvgMode, changedCells, fromUnassignedBlock) { if (typeof changedCells === 'object' && angular.isArray(changedCells) && changedCells.length > 0) { var resRow = getResourceRowById(resourceId); triggerResourceChanged(changedCells); recalculateResourceAvailability(resourceId); updateTotalRows(changedCells, resRow, expCatId, projectId, teamId); refreshResourceStyles(resourceId, resRow, changedCells); if (fromUnassignedBlock) { refreshProjectStyles(projectId, changedCells); } dataChanged(); } } function triggerResourceChanged(data) { if (typeof data !== 'object' || !angular.isArray(data)) { return; } for (var i = 0; i < data.length; i++) { $scope.$broadcast('resourceValueChanged', data[i]); } }; function triggerNptResourceChanged(data) { if (typeof data !== 'object' || !angular.isArray(data)) { return; } for (var i = 0; i < data.length; i++) { $scope.$broadcast('resourceNonProjectTimeChanged', data[i]); } }; function refreshResourceStyles(resourceId, resourceRow, data) { if (!resourceId || typeof data !== 'object' || !angular.isArray(data)) { return; } var resourceRows = []; if (resourceRow && typeof resourceRow === 'object') { resourceRows = [resourceRow]; if (resourceRow && resourceRow.Children) { for (var prIndex = 0; prIndex < resourceRow.Children.length; prIndex++) { resourceRows.push(resourceRow.Children[prIndex]); } } } var projectRows = []; if ($scope.ViewModel.UnassignedRow && $scope.ViewModel.UnassignedRow.Children) { // Get project rows to iterate by for (var pIndex = 0; pIndex < $scope.ViewModel.UnassignedRow.Children.length; pIndex++) { var pRow = $scope.ViewModel.UnassignedRow.Children[pIndex]; if (pRow && pRow.Children && pRow.Children.length) { if (pRow.IsMaster) { // Found row is master project for (var partIndex = 0; partIndex < pRow.Children.length; partIndex++) { projectRows.push(pRow.Children[partIndex]); } } else { projectRows.push(pRow); } } } for (var pIndex = 0; pIndex < projectRows.length; pIndex++) { var pRow = projectRows[pIndex]; if (pRow && pRow.Children) { for (var ecIndex = 0; ecIndex < pRow.Children.length; ecIndex++) { var ecRow = pRow.Children[ecIndex]; if (ecRow && ecRow.Children) { for (var trIndex = 0; trIndex < ecRow.Children.length; trIndex++) { // Under EC there may be Team row as well as resource Row var teamOrResourceRow = ecRow.Children[trIndex]; if (teamOrResourceRow) { if ((teamOrResourceRow.RowType == 'Team') && teamOrResourceRow.Children) { for (var tIndex = 0; tIndex < teamOrResourceRow.Children.length; tIndex++) { var resourceRow = teamOrResourceRow.Children[tIndex]; if (resourceRow.RowType == 'Resource' && (resourceRow.Id == resourceId)) resourceRows.push(resourceRow); } } if (teamOrResourceRow.RowType == 'Resource' && (teamOrResourceRow.Id == resourceId)) resourceRows.push(teamOrResourceRow); } } } } } } } activityCalendarUIService.updateResourceStyles($scope.ViewModel.Header, resourceId, resourceRows, data); }; function refreshProjectStyles(projectId, data) { if (!projectId || typeof data !== 'object' || !angular.isArray(data)) { return; } var foundUnassignedRows = activityCalendarUIService.getProjectRows($scope.ViewModel.UnassignedRow.Children, projectId); var rowToUpdateStylesIn = null; if (foundUnassignedRows) { if (foundUnassignedRows.projectRow) { var rowToUpdateStylesIn = foundUnassignedRows.projectRow; } if (foundUnassignedRows.masterProjectRow) { var rowToUpdateStylesIn = foundUnassignedRows.masterProjectRow; } activityCalendarUIService.updateProjectStyles($scope.ViewModel.Header, $scope.Filter, [rowToUpdateStylesIn], data); } }; function updateTotalRows(changedCells, resourceRow, expCatId, projectId, teamId) { if (typeof changedCells !== 'object' || !angular.isArray(changedCells) || !expCatId || !projectId) { return; } // gather rows to rollup changes var unassignedRowsToUpdate = getUnassignedRowsToUpdate(projectId, expCatId, teamId) || []; var assignedRowsToUpdate = resourceRow ? getAssignedRowsToUpdate(resourceRow, projectId) : []; if ((assignedRowsToUpdate.length + unassignedRowsToUpdate.length) < 1) { return; } var header = $scope.ViewModel.Header; var isUOMHours = $scope.ViewModel.DisplayMode.IsUOMHours; var isAvgMode = $scope.ViewModel.DisplayMode.IsAvgMode(); var project = activityCalendarService.getProjectById($scope.Filter, projectId); var weekEndings = changedCells.map(function (cell) { return cell.WeekEnding; }); var remainingNeed = {} var allocationMode = $scope.Filter.EntityType == activityCalendarUIService.filterEntityType.company ? activityCalendarUIService.allocationMode.unassignedNeedAllocation : activityCalendarUIService.allocationMode.remainingTeamAllocation; if (allocationMode == activityCalendarUIService.allocationMode.unassignedNeedAllocation) remainingNeed = activityCalendarService.getUnassignedNeedAllocations4Scenario($scope.Filter, project.ActiveScenario.Id) || {}; else remainingNeed = activityCalendarService.getRemainingTeamAllocations4Scenario($scope.Filter, Object.keys(project.Teams || {}), projectId, project.ActiveScenario.Id) || {}; for (var i = 0; i < changedCells.length; i++) { var cell = changedCells[i]; var month = header.Months[cell.MonthIndex]; var monthWeeksCount = month.Childs.length; var newRemainingNeedValue = ((remainingNeed[expCatId] || {}).Allocations || {})[cell.WeekEnding] || 0; var rollupRemainingNeedDeltaHours = cell.DeltaHoursValue || 0; if (rollupRemainingNeedDeltaHours < 0) { rollupRemainingNeedDeltaHours = (newRemainingNeedValue > 0) ? -Math.min(Math.abs(rollupRemainingNeedDeltaHours), newRemainingNeedValue) : 0; } var rollupRemainingNeedDeltaResources = hoursResourcesConverter.convertToResources(expCatId, rollupRemainingNeedDeltaHours); // team allocations delta values var rollupTeamAllocationDeltaHours = cell.DeltaHoursValue || 0; var rollupTeamAllocationDeltaResources = hoursResourcesConverter.convertToResources(expCatId, rollupTeamAllocationDeltaHours); var rollupValues = null; // rollup rows under no team section for (var rowIndex = 0; rowIndex < unassignedRowsToUpdate.length; rowIndex++) { var row = unassignedRowsToUpdate[rowIndex]; rollupValues = activityCalendarUIService.rollupRemainingRow(row, -(rollupRemainingNeedDeltaHours || 0), -(rollupRemainingNeedDeltaResources || 0), false, cell.WeekIndex, month.SelfIndexInWeeks); 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++) { rollupValues = activityCalendarUIService.rollupRemainingRow(assignedRowsToUpdate[rowIndex], (rollupTeamAllocationDeltaHours || 0), (rollupTeamAllocationDeltaResources || 0), false, cell.WeekIndex, month.SelfIndexInWeeks); setCellsValues4Row(assignedRowsToUpdate[rowIndex], cell.WeekIndex, month.SelfIndexInWeeks, isUOMHours, isAvgMode, monthWeeksCount); } } }; // set Cells values based on new values and apply AVG mode if necessary function setCellsValues4Row(rowObject, weekCellIndex, monthCellIndex, isUOMHours, isAvgMode, monthWeeksCount) { var row = rowObject.Row; var collectionNames = activityCalendarUIService.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++) { 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 : roundService.roundQuantity(row[collectionNames.resourcesCollectionName][monthCellIndex] / monthWeeksCount) : row[collectionNames.resourcesCollectionName][monthCellIndex]; row.TotalValue = isAvgMode ? row.VisibleCellsCount == 0 ? 0 : roundService.roundQuantity(row[collectionNames.resourceTotal] / row.VisibleCellsCount) : row[collectionNames.resourceTotal]; } } function getAssignedRowsToUpdate(resourceRow, projectId) { if (!resourceRow || !projectId) return []; var result = []; var resourceRowInfo = { Row: resourceRow, Children: [] }; var totalRow = { Row: $scope.ViewModel.TotalRow, Children: [resourceRowInfo] }; // Make rows tree from total row to include total row in rollup process result.push(totalRow); if (resourceRow.Children) { var foundUnassignedRows = activityCalendarUIService.getProjectRows(resourceRow.Children, projectId); if (foundUnassignedRows && foundUnassignedRows.masterProjectRow) { var masterProjectRowInfo = { Row: foundUnassignedRows.masterProjectRow, Children: [] }; resourceRowInfo.Children.push(masterProjectRowInfo); } } return result; }; function getUnassignedRowsToUpdate(projectId, expCatId, teamId) { if (!projectId || !expCatId || !$scope.ViewModel.UnassignedRow || !$scope.ViewModel.UnassignedRow.Children) return []; // Get corresponding row for unassigned project var result = []; var foundUnassignedRows = findUnassignedRows(projectId, expCatId, teamId); // activityCalendarUIService.getProjectRows($scope.ViewModel.UnassignedRow.Children, projectId); if (foundUnassignedRows && foundUnassignedRows.projectRow) { var projectRow = foundUnassignedRows.projectRow; var masterProjectRow = foundUnassignedRows.masterProjectRow; var unassignedRow = { Row: $scope.ViewModel.UnassignedRow, Children: [] }; var totalRow = { Row: $scope.ViewModel.TotalRow, Children: [unassignedRow] }; // Make rows tree from total row to include total row in rollup process result.push(totalRow); var pRow = { Row: projectRow, Children: [] }; if (masterProjectRow) { var mpRow = { Row: masterProjectRow, Children: [] }; mpRow.Children.push(pRow); unassignedRow.Children.push(mpRow); } else { unassignedRow.Children.push(pRow); } if (foundUnassignedRows.expCatRow) { var ecRow = { Row: foundUnassignedRows.expCatRow, Children: [] } pRow.Children.push(ecRow); if (foundUnassignedRows.teamRows && angular.isArray(foundUnassignedRows.teamRows)) { for (var index = 0; index < foundUnassignedRows.teamRows.length; index++) { ecRow.Children.push({ Row: foundUnassignedRows.teamRows[index] }); } } } } return result; } function findUnassignedRows(projectId, expCatId, teamId) { if (!projectId || !$scope.ViewModel.UnassignedRow || !$scope.ViewModel.UnassignedRow.Children || !$scope.ViewModel.UnassignedRow.Children.length) { return null; } var result = { masterProjectRow: null, projectRow: null, expCatRow: null, teamRows: null }; // Find project rows (master & part) var projectRowsInfo = activityCalendarUIService.getProjectRows($scope.ViewModel.UnassignedRow.Children, projectId); result.masterProjectRow = projectRowsInfo && projectRowsInfo.masterProjectRow ? projectRowsInfo.masterProjectRow : null; result.projectRow = projectRowsInfo && projectRowsInfo.projectRow ? projectRowsInfo.projectRow : null; if (expCatId && result.projectRow && result.projectRow.Children) { var foundExpCatRow = activityCalendarUIService.findRowInCollection(result.projectRow.Children, expCatId); if (foundExpCatRow.RowType && (foundExpCatRow.RowType == 'ExpCat')) { result.expCatRow = foundExpCatRow; if (foundExpCatRow && foundExpCatRow.Children) { result.teamRows = []; if (teamId) { var foundTeamRow = activityCalendarUIService.findRowInCollection(foundExpCatRow.Children, teamId); if (foundTeamRow && foundTeamRow.RowType && (foundTeamRow.RowType == 'Team')) { result.teamRows.push(foundTeamRow); } } else { // Get all child team rows for (var tKey in foundExpCatRow.Children) { var teamRow = foundExpCatRow.Children[tKey]; if (teamRow && teamRow.RowType && (teamRow.RowType == 'Team')) { result.teamRows.push(teamRow); } } } } } } return result; }; function recalculateResourceAvailability(resourceId) { if (!resourceId) { return; } activityCalendarUIService.recalculateResourceAvailability($scope.Filter, $scope.ViewModel.Header, $scope.ViewModel.UnassignedRow.Children, resourceId); }; /* ===== ToolTips display ==== */ $scope.getResourceTopTooltipContent = function (opts) { var tooltip = 'No data available'; var units = ' hours'; if (!$scope.DisplayMode.IsUOMHours) units = ' resources'; if (!opts || !opts.header || !opts.resourceId) return tooltip; var header = opts.header; var resourceId = opts.resourceId; var expCatId = opts.expCatId; var allocationsInfo = getResourceAllocationsInfo(header, resourceId, expCatId); if (allocationsInfo) { tooltip = 'Allocated to Projects: ' + String(allocationsInfo.ScenarioAllocations ? allocationsInfo.ScenarioAllocations : 0) + units; tooltip += ('
NPT Allocations: ' + String(allocationsInfo.NptAllocations ? allocationsInfo.NptAllocations : 0) + units); } else { tooltip = 'No Allocations present'; } return tooltip; }; $scope.getTooltipUnassignedContent = function (opts) { var tooltip = 'No data available'; var units = $scope.DisplayMode.IsUOMHours ? ' hours' : ' resources'; if (!opts || !opts.header || !opts.projectId) return tooltip; var header = opts.header; var projectId = opts.projectId; var expCatId = opts.expCatId; var teamId = opts.teamId; var rows = findUnassignedRows(projectId, expCatId); if (!rows || !rows.projectRow) return tooltip; var projectRow = rows.projectRow; var expCatRows = rows.expCatRow && rows.expCatRow.Id ? [rows.expCatRow.Id] : null; var allocationModeCalculated = activityCalendarUIService.getAllocationMode(projectRow); var scenarioId = projectRow.ActiveScenario ? projectRow.ActiveScenario.Id : null; if (!scenarioId) { return tooltip; } if (allocationModeCalculated == activityCalendarUIService.allocationMode.unassignedNeedAllocation) { // Business Unit filtering mode. Unassigned rows display Project Need minus Resource Allocations var expCatInfo = activityCalendarUIService.getExpCatUnassignedNeed($scope.Filter, $scope.ViewModel.DisplayMode, $scope.ViewModel.Header, header, projectId, scenarioId, expCatRows, true); if (expCatInfo) { var unassignedNeed = roundService.roundQuantity((expCatInfo.Need || 0) - (expCatInfo.ResourceAllocations || 0)); tooltip = 'Category Need: ' + String(expCatInfo.Need || 0) + units + '
' + 'Allocated to Resources: ' + String(expCatInfo.ResourceAllocations || 0) + units + '
' + 'Unassigned Need: ' + String(unassignedNeed) + units; } } if (allocationModeCalculated == activityCalendarUIService.allocationMode.remainingTeamAllocation) { // View or Team filtering mode. Unassigned rows display Team Allocations minus Resource Allocations var teamIds = teamId ? [teamId] : null; var teamAndResourceAllocations = activityCalendarUIService.getExpCatRemainingTeamAllocations($scope.Filter, $scope.ViewModel.DisplayMode, $scope.ViewModel.Header, header, projectId, allocationModeCalculated, expCatRows, teamIds); tooltip = 'Team Allocations: ' + String(teamAndResourceAllocations.TeamAllocations || 0) + units + '
' + 'Allocated to Resources: ' + String(teamAndResourceAllocations.ResourceAllocations || 0) + units; if (angular.isNumber(teamAndResourceAllocations.TeamAllocations) && angular.isNumber(teamAndResourceAllocations.ResourceAllocations)) { var remainingAllocations = roundService.roundQuantity(Math.abs(teamAndResourceAllocations.TeamAllocations - teamAndResourceAllocations.ResourceAllocations)); if (teamAndResourceAllocations.TeamAllocations > teamAndResourceAllocations.ResourceAllocations) { tooltip += ('
Underallocation: ' + String(remainingAllocations) + units); } if (teamAndResourceAllocations.ResourceAllocations > teamAndResourceAllocations.TeamAllocations) { tooltip += ('
Overallocation: ' + String(remainingAllocations) + units); } } } return tooltip; }; function getResourceAllocationsInfo(calendarDate, resourceId, expCatId) { if (!calendarDate || !resourceId || !expCatId) return null; var result = { ScenarioAllocations: 0, NptAllocations: 0 }; var weekendings = []; var scenarioAllocations = 0; var nptAllocations = 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 = $scope.ViewModel.Header.getWeeksInMonth(calendarDate.ParentIndex); } // Get sum of resource allocations in all scenarios var scenarioValuesCount = weekendings.length; var nptValuesCount = 0; var resourceAllocations = activityCalendarService.getResourceAllocations($scope.Filter, resourceId, false); for (var wIndex = 0; wIndex < weekendings.length; wIndex++) { var we = weekendings[wIndex]; if (resourceAllocations && resourceAllocations.Scenarios) { for (var scenarioId in resourceAllocations.Scenarios) { var scenarioData = resourceAllocations.Scenarios[scenarioId]; if (scenarioData && scenarioData.Teams) { for (var teamId in scenarioData.Teams) { var teamData = scenarioData.Teams[teamId]; if (teamData && teamData.Expenditures) { for (var expCatInTeamId in teamData.Expenditures) { var expCatData = teamData.Expenditures[expCatInTeamId]; if (expCatData && expCatData.Allocations && (we in expCatData.Allocations)) { if ($scope.DisplayMode.IsUOMHours) { scenarioAllocations += roundService.roundQuantity(expCatData.Allocations[we]); } else { scenarioAllocations += roundService.roundQuantity(hoursResourcesConverter.convertToResources(expCatId, expCatData.Allocations[we])); } } } } } } } } } // Get sum of NPT allocations var resourceNptData = teamInfoService.getResourceSummaryNptAllocations(resourceId, weekendings); if (resourceNptData) { for (var wIndex = 0; wIndex < weekendings.length; wIndex++) { var we = weekendings[wIndex]; var nptValue = resourceNptData[we]; if (angular.isNumber(nptValue)) { if ($scope.DisplayMode.IsUOMHours) { nptAllocations += roundService.roundQuantity(nptValue); } else { nptAllocations += roundService.roundQuantity(hoursResourcesConverter.convertToResources(expCatId, nptValue)); } nptValuesCount++; } } } if ($scope.ViewModel.DisplayMode.ShowAvgTotals && !$scope.ViewModel.DisplayMode.IsUOMHours) { result.ScenarioAllocations = (scenarioValuesCount > 0) ? roundService.roundQuantity(scenarioAllocations / scenarioValuesCount) : 0; result.NptAllocations = (nptValuesCount > 0) ? roundService.roundQuantity(nptAllocations / nptValuesCount) : 0; } else { result.ScenarioAllocations = roundService.roundQuantity(scenarioAllocations); result.NptAllocations = roundService.roundQuantity(nptAllocations); } return result; }; }]);