'use strict'; app .controller('activityCalendarByTeamController', ['$scope', '$q', 'activityCalendarService', 'teamInfoService', 'activityCalendarUIService', 'dataSources', 'hoursResourcesConverter', '$filter', 'roundService', 'calculateDistributionService', function ($scope, $q, activityCalendarService, teamInfoService, activityCalendarUIService, dataSources, hoursResourcesConverter, $filter, roundService, calculateDistributionService) { 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 teams" mode 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, NoTeamRow: 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); /* 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) { hideRedundantPopovers(); if ($($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); dataChanged(); }; $scope.assignTeam = function (projectRow, expCatRow) { if (!projectRow || !expCatRow || !expCatRow.TeamToAssignId) { return; } // prepare extendModel to keep new added team info var extendedModel = { Expenditures2Display: {} }; if (projectRow.Children) { for (var ecIndex = 0; ecIndex < projectRow.Children.length; ecIndex++) { var ecRow = projectRow.Children[ecIndex]; if (ecRow && ecRow.AvailableTeams) { for (var tIndex = 0; tIndex < ecRow.AvailableTeams.length; tIndex++) { if (ecRow.AvailableTeams[tIndex].Id == expCatRow.TeamToAssignId) { extendedModel.Expenditures2Display[ecRow.Id] = ecRow.Id; break; } } } } } // assign new team to DAL model activityCalendarUIService.assignTeam($scope.Filter, $scope.ViewModel.Header, projectRow.Id, expCatRow.TeamToAssignId, extendedModel); // add new team row to each EC of the project if it is available for assignment if (projectRow.Children) { for (var ecIndex = 0; ecIndex < projectRow.Children.length; ecIndex++) { var ecRow = projectRow.Children[ecIndex]; if (ecRow && ecRow.AvailableTeams) { for (var tIndex = 0; tIndex < ecRow.AvailableTeams.length; tIndex++) { if (ecRow.AvailableTeams[tIndex].Id == expCatRow.TeamToAssignId) { createAndFillNewEcTeamRow(projectRow, ecRow, expCatRow.TeamToAssignId); // expand ec row if (ecRow.Collapsed) activityCalendarUIService.toggleRow(ecRow, {}, false); break; } } } } } refreshProjectAvailableTeams(projectRow); expCatRow.TeamToAssignId = null; dataChanged(); }; function refreshProjectAvailableTeams(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 = getAvailableTeams(projectRow.Id, ecRow.Id); } } } }; $scope.checkResourceValue = function (projectRow, expCatRow, resRow, $index, $data) { if (!projectRow || !expCatRow || !resRow) { return; } var filter = $scope.Filter, header = $scope.ViewModel.Header, isUOMHours = $scope.ViewModel.DisplayMode.IsUOMHours, isAvgMode = $scope.ViewModel.DisplayMode.IsAvgMode(), parentRow = expCatRow || {}; var result = activityCalendarUIService.checkResourceValue(filter, header, projectRow.Id, parentRow, resRow.Id, resRow, isUOMHours, isAvgMode, $index, $data, false); if (typeof result === 'object' && angular.isArray(result) && result.length > 0) { triggerResourceChanged(result); recalculateResourceAvailability(resRow.Id); refreshResourceStyles(resRow.Id, result); refreshProjectStyles(projectRow, result); dataChanged(); return false; } return result; }; $scope.checkTeamValueUnassigned = function (projectRow, expCatRow, teamRow, $index, $data) { if (!projectRow || !expCatRow || !teamRow) { 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.checkTeamValue(filter, header, projectRow.Id, expCatRow, teamRow.Id, teamRow, isUOMHours, isAvgMode, $index, $data); onChangeTeamDataUnassigned(filter, header, projectRow, expCatRow, teamRow, startDate, endDate, isUOMHours, isAvgMode, result); return false; }; $scope.checkResourceGrandTotalValue = function (projectRow, expCatRow, resRow, $data) { 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(), parentRow = expCatRow || {}; var result = activityCalendarUIService.checkResourceGrandTotalValue(filter, header, projectRow.Id, projectRow.ActiveScenario.StartDate, projectRow.ActiveScenario.EndDate, parentRow, resRow.Id, resRow, isUOMHours, isAvgMode, $data, false); if (typeof result === 'object' && angular.isArray(result) && result.length > 0) { triggerResourceChanged(result); recalculateResourceAvailability(resRow.Id); refreshResourceStyles(resRow.Id, result); refreshProjectStyles(projectRow, result); dataChanged(); return false; } return result; }; $scope.checkTeamGrandTotalValueUnassigned = function (projectRow, expCatRow, teamRow, $data) { if (!projectRow || !projectRow.ActiveScenario || !expCatRow || !teamRow) { 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.checkTeamGrandTotalValue(filter, header, projectRow.Id, startDate, endDate, expCatRow, teamRow.Id, teamRow, isUOMHours, isAvgMode, $data); onChangeTeamDataUnassigned(filter, header, projectRow, expCatRow, teamRow, startDate, endDate, isUOMHours, isAvgMode, result); return false; }; $scope.takeRemaining = function (projectRow, expCatRow, resRow) { 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(), parentRow = expCatRow || {}; var result = activityCalendarUIService.takeRemainingCapacity(filter, header, projectRow.Id, projectRow.ActiveScenario.StartDate, projectRow.ActiveScenario.EndDate, parentRow, resRow.Id, resRow, isUOMHours, isAvgMode); if (typeof result === 'object' && angular.isArray(result) && result.length > 0) { triggerResourceChanged(result); recalculateResourceAvailability(resRow.Id); refreshResourceStyles(resRow.Id, result); refreshProjectStyles(projectRow, result); dataChanged(); } }; $scope.takeAll = function (projectRow, expCatRow, resRow) { 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(), parentRow = expCatRow || {}; var result = activityCalendarUIService.takeFullCapacity(filter, header, projectRow.Id, projectRow.ActiveScenario.StartDate, projectRow.ActiveScenario.EndDate, parentRow, resRow.Id, resRow, isUOMHours, isAvgMode); if (typeof result === 'object' && angular.isArray(result) && result.length > 0) { triggerResourceChanged(result); recalculateResourceAvailability(resRow.Id); refreshResourceStyles(resRow.Id, result); refreshProjectStyles(projectRow, result); dataChanged(); } }; $scope.zeroResource = function (projectRow, expCatRow, resRow) { if (!projectRow || !projectRow.ActiveScenario || !expCatRow || !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(), parentRow = expCatRow || {}; var changedCells = activityCalendarUIService.zeroResource(filter, header, projectRow.Id, projectRow.ActiveScenario.StartDate, projectRow.ActiveScenario.EndDate, parentRow, resRow.Id, resRow, isUOMHours, isAvgMode); if (typeof changedCells === 'object' && angular.isArray(changedCells) && changedCells.length > 0) { triggerResourceChanged(changedCells); recalculateResourceAvailability(resRow.Id); refreshResourceStyles(resRow.Id, changedCells); refreshProjectStyles(projectRow, changedCells); dataChanged(); } } }); }, className: "bootbox-sm" }); }; $scope.removeResource = function (projectRow, expCatRow, teamRow, resRow, $index) { if (!projectRow || !projectRow.ActiveScenario || !expCatRow || !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(), expCatId = (teamRow && teamRow.ExpenditureCategoryId) || expCatRow.Id, parentRow = expCatRow; var changedCells = activityCalendarUIService.cleanAndRemoveResource(filter, header, projectRow.Id, projectRow.ActiveScenario.StartDate, projectRow.ActiveScenario.EndDate, expCatId, parentRow, resRow.Id, resRow, isUOMHours, isAvgMode, $index); if (typeof changedCells === 'object' && angular.isArray(changedCells) && changedCells.length > 0) { triggerResourceChanged(changedCells); recalculateResourceAvailability(resRow.Id); refreshResourceStyles(resRow.Id, changedCells); refreshProjectStyles(projectRow, changedCells); dataChanged(); } } }); }, className: "bootbox-sm" }); }; $scope.checkNptResourceValue = function (teamRow, nptTotalRow, nptCatRow, nptItemRow, nptResourceRow, $index, $data) { if (!teamRow || !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(nptTotalRow); rollupRows.push(teamRow); 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); recalculateResourceAvailability(nptResourceRow.Id); dataChanged(); return false; } return result; }; $scope.checkTeamWideNptValue = function (teamRow, nptTotalRow, nptCatRow, nptItemRow, $index, $data) { if (!teamRow || !nptTotalRow || !nptCatRow || !nptItemRow) return; var filter = $scope.Filter, header = $scope.ViewModel.Header, isUOMHours = $scope.ViewModel.DisplayMode.IsUOMHours, isAvgMode = $scope.ViewModel.DisplayMode.IsAvgMode(); var rollupRows = []; rollupRows.push(nptTotalRow); rollupRows.push(companyRow); rollupRows.push(nptCatRow); rollupRows.push(nptItemRow); rollupRows.push($scope.ViewModel.TotalRow); var result = activityCalendarUIService.checkTeamWideNptValue(filter, header, $scope.ViewModel.RemainingCapacityRow, rollupRows, nptCatRow, nptItemRow, 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); // Recalculate resource availability for all related resources for (var i = 0; i < result.length; i++) { recalculateResourceAvailability(result[i].ResourceId); } dataChanged(); return false; } return result; }; $scope.takeTeamRemainingUnassigned = function (projectRow, expCatRow, teamRow) { if (!projectRow || !projectRow.ActiveScenario || !expCatRow || !teamRow) { 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.takeTeamRemainingCapacity(filter, header, projectRow.Id, startDate, endDate, expCatRow, teamRow, isUOMHours, isAvgMode); onChangeTeamDataUnassigned(filter, header, projectRow, expCatRow, teamRow, startDate, endDate, isUOMHours, isAvgMode, result); }; $scope.takeTeamAllUnassigned = function (projectRow, expCatRow, teamRow) { if (!projectRow || !projectRow.ActiveScenario || !expCatRow || !teamRow) 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.takeTeamFullCapacity(filter, header, projectRow.Id, startDate, endDate, expCatRow, teamRow, isUOMHours, isAvgMode); onChangeTeamDataUnassigned(filter, header, projectRow, expCatRow, teamRow, startDate, endDate, isUOMHours, isAvgMode, result); }; $scope.zeroTeamUnassigned = function (projectRow, expCatRow, teamRow) { if (!projectRow || !projectRow.ActiveScenario || !expCatRow || !teamRow) { return; } bootbox.confirm({ message: "Are you sure you want to fill this Team allocations 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(), startDate = projectRow.ActiveScenario.StartDate, endDate = projectRow.ActiveScenario.EndDate; var changedCells = activityCalendarUIService.zeroTeam(filter, header, projectRow.Id, startDate, endDate, expCatRow, teamRow, isUOMHours, isAvgMode); onChangeTeamDataUnassigned(filter, header, projectRow, expCatRow, teamRow, startDate, endDate, isUOMHours, isAvgMode, changedCells); } }); }, className: "bootbox-sm" }); }; $scope.removeTeamUnassigned = function (prjRow, expCatRow, teamRow, $index) { if (!prjRow || !prjRow.ActiveScenario || !expCatRow || !teamRow) { return; } bootbox.confirm({ message: "Are you sure you want to remove this team?", 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(), teamId = teamRow.Id, startDate = prjRow.ActiveScenario.StartDate, endDate = prjRow.ActiveScenario.EndDate; var changedCellsByExpCats = activityCalendarUIService.cleanAndRemoveTeam(filter, header, prjRow, teamId, startDate, endDate, isUOMHours, isAvgMode); if ((typeof changedCellsByExpCats === 'object') && (Object.keys(changedCellsByExpCats).length > 0)) { for (var expCatId in changedCellsByExpCats) { var currentExpCatRow = changedCellsByExpCats[expCatId].ExpCatRow; var changedCells = changedCellsByExpCats[expCatId].ChangedCells; onChangeTeamDataUnassigned(filter, header, prjRow, currentExpCatRow, teamRow, startDate, endDate, isUOMHours, isAvgMode, changedCells); } } refreshProjectAvailableTeams(prjRow); } }); }, className: "bootbox-sm" }); }; $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) { $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'); }; function onChangeTeamDataUnassigned(filter, header, projectRow, expCatRow, teamRow, startDate, endDate, isUOMHours, isAvgMode, changedCells) { if (typeof changedCells === 'object' && angular.isArray(changedCells) && changedCells.length > 0) { // Find out, if some categories where added to teams at bottom part var needRecreateRows = activityCalendarUIService.categoryRowsWereAdded(changedCells); if (needRecreateRows) // Categories where added, we need to redraw all rows in bottom part refreshBottomPart(); else triggerTeamChanged(changedCells); updateTotalRows(changedCells, teamRow, expCatRow, projectRow.Id); refreshNoTeamProjectStyles(projectRow, changedCells); dataChanged(); } }; function updateTotalRows(changedCells, teamRow, expCatRow, projectId) { if (typeof changedCells !== 'object' || !angular.isArray(changedCells) || !expCatRow || !projectId || !teamRow) { return; } var expenditureCategoryId = expCatRow.Id; // gather rows to rollup changes var unassignedRowsToUpdate = getUnassignedRowsToUpdate(projectId, expenditureCategoryId) || []; var assignedRowsToUpdate = getAssignedRowsToUpdate(teamRow.Id, projectId, expenditureCategoryId) || []; if ((assignedRowsToUpdate.length <= 0) && (unassignedRowsToUpdate.length <= 0)) { 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 = activityCalendarService.getRemainingNeedAllocations4Scenario($scope.Filter, Object.keys(project.Teams), project.ActiveScenario.Id, [expenditureCategoryId], weekEndings) || {}; var expCatCollectionNames = activityCalendarUIService.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 < unassignedRowsToUpdate.length; rowIndex++) { var row = unassignedRowsToUpdate[rowIndex]; 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++) { var rollupValues = activityCalendarUIService.rollupRemainingRow(assignedRowsToUpdate[rowIndex], (rollupTeamAllocationDeltaHours || 0), (rollupTeamAllocationDeltaResources || 0), false, cell.WeekIndex, month.SelfIndexInWeeks); 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) { refreshProjectStyles(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]; } } } } } } } }; function getUnassignedRowsToUpdate(projectId, expCatId) { if (!projectId || !expCatId || !$scope.ViewModel.NoTeamRow || !$scope.ViewModel.NoTeamRow.Children) return []; // Get corresponding row for unassigned project var result = []; var foundRowsToRollup = findUnassignedRows(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: $scope.ViewModel.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; }; function getAssignedRowsToUpdate(teamId, projectId, expCatId) { if (!teamId || !projectId || !expCatId || !$scope.ViewModel.Rows || !$scope.ViewModel.Rows.length) { return []; } var foundRowsToRollup = findAssignedRows(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; }; function findAssignedRows(teamId, projectId, expCatId) { if (!teamId || !projectId || !expCatId || !$scope.ViewModel.Rows || !$scope.ViewModel.Rows.length) return null; var result = { teamRow: null, masterProjectRow: null, projectRow: null, expCatRow: null }; result.teamRow = activityCalendarUIService.findRowInCollection($scope.ViewModel.Rows, teamId); if (result.teamRow && angular.isArray(result.teamRow.Children)) { // Get project rows (master & part) var projectRowsInfo = activityCalendarUIService.getProjectRows(result.teamRow.Children, 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 = activityCalendarUIService.findRowInCollection(result.projectRow.Children, expCatId); } } return result; }; function findUnassignedRows(projectId, expCatId) { if (!projectId || !expCatId || !$scope.ViewModel.NoTeamRow || !$scope.ViewModel.NoTeamRow.Children || !$scope.ViewModel.NoTeamRow.Children.length) { return null; } var result = { masterProjectRow: null, projectRow: null, expCatRow: null }; // Find project rows (master & part) var projectRowsInfo = activityCalendarUIService.getProjectRows($scope.ViewModel.NoTeamRow.Children, 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 = activityCalendarUIService.findRowInCollection(result.projectRow.Children, expCatId); } return result; }; // 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]; } } /* Methods for preparing data */ function removeEmptyUnassignedExpenditures(projectId, projectExpCatsCache) { if (!projectExpCatsCache) return; var result = {}; var ec2TeamsExisting = {}; var expCatsInPendingTeams = activityCalendarService.getExpendituresExistInPendingTeams($scope.Filter, projectId); for (var expCatId in projectExpCatsCache) { // check if there is any team that contains category if (ec2TeamsExisting[expCatId] == undefined) { ec2TeamsExisting[expCatId] = teamInfoService.teamsWithExpenditureCategoryExist(expCatId); } var expCatItem = projectExpCatsCache[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; }; function getDataModel(projects) { if (!projects) { return null; } var filterExpendituresByTeams = activityCalendarUIService.isFilteredByTeams($scope.Filter); var result = { NoTeam: { Id: '', Name: 'No Team', Projects: {} } }; for (var projectId in projects) { var project = projects[projectId]; if (project.Teams && Object.keys(project.Teams).length) { for (var teamId in project.Teams) { // skip newly assigned teams var type = project.Teams[teamId].Type || activityCalendarService.TeamType.Saved; if (type === activityCalendarService.TeamType.Pending) { continue; } if (!(teamId in result)) { var sourceTeam = teamInfoService.getById(teamId); if (sourceTeam) { result[teamId] = { Id: teamId, Name: sourceTeam.Name, Projects: {} }; } } // if for some reasons teamInfoService.getById got null if (!(teamId in result)) { console.error('There is no team with id = ' + teamId); continue; } var projectExpenditures = activityCalendarUIService.getExpendituresWithResources4Project(project, [teamId], null, $scope.ViewModel.Header, $scope.Filter, false, filterExpendituresByTeams); if (projectExpenditures && Object.keys(projectExpenditures).length) { result[teamId].Projects[projectId] = { Project: project, Expenditures: projectExpenditures }; } } } var noTeamProject = angular.extend({}, project, { AllocationMode: activityCalendarUIService.allocationMode.remainingNeedAllocation }); var projectNoTeamsExpenditures = activityCalendarUIService.getExpenditures4Project(noTeamProject, null, null, $scope.ViewModel.Header, $scope.Filter, true, false); var ecKeys = Object.keys(projectNoTeamsExpenditures); for (var i = 0; i < ecKeys.length; i++) { if (!projectNoTeamsExpenditures[ecKeys[i]].AllowResourceAssignment && ($.inArray(ecKeys[i], $scope.Filter.ProjectRoles || []) < 0)) delete projectNoTeamsExpenditures[ecKeys[i]]; } var projectNoTeamsExpendituresNonEmpty = removeEmptyUnassignedExpenditures(projectId, projectNoTeamsExpenditures); if (projectNoTeamsExpendituresNonEmpty && (Object.keys(projectNoTeamsExpendituresNonEmpty).length > 0)) { result['NoTeam'].Projects[projectId] = { Project: noTeamProject, Expenditures: projectNoTeamsExpendituresNonEmpty }; } } // Add teams without projects to display NPT var teams = teamInfoService.getAll(); if (teams) { for (var teamId in teams) { if (!(teamId in result)) { var sourceTeam = teamInfoService.getById(teamId); if (sourceTeam) { result[teamId] = { Id: teamId, Name: sourceTeam.Name, Projects: {} }; } } } } return result; }; function getDataModel4NewTeam(teamId, projectId, expCatId) { if (!teamId || !projectId || !expCatId) { return null; } var project = activityCalendarService.getProjectById($scope.Filter, projectId); if (!project || !project.Teams || !(teamId in project.Teams)) { return null; } var expCats = activityCalendarUIService.getExpendituresWithResources4Project(project, [teamId], [expCatId], $scope.ViewModel.Header, $scope.Filter, true, false); if (expCats && expCats[expCatId]) return expCats[expCatId]; else return null; }; /* Methods for creating view models */ function createViewModel(model) { createHeaderViewModel(model.WeekEndings); createRowsViewModel(model); }; function createHeaderViewModel(weekEndings) { $scope.ViewModel.Header = (new GridHeader(weekEndings || {}).create()); }; function createRowsViewModel(model) { $scope.ViewModel.Rows = []; $scope.ViewModel.NonProjectTimeTotalRows = []; if (!model || !model.Projects) { return; } projectExpendituresCache = getDataModel(model.Projects); nonProjectTimeCache = activityCalendarUIService.getNonProjectTimeDataModel(nonProjectTimeCategoryCache, $scope.DisplayMode.GroupBy.Value); // Set Team-wide npts read-only for current view, because they displayed partially activityCalendarUIService.setTeamWideNptItemsReadOnly(nonProjectTimeCache); if (projectExpendituresCache && Object.keys(projectExpendituresCache).length) { // sort teams by name var sortedTeams = $filter('sortObjectsBy')(projectExpendituresCache, 'Name', false); for (var i = 0; i < sortedTeams.length; i++) { var teamData = sortedTeams[i]; if (!teamData.Id) { // No Team section continue; } var teamViewModel = activityCalendarUIService.createViewModel4Team(teamData); if (teamViewModel) { setTooltips4TopPartRows(teamViewModel); var teamNptData = (nonProjectTimeCache && (teamData.Id in nonProjectTimeCache)) ? nonProjectTimeCache[teamData.Id] : null; var teamNptModel = activityCalendarUIService.createViewModel4NptTotalRow('Non-Project Time', teamNptData); teamViewModel.NonProjectTime = teamNptModel; $scope.ViewModel.NonProjectTimeTotalRows.push(teamNptModel); $scope.ViewModel.Rows.push(teamViewModel); } } if ('NoTeam' in projectExpendituresCache && projectExpendituresCache['NoTeam'].Projects) { $scope.ViewModel.NoTeamRow = activityCalendarUIService.createViewModel4TotalRow('No Team'); $scope.ViewModel.NoTeamRow.AllocationMode = activityCalendarUIService.allocationMode.remainingNeedAllocation; $scope.ViewModel.NoTeamRow.Children = activityCalendarUIService.createViewModel4Projects(projectExpendituresCache['NoTeam'].Projects); for (var pIndex = 0; pIndex < $scope.ViewModel.NoTeamRow.Children.length; pIndex++) { var projectRow = $scope.ViewModel.NoTeamRow.Children[pIndex]; projectRow.getTooltipContent = $scope.getTooltipProjectUnassignedContent; if (projectRow.Children) { for (var ecIndex = 0; ecIndex < projectRow.Children.length; ecIndex++) { var ecRow = projectRow.Children[ecIndex]; if (ecRow) { ecRow.getTooltipContent = $scope.getTooltipEcUnassignedContent; ecRow.AvailableTeams = getAvailableTeams(projectRow.Id, ecRow.Id); setCustomRowTemplates(ecRow); } } } } } } // Create total rows createTotalRowModels(); }; function setCustomRowTemplates(expCatRow) { if (!expCatRow) return; expCatRow.Templates = { Main: activityCalendarUIService.viewRowTemplates.expCatUnallocatedRowTemplate, Numbers: activityCalendarUIService.viewRowTemplates.expCatUnallocatedRowNumbersTemplate }; if (expCatRow.Children) { for (var tIndex = 0; tIndex < expCatRow.Children.length; tIndex) { var teamRow = expCatRow.Children[tIndex]; teamRow.Templates = { Main: activityCalendarUIService.viewRowTemplates.teamUnallocatedRowTemplate, Numbers: activityCalendarUIService.viewRowTemplates.teamUnallocatedRowNumbersTemplate }; } } }; function setTooltips4TopPartRows(teamRow) { if (!teamRow || !teamRow.Children) return; for (var pIndex = 0; pIndex < teamRow.Children.length; pIndex++) { var projectRow = teamRow.Children[pIndex]; if (projectRow && projectRow.Children) { for (var ecIndex = 0; ecIndex < projectRow.Children.length; ecIndex++) { var expCatRow = projectRow.Children[ecIndex]; if (expCatRow) { expCatRow.getTooltipContent = $scope.getTooltipExpCatTopContent; } } } } }; 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 getResourceRows(resourceId) { var result = activityCalendarUIService.getResourceRows($scope.ViewModel.Rows, resourceId); return result; }; function getAvailableTeams(projectId, expenditureCategoryId) { if (!projectId || !expenditureCategoryId) { return null; } var availableTeams = activityCalendarService.getTeamsAvailable4Assign($scope.Filter, projectId, expenditureCategoryId); var availableTeamsViewModel = activityCalendarUIService.createViewModel4AvailableTeams(availableTeams); if (!availableTeamsViewModel || !Object.keys(availableTeamsViewModel).length) { return null; } return $filter('sortObjectsBy')(availableTeamsViewModel, 'Name', false); }; /* Root methods for creating view models and filling them with data */ function fillViewModelWithData(model) { if (!$scope.ViewModel.Rows) { return; } for (var i = 0; i < $scope.ViewModel.Rows.length; i++) { var teamRow = $scope.ViewModel.Rows[i]; var projectExpenditures = projectExpendituresCache[teamRow.Id] || {}; for (var j = 0; j < teamRow.Children.length; j++) { var projectRow = teamRow.Children[j]; var projectData = (projectExpenditures.Projects || {})[projectRow.Id] || {}; activityCalendarUIService.fillProjectRowWithData(teamRow, projectRow, projectData.Expenditures, $scope.ViewModel.Header, $scope.Filter); } if (teamRow.NonProjectTime) { var teamNptData = (teamRow.Id in nonProjectTimeCache) ? nonProjectTimeCache[teamRow.Id] : null; activityCalendarUIService.fillTotalNptRowData(teamRow.NonProjectTime, teamNptData, $scope.ViewModel.Header, [teamRow]); } } if ($scope.ViewModel.NoTeamRow && $scope.ViewModel.NoTeamRow.Children && $scope.ViewModel.NoTeamRow.Children.length) { var projectExpenditures = projectExpendituresCache['NoTeam'] || {}; for (var j = 0; j < $scope.ViewModel.NoTeamRow.Children.length; j++) { var project = $scope.ViewModel.NoTeamRow.Children[j]; var projectData = (projectExpenditures.Projects || {})[project.Id] || {}; activityCalendarUIService.fillProjectRowWithData(null, project, projectData.Expenditures, $scope.ViewModel.Header, $scope.Filter); if (project.Children) { for (var ecIndex = 0; ecIndex < project.Children.length; ecIndex++) { var ecRow = project.Children[ecIndex]; ecRow.RemainingCapacityValues = angular.extend([], ecRow.RemainingNeedHoursValues); } } // add additional ec-team rows for recently added teams if (projectData && projectData.Project && projectData.Project.Teams) { angular.forEach(projectData.Project.Teams, function (pTeamCache, teamKey) { // we should care only about pending (newly assigned)/saved pending (assigned multiple times) teams var type = pTeamCache.Type || activityCalendarService.TeamType.Saved; if (type !== activityCalendarService.TeamType.Saved && pTeamCache.Expenditures2Display && project.Children) { for (var ecIndex = 0; ecIndex < project.Children.length; ecIndex++) { var ecRow = project.Children[ecIndex]; if (ecRow.Id in pTeamCache.Expenditures2Display) { createAndFillNewEcTeamRow(project, ecRow, teamKey); // expand project row if (project.Collapsed) activityCalendarUIService.toggleRow(project, {}, false); // expand ec row if (ecRow.Collapsed) activityCalendarUIService.toggleRow(ecRow, {}, false); } } } }); } refreshProjectAvailableTeams(project); } activityCalendarUIService.fillTotalRowData($scope.ViewModel.NoTeamRow.Children, $scope.ViewModel.NoTeamRow, $scope.ViewModel.Header); } // Clear NPT caches. Caches not need more nonProjectTimeCache = {}; nonProjectTimeCategoryCache = {}; // Fill total rows with data fillTotalRowsWithData(); }; function fillTotalRowsWithData() { var rowsForTotal = ($scope.ViewModel.Rows) ? angular.copy($scope.ViewModel.Rows) : []; if ($scope.ViewModel.NoTeamRow) { rowsForTotal = rowsForTotal.concat(angular.copy($scope.ViewModel.NoTeamRow)); } 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); }; function createAndFillNewEcTeamRow(projectRow, expCatRow, teamId) { // create a UI row model and fill it with data var team = teamInfoService.getById(teamId); var ecTeamData = getDataModel4NewTeam(teamId, projectRow.Id, expCatRow.Id); ecTeamData = angular.extend({}, ecTeamData, { Id: team.Id, ExpenditureCategoryId: ecTeamData.Id, Name: team.Name, Initialized: expCatRow.Initialized, Show: expCatRow.Show, Level: expCatRow.Level + 1 }); var newTeamRow = activityCalendarUIService.createViewModel4ECTeam(ecTeamData); activityCalendarUIService.fillECTeamRowWithData($scope.ViewModel.Header, newTeamRow, expCatRow, ecTeamData, projectRow.Id, projectRow.ActiveScenario.StartDate, projectRow.ActiveScenario.EndDate, projectRow.ReadOnly); activityCalendarUIService.toggleGridSource([newTeamRow], $scope.ViewModel.DisplayMode.IsUOMHours); if ($scope.ViewModel.DisplayMode.IsAvgMode()) { activityCalendarUIService.applyAvgMode([newTeamRow], $scope.ViewModel.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 = activityCalendarUIService.toggleSortBy(expCatRow.Children, $scope.ViewModel.DisplayMode.SortBy, $scope.ViewModel.DisplayMode.SortOrder); }; /* 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.NoTeamRow) { activityCalendarUIService.toggleGridSource([$scope.ViewModel.NoTeamRow], $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.NoTeamRow) { activityCalendarUIService.applyAvgMode([$scope.ViewModel.NoTeamRow], $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.NoTeamRow && $scope.ViewModel.NoTeamRow.Children) { activityCalendarUIService.toggleBarsValuesMode($scope.ViewModel.NoTeamRow.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; for (var i = 0; i < $scope.ViewModel.Rows.length; i++) { var team = $scope.ViewModel.Rows[i]; if (team.Children && team.Children.length > 0) { team.Children = activityCalendarUIService.toggleParts(team.Children, $scope.ViewModel.DisplayMode.ShowParts.Value); activityCalendarUIService.updateProjectStyles($scope.ViewModel.Header, $scope.Filter, team.Children, null, null, [team.Id]); } } if ($scope.ViewModel.NoTeamRow && $scope.ViewModel.NoTeamRow.Children && $scope.ViewModel.NoTeamRow.Children.length) { $scope.ViewModel.NoTeamRow.Children = activityCalendarUIService.toggleParts($scope.ViewModel.NoTeamRow.Children, $scope.ViewModel.DisplayMode.ShowParts.Value); activityCalendarUIService.updateProjectStyles($scope.ViewModel.Header, $scope.Filter, $scope.ViewModel.NoTeamRow.Children); } }; function toggleSortBy() { if (!$scope.ViewModel || !$scope.ViewModel.Rows) return; for (var i = 0; i < $scope.ViewModel.Rows.length; i++) { var team = $scope.ViewModel.Rows[i]; if (team.Children && team.Children.length > 0) { team.Children = activityCalendarUIService.toggleSortBy(team.Children, $scope.ViewModel.DisplayMode.SortBy, $scope.ViewModel.DisplayMode.SortOrder); } } if ($scope.ViewModel.NoTeamRow && $scope.ViewModel.NoTeamRow.Children && $scope.ViewModel.NoTeamRow.Children.length) { $scope.ViewModel.NoTeamRow.Children = activityCalendarUIService.toggleSortBy($scope.ViewModel.NoTeamRow.Children, $scope.ViewModel.DisplayMode.SortBy, $scope.ViewModel.DisplayMode.SortOrder); } }; function toggleCapacityType() { // reset total row values createTotalRowModels(); // recalculate total row UI data fillTotalRowsWithData(); toggleHoursResourcesMode(); }; function recalculateResourceAvailability(resourceId) { if (!resourceId || !$scope.ViewModel.Rows) { return; } for (var teamIndex = 0; teamIndex < $scope.ViewModel.Rows.length; teamIndex++) { var teamRow = $scope.ViewModel.Rows[teamIndex]; if (teamRow && teamRow.Children) { activityCalendarUIService.recalculateResourceAvailability($scope.Filter, $scope.ViewModel.Header, teamRow.Children, resourceId); } } }; 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 refreshBottomPart() { $scope.$broadcast('teaminfo.recreateRows'); }; 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 triggerTeamChanged(data) { if (typeof data !== 'object' || !angular.isArray(data)) { return; } for (var i = 0; i < data.length; i++) { $scope.$broadcast('teamValueChanged', 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, data) { if (!resourceId || typeof data !== 'object' || !angular.isArray(data)) { return; } var resourceRows = getResourceRows(resourceId); if (!resourceRows || !resourceRows.length) { return; } activityCalendarUIService.updateResourceStyles($scope.ViewModel.Header, resourceId, resourceRows, data); }; function refreshProjectStyles(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 for (var i = 0; i < $scope.ViewModel.Rows.length; i++) { var team = $scope.ViewModel.Rows[i]; if (team.Children && team.Children.length) { var projectRowsInfo = activityCalendarUIService.getProjectRows(team.Children, projectRow.Id); if (projectRowsInfo && projectRowsInfo.masterProjectRow && projectRowsInfo.projectRow && (projectRowsInfo.projectRow.ParentTeamId == projectRow.ParentTeamId)) { rowToUpdateStylesIn = projectRowsInfo.masterProjectRow; break; } } } } activityCalendarUIService.updateProjectStyles($scope.ViewModel.Header, $scope.Filter, [rowToUpdateStylesIn], data, null, [projectRow.ParentTeamId]); }; function refreshNoTeamProjectStyles(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 = activityCalendarUIService.getProjectRows($scope.ViewModel.NoTeamRow.Children, projectRow.Id); if (projectRowsInfo && projectRowsInfo.masterProjectRow) { rowToUpdateStylesIn = projectRowsInfo.masterProjectRow; } } activityCalendarUIService.updateProjectStyles($scope.ViewModel.Header, $scope.Filter, [rowToUpdateStylesIn], data); }; /* ===== ToolTips display ==== */ $scope.getTooltipExpCatTopContent = function (opts) { var tooltip = 'No data available'; var units = ' hours'; if (!$scope.DisplayMode.IsUOMHours) units = ' resources'; if (!opts || !opts.header || !opts.projectId || !opts.expCatId) return tooltip; var header = opts.header; var projectId = opts.projectId; var expCatId = opts.expCatId; var teamId = opts.teamId; var teamAllocations = activityCalendarUIService.getTeamAllocations4Display($scope.Filter, $scope.ViewModel.DisplayMode, $scope.ViewModel.Header, header, projectId, expCatId, teamId); if (teamAllocations && (teamAllocations.length > 0)) { tooltip = 'Allocated to Teams:'; for (var index = 0; index < teamAllocations.length; index++) { var teamInfo = teamAllocations[index]; tooltip += ('
' + teamInfo.Name + ': ' + teamInfo.Need + units); } } else { tooltip = 'No Team Allocations present'; } return tooltip; }; $scope.getTooltipEcUnassignedContent = function (opts) { var tooltip = 'No data available'; var units = $scope.DisplayMode.IsUOMHours ? ' hours' : ' resources'; if (!opts || !opts.header || !opts.projectId || !opts.expCatId) return tooltip; var header = opts.header; var projectId = opts.projectId; var expCatId = opts.expCatId; var rows = findUnassignedRows(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 = activityCalendarUIService.getExpCatUnassignedNeed($scope.Filter, $scope.ViewModel.DisplayMode, $scope.ViewModel.Header, header, 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; }; $scope.getTooltipProjectUnassignedContent = 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 projectNeed = 0; var projectAllocatedInView = 0; var projectAllocatedOutOfView = 0; var projectRowsInfo = activityCalendarUIService.getProjectRows($scope.ViewModel.NoTeamRow.Children, 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 = activityCalendarUIService.getExpCatUnassignedNeed($scope.Filter, $scope.ViewModel.DisplayMode, $scope.ViewModel.Header, header, projectId, scenarioId, [expCatRow.Id], false); if (expCatSummaryInfo) { projectNeed += (expCatSummaryInfo.Need || 0); projectAllocatedInView += (expCatSummaryInfo.AllocatedToTeamsInView || 0); projectAllocatedOutOfView += (expCatSummaryInfo.AllocatedToTeamsOutOfView || 0); } } } } projectNeed = roundService.roundQuantity(projectNeed); projectAllocatedInView = roundService.roundQuantity(projectAllocatedInView); projectAllocatedOutOfView = roundService.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; }; }]);