'use strict'; app .controller('activityCalendarByCompanyController', ['$scope', '$q', 'activityCalendarService', 'teamInfoService', 'activityCalendarUIService', 'dataSources', 'hoursResourcesConverter', '$filter', function ($scope, $q, activityCalendarService, teamInfoService, activityCalendarUIService, dataSources, hoursResourcesConverter, $filter) { var commonErrorMessage = 'An error occurred while processing your request. Please, try again later.'; var calendarDataUrl = '/CapacityManagement/GetActivityCalendar'; 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, 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; } } } } } activityCalendarUIService.refreshProjectAvailableTeams($scope.Filter, projectRow); expCatRow.TeamToAssignId = null; dataChanged(); }; $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(), parentRow = teamRow || 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); activityCalendarUIService.refreshProjectStyles($scope.ViewModel.Rows, $scope.ViewModel.Header, $scope.Filter, 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, 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(), parentRow = teamRow || 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); activityCalendarUIService.refreshProjectStyles($scope.ViewModel.Rows, $scope.ViewModel.Header, $scope.Filter, 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, 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(), parentRow = teamRow || 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); activityCalendarUIService.refreshProjectStyles($scope.ViewModel.Rows, $scope.ViewModel.Header, $scope.Filter, projectRow, result); dataChanged(); } }; $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(), parentRow = teamRow || 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); activityCalendarUIService.refreshProjectStyles($scope.ViewModel.Rows, $scope.ViewModel.Header, $scope.Filter, projectRow, result); dataChanged(); } }; $scope.zeroResource = function (projectRow, expCatRow, resRow, teamRow) { 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 = teamRow || expCatRow || { }; console.log('parentRow Type = ' +parentRow.RowType); 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); activityCalendarUIService.refreshProjectStyles($scope.ViewModel.Rows, $scope.ViewModel.Header, $scope.Filter, 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 = teamRow || 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); activityCalendarUIService.refreshProjectStyles($scope.ViewModel.Rows, $scope.ViewModel.Header, $scope.Filter, projectRow, changedCells); dataChanged(); } } }); }, className: "bootbox-sm" }); }; $scope.checkNptResourceValue = function (companyRow, nptTotalRow, nptCatRow, nptItemRow, nptResourceRow, $index, $data) { if (!companyRow || !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(companyRow); 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 (companyRow, nptTotalRow, nptCatRow, nptItemRow, $index, $data) { if (!companyRow || !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); } } activityCalendarUIService.refreshProjectAvailableTeams($scope.Filter, 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 loadCompaniesTask = dataSources.loadCompanies(); var cachedTemplates = activityCalendarUIService.cacheTemplates(); var dateRangeTask = dataSources.loadAvailableDateRange(); var allTasks = [loadCalendarTask, nptCategories, loadCompaniesTask, 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); 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'); }; 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); activityCalendarUIService.updateTotalRows(changedCells, teamRow, expCatRow, projectRow.Id, $scope.ViewModel.NoTeamRow, $scope.ViewModel.Rows, $scope.ViewModel.Header, $scope.Filter, $scope.ViewModel.DisplayMode); activityCalendarUIService.refreshProjectStyles($scope.ViewModel.NoTeamRow.Children, $scope.ViewModel.Header, $scope.Filter, projectRow, changedCells); dataChanged(); } }; /* Methods for preparing data */ function getDataModel(projects, noTeamProjects, filteredByView) { if (!projects) { return null; } var filteredByTeamsOrViews = activityCalendarUIService.isFilteredByTeams($scope.Filter); var allocationMode = $scope.Filter.EntityType == activityCalendarUIService.filterEntityType.company ? activityCalendarUIService.allocationMode.needAllocation : activityCalendarUIService.allocationMode.teamAllocation; var result = {}; for (var projectId in projects) { var project = projects[projectId]; if (!(project.CompanyId in result)) { var company = dataSources.getCompanyById(project.CompanyId) || { Id: project.CompanyId, Name: 'Company is unknown' }; result[project.CompanyId] = { Id: company.Id, Name: company.Name, AllocationMode: allocationMode, Projects: {} }; } var extendedProjectModel = angular.extend({}, projects[projectId], { AllocationMode: allocationMode }); var projectExpenditures = activityCalendarUIService.getExpendituresWithResources4Project(extendedProjectModel, null, null, $scope.ViewModel.Header, $scope.Filter, true, filteredByTeamsOrViews, filteredByView); if (projectExpenditures && Object.keys(projectExpenditures).length) { result[extendedProjectModel.CompanyId].Projects[projectId] = { Project: extendedProjectModel, Expenditures: projectExpenditures }; } if (noTeamProjects) { // put project to No Team section if there are any unassigned project need in this project var noTeamProject = angular.extend({}, extendedProjectModel, { AllocationMode: activityCalendarUIService.allocationMode.remainingNeedAllocation }); // load data for project expenditures var projectNoTeamsExpenditures = activityCalendarUIService.getExpenditures4Project(noTeamProject, null, $scope.Filter.ProjectRoles || [], $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) delete projectNoTeamsExpenditures[ecKeys[i]]; } // remove rows where nothing to assign or to display var projectNoTeamsExpendituresNonEmpty = activityCalendarUIService.removeEmptyUnassignedExpenditures(projectId, projectNoTeamsExpenditures, $scope.Filter); if (projectNoTeamsExpendituresNonEmpty && (Object.keys(projectNoTeamsExpendituresNonEmpty).length > 0)) { noTeamProjects[projectId] = { Project: noTeamProject, Expenditures: projectNoTeamsExpendituresNonEmpty }; } } } // Add companies without projects to display NPT var teams = teamInfoService.getAll(); if (teams) { for (var teamId in teams) { var companyId = teams[teamId].CompanyId; if (companyId && !(companyId in result)) { var company = dataSources.getCompanyById(companyId) || { Id: companyId, Name: 'Company is unknown' }; result[companyId] = { Id: company.Id, Name: company.Name, Projects: {} }; } } } return result; }; /* Methods for creating view models */ function createViewModel(model) { var projectExpenditures = {}; // cache for aggregated ECs by project for "group by companies" mode var noTeamProjects = {}; createHeaderViewModel(model.WeekEndings); createRowsViewModel(model, projectExpenditures, noTeamProjects); fillViewModelWithData(model, projectExpenditures, noTeamProjects); projectExpenditures = null; noTeamProjects = null; }; function createHeaderViewModel(weekEndings) { $scope.ViewModel.Header = (new GridHeader(weekEndings || {}).create()); }; function createRowsViewModel(model, projectExpenditures, noTeamProjects) { $scope.ViewModel.Rows = []; $scope.ViewModel.NoTeamRow = null; $scope.ViewModel.NonProjectTimeTotalRows = []; if (!model || !model.Projects) { return; } var filteredByView = $scope.Filter.EntityType == activityCalendarUIService.filterEntityType.view; var businessUnitModels = getDataModel(model.Projects, noTeamProjects, filteredByView); for (var businessUnitId in businessUnitModels) { projectExpenditures[businessUnitId] = businessUnitModels[businessUnitId]; } nonProjectTimeCache = activityCalendarUIService.getNonProjectTimeDataModel(nonProjectTimeCategoryCache, $scope.ViewModel.DisplayMode.GroupBy.Value); // Set Team-wide npts read-only for current view, because the displayed partially activityCalendarUIService.setTeamWideNptItemsReadOnly(nonProjectTimeCache); if (projectExpenditures && Object.keys(projectExpenditures).length) { // sort companies by name var sortedCompanies = $filter('sortObjectsBy')(projectExpenditures, 'Name', false); for (var i = 0; i < sortedCompanies.length; i++) { var companyData = sortedCompanies[i]; var companyViewModel = activityCalendarUIService.createViewModel4Company(companyData, $scope.Filter); if (companyViewModel) { setTooltips(companyViewModel); var companyNptData = (nonProjectTimeCache && (companyData.Id in nonProjectTimeCache)) ? nonProjectTimeCache[companyData.Id] : null; var companyNptModel = activityCalendarUIService.createViewModel4NptTotalRow('Non-Project Time', companyNptData); companyViewModel.NonProjectTime = companyNptModel; $scope.ViewModel.NonProjectTimeTotalRows.push(companyNptModel); $scope.ViewModel.Rows.push(companyViewModel); } } } if (noTeamProjects && Object.keys(noTeamProjects).length) { $scope.ViewModel.NoTeamRow = activityCalendarUIService.createViewModel4TotalRow('No Team'); $scope.ViewModel.NoTeamRow.AllocationMode = activityCalendarUIService.allocationMode.remainingNeedAllocation; $scope.ViewModel.NoTeamRow.Children = activityCalendarUIService.createViewModel4Projects(noTeamProjects); 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 = activityCalendarUIService.getAvailableTeams(projectRow.Id, ecRow.Id); activityCalendarUIService.setNoTeamRowTemplates(ecRow); } } } } } // Create total rows createTotalRowModels(); }; function setTooltips(companyRow) { if (!companyRow || !companyRow.Children) return; for (var pIndex = 0; pIndex < companyRow.Children.length; pIndex++) { var projectRow = companyRow.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.getTooltipContent; if (expCatRow.Children && expCatRow.Children.length) { for (var teamKey in expCatRow.Children) { // set tooltips for child team rows var teamRow = expCatRow.Children[teamKey]; if (teamRow.RowType != 'Team') // if child is not a team (e.g. resource) then do not set tooltips break; if (teamRow) { teamRow.getTooltipContent = $scope.getTooltipContent; } } } } } } } }; 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; }; /* Root methods for creating view models and filling them with data */ function fillViewModelWithData(model, projectExpenditures, noTeamProjects) { if (!$scope.ViewModel.Rows) { return; } var filteredByView = $scope.Filter.EntityType == activityCalendarUIService.filterEntityType.view; for (var i = 0; i < $scope.ViewModel.Rows.length; i++) { var companyRow = $scope.ViewModel.Rows[i]; var projectExpenditures = projectExpenditures[companyRow.Id] || {}; for (var j = 0; j < companyRow.Children.length; j++) { var project = companyRow.Children[j]; var projectData = (projectExpenditures.Projects || {})[project.Id] || {}; activityCalendarUIService.fillProjectRowWithData(companyRow, project, projectData.Expenditures, $scope.ViewModel.Header, $scope.Filter, filteredByView); } if (companyRow.NonProjectTime) { var companyNptData = (companyRow.Id in nonProjectTimeCache) ? nonProjectTimeCache[companyRow.Id] : null; activityCalendarUIService.fillTotalNptRowData(companyRow.NonProjectTime, companyNptData, $scope.ViewModel.Header, [companyRow]); } } // Clear NPT caches. Caches not need more nonProjectTimeCache = {}; nonProjectTimeCategoryCache = {}; // Fill No Team section if ($scope.ViewModel.NoTeamRow && $scope.ViewModel.NoTeamRow.Children && $scope.ViewModel.NoTeamRow.Children.length) { var noTeamProject = noTeamProjects || {}; for (var j = 0; j < $scope.ViewModel.NoTeamRow.Children.length; j++) { var project = $scope.ViewModel.NoTeamRow.Children[j]; var projectData = (noTeamProject || {})[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); } } } }); } activityCalendarUIService.refreshProjectAvailableTeams($scope.Filter, project); } activityCalendarUIService.fillTotalRowData($scope.ViewModel.NoTeamRow.Children, $scope.ViewModel.NoTeamRow, $scope.ViewModel.Header); } // Fill total rows with data fillTotalRowsWithData(); }; function fillTotalRowsWithData() { var startDate = new Date().getTime(); var rowsForTotal = angular.copy($scope.ViewModel.Rows) || []; if ($scope.ViewModel.NonProjectTimeTotalRows && ($scope.ViewModel.NonProjectTimeTotalRows.length > 0)) { rowsForTotal = rowsForTotal.concat(angular.copy($scope.ViewModel.NonProjectTimeTotalRows)); } if ($scope.ViewModel.NoTeamRow && $scope.Filter.EntityType !== activityCalendarUIService.filterEntityType.company) { 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); var endDate = new Date().getTime(); console.log('filling total rows with data: ' + (endDate - startDate) + ' ms'); }; function createAndFillNewEcTeamRow(projectRow, expCatRow, teamId) { var teamInfo = teamInfoService.getById(teamId); activityCalendarUIService.createAndFillNewEcTeamRow(projectRow, expCatRow, teamId, teamInfo, $scope.ViewModel.Header, $scope.ViewModel.DisplayMode, $scope.Filter); }; /* 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); activityCalendarUIService.toggleGridSource(totalRows, $scope.ViewModel.DisplayMode.IsUOMHours); if ($scope.ViewModel.NoTeamRow) { activityCalendarUIService.toggleGridSource([$scope.ViewModel.NoTeamRow], $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); activityCalendarUIService.applyAvgMode(totalRows, $scope.ViewModel.Header); if ($scope.ViewModel.NoTeamRow) { activityCalendarUIService.applyAvgMode([$scope.ViewModel.NoTeamRow], $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 companyRow = $scope.ViewModel.Rows[i]; if (companyRow.Children && companyRow.Children.length > 0) { companyRow.Children = activityCalendarUIService.toggleParts(companyRow.Children, $scope.ViewModel.DisplayMode.ShowParts.Value); activityCalendarUIService.updateProjectStyles($scope.ViewModel.Header, $scope.Filter, companyRow.Children); } } 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 companyRow = $scope.ViewModel.Rows[i]; if (companyRow.Children && companyRow.Children.length > 0) { companyRow.Children = activityCalendarUIService.toggleSortBy(companyRow.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 rowIndex = 0; rowIndex < $scope.ViewModel.Rows.length; rowIndex++) { var companyRow = $scope.ViewModel.Rows[rowIndex]; if (companyRow && companyRow.Children) { activityCalendarUIService.recalculateResourceAvailability($scope.Filter, $scope.ViewModel.Header, companyRow.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); }; /* ===== ToolTips display ==== */ $scope.getTooltipContent = 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.getTooltipProjectUnassignedContent = function (opts) { return activityCalendarUIService.getTooltipProjectUnassignedContent(opts, $scope.ViewModel.DisplayMode, $scope.Filter, $scope.ViewModel.Header, $scope.ViewModel.NoTeamRow.Children); }; $scope.getTooltipEcUnassignedContent = function (opts) { return activityCalendarUIService.getTooltipEcUnassignedContent(opts, $scope.ViewModel.DisplayMode, $scope.Filter, $scope.ViewModel.Header, $scope.ViewModel.NoTeamRow.Children); }; }]);