'use strict'; var enVisageServices = angular.module('enVisageServices', []); enVisageServices.factory('hoursResourcesConverter', ['$http', '$q', 'dataSources', function ($http, $q, dataSources) { var ec2uom; var retrieveHoursForEC = function (expCatId) { var expCatInfo = dataSources.getExpenditureById(expCatId); if (!expCatInfo || !angular.isNumber(expCatInfo.UOMValue)) { throw 'Error with data for h/r converter'; } var hours = parseFloat(expCatInfo.UOMValue); if (hours <= 0) { throw 'Incorrect data for expenditure [' + expCatId + ']: ' + expCatInfo.UOMValue; } return hours; } var converter = { load: function (forceDataLoad) { var promise = dataSources.load(forceDataLoad); return promise; }, convertToHours: function (expCatId, value) { if (!expCatId || !value) return 0; if (!angular.isNumber(value)) return value; var hours = retrieveHoursForEC(expCatId); if (hours <= 0) return 0; return hours * value; }, convertToResources: function (expCatId, value) { if (!expCatId || !value) return 0; if (!angular.isNumber(value)) return value; var hours = retrieveHoursForEC(expCatId); if (hours <= 0) return 0; return value / hours; } }; return converter; }]); enVisageServices.factory('roundService', [function () { var quantityPrecision = 1000000, // 6 signs costPrecision = 10000; // 4 signs var service = { roundQuantity: function (value) { return Math.round(value * quantityPrecision) / quantityPrecision; }, roundCost: function (value) { return Math.round(value * costPrecision) / costPrecision; } }; return service; }]); enVisageServices.factory('calculateDistributionService', ['roundService', function (roundService) { var service = { mergeAllocations: function (target, source, weekEndings) { target = target || {}; source = source || {}; var result = {}; var keys = (weekEndings && weekEndings.length) ? weekEndings : union(Object.keys(target), Object.keys(source)); for (var i = 0; i < keys.length; i++) { var key = keys[i]; var targetValue = target[key] || 0; var sourceValue = source[key] || 0; result[key] = roundService.roundQuantity(targetValue + sourceValue); } return result; }, calculateSumOnRange: function (sourceValues, targetIndexes) { if (!sourceValues || !sourceValues.length || !targetIndexes || !targetIndexes.length) { return 0; } var result = 0; for (var i = 0; i < targetIndexes.length; i++) { result += (sourceValues[targetIndexes[i]] || 0); } return result; }, alignValues: function (sourceValues, targetIndexes, total) { if (!sourceValues || !sourceValues.length) { return null; } // if targetIndexes collection is not passed we need to align total on all sourceValues collection targetIndexes = targetIndexes || Array.apply(null, { length: sourceValues.length }).map(Number.call, Number); var result = {}; var oldTotal = roundService.roundQuantity(this.calculateSumOnRange(sourceValues, targetIndexes) || 0); var newTotal = parseFloat(total) || 0; var distributedNewValue = 0; var lastIndex = targetIndexes[targetIndexes.length - 1]; if (oldTotal == 0) { var newValue = roundService.roundQuantity(newTotal / targetIndexes.length); for (var i = 0; i < targetIndexes.length - 1; i++) { // Example: we need to distribute 0.000002 between 4 cells. // newValue will be 0.000002 / 4 = 0.0000005 ~ 0.000001 // if we distribute this value in targetIndexes.length - 1 cells (3) we get distributedNewValue = 0.000003 // and -0.000001 in the last cell: newTotal - distributedNewValue = (0.000002 - 0.000003) // so we should check over allocation and break distribution var tempResult = roundService.roundQuantity(distributedNewValue + newValue); if (tempResult <= newTotal) { result[targetIndexes[i]] = newValue; distributedNewValue = tempResult; } else { lastIndex = targetIndexes[i]; break; } } } else { var factor = newTotal / oldTotal; var distributedOldValue = 0; for (var i = 0; i < targetIndexes.length - 1; i++) { var oldValue = (sourceValues[targetIndexes[i]] || 0); var newValue = roundService.roundQuantity(oldValue * factor); distributedOldValue = roundService.roundQuantity(distributedOldValue + oldValue); // example: passed 31 cells, but only 5 of them have values; so we should affect only these cells if (distributedOldValue == oldTotal) { lastIndex = targetIndexes[i]; break; } else { result[targetIndexes[i]] = newValue; distributedNewValue = roundService.roundQuantity(distributedNewValue + newValue); } } } result[lastIndex] = roundService.roundQuantity(newTotal - distributedNewValue); return result; }, isEmptyAllocations: function (allocations, treatNegativeValueAsZero) { if (!allocations || !angular.isObject(allocations) || (Object.keys(allocations).length < 1)) return true; var result = true; for (var we in allocations) { var value = allocations[we]; if (value && angular.isNumber(value) && !isNaN(value) && (Number(value) != 0)) { // Found at least one non-zero allocation value in the collection if (!treatNegativeValueAsZero || (treatNegativeValueAsZero && Number(value) > 0)) { result = false; break; } } } return result; } }; return service; }]); enVisageServices.factory('dataSources', ['$q', '$http', function ($q, $http) { // DAL, used to get data from server var expCats = null; var resources = null; var companies = null; var clients = null; var projectStatuses = null; var costCenters = null; var dateRange = null; var NoteCache = []; var teamSummary, nonProjectTimeCategories; var serviceContainer = { load: function (forceDataLoad) { // return once loaded data instead of get from server if (!forceDataLoad && !!expCats) { var deferrer = $q.defer(); deferrer.resolve(expCats); return deferrer.promise; } var request = getAntiXSRFRequest('/ExpenditureCategory/GetAllCategories'); var promise = $http(request). then(function (response) { expCats = response.data; return expCats; }, function (response) { throw 'An error occurred while loading ECs /ExpenditureCategory/GetAllCategories'; }); return promise; }, loadResourcesByResourceIds: function (resourceIds, keepExistingResources) { if (!resourceIds || !resourceIds.length) { var deferrer = $q.defer(); deferrer.resolve(null); return deferrer.promise; } var request = getAntiXSRFRequest('/PeopleResource/LoadResourcesWithTeams4Resources', resourceIds); var promise = $http(request). then(function (response) { if (!resources) resources = {}; angular.forEach(response.data, function (resItem, resKey) { if (resources[resKey]) { if (!keepExistingResources) // do not override existing resources resources[resKey] = resItem; } else { resources[resKey] = resItem; } }); return resources; }, function (response) { throw 'An error occurred while loading dictionary with resources /PeopleResource/LoadResourcesWithTeams4Teams'; }); return promise; }, loadStaticFile: function (fileUrl) { var promise = $http.get(fileUrl) .then(function (response) { return response; }, function (response) { throw 'An error occurred while loading static file "' + fileUrl + '"'; }); return promise; }, loadResourcesByTeamIds: function (teamIds, keepExistingResources) { if (!teamIds || !teamIds.length) { var deferrer = $q.defer(); deferrer.resolve(null); return deferrer.promise; } console.debug("loadResourcesByTeamIds query"); // DEBUG console.time("loadResourcesByTeamIds"); // DEBUG var request = getAntiXSRFRequest('/PeopleResource/LoadResourcesWithTeams4Teams', teamIds); var promise = $http(request). then(function (response) { console.timeEnd("loadResourcesByTeamIds"); // DEBUG if (!resources) resources = {}; angular.forEach(response.data, function (resItem, resKey) { if (resources[resKey]) { if (!keepExistingResources) // do not override existing resources resources[resKey] = resItem; } else { resources[resKey] = resItem; } }); return resources; }, function (response) { throw 'An error occurred while loading dictionary with resources /PeopleResource/LoadResourcesWithTeams4Teams'; }); return promise; }, loadCompanies: function () { if (!!companies) { var deferrer = $q.defer(); deferrer.resolve(companies); return deferrer.promise; } var request = getAntiXSRFRequest('/Company/LoadCompanies'); var promise = $http(request). then(function (response) { companies = response.data; return companies; }, function (response) { throw 'An error occurred while loading dictionary with companies /Company/LoadCompanies'; }); return promise; }, loadClients: function () { if (!!clients) { var deferrer = $q.defer(); deferrer.resolve(clients); return deferrer.promise; } console.debug("loadClients query"); // DEBUG console.time("loadClients"); // DEBUG var request = getAntiXSRFRequest('/Clients/LoadClients'); var promise = $http(request). then(function (response) { console.timeEnd("loadClients"); // DEBUG clients = response.data; return clients; }, function (response) { throw 'An error occurred while loading dictionary with clients /Clients/LoadClients'; }); return promise; }, loadCostCenters: function () { if (!!costCenters) { var deferrer = $q.defer(); deferrer.resolve(costCenters); return deferrer.promise; } var request = getAntiXSRFRequest('/CreditDepartment/LoadCreditDepartments'); var promise = $http(request). then(function (response) { costCenters = response.data; return costCenters; }, function (response) { throw 'An error occurred while loading dictionary with cost centers /CreditDepartment/LoadCreditDepartments'; }); return promise; }, loadProjectStatuses: function () { if (!!projectStatuses) { var deferrer = $q.defer(); deferrer.resolve(projectStatuses); return deferrer.promise; } var request = getAntiXSRFRequest('/Status/LoadStatuses'); var promise = $http(request). then(function (response) { projectStatuses = response.data; return projectStatuses; }, function (response) { throw 'An error occurred while loading dictionary with project statuses /Status/LoadStatuses'; }); return promise; }, loadAvailableDateRange: function () { if (!!dateRange) { var deferrer = $q.defer(); deferrer.resolve(dateRange); return deferrer.promise; } console.debug("loadAvailableDateRange query"); // DEBUG console.time("loadAvailableDateRange"); // DEBUG var request = getAntiXSRFRequest('/Calendar/GetAvailableDateRange'); var promise = $http(request). then(function (response) { console.timeEnd("loadAvailableDateRange"); // DEBUG dateRange = response.data; return dateRange; }, function (response) { throw 'An error occurred while loading dateRange /Calendar/GetAvailableDateRange'; }); return promise; }, getResourceById: function (resourceId) { if (!resourceId || !resources) { return null; } return resources[resourceId]; }, getResourceTeam4Week: function (resourceId, weekEndingMs) { if (!resourceId || !weekEndingMs) { return null; } var resource = this.getResourceById(resourceId); if (!resource || !resource.Teams || !resource.Teams.length) { return null; } for (var i = 0; i < resource.Teams.length; i++) { var team = resource.Teams[i]; if ((!!team.StartDate && team.StartDate <= weekEndingMs) && (!team.EndDate || team.EndDate >= weekEndingMs)) { return team; } } return null; }, getCompanyById: function (companyId) { if (!companyId || !companies) { return null; } return companies[companyId]; }, getCompanies: function () { return companies; }, getClientById: function (clientId) { if (!clientId || !clients) { return null; } return clients[clientId]; }, getCostCenterById: function (costCenterId) { if (!costCenterId || !costCenters) { return null; } return costCenters[costCenterId]; }, getCostCenters: function () { return costCenters; }, getDateRange: function () { return dateRange; }, getCostCenterByExpenditureId: function (expenditureCategoryId) { var category = this.getExpenditureById(expenditureCategoryId); if (!category || !category.CreditId) { return null; } return this.getCostCenterById(category.CreditId) || null; }, checkExpenditureCategory: function (expenditureCategoryId, categoryType, glAccount, creditDepartment, expenditureCategories) { if (!expenditureCategoryId) return; var expenditures = this.getExpenditures(); if (!expenditures) throw 'Expenditures in dataSources service didn\'t load yet'; var category = expenditures[expenditureCategoryId]; if (!category) throw 'Incorrect id for expenditure category = ' + expenditureCategoryId; var isMatch = !((categoryType && category.Type != categoryType) || (glAccount && category.GLAccount != glAccount) || (creditDepartment && category.CreditId != creditDepartment) || (expenditureCategories && expenditureCategories.length > 0 && expenditureCategories.indexOf(category.ExpenditureCategoryId) < 0)) return isMatch; }, getExpenditures: function () { return expCats || {}; }, getExpenditureById: function (id) { if (!expCats) return null; return expCats[id] || null; }, isSuperEC: function (id) { if (!id) { throw 'id is invalid'; } if (!expCats) { throw 'expCats have not been uploaded yet'; } var category = expCats[id]; if (!category) { throw 'Category with id = ' + id + ' does not exist'; } return !category.AllowResourceAssignment; }, getSkills: function (openerType, openerId, filterObj) { // prepare and return promise var postData = { OpenerType: openerType, OpenerId: openerId, Filter: filterObj }; var request = getAntiXSRFRequest('/Skills/GetSkillTree', postData); var promise = $http(request). then(function (response) { return response.data; }, function (response) { // throw exception through the pipe throw 'An error occurred while loading skills'; }); return promise; }, saveSkills: function (skills) { // prepare and return promise var request = getAntiXSRFRequest('/Skills/SaveSkillTree', skills); var promise = $http(request). then(function (response) { return response.data; }, function (response) { // throw exception through the pipe throw 'An error occurred while saving skills'; }); return promise; }, getTeams: function (teamIds, forceLoadFromServer) { var teams2Load = teamIds || []; var result = {}; // return once loaded data instead of get from server if (!forceLoadFromServer) { teams2Load = $.grep(teams2Load, function (teamId, index) { return !teamSummary || !teamSummary[teamId]; }); } // we should return only existing teams, but if new teams were requested we should load them if (teams2Load.length <= 0) { var deferrer = $q.defer(); for (var i = 0; i < teamIds.length; i++) result[teamIds[i]] = teamSummary[teamIds[i]]; deferrer.resolve(result); return deferrer.promise; } // prepare and return promise var postData = { ids: teams2Load }; var request = getAntiXSRFRequest('/Team/GetTeamsById'); request.data = JSON.stringify(postData); var getTeamsTask = $http(request). then(null, function () { throw 'An error occurred while loading teams in dataSources module'; }); var getResourcesTask = this.loadResourcesByTeamIds(teams2Load); var promise = $q.all([getTeamsTask, getResourcesTask]) .then(function (asyncResults) { var response = asyncResults[0]; teamSummary = teamSummary || {}; for (var teamId in response.data) teamSummary[teamId] = response.data[teamId]; for (var i = 0; i < teamIds.length; i++) result[teamIds[i]] = teamSummary[teamIds[i]]; return result; }); return promise; }, getSkillsMatrix: function (dataType, readOnly, filter) { // prepare and return promise var postData = { DataType: dataType, ReadOnly: readOnly, Filter: filter }; var request = getAntiXSRFRequest('/Skills/GetSkillsMatrix', postData); var promise = $http(request). then(function (response) { return response.data; }, function (response) { // throw exception through the pipe throw 'An error occurred while loading skills matrix'; }); return promise; }, saveSkillsMatrix: function (data) { // prepare and return promise var request = getAntiXSRFRequest('/Skills/SaveSkillsMatrix', data); var promise = $http(request). then(function (response) { return (response.status == 200); }, function (response) { // throw exception through the pipe throw 'An error occurred while saving skills matrix'; }); return promise; }, getNonProjectTimeCategories: function () { if (nonProjectTimeCategories) { var deferrer = $q.defer(); deferrer.resolve(nonProjectTimeCategories); return deferrer.promise; } console.debug("getNonProjectTimeCategories query"); // DEBUG console.time("getNonProjectTimeCategories"); // DEBUG var request = getAntiXSRFRequest('/NonProjectTimeCategory/GetCategories'); var promise = $http(request). then(function (response) { console.timeEnd("getNonProjectTimeCategories"); // DEBUG nonProjectTimeCategories = response.data; return response.data; }, function (response) { // throw exception through the pipe throw 'An error occurred while loading non-project time categories'; }); return promise; }, getNPTCategory: function (npTimeCategoryId) { if (!nonProjectTimeCategories) { return null; } return nonProjectTimeCategories[npTimeCategoryId] || null; }, loadNotes: function (DomainId, ParentId, Type) { var postData = { DomainId: DomainId, ParentId: ParentId, NoteType: Type }; var request = getAntiXSRFRequest('/Note/List'); request.data = JSON.stringify(postData); var promise = $http(request). then(function (response) { return response.data; }, function (response) { throw 'An error occurred while loading Notes /Note/List'; }); return promise; }, LoadNote: function (NoteId) { var postData = { NoteId: NoteId }; var request = getAntiXSRFRequest('/Note/View'); request.data = JSON.stringify(postData); var promise = $http(request). then(function (response) { return response.data; }, function (response) { throw 'An error occurred while loading note /Note/View'; }); return promise; }, CacheNote: function (Note) { for (var i = 0; i < NoteCache.length; i++) { if (NoteCache[i].Id == Note.Id) { NoteCache.splice(i, 1, Note); return } } NoteCache.push(Note); }, LoadFromCache: function (ParentId) { var notes = []; for (var i = 0; i < NoteCache.length; i++) { if (NoteCache[i].ParentId == ParentId) { notes.push(NoteCache[i]); } } return notes; }, RemoveFromCache: function (note) { var idx = GetCacheIndex(note); if (idx < 0) return; NoteCache.splice(idx, 1); }, ClearCache: function () { NoteCache = []; }, EditNote: function (Note) { var request = getAntiXSRFRequest('/Note/Edit'); request.data = JSON.stringify(Note); var promise = $http(request). then(function (response) { return response.data; }, function (response) { throw 'An error occurred while adding/editing a note /Note/Edit'; }); return promise; }, RemoveNote: function (NoteId) { var postData = { NoteId: NoteId }; var request = getAntiXSRFRequest('/Note/Delete'); request.data = JSON.stringify(postData); var promise = $http(request). then(function (response) { return response.data; }, function (response) { throw 'An error occurred while removing note /Note/Delete'; }); return promise; }, }; function GetCacheIndex(note) { for (var i = 0; i < NoteCache.length; i++) { if (NoteCache[i].Id == note.Id) { return i; } } return -1 } return serviceContainer; }]); enVisageServices.factory('cellHighlightingService', [function () { var service = { cellOverClass: 'cellover', cellLessClass: 'cellless', cellEqualClass: 'cellequal', // compares 2 decimals rounded to 3 decimals // if difference is less than 0.005 then consider values are equal compare: function (val1, val2) { val1 = val1 || 0; val2 = val2 || 0; var val1_ = Math.round(parseFloat(val1) * 1000) / 1000; var val2_ = Math.round(parseFloat(val2) * 1000) / 1000; if ((-0.005 > (val1_ - val2_))) return -1; else if ((0.005 < (val1_ - val2_))) return 1; else if (val1_ == 0 && val2_ == 0) return null; else return 0; }, getCssClass: function (compareResult) { if (this.checkOverResult(compareResult)) return this.cellOverClass; if (this.checkLessResult(compareResult)) return this.cellLessClass; if (this.checkEqualResult(compareResult)) return this.cellEqualClass // assign more classes if needed return ''; }, removeHighlightClasses: function (originalClasses) { var cellOverRegExp = new RegExp(this.cellOverClass, 'g'), cellLessRegExp = new RegExp(this.cellLessClass, 'g'), cellEqualRegExp = new RegExp(this.cellEqualClass, 'g'); return (originalClasses || '').replace(cellOverRegExp, '') .replace(cellLessRegExp, '') .replace(cellEqualRegExp) .trim(); }, addCssClass: function (originalClasses, cssClass) { if (!(cssClass || '').trim()) return originalClasses; return (originalClasses + ' ' + cssClass).trim(); }, checkOverResult: function (compareResult) { return compareResult > 0; }, checkLessResult: function (compareResult) { return compareResult < 0; }, checkEqualResult: function (compareResult) { return compareResult == 0; }, setCellCssClasses: function (row, colIndex) { if (colIndex == null || row.Cells == null || row.Cells.length <= colIndex) return; if (row.CSSClass == null) row.CSSClass = new Array(row.Cells.length); row.CSSClass[colIndex] = this.removeHighlightClasses(row.CSSClass[colIndex]); // if top value is negative we should always highlight cell with red var compareResult = row.Cells[colIndex].Value1 < 0 ? 1 : this.compare(row.Cells[colIndex].Value1, row.Cells[colIndex].Value2), compareResultClass = this.getCssClass(compareResult), resultCellClasses = this.addCssClass(row.CSSClass[colIndex], compareResultClass); row.CSSClass[colIndex] = resultCellClasses; }, highlightAsSuccess: function (row, colIndex) { if (colIndex == null || row.Cells == null) return; if (row.CSSClass == null) row.CSSClass = new Array(row.Cells.length ? row.Cells.length : colIndex + 1); row.CSSClass[colIndex] = this.removeHighlightClasses(row.CSSClass[colIndex]); var successResultClass = this.getCssClass(0), resultCellClasses = this.addCssClass(row.CSSClass[colIndex], successResultClass); row.CSSClass[colIndex] = resultCellClasses; }, getWithValidationCssClass: function (originalCss, value1, value2) { value1 = parseFloat(value1) || 0; value2 = parseFloat(value2) || 0; var compareResult = this.compare(value1, value2); var compareResultClass = this.getCssClass(compareResult); var resultCellClasses = this.removeHighlightClasses(originalCss || ''); return this.addCssClass(resultCellClasses, compareResultClass); }, }; return service; }]); enVisageServices.factory('teamInfoService', ['$q', 'dataSources', 'calculateDistributionService', function ($q, dataSources, calculateDistributionService) { var teamsUnfiltered = {}; var teams = {}; var needByCategories = {}; // Index for quick access to all related data to every resource var resourceQuickAccess = {}; // Creates index for fast access to all recource records and quick localization of Teams and ECs, it is member of var recreateResourceQuickAccessIndex = function () { resourceQuickAccess = {}; if (!teams) { return; } for (var teamId in teams) { var team = teams[teamId]; if (!team.ExpCategories) continue; for (var expCatId in team.ExpCategories) { var expCat = team.ExpCategories[expCatId]; if (!expCat.Resources) continue; for (var resourceId in expCat.Resources) { var resource = expCat.Resources[resourceId]; if (!resourceQuickAccess[resourceId]) { resourceQuickAccess[resourceId] = { Resources: [], Expenditures: [] }; } var expCatIndexItem = { TeamId: teamId, ExpenditureCategoryId: expCatId }; resourceQuickAccess[resourceId].Resources.push(resource); resourceQuickAccess[resourceId].Expenditures.push(expCatIndexItem); } } } }; return { init: function (teamsArray, categoryNeed) { teams = {}; needByCategories = {}; if (teamsArray) { if (!angular.isArray(teamsArray)) { teams = angular.copy(teamsArray); } else { teams = {}; for (var i = 0; i < teamsArray.length; i++) { teams[teamsArray[i].Id] = angular.copy(teamsArray[i]); } } } if (categoryNeed) { needByCategories = angular.copy(categoryNeed); // needByCategoriesUnfiltered = angular.copy(needByCategories); } // Create copy of the source data teamsUnfiltered = angular.copy(teams); recreateResourceQuickAccessIndex(); }, applyFilter: function (visibleExpendituresDictionary) { console.time("teamInfoService: applyFilter"); if (!visibleExpendituresDictionary) { teams = {}; return; } // Perform teams filtering if (teamsUnfiltered) { var teamsFiltered = {}; for (var teamId in teamsUnfiltered) { var teamData = teamsUnfiltered[teamId]; if (teamData) { var teamDataCopy = (teamId in teamsFiltered) ? teamsFiltered[teamId] : null; if (!teamDataCopy) { teamDataCopy = { Id: teamData.Id, CompanyId: teamData.CompanyId, Name: teamData.Name, AccessLevel: teamData.AccessLevel, ExpCategories: {} }; teamsFiltered[teamId] = teamDataCopy; } if (teamData.ExpCategories) { for (var expCatId in teamData.ExpCategories) { if (expCatId in visibleExpendituresDictionary) { if (!(expCatId in teamDataCopy.ExpCategories)) { teamDataCopy.ExpCategories[expCatId] = angular.copy(teamData.ExpCategories[expCatId]); } } } } } } teams = teamsFiltered; } recreateResourceQuickAccessIndex(); console.timeEnd("teamInfoService: applyFilter"); }, resetFilter: function () { teams = angular.copy(teamsUnfiltered); //needByCategories = angular.copy(needByCategoriesUnfiltered); recreateResourceQuickAccessIndex(); }, // OBSOLETE: use async getTeamsById method (instead this one) which loads team info from server if it does not exist in already loaded data getById: function (teamId, useSourceDataCache) { if (!useSourceDataCache) { if (!teams) return null; else return teams[teamId]; } if (useSourceDataCache) { if (!teamsUnfiltered) return null; else return teamsUnfiltered[teamId]; } }, getAll: function (useSourceDataCache) { if (useSourceDataCache) return teamsUnfiltered; else return teams; }, getAllResources: function () { var teams = this.getAll(); if (!teams) return []; var foundResources = {}; for (var teamId in teams) { var team = this.getById(teamId); if (team && team.ExpCategories) { for (var categoryId in team.ExpCategories) { var category = team.ExpCategories[categoryId]; if (category && category.Resources) { for (var resourceId in category.Resources) { if (!(resourceId in foundResources)) { foundResources[resourceId] = true; } } } } } } var result = Object.keys(foundResources); return result; }, getTeamsById: function (teamIds, forceLoadFromServer, useSourceDataCache) { var data2Return = {}; var ids2Load = []; for (var i = 0; i < teamIds.length; i++) { var teamInBottomPart = this.getById(teamIds[i], useSourceDataCache); if (teamInBottomPart && !forceLoadFromServer) { data2Return[teamIds[i]] = teamInBottomPart; } else { ids2Load.push(teamIds[i]); } } // return once loaded data instead of get from server if (ids2Load.length == 0) { var deferrer = $q.defer(); deferrer.resolve(data2Return); return deferrer.promise; } // prepare and return promise return dataSources.getTeams(ids2Load, forceLoadFromServer).then(function (data) { angular.forEach(data, function (item) { if (item) data2Return[item.Id] = item; }); return data2Return; }); }, getTeamsByCompanyId: function (companyId) { var companyTeams = {}; if (!companyId || !teams) { return companyTeams; } for (var teamId in teams) { if (teams[teamId].CompanyId === companyId) { companyTeams[teamId] = teams[teamId]; } } return companyTeams; }, getExpendituresByCostCentersSummary: function () { var allCostCenters = dataSources.getCostCenters(); if (!allCostCenters) return; // Here will put all cost centers, which have at least one EC in current data set var resultCostCenters = {}; // Create cost centers list var teams = this.getAll(); if (!teams) return; for (var teamId in teams) { var teamExpCats = this.getExpenditureCategoriesInTeam(teamId); var teamExpCatsCount = teamExpCats && angular.isArray(teamExpCats) && teamExpCats.length ? teamExpCats.length : 0; for (var eIndex = 0; eIndex < teamExpCatsCount; eIndex++) { var expCatId = teamExpCats[eIndex]; var expCatInfo = dataSources.getExpenditureById(expCatId); if (expCatInfo && expCatInfo.CreditId) { var costCenterId = expCatInfo.CreditId; if (costCenterId in allCostCenters) { if (!(costCenterId in resultCostCenters)) { resultCostCenters[costCenterId] = {}; } var costCenter = resultCostCenters[costCenterId]; if (!costCenter.Expenditures) { costCenter.Expenditures = {}; } if (!(expCatId in costCenter.Expenditures)) { costCenter.Expenditures[expCatId] = { Teams: [] }; } costCenter.Expenditures[expCatId].Teams.push(teamId); } } } } return resultCostCenters; }, teamsWithExpenditureCategoryExist: function (expenditureCategoryId) { if (!expenditureCategoryId) { return false; } var teamsWithAC = this.getTeamsByExpenditureCategoryId(expenditureCategoryId, true); var teamsExist = teamsWithAC && Object.keys(teamsWithAC).length > 0; return teamsExist; }, getTeamsByExpenditureCategoryId: function (expenditureCategoryId, applyFiltering) { var teamsByEC = {}; if (!expenditureCategoryId || !teams) { return teamsByEC; } for (var teamId in teams) { var team = teams[teamId]; if (!team.ExpCategories) { continue; } if (applyFiltering) { var isNormalEC = !dataSources.isSuperEC(expenditureCategoryId); var isActualEC = (expenditureCategoryId in team.ExpCategories) && (team.ExpCategories[expenditureCategoryId].ECScenario & 1) === 1; if (isNormalEC && !isActualEC) { continue; } teamsByEC[teamId] = team; } else { if (expenditureCategoryId in team.ExpCategories) { teamsByEC[teamId] = team; } } } return teamsByEC; }, isExists: function (teamId, useSourceDataCache) { if (!teamId || (!useSourceDataCache && !teams) || (useSourceDataCache && !teamsUnfiltered)) return false; return (!useSourceDataCache && !!teams[teamId]) || (useSourceDataCache && !!teamsUnfiltered[teamId]); }, getExpenditureCategoryInTeam: function (teamId, expCatId, insertOnNotFound, useSourceDataCache) { if (!teamId || !expCatId) return null; var team = this.getById(teamId, useSourceDataCache); if (!team || !team.ExpCategories) return null; if (!(expCatId in team.ExpCategories)) { if (insertOnNotFound) { if (!this.addExpenditureCategoryToTeam(teamId, expCatId)) { throw "Unable to add expenditure category '" + expCatId + "' to team '" + teamId + "'"; } } else { // EC not found in team ECs collection return null; } } var result = team.ExpCategories[expCatId]; return result; }, getExpenditureCategoriesInTeamCount: function (teamId, useSourceDataCache) { if (!teamId) return null; var team = this.getById(teamId, useSourceDataCache); if (!team || !team.ExpCategories) return 0; return Object.keys(team.ExpCategories).length; }, getExpenditureCategoriesInTeam: function (teamId, expCatsFilter) { if (!teamId) return null; var team = this.getById(teamId); if (!team) return null; var result = []; if (team.ExpCategories) { var teamExpCats = Object.keys(team.ExpCategories); for (var index = 0; index < teamExpCats.length; index++) { var expCatId = teamExpCats[index]; if (!expCatsFilter || !angular.isArray(expCatsFilter) || (expCatsFilter.indexOf(expCatId) >= 0)) { result.push(expCatId); } } } return result; }, getExpenditureCategoriesInTeams: function (teamIds) { if (!teamIds || !angular.isArray(teamIds)) return null; var foundCategories = {}; if (!teams) return []; for (var teamId in teams) { var team = teams[teamId]; if (team && team.ExpCategories) { for (var expCatId in team.ExpCategories) { if (!(expCatId in foundCategories)) { foundCategories[expCatId] = true; } } } } return Object.keys(foundCategories); }, getSuperExpenditureCategoriesInTeam: function (teamId) { if (!teamId) { return null; } var team = this.getById(teamId); if (!team || !team.ExpCategories) { return null; } var result = {}; for (var categoryId in team.ExpCategories) { if (dataSources.isSuperEC(categoryId)) { var category = team.ExpCategories[categoryId]; if (category && category.Resources && Object.keys(category.Resources).length) { result[categoryId] = team.ExpCategories[categoryId]; } } } return result; }, getExpenditureTotalNeed: function (expCatId, weekEndingsMs) { if (!expCatId) return null; var result = {}; if (!needByCategories) { // Info by categories need doesn't exist if (weekEndingsMs && angular.isArray(weekEndingsMs) && weekEndingsMs.length) { for (var wIndex = 0; wIndex < weekEndingsMs.length; wIndex++) { var we = weekEndingsMs[wIndex]; result[we] = 0; } } return result; } for (var scenarioId in needByCategories) { var scenarioData = needByCategories[scenarioId]; if (scenarioData && scenarioData.Expenditures && (expCatId in scenarioData.Expenditures)) { var expCatData = scenarioData.Expenditures[expCatId]; if (expCatData && expCatData.Allocations) { var weeks = weekEndingsMs && angular.isArray(weekEndingsMs) && weekEndingsMs.length ? weekEndingsMs : Object.keys(expCatData.Allocations); for (var wIndex = 0; wIndex < weeks.length; wIndex++) { var we = weeks[wIndex]; if (!(we in result)) { result[we] = 0; } result[we] += (we in expCatData.Allocations) ? (expCatData.Allocations[we] || 0) : 0; } } } } return result; }, getResourceInCategory: function (teamId, expCatId, resourceId, useSourceDataCache) { if (!teamId || !expCatId || !resourceId) { return null; } var category = this.getExpenditureCategoryInTeam(teamId, expCatId, false, useSourceDataCache); if (category && category.Resources) { return category.Resources[resourceId]; } return null; }, getResourceByTeam: function (teamId, expCatId, resourceId) { if (!resourceId) { return null; } var resources = this.getResourcesByTeam(teamId, expCatId, [resourceId]); if (resources) { return resources[resourceId]; } return null; }, getResourcesByTeam: function (teamId, expCatId, resourceIds, useSourceDataCache) { if (!teamId) { return null; } var team = this.getById(teamId); if (!team || !team.ExpCategories) { return null; } var resources = {}; var requestedECs = expCatId ? [expCatId] : Object.keys(team.ExpCategories); for (var i = 0; i < requestedECs.length; i++) { var categoryId = requestedECs[i]; var category = team.ExpCategories[categoryId]; if (category && category.Resources) { var requestedResources = resourceIds || Object.keys(category.Resources); for (var resourceIndex = 0; resourceIndex < requestedResources.length; resourceIndex++) { var resourceId = requestedResources[resourceIndex]; if (resourceId in category.Resources && !(resourceId in resources)) { var resourceModel = this.extendResourceModelWithAttributes(category.Resources[resourceId], teamId); resources[resourceId] = resourceModel; } } } } return resources; }, getResourcesWithAllocationsByTeam: function (teamId, expCatId, resourceIds) { if (!teamId) { return null; } var team = this.getById(teamId); if (!team || !team.ExpCategories) { return null; } var resources = {}; var requestedECs = expCatId ? [expCatId] : Object.keys(team.ExpCategories); for (var i = 0; i < requestedECs.length; i++) { var categoryId = requestedECs[i]; var category = team.ExpCategories[categoryId]; if (category && category.Resources) { var requestedResources = resourceIds || Object.keys(category.Resources); for (var resourceIndex = 0; resourceIndex < requestedResources.length; resourceIndex++) { var resourceId = requestedResources[resourceIndex]; if (resourceId in category.Resources) { if (resourceId in resources) { var targetResource = resources[resourceId]; var sourceResource = category.Resources[resourceId]; targetResource.AllocatedCapacity = calculateDistributionService.mergeAllocations(targetResource.AllocatedCapacity, sourceResource.AllocatedCapacity); } else { var resourceModel = this.extendResourceModelWithAttributes(category.Resources[resourceId], teamId); resources[resourceId] = angular.copy(resourceModel); } } } } } var superECs = this.getSuperExpenditureCategoriesInTeam(teamId); if (superECs) { for (var categoryId in superECs) { if (requestedECs.indexOf(categoryId) >= 0) { continue; } var category = superECs[categoryId]; if (category && category.Resources) { var requestedResources = resourceIds || Object.keys(category.Resources); for (var resourceIndex = 0; resourceIndex < requestedResources.length; resourceIndex++) { var resourceId = requestedResources[resourceIndex]; if (!(resourceId in resources)) { continue; } var targetResource = resources[resourceId]; var sourceResource = category.Resources[resourceId]; if (sourceResource) { targetResource.AllocatedCapacity = calculateDistributionService.mergeAllocations(targetResource.AllocatedCapacity, sourceResource.AllocatedCapacity); } } } } } return resources; }, getResourceSummary: function (teamIds, expCatId, resourceId) { if (!teamIds || !expCatId || !resourceId) { return null; } var resource = null; for (var i = 0; i < teamIds.length; i++) { var resources = this.getResourcesWithAllocationsByTeam(teamIds[i], expCatId, [resourceId]); if (resources && (resourceId in resources)) { if (resource == null) { resource = {}; } angular.merge(resource, angular.copy(resources[resourceId])); } } return resource; }, getResourceTotalAllocatedHrs: function (expCatId, teamId, resourceIds, weekEndingsMs) { throw "teamInfoService.getResourceTotalAllocatedHrs is not implemented"; }, changeExpenditureCategoryValue: function (teamId, expCatId, weekEndingMs, deltaValue, insertOnNotFound) { if (!teamId || !expCatId || !weekEndingMs || !deltaValue) return; // Perform changes in filtered data source as well as in unfiltered data var category = this.getExpenditureCategoryInTeam(teamId, expCatId, insertOnNotFound, false); var categorySource = this.getExpenditureCategoryInTeam(teamId, expCatId, insertOnNotFound, true); // Changes in filtered data set if (category) { if (!category.NeedCapacity[weekEndingMs]) category.NeedCapacity[weekEndingMs] = 0; var newNeedCapacity = category.NeedCapacity[weekEndingMs] + deltaValue; category.NeedCapacity[weekEndingMs] = Math.max(newNeedCapacity, 0); } // Changes in unfiltered data set if (categorySource) { if (!categorySource.NeedCapacity[weekEndingMs]) categorySource.NeedCapacity[weekEndingMs] = 0; var newNeedCapacity = categorySource.NeedCapacity[weekEndingMs] + deltaValue; categorySource.NeedCapacity[weekEndingMs] = Math.max(newNeedCapacity, 0); } }, changeTeamValue: function (teamId, expCatId, weekEndingMs, deltaValue, insertOnNotFound) { this.changeExpenditureCategoryValue(teamId, expCatId, weekEndingMs, deltaValue, insertOnNotFound); }, changeResourceValue: function (teamId, expCatId, resourceId, weekEndingMs, deltaValue, insertOnNotFound) { if (!teamId || !expCatId || !resourceId || !weekEndingMs || !deltaValue) return; // Perform changes in filtered data source as well as in unfiltered data var category = this.getExpenditureCategoryInTeam(teamId, expCatId, true, false); var categorySource = this.getExpenditureCategoryInTeam(teamId, expCatId, true, true); // Changes in filtered data set if (category && (insertOnNotFound || category.Resources)) { if (insertOnNotFound && !category.Resources) category.Resources = {}; if (insertOnNotFound && !(resourceId in category.Resources)) { this.addResourceToSuperEC(teamId, expCatId, resourceId); } if (resourceId in category.Resources) { var resource = category.Resources[resourceId]; if (!resource.AllocatedCapacity) { resource.AllocatedCapacity = {}; } if (!resource.AllocatedCapacity[weekEndingMs]) resource.AllocatedCapacity[weekEndingMs] = 0; if (!category.AllocatedCapacity[weekEndingMs]) category.AllocatedCapacity[weekEndingMs] = 0; var newResourceAllocatedCapacity = resource.AllocatedCapacity[weekEndingMs] + deltaValue; var newCategoryAllocatedCapacity = category.AllocatedCapacity[weekEndingMs] + deltaValue; resource.AllocatedCapacity[weekEndingMs] = newResourceAllocatedCapacity; category.AllocatedCapacity[weekEndingMs] = newCategoryAllocatedCapacity; } } // Changes in unfiltered data set if (categorySource && (insertOnNotFound || categorySource.Resources)) { if (insertOnNotFound && !categorySource.Resources) categorySource.Resources = {}; if (insertOnNotFound && !(resourceId in categorySource.Resources)) { this.addResourceToSuperEC(teamId, expCatId, resourceId); } if (resourceId in categorySource.Resources) { var resource = categorySource.Resources[resourceId]; if (!resource.AllocatedCapacity) { resource.AllocatedCapacity = {}; } if (!resource.AllocatedCapacity[weekEndingMs]) resource.AllocatedCapacity[weekEndingMs] = 0; if (!categorySource.AllocatedCapacity[weekEndingMs]) categorySource.AllocatedCapacity[weekEndingMs] = 0; var newResourceAllocatedCapacity = resource.AllocatedCapacity[weekEndingMs] + deltaValue; var newCategoryAllocatedCapacity = categorySource.AllocatedCapacity[weekEndingMs] + deltaValue; resource.AllocatedCapacity[weekEndingMs] = newResourceAllocatedCapacity; categorySource.AllocatedCapacity[weekEndingMs] = newCategoryAllocatedCapacity; } } }, changeExpenditureNeedValue: function (scenarioId, expCatId, weekEndingMs, deltaValue, insertOnNotFound) { if (!scenarioId || !expCatId || !weekEndingMs || !deltaValue) return; console.error("teamInfoService.changeExpenditureNeedValue method not implemented"); // Not implemented. For future use }, changeExpenditureNeedValues: function (changeInfoStruct, insertOnNotFound) { /* Example of the changeInfoStruct: changeInfoStruct.push({ scenarioId: scenarioId, expCatId: expCatId, weekending: we, value: delta, action: 'change' // may be change, add, remove }); */ if (!changeInfoStruct || !angular.isArray(changeInfoStruct) || !needByCategories) return; for (var index = 0; index < changeInfoStruct.length; index++) { var dataItem = changeInfoStruct[index]; if (dataItem && dataItem.scenarioId && dataItem.expCatId && angular.isNumber(dataItem.value)) { var scenarioData = dataItem.scenarioId in needByCategories ? needByCategories[dataItem.scenarioId] : null; if (!scenarioData && (dataItem.action != 'remove') && insertOnNotFound) { scenarioData = { Expenditures: {} }; needByCategories[dataItem.scenarioId] = scenarioData; } if (scenarioData) { if (!scenarioData.Expenditures) scenarioData.Expenditures = {}; if (scenarioData.Expenditures) { var expCatData = dataItem.expCatId in scenarioData.Expenditures ? scenarioData.Expenditures[dataItem.expCatId] : null; if (!expCatData && (dataItem.action != 'remove') && insertOnNotFound) { expCatData = { Allocations: {} }; scenarioData.Expenditures[dataItem.expCatId] = expCatData; } if (expCatData) { if (!expCatData.Allocations) expCatData.Allocations = {}; if (expCatData.Allocations) { if (dataItem.action != 'remove') { var newValue = angular.isNumber(dataItem.value) && !isNaN(dataItem.value) ? Number(dataItem.value || 0) : 0; if (newValue < 0) { newValue = 0; } expCatData.Allocations[dataItem.weekending] = newValue; } else { delete expCatData.Allocations[dataItem.weekending]; } } } } } } } }, setNonProjectTimeResourceValue: function (nptItemId, resourceId, expenditureCategoryId, weekEndingMs, newValue) { if (!nptItemId || !resourceId || !expenditureCategoryId || !weekEndingMs) return false; var availableTeam = dataSources.getResourceTeam4Week(resourceId, weekEndingMs); if (!availableTeam) { console.error('There is no available team for resource ' + resourceId + ' on week ending ' + weekEndingMs); return false; } var result = false; var teamId = availableTeam.TeamId; var resource = this.getResourceInCategory(teamId, expenditureCategoryId, resourceId, false); var resourceSource = this.getResourceInCategory(teamId, expenditureCategoryId, resourceId, true); // Perform changes in filtered data set if (resource && resource.NonProjectTime && (nptItemId in resource.NonProjectTime)) { var npTime = resource.NonProjectTime[nptItemId]; if (npTime && npTime.Allocations && (weekEndingMs in npTime.Allocations)) { npTime.Allocations[weekEndingMs] = (newValue || 0); result = true; } } // Perform changes in unfiltered data set if (resourceSource && resourceSource.NonProjectTime && (nptItemId in resourceSource.NonProjectTime)) { var npTime = resourceSource.NonProjectTime[nptItemId]; if (npTime && npTime.Allocations && (weekEndingMs in npTime.Allocations)) { npTime.Allocations[weekEndingMs] = (newValue || 0); result = true; } } return result; }, getResourceSummaryNptAllocation: function (resource, weekEndingMs) { if (!resource || !resource.NonProjectTime || Object.keys(resource.NonProjectTime).length <= 0 || !weekEndingMs || weekEndingMs < 0) return 0; var totalHours = 0; angular.forEach(resource.NonProjectTime, function (npTime) { totalHours += npTime.Allocations[weekEndingMs] || 0; }); return totalHours; }, getResourceSummaryNptAllocations: function (resourceId, weekEndingsMs) { if (!resourceId || !weekEndingsMs || !angular.isArray(weekEndingsMs) || !weekEndingsMs.length) return null; if (!resourceQuickAccess) { throw "Resource quick access index not created"; } var resourceInIndex = resourceQuickAccess[resourceId]; if (!resourceInIndex || !resourceInIndex.Resources || !resourceInIndex.Resources.length) // Resource not found in quick index return null; var result = {}; var allocations = {}; for (var index = 0; index < resourceInIndex.Resources.length; index++) { var resource = resourceInIndex.Resources[index]; if (resource && resource.NonProjectTime) { for (var nptId in resource.NonProjectTime) { var nptItem = resource.NonProjectTime[nptId]; if (nptItem && nptItem.Allocations) { allocations = calculateDistributionService.mergeAllocations(allocations, nptItem.Allocations, weekEndingsMs); } } } } for (var wIndex = 0; wIndex < weekEndingsMs.length; wIndex++) { var we = weekEndingsMs[wIndex]; result[we] = (we in allocations) && angular.isNumber(allocations[we]) ? Number(allocations[we]) : 0; } return result; }, getTeamSummaryNptAllocations: function (teamId, expCatId) { if (!teamId) return 0; var result = {}; var teamResources = this.getResourcesWithAllocationsByTeam(teamId, expCatId); if (teamResources) { for (var resourceId in teamResources) { var resourceModel = teamResources[resourceId]; if (!expCatId || (resourceModel.OwnExpenditureCategoryId && (resourceModel.OwnExpenditureCategoryId == expCatId))) { if (resourceModel && resourceModel.NonProjectTime) { for (var nptId in resourceModel.NonProjectTime) { var nptModel = resourceModel.NonProjectTime[nptId]; if (nptModel && nptModel.Allocations) { result = calculateDistributionService.mergeAllocations(result, nptModel.Allocations); } } } } } } return result; }, getTeamAllocationsTotalByExpCat: function (expCatId, teamIds, weekEndingsMs) { if (!expCatId) return null; var result = {}; if (!teams) { // Info by teams doesn't exist if (weekEndingsMs && angular.isArray(weekEndingsMs) && weekEndingsMs.length) { for (var wIndex = 0; wIndex < weekEndingsMs.length; wIndex++) { var we = weekEndingsMs[wIndex]; result[we] = 0; } } return result; } var teamsToBrowse = teamIds && angular.isArray(teamIds) && teamIds.length ? teamIds : Object.keys(teams); for (var tIndex = 0; tIndex < teamsToBrowse.length; tIndex++) { var teamId = teamsToBrowse[tIndex]; var teamData = teams[teamId]; if (teamData && teamData.ExpCategories && (expCatId in teamData.ExpCategories)) { var expCatData = teamData.ExpCategories[expCatId]; if (expCatData && expCatData.NeedCapacity) { var weeks = weekEndingsMs && angular.isArray(weekEndingsMs) && weekEndingsMs.length ? weekEndingsMs : Object.keys(expCatData.NeedCapacity); for (var wIndex = 0; wIndex < weeks.length; wIndex++) { var we = weeks[wIndex]; if (!(we in result)) { result[we] = 0; } result[we] += (we in expCatData.NeedCapacity) ? (expCatData.NeedCapacity[we] || 0) : 0; } } } } return result; }, getTeamTotalCapacity: function (teamId, isPlanned, weekEndingMs) { if (!teamId) { return; } var result = 0; var collectioName = isPlanned ? 'PlannedCapacityValues' : 'ActualCapacityValues'; var team = this.getById(teamId); if (team && team.ExpCategories) { for (var expCatId in team.ExpCategories) { var expCat = team.ExpCategories[expCatId]; if (expCat && expCat.AllowResourceAssignment) { if (expCat[collectioName] && (weekEndingMs in expCat[collectioName]) && angular.isNumber(expCat[collectioName][weekEndingMs])) { result += Number(expCat[collectioName][weekEndingMs]); } } } } return result; }, getNonProjectTimeItemsById: function (nptItemId) { if (!nptItemId) return null; // Returns NPT items, grouped by resource Id var result = {}; var allTeams = this.getAll(); if (!allTeams) return false; for (var teamId in allTeams) { var teamResources = this.getResourcesWithAllocationsByTeam(teamId); if (!teamResources) continue; for (var resourceId in teamResources) { var resource = teamResources[resourceId]; if (resource.NonProjectTime && (nptItemId in resource.NonProjectTime)) { result[resourceId] = resource.NonProjectTime[nptItemId]; } } } return result; }, getResourceCapacity: function (resourceId, weekEndingMs) { if (!resourceId || !weekEndingMs) { return 0; } if (!resourceQuickAccess) { throw "Resource quick access index not created"; } var resourceInIndex = resourceQuickAccess[resourceId]; if (!resourceInIndex || !resourceInIndex.Resources || !resourceInIndex.Resources.length || !resourceInIndex.Expenditures || !resourceInIndex.Expenditures.length) { return 0; } var availableTeam = dataSources.getResourceTeam4Week(resourceId, weekEndingMs); if (!availableTeam) { return 0; } for (var i = 0; i < resourceInIndex.Expenditures.length; i++) { var ecItem = resourceInIndex.Expenditures[i]; if (ecItem.TeamId !== availableTeam.TeamId) { continue; } var resource = resourceInIndex.Resources[i]; if (resource) { return (resource.TotalCapacity || {})[weekEndingMs] || 0; } } return 0; }, getResourceSummaryAllocatedHrs: function (resourceId, weekEndingMs, includeNonProjectTime) { if (!resourceId || !weekEndingMs) { return 0; } if (!resourceQuickAccess) { throw "Resource quick access index not created"; } var resourceInIndex = resourceQuickAccess[resourceId]; if (!resourceInIndex) return 0; var result = 0; if (resourceInIndex.Resources) { for (var index = 0; index < resourceInIndex.Resources.length; index++) { var resourceRec = resourceInIndex.Resources[index]; if (resourceRec && resourceRec.AllocatedCapacity && resourceRec.AllocatedCapacity[weekEndingMs]) { var value = resourceRec.AllocatedCapacity[weekEndingMs]; if (value && angular.isNumber(value) && !isNaN(value)) { result += value; } } } } if (includeNonProjectTime) { var resourceNpt = this.getResourceSummaryNptAllocations(resourceId, [weekEndingMs]); if (resourceNpt && (weekEndingMs in resourceNpt) && angular.isNumber(resourceNpt[weekEndingMs])) { result += resourceNpt[weekEndingMs] } } return result; }, getSuperEcsTotalAllocatedHrs: function (resourceId, weekEndingMs) { if (!resourceQuickAccess) { throw "Resource quick access index not created"; } if (!resourceId || !weekEndingMs) return 0; var resourceInIndex = resourceQuickAccess[resourceId]; if (!resourceInIndex) return 0; var result = 0; if (resourceInIndex.Resources) { for (var index = 0; index < resourceInIndex.Resources.length; index++) { var resourceRec = resourceInIndex.Resources[index]; var teamExpCatRec = resourceInIndex.Expenditures != null ? resourceInIndex.Expenditures[index] : null; if (!teamExpCatRec) { console.error('Resource cache is corrupted. Cannot find an EC with index=' + index); continue; } var expCatObj = dataSources.getExpenditureById(teamExpCatRec.ExpenditureCategoryId); if (!expCatObj) { console.error('EC cache is corrupted. Cannot find an EC with Id=' + teamExpCatRec.ExpenditureCategoryId); continue; } if (expCatObj.AllowResourceAssignment) continue; // skip original ECs, summarize only Super ECs if (resourceRec && resourceRec.AllocatedCapacity && resourceRec.AllocatedCapacity[weekEndingMs]) { var value = resourceRec.AllocatedCapacity[weekEndingMs]; if (value && angular.isNumber(value) && !isNaN(value)) { result += value; } } } } return result; }, hasResourceNonProjectTimeAllocated: function (resourceId) { if (!resourceId) return null; if (!(resourceId in resourceQuickAccess)) return null; // Get resource info from resources fast access cache var resourceCacheInfoCell = resourceQuickAccess[resourceId]; if (!resourceCacheInfoCell) { throw "teamInfoService.resourceQuickAccess cache is broken. Empty record found for resourceId: " + resourceId; } if (!resourceCacheInfoCell.Resources || (resourceCacheInfoCell.Resources.length < 1)) { // Resource has no entries in teams and team expenditures. It means, the resource also has no NPT return false; } var result = false; for (var entryIndex = 0; entryIndex < resourceCacheInfoCell.Resources.length; entryIndex++) { var resourceEntry = resourceCacheInfoCell.Resources[entryIndex]; if (resourceEntry && resourceEntry.NonProjectTime && (Object.keys(resourceEntry.NonProjectTime).length > 0)) { // NPT items for resource found result = true; break; } } // resourceQuickAccess['1b96e54e-ffb1-497c-a3f7-f99bcd593b6d'].Resources[0].NonProjectTime['0e23d4c5-b31c-420d-b09b-5c3cfb18287c'].Id return result; }, extendResourceModelWithAttributes: function (resource, teamId) { if (!resource || !resource.Id) return; if (resource.IsExtended) { // Already extended with attributes return resource; } var resourceExtendedInfo = dataSources.getResourceById(resource.Id); if (resourceExtendedInfo) { resource.OwnExpenditureCategoryId = resourceExtendedInfo.ExpenditureCategoryId; resource.Teams = []; if (resourceExtendedInfo.FirstName && resourceExtendedInfo.LastName) { resource.Name = resourceExtendedInfo.FirstName + ' ' + resourceExtendedInfo.LastName; resource.SortingKey = resourceExtendedInfo.LastName + ' ' + resourceExtendedInfo.FirstName; } for (var index = 0; index < resourceExtendedInfo.Teams.length; index++) { if (resourceExtendedInfo.Teams[index] && resourceExtendedInfo.Teams[index].TeamId && (!teamId || (resourceExtendedInfo.Teams[index].TeamId == teamId))) { var teamMembershipInfo = resourceExtendedInfo.Teams[index]; resource.Teams.push(angular.copy(teamMembershipInfo)) teamMembershipInfo.IsInRange = function (we) { if (!this || !this.StartDate || !we) return false; return (we >= this.StartDate) && (!this.EndDate || we <= this.EndDate); }; } } resource.Teams.IsInRange = function (we) { if (!this || !this.length) return false; var result = false; for (var index = 0; index < this.length; index++) { var teamMembership = this[index]; result = teamMembership.IsInRange(we); if (result) break; } }; resource.Teams.GetMaxEndDate = function () { if (!this || !this.length) return; var result = undefined; for (var index = 0; index < this.length; index++) { var teamMembership = this[index]; if ((result === undefined) || ((result !== undefined) && (!teamMembership.EndDate || (result < teamMembership.EndDate)))) result = teamMembership.EndDate; if (result === null) break; } return result; }; resource.Teams.GetMinStartDate = function () { if (!this || !this.length) return; var result = undefined; for (var index = 0; index < this.length; index++) { var teamMembership = this[index]; if ((result === undefined) || ((result !== undefined) && teamMembership.StartDate || (result > teamMembership.StartDate))) result = teamMembership.StartDate; } return result; }; resource.StartDate = resource.Teams.GetMinStartDate(); resource.EndDate = resource.Teams.GetMaxEndDate(); resource.IsExtended = true; } else { throw "Unable to extend resource attributes: resource not found in dataSource dictionary"; } return resource; }, addExpenditureCategoryToTeam: function (teamId, expCatId) { // Adds EC to team. Gets EC info from dataSources if (!teamId || !expCatId) return false; var beenAdded = false; beenAdded = this.addExpenditureCategoryToTeamInternal(teamId, expCatId, true) || beenAdded; beenAdded = this.addExpenditureCategoryToTeamInternal(teamId, expCatId, false) || beenAdded; return beenAdded; }, addExpenditureCategoryToTeamInternal: function (teamId, expCatId, useSourceDataCache) { // Adds EC to team. Gets EC info from dataSources if (!teamId || !expCatId) return false; var team = this.getById(teamId, useSourceDataCache); if (!team) return false; var result = false; if (!team.ExpCategories) team.ExpCategories = {}; if (!(expCatId in team.ExpCategories)) { var expCatModel = dataSources.getExpenditureById(expCatId); if (expCatModel) { var newExpCat = { Id: expCatId, Name: expCatModel.ExpenditureCategoryName, UomValue: expCatModel.UOMValue, AllowResourceAssignment: expCatModel.AllowResourceAssignment, ECScenario: 4, PlannedCapacityValues: {}, ActualCapacityValues: {}, NeedCapacity: {}, AllocatedCapacity: {}, Resources: {} }; team.ExpCategories[expCatId] = newExpCat; return true; } } else { // Category already exists in the team result = true; } return result; }, // Removes Super EC. Skips general ECs. removeAssignedExpenditure: function (teamId, expCatId) { if (!teamId || !expCatId) return false; // Remove expenditure from both data sets: unfiltered and filtered var beenRemoved = false; beenRemoved = this.removeAssignedExpenditureInternal(teamId, expCatId, true) || beenRemoved; beenRemoved = this.removeAssignedExpenditureInternal(teamId, expCatId, false) || beenRemoved; if (beenRemoved) { recreateResourceQuickAccessIndex(); } return beenRemoved; }, removeAssignedExpenditureInternal: function (teamId, expCatId, useSourceDataCache) { if (!teamId || !expCatId) return false; var team = this.getById(teamId, useSourceDataCache); if (!team || !team.ExpCategories || !(expCatId in team.ExpCategories)) return false; var expCatModel = team.ExpCategories[expCatId]; if (!expCatModel || expCatModel.AllowResourceAssignment) return false; delete team.ExpCategories[expCatId]; return true; }, addResourceToSuperEC: function (teamId, expCatId, resourceId) { if (!teamId || !expCatId || !resourceId) return; // Add to both data sets: unfiltered and filtered this.addResourceToSuperECInternal(teamId, expCatId, resourceId, true); this.addResourceToSuperECInternal(teamId, expCatId, resourceId, false); recreateResourceQuickAccessIndex(); }, addResourceToSuperECInternal: function (teamId, expCatId, resourceId, useSourceDataCache) { if (!teamId || !expCatId || !resourceId) return; // Adds EC to team. Gets EC info from dataSources var expCat = this.getExpenditureCategoryInTeam(teamId, expCatId, true, useSourceDataCache); if (!expCat || !resourceId) return; if (expCat.AllowResourceAssignment) return; if (!expCat.Resources) expCat.Resources = {}; if (!(resourceId in expCat.Resources)) { var resourcesInOtherExpCats = this.getResourcesByTeam(teamId, null, [resourceId], useSourceDataCache); if (resourcesInOtherExpCats && (resourceId in resourcesInOtherExpCats)) { var resourceInOtherExpCat = resourcesInOtherExpCats[resourceId]; var newResource = angular.copy(resourceInOtherExpCat); newResource.AllocatedCapacity = {}; expCat.Resources[resourceId] = newResource; } } }, // Removes resource from Super EC. Skips general ECs. removeAssignedResourceFromSuperEC: function (teamId, expCatId, resourceId) { if (!teamId || !expCatId || !resourceId) return; // Remove resource in both data sets: unfiltered and filtered this.removeAssignedResourceFromSuperECInternal(teamId, expCatId, resourceId, true); this.removeAssignedResourceFromSuperECInternal(teamId, expCatId, resourceId, false); recreateResourceQuickAccessIndex(); }, removeAssignedResourceFromSuperECInternal: function (teamId, expCatId, resourceId, useSourceDataCache) { if (!teamId || !expCatId || !resourceId) return; var team = this.getById(teamId, useSourceDataCache); if (!team || !team.ExpCategories || !(expCatId in team.ExpCategories)) return; var expCat = team.ExpCategories[expCatId]; if (!expCat || expCat.AllowResourceAssignment || !expCat.Resources) return; if (!(resourceId in expCat.Resources)) return; delete expCat.Resources[resourceId]; }, mergeExpenditureInTeams: function (expCatId, expCatTeams, mergeCapacityOnly) { if (!expCatId || !expCatTeams || !angular.isArray(expCatTeams) || !expCatTeams.length) return null; var result = null; for (var tIndex = 0; tIndex < expCatTeams.length; tIndex++) { var teamId = expCatTeams[tIndex]; var expCatInTeam = this.getExpenditureCategoryInTeam(teamId, expCatId, false); if (!result) { result = angular.copy(expCatInTeam); } else { result.ActualCapacityValues = calculateDistributionService.mergeAllocations(result.ActualCapacityValues, expCatInTeam.ActualCapacityValues); result.PlannedCapacityValues = calculateDistributionService.mergeAllocations(result.PlannedCapacityValues, expCatInTeam.PlannedCapacityValues); if (!mergeCapacityOnly) { result.AllocatedCapacity = calculateDistributionService.mergeAllocations(result.AllocatedCapacity, expCatInTeam.AllocatedCapacity); result.NeedCapacity = calculateDistributionService.mergeAllocations(result.NeedCapacity, expCatInTeam.NeedCapacity); } } } return result; }, mergeExpenditureResources: function (expCatId, expCatTeams, withAllocations) { if (!expCatId || !expCatTeams || !angular.isArray(expCatTeams) || !expCatTeams.length) return null; var result = {}; for (var tIndex = 0; tIndex < expCatTeams.length; tIndex++) { var teamId = expCatTeams[tIndex]; var resourcesInTeam = this.getResourcesByTeam(teamId, expCatId); if (resourcesInTeam) { for (var resourceId in resourcesInTeam) { var resource = resourcesInTeam[resourceId]; if (!(resourceId in result)) { if (resource.TotalCapacity && Object.keys(resource.TotalCapacity).length) { resource = this.extendResourceModelWithAttributes(resource); result[resourceId] = angular.copy(resource); } } else { var summaryResourceItem = result[resourceId]; summaryResourceItem.Teams = summaryResourceItem.Teams.concat(resource.Teams); summaryResourceItem.StartDate = resource.StartDate < summaryResourceItem.StartDate ? resource.StartDate : summaryResourceItem.StartDate; summaryResourceItem.EndDate = resource.EndDate || summaryResourceItem.EndDate ? null : ((resource.EndDate > summaryResourceItem.EndDate) ? resource.EndDate : summaryResourceItem.EndDate); if (withAllocations) { summaryResourceItem.AllocatedCapacity = calculateDistributionService.mergeAllocations(summaryResourceItem.AllocatedCapacity, resource.AllocatedCapacity); summaryResourceItem.TotalCapacity = calculateDistributionService.mergeAllocations(summaryResourceItem.TotalCapacity, resource.TotalCapacity); } } } } } return result; }, expenditureCategoryHasNonZeroTeamAllocations: function (expCatId, weekendings) { if (!expCatId) return; var result = false; if (teamsUnfiltered) { for (var teamId in teamsUnfiltered) { var teamData = teamsUnfiltered[teamId]; if (teamData && teamData.ExpCategories && (expCatId in teamData.ExpCategories)) { var expCatData = teamData.ExpCategories[expCatId]; if (expCatData && expCatData.NeedCapacity) { var weeks = weekendings && angular.isArray(weekendings) && weekendings.length ? weekendings : Object.keys(expCatData.NeedCapacity); for (var wIndex = 0; wIndex < weeks.length; wIndex++) { var we = weeks[wIndex]; if ((we in expCatData.NeedCapacity) && angular.isNumber(expCatData.NeedCapacity[we]) && Number(expCatData.NeedCapacity[we]) != 0) { result = true; break; } } } } if (result) { break; } } } return result; } }; }]); enVisageServices.factory('mixProjectService', ['$http', function ($http) { var urls = { recalculateScenarioFinInfoUrl: '/Mix/RecalculateScenarioFinInfo' }; var container = { recalculateScenarioFinInfo: function (scenario) { if (!scenario) return; var request = getAntiXSRFRequest(urls.recalculateScenarioFinInfoUrl, scenario); var promise = $http(request).then(function (response) { return response.data; }, function () { throw 'Error during recalculation of scenario financial information'; }); return promise; } }; return container; }]); enVisageServices.factory('costSavingService', ['$filter', function ($filter) { var _costSavingData = {}; function _formatRangeResult(range) { var result = { startDate: null, endDate: null }; if (!range) return result; if (parseInt(range.startDate) > 0) result.startDate = $filter('date')(range.startDate, 'MM/dd/yyyy'); if (parseInt(range.endDate) > 0) result.endDate = $filter('date')(range.endDate, 'MM/dd/yyyy'); return result; }; var service = { init: function (data) { if (!data || !data.ScenarioId) return; var scenarioStartDate = new Date(data.ScenarioStartDate), scenarioEndDate = new Date(data.ScenarioEndDate); _costSavingData[data.ScenarioId] = { StartDate: new Date(data.StartDate), PrevScenarioStartDate: scenarioStartDate.getTime() > 0 ? scenarioStartDate : null, ScenarioStartDate: scenarioStartDate.getTime() > 0 ? scenarioStartDate : null, PrevScenarioEndDate: scenarioEndDate.getTime() > 0 ? scenarioEndDate : null, ScenarioEndDate: scenarioEndDate.getTime() > 0 ? scenarioEndDate : null, EndDate: new Date(data.EndDate), CostSavings: data.CostSavings, CostSavingType: data.CostSavingType, Description: data.Description, Items: data.Items }; }, recalculateCostSavings: function (costSavingTotal, startDate, endDate) { if (!startDate || !endDate) return null; var sd = new Date(startDate); var ed = new Date(endDate); var cs = parseFloat(costSavingTotal); if (!sd.getTime() || !ed.getTime() || isNaN(cs) || cs <= 0) { return null; } // it is strange logic, 3155760000000 ms ~ 100.06849315069 years if (ed - sd > 3155760000000) ed = new Date(sd + 3155760000000); var sdYear = sd.getFullYear(); var edYear = ed.getFullYear(); var data = new Array(); var startMonth = sd.getMonth(); var endMonth = ed.getMonth(); var monthNum = 0; for (var yearIndex = 0; yearIndex <= edYear - sdYear; yearIndex++) { var arrYear = new Array(); arrYear.push(0); for (var monthIndex = 0; monthIndex < 12; monthIndex++) { if (yearIndex == 0 && monthIndex < startMonth) {// before start date arrYear.push(null); } else if (yearIndex == (edYear - sdYear) && monthIndex > endMonth) {// after end date arrYear.push(null); } else {// inside date range arrYear.push(0); monthNum++; } } data.push({ Year: sdYear + yearIndex, Costs: arrYear }); } var dec = Math.pow(10, 3);//precision decimals var itemCost = Math.round(cs * dec / monthNum) / dec; for (var i = 0; i < data.length; i++) { for (var j = 1; j < data[i].Costs.length; j++) { if (data[i].Costs[j] != null) { data[i].Costs[j] = itemCost; data[i].Costs[0] += itemCost; } } data[i].Costs[0] = Math.round(data[i].Costs[0] * dec) / dec; } return data; }, getCostSavingData: function (scenarioId) { return _costSavingData[scenarioId]; }, getCostSavings4Save: function (scenarioId) { if (!_costSavingData[scenarioId]) return null; var startDate = new Date(_costSavingData[scenarioId].StartDate), endDate = new Date(_costSavingData[scenarioId].EndDate); return { CostSavingItems: _costSavingData[scenarioId].Items, CostSavingStartDate: !startDate.getTime() ? null : Date.UTC(startDate.getFullYear(), startDate.getMonth(), startDate.getDate()), CostSavingEndDate: !endDate.getTime() ? null : Date.UTC(endDate.getFullYear(), endDate.getMonth(), endDate.getDate()), CostSavings: _costSavingData[scenarioId].CostSavings, CostSavingType: _costSavingData[scenarioId].CostSavingType, CostSavingDescription: _costSavingData[scenarioId].Description }; }, setCostSavings: function (scenarioId, value) { if (!_costSavingData[scenarioId]) return false; var newValue = parseFloat(value); if (newValue == _costSavingData[scenarioId].CostSavings) return false; _costSavingData[scenarioId].CostSavings = (isNaN(newValue) || newValue < 0) ? null : newValue; _costSavingData[scenarioId].Items = this.recalculateCostSavings(_costSavingData[scenarioId].CostSavings, _costSavingData[scenarioId].StartDate, _costSavingData[scenarioId].EndDate); return true; }, getCostSavings: function (scenarioId) { if (!_costSavingData[scenarioId]) return null; return _costSavingData[scenarioId].CostSavings; }, setStartDate: function (scenarioId, value) { if (!_costSavingData[scenarioId]) return false; var newValue = new Date(value); var oldValue = new Date(_costSavingData[scenarioId].StartDate); if (newValue.getTime() === oldValue.getTime()) return false; _costSavingData[scenarioId].StartDate = !newValue.getTime() ? null : newValue; _costSavingData[scenarioId].Items = this.recalculateCostSavings(_costSavingData[scenarioId].CostSavings, _costSavingData[scenarioId].StartDate, _costSavingData[scenarioId].EndDate); return true; }, setEndDate: function (scenarioId, value) { if (!_costSavingData[scenarioId]) return false; var newValue = new Date(value); var oldValue = new Date(_costSavingData[scenarioId].EndDate); if (newValue.getTime() === oldValue.getTime()) return false; _costSavingData[scenarioId].EndDate = !newValue.getTime() ? null : newValue; _costSavingData[scenarioId].Items = this.recalculateCostSavings(_costSavingData[scenarioId].CostSavings, _costSavingData[scenarioId].StartDate, _costSavingData[scenarioId].EndDate); return true; }, setRange: function (scenarioId, startDate, endDate) { if (!_costSavingData[scenarioId]) return false; var newStartDate = new Date(startDate); var oldStartDate = new Date(_costSavingData[scenarioId].StartDate); var newEndDate = new Date(endDate); var oldEndDate = new Date(_costSavingData[scenarioId].EndDate); if (newStartDate.getTime() === oldStartDate.getTime() && newEndDate.getTime() == oldEndDate.getTime()) return false; _costSavingData[scenarioId].StartDate = !newStartDate.getTime() ? null : newStartDate; _costSavingData[scenarioId].EndDate = !newEndDate.getTime() ? null : newEndDate; _costSavingData[scenarioId].Items = this.recalculateCostSavings(_costSavingData[scenarioId].CostSavings, _costSavingData[scenarioId].StartDate, _costSavingData[scenarioId].EndDate); return true; }, setCostSavingType: function (scenarioId, value) { if (!_costSavingData[scenarioId]) return false; if (value == _costSavingData[scenarioId].CostSavingType) return false; _costSavingData[scenarioId].CostSavingType = value; return true; }, setDescription: function (scenarioId, value) { if (!_costSavingData[scenarioId]) return false; if (value == _costSavingData[scenarioId].Description) return false; _costSavingData[scenarioId].Description = value; return true; }, setCellValue: function (scenarioId, yearIndex, colIndex, value) { if (!_costSavingData[scenarioId]) return false; if (!_costSavingData[scenarioId].Items) return false; yearIndex = parseInt(yearIndex); colIndex = parseInt(colIndex); value = parseFloat(value) || 0; if (isNaN(yearIndex) || yearIndex < 0 || isNaN(colIndex) || colIndex < 0) return false; if (!_costSavingData[scenarioId].Items[yearIndex] || !_costSavingData[scenarioId].Items[yearIndex].Costs) return false; var precision = Math.pow(10, 3); //precision decimals if (colIndex == 0) { _costSavingData[scenarioId].Items[yearIndex].Costs[0] = Math.round(value * precision) / precision; var num = 0; for (var j = 1; j <= 12; j++) { if (_costSavingData[scenarioId].Items[yearIndex].Costs[j] != null) num++; } var val = Math.round(value * precision / num) / precision; for (var j = 1; j <= 12; j++) { if (_costSavingData[scenarioId].Items[yearIndex].Costs[j] != null) _costSavingData[scenarioId].Items[yearIndex].Costs[j] = val; } } else { var oldValue = _costSavingData[scenarioId].Items[yearIndex].Costs[colIndex]; _costSavingData[scenarioId].Items[yearIndex].Costs[colIndex] = Math.round(value * precision) / precision; _costSavingData[scenarioId].Items[yearIndex].Costs[0] += Math.round((value - oldValue) * precision) / precision; if (_costSavingData[scenarioId].Items[yearIndex].Costs[0] < 0) _costSavingData[scenarioId].Items[yearIndex].Costs[0] = 0; } var sum = 0; for (var i = 0; i < _costSavingData[scenarioId].Items.length; i++) { sum += _costSavingData[scenarioId].Items[i].Costs[0]; } sum = Math.round(sum * precision) / precision; _costSavingData[scenarioId].CostSavings = sum; return true; }, changeScenarioRange: function (scenarioId, startDate, endDate) { if (!_costSavingData[scenarioId]) return null; startDate = new Date(startDate); endDate = new Date(endDate); _costSavingData[scenarioId].PrevScenarioStartDate = _costSavingData[scenarioId].ScenarioStartDate || _costSavingData[scenarioId].PrevScenarioStartDate; _costSavingData[scenarioId].ScenarioStartDate = startDate.getTime() > 0 ? startDate : null; _costSavingData[scenarioId].PrevScenarioEndDate = _costSavingData[scenarioId].ScenarioEndDate || _costSavingData[scenarioId].PrevScenarioEndDate; _costSavingData[scenarioId].ScenarioEndDate = endDate.getTime() > 0 ? endDate : null; return this.recalculateRange(scenarioId, _costSavingData[scenarioId].PrevScenarioStartDate, _costSavingData[scenarioId].ScenarioStartDate, _costSavingData[scenarioId].PrevScenarioEndDate, _costSavingData[scenarioId].ScenarioEndDate); }, recalculateRange: function (scenarioId, scenarioStartDateOld, scenarioStartDateNew, scenarioEndDateOld, scenarioEndDateNew) { if (!_costSavingData[scenarioId]) return null; var scenarioStartDateOld = new Date(scenarioStartDateOld).getTime() || 0, scenarioStartDateNew = new Date(scenarioStartDateNew).getTime() || 0, scenarioEndDateOld = new Date(scenarioEndDateOld).getTime() || 0, scenarioEndDateNew = new Date(scenarioEndDateNew).getTime() || 0, scenarioStartDateChanged = scenarioStartDateNew != scenarioStartDateOld, scenarioEndDateChanged = scenarioEndDateNew != scenarioEndDateOld; var result = { startDate: new Date(_costSavingData[scenarioId].StartDate).getTime() || 0, endDate: new Date(_costSavingData[scenarioId].EndDate).getTime() || 0 }; // if scenario range is not changed we should not recalculate range for cost saving if (!scenarioStartDateChanged && !scenarioEndDateChanged) return _formatRangeResult(result); // cost saving range is not defined - we should not set range according to scenario range if (result.startDate <= 0 && result.endDate <= 0) return _formatRangeResult(result); // scenario range is not defined - we should not set range according to scenario range if (scenarioStartDateNew <= 0 && scenarioEndDateNew <= 0) return _formatRangeResult(result); // if cost saving range exists if (result.startDate > 0 && result.endDate > 0) { // if cost saving range equals to scenario range we need to set cost saving range equals to new scenario range if (result.startDate == scenarioStartDateOld && result.endDate == scenarioEndDateOld) { result.startDate = scenarioStartDateNew || result.startDate; result.endDate = scenarioEndDateNew || result.endDate; }// if cost saving period starts after scenario end date we need to change cost saving range according to scenario end date only else if (scenarioEndDateChanged && scenarioEndDateOld > 0 && scenarioEndDateNew > 0 && result.startDate >= scenarioEndDateOld) { var shift = scenarioEndDateNew - scenarioEndDateOld; result.startDate += shift; result.endDate += shift; }// otherwise we need to change range according to scenario start date else if (scenarioStartDateChanged && scenarioStartDateNew > 0 && (scenarioEndDateOld <= 0 || result.startDate < scenarioEndDateOld)) { // if previous value for scenario start date does not exist and new start date will be later than cost saving start date // we need to set cost saving start date equals to scenario start date and save duration var shift = 0; if (scenarioStartDateOld <= 0) { if (scenarioStartDateNew > result.startDate) shift = scenarioStartDateNew - result.startDate; } else { shift = scenarioStartDateNew - scenarioStartDateOld; if (scenarioStartDateOld > result.startDate) shift += (scenarioStartDateOld - result.startDate); } result.startDate += shift; result.endDate += shift; } } // if exists only cost saving start date else if (result.startDate > 0) { // if cost saving period starts after scenario end date we need to change cost saving range according to scenario end date only if (scenarioEndDateChanged && scenarioEndDateOld > 0 && scenarioEndDateNew > 0 && result.startDate >= scenarioEndDateOld) { result.startDate += (scenarioEndDateNew - scenarioEndDateOld); }// otherwise we need to change range according to scenario start date else if (scenarioStartDateChanged && scenarioStartDateNew > 0 && (scenarioEndDateOld <= 0 || result.startDate < scenarioEndDateOld)) { // if previous value for scenario start date does not exist and new start date will be later than cost saving start date // we need to set cost saving start date equals to scenario start date var shift = 0; if (scenarioStartDateOld <= 0) { if (scenarioStartDateNew > result.startDate) shift = scenarioStartDateNew - result.startDate; } else { shift = scenarioStartDateNew - scenarioStartDateOld; if (scenarioStartDateOld > result.startDate) shift += (scenarioStartDateOld - result.startDate); } result.startDate += shift; } } // if exists only cost saving end date else if (result.endDate > 0) { // if new scenario start date more than cost saving end date we need to set last equals to scenario start date if (scenarioStartDateChanged && scenarioStartDateNew > result.endDate) result.endDate = scenarioStartDateNew; } return _formatRangeResult(result); }, createBackup: function (scenarioId) { if (!_costSavingData[scenarioId]) return null; return angular.copy(_costSavingData[scenarioId]); }, restoreBackup: function (scenarioId, backup) { if (!_costSavingData[scenarioId]) return; if (!backup) throw 'Backup cannot be rollbacked because it is null or undefined'; _costSavingData[scenarioId] = angular.copy(backup); } }; return service; }]); enVisageServices.factory('scenarioDetailsService', ['$http', '$q', 'roundService', 'teamInfoService', function ($http, $q, roundService, teamInfoService) { // each element has the next structure: // scenarioId: { // TeamsInScenario, // array of teams information, related to certain scenario // Expenditures // collection of scenario details with team and resource allocations // } var scenarios = {}; var _dataSources = null; var service = { loadDataSources: function () { if (_dataSources) { var deferrer = $q.defer(); deferrer.resolve(_dataSources); return deferrer.promise; } var request = getAntiXSRFRequest('/Scenarios/GetDataSources'); var promise = $http(request). then(function (response) { _dataSources = response.data; return response.data; }, function (response) { // throw exception through the pipe throw 'An error occurred while loading data sources for scenario details page'; }); return promise; }, initScenarioInfo: function (scenarioId) { if (!scenarioId) return null; scenarios[scenarioId] = { TeamsInScenario: {}, Expenditures: {}, Rates: [] }; return scenarios[scenarioId]; }, getScenarioInfo: function (scenarioId) { return scenarios[scenarioId]; }, setExpenditures: function (scenarioId, expenditures) { if (!scenarioId) return; if (!scenarios[scenarioId]) scenarios[scenarioId] = this.initScenarioInfo(scenarioId); scenarios[scenarioId].Expenditures = expenditures || {}; }, setRates: function (scenarioId, rates) { if (!scenarioId) return; if (!scenarios[scenarioId]) scenarios[scenarioId] = this.initScenarioInfo(scenarioId); scenarios[scenarioId].Rates = rates || []; }, getExpenditures: function (scenarioId) { if (!scenarioId || !scenarios[scenarioId]) return null; return scenarios[scenarioId].Expenditures; }, setTeamsInScenario: function (scenarioId, teamsInScenarioArray) { if (!scenarioId || !teamsInScenarioArray || !angular.isArray(teamsInScenarioArray)) return; if (!scenarios[scenarioId]) scenarios[scenarioId] = this.initScenarioInfo(scenarioId); scenarios[scenarioId].TeamsInScenario = {}; angular.forEach(teamsInScenarioArray, function (teamInfo) { scenarios[scenarioId].TeamsInScenario[teamInfo.TeamId] = angular.copy(teamInfo); }); }, getTeamsInScenarioArray: function (scenarioId) { if (!scenarioId) return []; var scenario = this.getScenarioInfo(scenarioId); if (!scenario || !scenario.TeamsInScenario) return []; var result = []; angular.forEach(scenario.TeamsInScenario, function (teamInScenario) { result.push(teamInScenario); }); return result; }, checkTeamAssignedToScenario: function (scenarioId, teamId) { if (!scenarioId || !teamId || !scenarios[scenarioId]) return false; var teams = scenarios[scenarioId].TeamsInScenario; if (teams && teams[teamId]) return true; return false; }, isExpenditureExists: function (scenarioId, expenditureCategoryId) { if (!scenarioId || !expenditureCategoryId) throw 'scenarioId and expenditureCategoryId are required for the isExpenditureExists method'; var scenario = scenarios[scenarioId]; if (!scenario) throw 'scenario with id = ' + scenarioId + ' does not exist'; return (!!scenario.Expenditures && !!scenario.Expenditures[expenditureCategoryId]); }, getCategoryInScenario: function (scenarioId, expenditureCategoryId) { if (!scenarioId || !expenditureCategoryId) return null; var scenario = scenarios[scenarioId]; if (!scenario || !scenario.Expenditures) return null; return scenario.Expenditures[expenditureCategoryId]; }, getTeamInScenario: function (scenarioId, expenditureCategoryId, teamId) { if (!scenarioId || !expenditureCategoryId || !teamId) return null; var category = this.getCategoryInScenario(scenarioId, expenditureCategoryId); if (!category || !category.Teams) return null; return category.Teams[teamId]; }, getResourceInScenario: function (scenarioId, expenditureCategoryId, teamId, resourceId) { if (!scenarioId || !expenditureCategoryId || !teamId || !resourceId) return null; var teamInScenario = this.getTeamInScenario(scenarioId, expenditureCategoryId, teamId); if (!teamInScenario || !teamInScenario.Resources) return null; return teamInScenario.Resources[resourceId]; }, getScenarioRates: function (scenarioId) { if (!scenarioId) return null; var scenario = this.getScenarioInfo(scenarioId); if (!scenario || !scenario.Rates) return null; return scenario.Rates; }, getRate: function (scenarioId, expenditureCategoryId, date) { if (!scenarioId || !expenditureCategoryId) return 0; var rates = this.getScenarioRates(scenarioId); if (!rates || rates.length <= 0) return 0; for (var i = 0; i < rates.length; i++) { if (rates[i].expCatId === expenditureCategoryId) { if (rates[i].rateValues && rates[i].rateValues.length > 0) { for (var rIndex = 0; rIndex < rates[i].rateValues.length; rIndex++) { if (date >= rates[i].rateValues[rIndex].weekStartDate && date <= rates[i].rateValues[rIndex].weekEndDate) { return rates[i].rateValues[rIndex].rateValue; } } } break; } } return 0; }, deleteTeamsFromScenario: function (scenarioId, teamId) { if (!scenarioId) return; var scenario = scenarios[scenarioId]; if (!scenario || !scenario.Expenditures) return; angular.forEach(scenario.Expenditures, function (category) { if (category.Teams) { if (!teamId) { delete category.Teams; } else delete category.Teams[teamId]; } }); if (scenario.TeamsInScenario) { if (!teamId) scenario.TeamsInScenario = null; else delete scenario.TeamsInScenario[teamId]; } }, // teams is array of GUIDs - teams identifiers addTeamsInScenario: function (scenarioId, teams) { if (!scenarioId || !teams || !teams.length) return; var scenario = this.getScenarioInfo(scenarioId); if (!scenario) return; if (!scenario.TeamsInScenario) scenario.TeamsInScenario = {}; for (var i = 0; i < teams.length; i++) { if (!scenario.TeamsInScenario[teams[i]]) { scenario.TeamsInScenario[teams[i]] = { TeamId: teams[i], Allocation: 0, IsNew: true }; } } }, addTeamInScenario: function (scenarioId, teamId) { if (!scenarioId || !teamId) return; var scenario = this.getScenarioInfo(scenarioId); if (!scenario) return; if (!scenario.TeamsInScenario) scenario.TeamsInScenario = {}; if (!scenario.TeamsInScenario[teamId]) { scenario.TeamsInScenario[teamId] = { TeamId: teamId, Allocation: 0, IsNew: true }; } }, assignResource: function (scenarioId, expenditureCategoryId, teamId, resourceId) { if (!scenarioId || !expenditureCategoryId || !teamId || !resourceId) return; var targetTeam = this.getTeamInScenario(scenarioId, expenditureCategoryId, teamId); if (!targetTeam || !targetTeam.AllResources) return; var sourceResource = targetTeam.Resources[resourceId] || targetTeam.AllResources[resourceId]; if (!sourceResource) return; var targetResource = angular.copy(sourceResource); if (!targetTeam.Resources) targetTeam.Resources = {}; targetResource.Changed = true; targetResource.Deleted = false; targetTeam.Resources[targetResource.Id] = targetResource; targetTeam.Changed = true; }, removeResource: function (scenarioId, expenditureCategoryId, teamId, resourceId) { if (!scenarioId || !expenditureCategoryId || !teamId || !resourceId) return; var targetTeam = this.getTeamInScenario(scenarioId, expenditureCategoryId, teamId); if (!targetTeam || !targetTeam.Resources || !targetTeam.Resources[resourceId]) return; targetTeam.Resources[resourceId].Changed = true; targetTeam.Resources[resourceId].Deleted = true; targetTeam.Changed = true; }, checkResourceAssignedToScenario: function (scenarioId, expenditureCategoryId, teamId, resourceId) { if (!scenarioId || !expenditureCategoryId || !teamId || !resourceId) return false; var resource = this.getResourceInScenario(scenarioId, expenditureCategoryId, teamId, resourceId); if (resource && !resource.Deleted) return true; return false; }, checkExpenditureCategoryHasAssignedResources: function (scenarioId, expenditureCategoryId) { if (!scenarioId || !expenditureCategoryId) return false; var category = this.getCategoryInScenario(scenarioId, expenditureCategoryId); if (!category || !category.Teams) return false; for (var teamId in category.Teams) { var resources = category.Teams[teamId].Resources; if (!resources || Object.keys(resources).length <= 0) continue; for (var resourceId in resources) { if (!resources[resourceId].Deleted) return true; } } return false; }, changeExpenditureCategoryValue: function (scenarioId, expenditureCategoryId, weekEndingMs, deltaHours, deltaCost) { if (!scenarioId || !expenditureCategoryId || isNaN(parseFloat(weekEndingMs)) || isNaN(parseFloat(deltaHours))) return; if (isNaN(parseFloat(deltaCost))) deltaCost = 0; var category = this.getCategoryInScenario(scenarioId, expenditureCategoryId); if (!category) return; if (!category.Details) category.Details = {}; if (!category.Details[weekEndingMs]) { category.Details[weekEndingMs] = { ForecastQuantity: 0, ForecastCost: 0 }; } category.Details[weekEndingMs].ForecastQuantity = roundService.roundQuantity(category.Details[weekEndingMs].ForecastQuantity + deltaHours); category.Details[weekEndingMs].ForecastCost = roundService.roundCost(category.Details[weekEndingMs].ForecastCost + deltaCost); category.Details[weekEndingMs].Changed = true; }, getExpenditureCategoryValue: function (scenarioId, expenditureCategoryId, weekEndingMs) { if (!scenarioId || !expenditureCategoryId || isNaN(parseFloat(weekEndingMs))) return null; var category = this.getCategoryInScenario(scenarioId, expenditureCategoryId); if (!category || !category.Details) return null; return category.Details[weekEndingMs]; }, getTeamValue: function (scenarioId, expenditureCategoryId, teamId, weekEndingMs) { if (!scenarioId || !expenditureCategoryId || !teamId || isNaN(parseFloat(weekEndingMs))) return 0; var team = this.getTeamInScenario(scenarioId, expenditureCategoryId, teamId); if (!team || !team.QuantityValues) return 0; return team.QuantityValues[weekEndingMs] || 0; }, changeTeamValue: function (scenarioId, expenditureCategoryId, teamId, weekEndingMs, deltaHours) { if (!scenarioId || !expenditureCategoryId || !teamId || isNaN(parseFloat(weekEndingMs)) || isNaN(parseFloat(deltaHours))) return; var team = this.getTeamInScenario(scenarioId, expenditureCategoryId, teamId); if (!team) return; if (!team.QuantityValues) team.QuantityValues = {}; if (!team.RestQuantityValues) team.RestQuantityValues = {}; if (!team.QuantityValues[weekEndingMs]) team.QuantityValues[weekEndingMs] = 0; if (!team.RestQuantityValues[weekEndingMs]) team.RestQuantityValues[weekEndingMs] = 0; team.QuantityValues[weekEndingMs] = roundService.roundQuantity(team.QuantityValues[weekEndingMs] + deltaHours); team.RestQuantityValues[weekEndingMs] = roundService.roundQuantity(team.RestQuantityValues[weekEndingMs] - deltaHours); team.Changed = true; }, changeResourceValue: function (scenarioId, expenditureCategoryId, teamId, resourceId, weekEndingMs, deltaHours) { if (!scenarioId || !expenditureCategoryId || !teamId || !resourceId || isNaN(parseFloat(weekEndingMs)) || isNaN(parseFloat(deltaHours))) return; var resource = this.getResourceInScenario(scenarioId, expenditureCategoryId, teamId, resourceId); if (!resource) return; if (!resource.QuantityValues) resource.QuantityValues = {}; if (!resource.RestQuantityValues) resource.RestQuantityValues = {}; if (!resource.QuantityValues[weekEndingMs]) resource.QuantityValues[weekEndingMs] = 0; if (!resource.RestQuantityValues[weekEndingMs]) resource.RestQuantityValues[weekEndingMs] = 0; resource.QuantityValues[weekEndingMs] = roundService.roundQuantity(resource.QuantityValues[weekEndingMs] + deltaHours); resource.RestQuantityValues[weekEndingMs] = roundService.roundQuantity(resource.RestQuantityValues[weekEndingMs] - deltaHours); resource.Changed = true; }, recalculateTeamsAllocation: function (scenarioId) { if (!scenarioId) return null; var scenarioInfo = this.getScenarioInfo(scenarioId); if (!scenarioInfo || !scenarioInfo.TeamsInScenario) return null; var totals = this.getTotalAllocation4Scenario(scenarioId); if (!totals || totals.length <= 0) totals = []; var totalByScenario = 0, teamsTotal = {}; angular.forEach(scenarioInfo.TeamsInScenario, function (team) { teamsTotal[team.TeamId] = 0; }); for (var i = 0; i < totals.length; i++) { totalByScenario += totals[i].Total; for (var j = 0; j < totals[i].Teams.length; j++) { var team = totals[i].Teams[j]; teamsTotal[team.TeamId] += team.Total; } } var result = []; angular.forEach(scenarioInfo.TeamsInScenario, function (team, teamId) { team.Allocation = Math.round((totalByScenario <= 0 ? 0 : teamsTotal[teamId] / totalByScenario) * 100); result.push(angular.copy(team)); }); return result; }, getTotalAllocation4Scenario: function (scenarioId) { if (!scenarioId) return null; var expendituresInScenario = this.getExpenditures(scenarioId); if (!expendituresInScenario) return null; var result = []; angular.forEach(expendituresInScenario, function (expCat, expCatId) { var allocation = { ExpCatId: expCatId, ExpCatName: expCat.ExpenditureCategoryName, Teams: [], TeamsTotal: 0, ResourcesTotal: 0, Total: 0 }; angular.forEach(expCat.Details, function (detail) { allocation.Total += detail.ForecastQuantity || 0; }); angular.forEach(expCat.Teams, function (team, teamId) { var teamAllocation = { TeamId: teamId, Total: 0 }; angular.forEach(team.QuantityValues, function (value) { teamAllocation.Total += value || 0; }); angular.forEach(team.Resources, function (resource) { if (!resource.Deleted && resource.QuantityValues) { angular.forEach(resource.QuantityValues, function (value) { allocation.ResourcesTotal += value || 0; }); } }); allocation.TeamsTotal += teamAllocation.Total; allocation.Teams.push(teamAllocation); }); result.push(allocation); }); return result; }, getCost: function (scenarioId, expenditureCategoryId, weekEndingMs, hours) { if (!scenarioId || !expenditureCategoryId || !weekEndingMs || !hours) return 0; return hours * this.getRate(scenarioId, expenditureCategoryId, weekEndingMs); }, _getResourceAvailableHoursOnWeekEnding: function (resource, weekEnding) { if (!resource || !weekEnding) throw 'resource or weekEnding parameter does not exist'; if (resource.AllocatedCapacity && resource.TotalCapacity) { var nptValue = 0; if (resource.NonProjectTime) nptValue = teamInfoService.getResourceSummaryNptAllocation(resource, weekEnding); return (resource.TotalCapacity[weekEnding] || 0) - (resource.AllocatedCapacity[weekEnding] || 0) - nptValue; } else { // original formula: // A = (1 - Allocated In Other Scenarios / Capacity) * 100 // but we do not have "Allocated In Other Scenarios" value and we can use the similar formula: // A = ((Rest + Current Scenario) * 100 / UOM) var quantityValues = resource.QuantityValues || { }; var quantity = quantityValues[weekEnding] || 0; var restQuantityValues = resource.RestQuantityValues || { }; var restQuantity = restQuantityValues[weekEnding] || 0; return (restQuantity + quantity); } }, recalculateResourceAvailability: function (resource, weekEndings, capacityValues) { if (!resource) return; weekEndings = weekEndings || []; if (weekEndings.length <= 0) return; resource.MinAvailability = Number.MAX_VALUE; resource.MaxAvailability = -Number.MAX_VALUE; resource.AvgAvailability = 0; var today = new Date(); today = new Date(today.getUTCFullYear(), today.getUTCMonth(), today.getUTCDate()); var UTCmilliseconds = today.getTime() - today.getTimezoneOffset() * 60 * 1000; resource.IsVisible = false; var weeks = 0; for (var i = 0; i < weekEndings.length; i++) { if (!resource.IsVisible && resource.StartDate && ((resource.StartDate - 1) < weekEndings[i])) { if (resource.EndDate) { if (resource.EndDate >= weekEndings[i]) { resource.IsVisible = true; } } else { resource.IsVisible = true; } } if (UTCmilliseconds > weekEndings[i] + 86400000 - 1) // if today is later, than end of WeekEndingDate continue; var availableHours = this._getResourceAvailableHoursOnWeekEnding(resource, weekEndings[i]); var capacity = 0; var capacityArr = capacityValues || resource.TotalCapacity; if (capacityArr && capacityArr[weekEndings[i]]) { capacity = capacityArr[weekEndings[i]] || 0; } var availability = capacity > 0 ? Math.round(availableHours * 100 / capacity) : 0; if (resource.MinAvailability > availability) resource.MinAvailability = availability; if (resource.MaxAvailability < availability) resource.MaxAvailability = availability; resource.AvgAvailability += availability; weeks++; } resource.AvgAvailability = weeks > 0 ? Math.round(resource.AvgAvailability / weeks) : 0; }, recalculateResourceAvailability4Categories: function (expenditures, weekEndings, allExpenditures) { if (!expenditures) return; for (var expCatId in expenditures) { var category = expenditures[expCatId]; if (!category || !category.Teams) continue; for (var teamId in category.Teams) { var team = category.Teams[teamId]; if (!team || !team.AllResources) continue; for (var resourceId in team.AllResources) { var resource = this.extendResourceModelWithAttributes(team.AllResources[resourceId], teamId); this.recalculateResourceAvailability(resource, weekEndings, resource.CapacityQuantityValues); } } } }, getAssignableResourceOptionHtml: function (resource) { if (!resource) throw 'resource parameter for formatResource4SelectOption cannot be null'; var minAvailability = resource.minAvailability || 0; var maxAvailability = resource.maxAvailability || 0; var avgAvailability = resource.avgAvailability || 0; var optionHtml = ''; if (minAvailability == Number.MAX_VALUE || maxAvailability == -Number.MAX_VALUE) optionHtml = '
' + '
' + resource.name + '
' + '
'; else optionHtml = '
' + '
' + resource.name + '
' + '
' + '' + avgAvailability + '%' + '
' + '
' + '(' + minAvailability + '% - ' + '' + maxAvailability + '%)' + '
' + '
'; return optionHtml; }, getAssignableTeamOptionHtml: function (team) { if (!team) throw 'team parameter for formatTeam4SelectOption cannot be null'; var optionHtml = '
' + '
' + team.name + '
' + '
'; return optionHtml; }, extendResourceModelWithAttributes: function (resource, teamId) { return teamInfoService.extendResourceModelWithAttributes(resource, teamId); }, }; return service; }]); enVisageServices.factory('activityCalendarService', ['$q', '$http', '$filter', 'teamInfoService', 'dataSources', 'calculateDistributionService', 'roundService', function ($q, $http, $filter, teamInfoService, dataSources, calculateDistributionService, roundService) { var activityCalendarCache = {}; var WorkFlowActions = []; var availableExpendituresCache = {}; var activityCalendarSaveModelCache = {}; var acFilterOptions; var getCacheKey = function (filter, useClientSideFiltering) { if (!filter) { return null; } // unique filter combination contains the following fields: EntityType, EntityId, StartDate, EndDate // so we need to create unique key to store activity calendar data, because we need it to switch display modes: Group by teams, group by resources, group by projects var startDateMs = filter.StartDate ? (new Date(filter.StartDate)).getTime() : 0; var endDateMs = filter.EndDate ? (new Date(filter.EndDate)).getTime() : 0; var cacheKey = filter.EntityType + '-' + filter.EntityId + '-' + startDateMs + '-' + endDateMs; if (useClientSideFiltering) cacheKey = cacheKey + '#filtered'; return cacheKey; }; var service = { TeamType: { // team is already assigned for project Saved: 0, // team is new for project Pending: 1, // team is already assigned for project, but it assigned again SavedPending: 2, }, getActivityCalendar: function (url, filter, forceDataReload) { if (!filter) { throw 'Filter object is invalid'; } if (!filter.EntityId) { throw 'EntityId field is invalid'; } var loadDataFromServer = forceDataReload; var that = this; var sourceCacheKey = getCacheKey(filter, false); if (!forceDataReload) { if (sourceCacheKey in activityCalendarCache) { // Create prefiltered data set and cache it. Apply filtering to Resource part of the AC this.createActivityCalendarPreFilteredCache(filter); if (this.isClientSideFilteringUsed(filter)) { teamInfoService.applyFilter(this.availableExpendituresCache); } else { teamInfoService.resetFilter(); } var deferrer = $q.defer(); deferrer.resolve(activityCalendarCache[sourceCacheKey]); return deferrer.promise; } else { loadDataFromServer = true; } } if (loadDataFromServer) { console.debug("getActivityCalendar query"); // DEBUG console.time("getActivityCalendar"); // DEBUG var request = getAntiXSRFRequest(url, filter); var promise = $http(request) .then(function (response) { console.timeEnd("getActivityCalendar"); // DEBUG // we need teams only once to init teamInfoService, we do not need to store this collection in the cache activityCalendarCache[sourceCacheKey] = angular.extend({}, response.data, { CostCenters: null, Teams: null }); WorkFlowActions = response.data.WorkFlowActions; // We need to clear save model cache if calendar has been reloaded delete activityCalendarSaveModelCache[sourceCacheKey]; // Create prefiltered data set and cache it teamInfoService.init(response.data.Teams, response.data.NeedAllocations); that.createActivityCalendarPreFilteredCache(filter); if (that.isClientSideFilteringUsed(filter)) { teamInfoService.applyFilter(that.availableExpendituresCache); } else { teamInfoService.resetFilter(); } return response.data; }, function (response) { throw 'An error occurred while loading activity calendar /CapacityManagement/GetActivityCalendar'; }); } return promise; }, getActivityCalendarFromCache: function (filter, forDataChange) { if (!filter) { return null; } var result = null; var useFilteredCache = !forDataChange && this.isClientSideFilteringUsed(filter); var cacheKey = getCacheKey(filter, useFilteredCache); if (cacheKey in activityCalendarCache) { // Return source or pre-filtered data from cache according to forDataChange value result = activityCalendarCache[cacheKey]; } else { if (!useFilteredCache) { // Source calendar data not found in cache throw 'There is no loaded calendar with cacheKey = ' + cacheKey; } else { // Pre-filtered data copy not found in cache. Create this data and store in cache this.createActivityCalendarPreFilteredCache(filter); if (this.isClientSideFilteringUsed(filter)) teamInfoService.applyFilter(this.availableExpendituresCache); else teamInfoService.resetFilter(); if (!(cacheKey in activityCalendarCache)) { throw 'Error creating pre-filtered data cache'; } else { result = activityCalendarCache[cacheKey]; } } } return result; }, getActivityCalendarSaveModel: function (filter) { if (!filter) { return null; } var cacheKey = getCacheKey(filter, false); if (cacheKey in activityCalendarSaveModelCache) return activityCalendarSaveModelCache[cacheKey]; activityCalendarSaveModelCache[cacheKey] = { Scenarios: {}, NonProjectTime: {}, }; return activityCalendarSaveModelCache[cacheKey]; }, getResourceDetailsRights: function () { var request = getAntiXSRFRequest('/CapacityManagement/GetResourceDetailsRights'); var promise = $http(request) .then(function (response) { return response.data; }, function (response) { throw 'An error occurred while loading resource details rights /CapacityManagement/GetResourceDetailsRights'; }); return promise; }, getProjects: function (filter) { // Return data as is, because no filtering ever applied to .Project collection return (this.getActivityCalendarFromCache(filter, true) || {}).Projects; }, getScenarioToProjectMap: function (filter) { var calendar = this.getActivityCalendarFromCache(filter, true); if (!calendar || !calendar.Projects) { return null; } var map = {}; for (var projectId in calendar.Projects) { var project = calendar.Projects[projectId]; if (project && project.ActiveScenario && project.ActiveScenario.Id) { map[project.ActiveScenario.Id] = projectId; } } return map; }, getProjectById: function (filter, projectId, forDataChange) { if (!filter || !projectId) { return null; } // Return data as is, because no filtering ever applied to .Project collection var calendar = this.getActivityCalendarFromCache(filter, forDataChange); if (!calendar || !calendar.Projects) { return null; } return calendar.Projects[projectId]; }, getTeamsAvailable4Assign: function (filter, projectId, expenditureCategoryId) { var availableTeams = {}; if (!filter || !projectId || !expenditureCategoryId) { return availableTeams; } var project = this.getProjectById(filter, projectId); if (!project) { return availableTeams; } var teamsWithEC = teamInfoService.getTeamsByExpenditureCategoryId(expenditureCategoryId, true); if (!teamsWithEC || !Object.keys(teamsWithEC).length) { return availableTeams; } for (var teamId in teamsWithEC) { // skip teams already added in the No Team section if (project.Teams && (teamId in project.Teams) && (project.Teams[teamId].Type || this.TeamType.Saved) != this.TeamType.Saved) { continue; } var availableTeam = teamsWithEC[teamId]; if (availableTeam.AccessLevel !== 1) continue; var availableTeamModel = { Id: availableTeam.Id, Name: availableTeam.Name, }; availableTeams[teamId] = availableTeamModel; } return availableTeams; }, getTeamAllocations: function (filter, teamId, forDataChange) { if (!filter || !teamId) { return null; } var calendar = this.getActivityCalendarFromCache(filter, forDataChange) || {}; var teamAllocations = (calendar.TeamAllocations || {})[teamId]; return teamAllocations; }, getNeedAllocations4Scenario: function (filter, scenarioId) { if (!filter || !scenarioId) { return null; } var calendar = this.getActivityCalendarFromCache(filter, false) || {}; var needAllocations = (calendar.NeedAllocations || {})[scenarioId]; return (needAllocations || {}).Expenditures; }, getRestTeamAllocations4Scenario: function (filter, scenarioId) { if (!filter || !scenarioId) { return null; } var calendar = this.getActivityCalendarFromCache(filter, false) || {}; var restData = (calendar.RestData || {}); var restAllocations = (restData.TeamAllocations || {})[scenarioId]; return (restAllocations || {}).Expenditures; }, getRemainingNeedAllocations4Scenario: function (filter, visibleTeams, scenarioId, expenditureCategories, weekEndings) { if (!filter || !scenarioId) { return null; } var result = {}; var teamAllocations = this.getTeamAllocations4Scenario(filter, visibleTeams, scenarioId, expenditureCategories, weekEndings) || {}; var needAllocations = this.getNeedAllocations4Scenario(filter, scenarioId) || {}; var restAllocations = this.getRestTeamAllocations4Scenario(filter, scenarioId) || {}; var requiredECs = (expenditureCategories && expenditureCategories.length) ? expenditureCategories : Object.keys(needAllocations || {}); for (var ecIndex = 0; ecIndex < requiredECs.length; ecIndex++) { var expenditureCategoryId = requiredECs[ecIndex]; var needAllocations4EC = needAllocations[expenditureCategoryId] || {}; var teamAllocations4EC = teamAllocations[expenditureCategoryId] || {}; var restAllocations4EC = restAllocations[expenditureCategoryId] || {}; if (!needAllocations4EC.Allocations) { continue; } var weeks = (weekEndings && weekEndings.length) ? weekEndings : Object.keys(needAllocations4EC.Allocations); result[expenditureCategoryId] = { Allocations: {} }; for (var weekIndex = 0; weekIndex < weeks.length; weekIndex++) { var needAllocations4Week = (needAllocations4EC.Allocations || {})[weeks[weekIndex]] || 0; var teamAllocations4Week = (teamAllocations4EC.Allocations || {})[weeks[weekIndex]] || 0; var restAllocations4Week = (restAllocations4EC.Allocations || {})[weeks[weekIndex]] || 0; result[expenditureCategoryId].Allocations[weeks[weekIndex]] = roundService.roundQuantity(needAllocations4Week - teamAllocations4Week - restAllocations4Week); } } return result; }, getRemainingTeamAllocations4Scenario: function (filter, teamIds, projectId, scenarioId, expenditureCategories, weekEndings) { if (!filter || !scenarioId || !teamIds || !teamIds.length) { return null; } var scenarioAllocations = {}; for (var i = 0; i < teamIds.length; i++) { var teamKey = teamIds[i]; var teamAllocations = this.getTeamAllocations(filter, teamKey); if (!teamAllocations) { continue; } var scenario = (teamAllocations.Scenarios || {})[scenarioId]; if (!scenario || !scenario.Expenditures) { continue; } var requiredECs = (expenditureCategories && expenditureCategories.length) ? expenditureCategories : Object.keys(scenario.Expenditures); for (var ecIndex = 0; ecIndex < requiredECs.length; ecIndex++) { var expenditureCategoryId = requiredECs[ecIndex]; var ecExistsInSource = (scenario.Expenditures[expenditureCategoryId] && scenario.Expenditures[expenditureCategoryId].Allocations); if (!ecExistsInSource) { continue; } if (!(expenditureCategoryId in scenarioAllocations)) { scenarioAllocations[expenditureCategoryId] = { Allocations: {}, Teams: {} }; } var scenarioECTeamAllocations = angular.copy(scenario.Expenditures[expenditureCategoryId].Allocations || {}); var resources = this.getResourcesByProject(filter, projectId, expenditureCategoryId, [teamKey]); var resourceAllocations = {}; var remainingTeamAllocations = {}; for (var resKey in resources) { resourceAllocations[resKey] = this.getResourceAllocations4Scenario(filter, resources[resKey], scenarioId, teamKey, expenditureCategoryId); } var weeks = (weekEndings && weekEndings.length) ? weekEndings : Object.keys(scenarioECTeamAllocations); for (var weekIndex = 0; weekIndex < weeks.length; weekIndex++) { var teamAllocations4Week = scenarioECTeamAllocations[weeks[weekIndex]] || 0; var resAllocations4Week = 0; for (var resKey in resourceAllocations) { resAllocations4Week += resourceAllocations[resKey][weeks[weekIndex]] || 0; } remainingTeamAllocations[weeks[weekIndex]] = roundService.roundQuantity(teamAllocations4Week - resAllocations4Week); } scenarioAllocations[expenditureCategoryId].Teams[teamKey] = { Allocations: remainingTeamAllocations } scenarioAllocations[expenditureCategoryId].Allocations = calculateDistributionService.mergeAllocations(scenarioAllocations[expenditureCategoryId].Allocations, remainingTeamAllocations, weekEndings); } } return scenarioAllocations; }, getUnassignedNeedAllocations4Scenario: function (filter, scenarioId, forDataChange) { if (!filter || !scenarioId) { return null; } var result = null; var calendar = this.getActivityCalendarFromCache(filter, forDataChange) || {}; var needAllocations = (calendar.UnassignedNeed || {})[scenarioId]; result = needAllocations ? needAllocations.Expenditures : null; return result; }, getTeamAllocations4Scenario: function (filter, teamIds, scenarioId, expenditureCategories, weekEndings) { if (!filter || !scenarioId || !teamIds || !teamIds.length) { return null; } var scenarioAllocations = {}; for (var i = 0; i < teamIds.length; i++) { var teamAllocations = this.getTeamAllocations(filter, teamIds[i]); if (!teamAllocations) { continue; } var scenario = (teamAllocations.Scenarios || {})[scenarioId]; if (!scenario || !scenario.Expenditures) { continue; } var requiredECs = (expenditureCategories && angular.isArray(expenditureCategories)) ? expenditureCategories : Object.keys(scenario.Expenditures); for (var ecIndex = 0; ecIndex < requiredECs.length; ecIndex++) { var expenditureCategoryId = requiredECs[ecIndex]; var ecExistsInSource = (scenario.Expenditures[expenditureCategoryId] && scenario.Expenditures[expenditureCategoryId].Allocations); if (!ecExistsInSource) { continue; } if (!(expenditureCategoryId in scenarioAllocations)) { scenarioAllocations[expenditureCategoryId] = { Allocations: {}, Teams: {} }; } var scenarioECTeamAllocations = angular.copy(scenario.Expenditures[expenditureCategoryId].Allocations); scenarioAllocations[expenditureCategoryId].Teams[teamIds[i]] = { Allocations: scenarioECTeamAllocations } scenarioAllocations[expenditureCategoryId].Allocations = calculateDistributionService.mergeAllocations(scenarioAllocations[expenditureCategoryId].Allocations, scenarioECTeamAllocations, weekEndings); } } return scenarioAllocations; }, getECTeamAllocations: function (filter, teamId, expCatId, scenarioId, forDataChange) { if (!filter || !scenarioId || !teamId || !expCatId) { return null; } var teamAllocations = this.getTeamAllocations(filter, [teamId], forDataChange); if (!teamAllocations) { return null; } var scenario = teamAllocations.Scenarios[scenarioId]; if (!scenario || !scenario.Expenditures) { return null; } var ecTeamAllocations = scenario.Expenditures[expCatId]; if (!ecTeamAllocations) return null; return ecTeamAllocations.Allocations; }, getTeamAllocations4Scenario4Week: function (filter, scenarioId, teamIds, expenditureCategoryId, weekEndingMs, addAllocationsForProjectRoles) { if (!filter || !scenarioId || !teamIds || !teamIds.length || !weekEndingMs) { return 0; } var result = undefined; var expCats = expenditureCategoryId ? [expenditureCategoryId] : undefined; if (expCats && addAllocationsForProjectRoles) { // If method is called for specified EC, and it is necessary to add all scenario project roles in calculations var projectRoles = this.getProjectRolesInScenario(filter, scenarioId, teamIds); if (projectRoles && angular.isArray(projectRoles)) { for (var pIndex = 0; pIndex < projectRoles.length; pIndex++) { var projectRoleId = projectRoles[pIndex]; if (expCats.indexOf(projectRoleId) < 0) { expCats.push(projectRoleId); } } } } var scenarioTeamAllocations = this.getTeamAllocations4Scenario(filter, teamIds, scenarioId, expCats); if (!scenarioTeamAllocations) { return result; } if (!expCats || !angular.isArray(expCats)) expCats = Object.keys(scenarioTeamAllocations); for (var eIndex = 0; eIndex < expCats.length; eIndex++) { var expCatId = expCats[eIndex]; if (expCatId in scenarioTeamAllocations) { var category = scenarioTeamAllocations[expCatId]; if (category && category.Allocations && (weekEndingMs in category.Allocations)) { if (!angular.isNumber(result)) { result = category.Allocations[weekEndingMs]; } else { result += category.Allocations[weekEndingMs]; } } } } return result; }, getTeamAllocationsSummary4Week: function (filter, teamIds, expenditureCategoryId, weekEndingMs, addAllocationsForProjectRoles) { if (!filter || !teamIds || !teamIds.length) { return null; } var calendar = this.getActivityCalendarFromCache(filter, false); if (!calendar) return null; var result = {}; for (var index = 0; index < teamIds.length; index++) { result[teamIds[index]] = {}; } if (!calendar.TeamAllocations) return result; for (var tIndex = 0; tIndex < teamIds.length; tIndex++) { var teamId = teamIds[tIndex]; if (teamId in calendar.TeamAllocations) { var teamAllocations = calendar.TeamAllocations[teamId]; if (teamAllocations && teamAllocations.Scenarios) { for (var scenarioId in teamAllocations.Scenarios) { var scenarioTeamAllocations = teamAllocations.Scenarios[scenarioId]; if (scenarioTeamAllocations && scenarioTeamAllocations.Expenditures) { var expCats = null; if (expenditureCategoryId) { if (addAllocationsForProjectRoles) { // Create list of ECs to take in calculations: specified EC & all project roles in scenario expCats = this.getProjectRolesInScenario(filter, scenarioId, [teamId]); if (!expCats || !angular.isArray(expCats)) { expCats = []; } expCats.push(expenditureCategoryId); } } else { // We'll perform calculations for all ECs in scenario, since EC was not specified in method parameters expCats = Object.keys(scenarioTeamAllocations.Expenditures); } for (var eIndex = 0; eIndex < expCats.length; eIndex++) { var expCatId = expCats[eIndex]; var expCatAllocations = scenarioTeamAllocations.Expenditures[expCatId]; if (expCatAllocations && expCatAllocations.Allocations) { var weeks = weekEndingMs && angular.isArray(weekEndingMs) ? weekEndingMs : Object.keys(expCatAllocations.Allocations); for (var wIndex = 0; wIndex < weeks.length; wIndex++) { var we = Number(weeks[wIndex]); if (we in expCatAllocations.Allocations) { var value = expCatAllocations.Allocations[we]; if (angular.isNumber(value) && !isNaN(value)) { if (!(we in result[teamIds])) { result[teamIds][we] = 0; } result[teamIds][we] += Number(value); } } } } } } } } } } return result; }, getNeed4WeekEnding: function (filter, scenarioId, expenditureCategoryId, weekEndingMs) { if (!filter || !scenarioId || !expenditureCategoryId || !weekEndingMs) { return 0; } var scenarioNeed = this.getNeedAllocations4Scenario(filter, scenarioId); if (!scenarioNeed) { return 0; } var category = scenarioNeed[expenditureCategoryId]; if (!category || !category.Allocations) { return undefined; } return category.Allocations[weekEndingMs]; }, getResourceAllocations: function (filter, resourceId, forDataChange) { if (!filter || !resourceId) { return null; } var calendar = this.getActivityCalendarFromCache(filter, forDataChange) || {}; var resourceAllocations = (calendar.ResourceAllocations || {})[resourceId]; return resourceAllocations; }, getResourceActuals: function (filter, resourceId, forDataChange) { if (!filter || !resourceId) { return null; } var calendar = this.getActivityCalendarFromCache(filter, forDataChange) || {}; var resourceActuals = (calendar.ResourceActuals || {})[resourceId]; return resourceActuals; }, getResourceAllocationsExpenditure: function (filter, resourceId, scenarioId, teamId, expenditureCategoryId, forDataChange) { if (!filter || !resourceId || !scenarioId || !teamId || !expenditureCategoryId) { return null; } var allocations = this.getResourceAllocations(filter, resourceId, forDataChange); if (!allocations || !allocations.Scenarios) { return null; } var allocationsByScenario = allocations.Scenarios[scenarioId]; if (!allocationsByScenario || !allocationsByScenario.Teams) { return null; } var allocationsByTeam = allocationsByScenario.Teams[teamId]; if (!allocationsByTeam || !allocationsByTeam.Expenditures) { return null; } return allocationsByTeam.Expenditures[expenditureCategoryId]; }, getTotalResourcesAllocations4WeekEnding: function (filter, resources, scenarioId, teamId, expenditureCategoryId, weekEndingMs) { if (!filter || !resources || !resources.length || !scenarioId || !teamId || !expenditureCategoryId || !weekEndingMs) { return 0; } var result = 0; for (var i = 0; i < resources.length; i++) { var category = this.getResourceAllocationsExpenditure(filter, resources[i], scenarioId, teamId, expenditureCategoryId) || {}; var allocations = category.Allocations || {}; result += (allocations[weekEndingMs] || 0); } return result; }, getResourceActualsExpenditure: function (filter, resourceId, scenarioId, teamId, expenditureCategoryId, forDataChange) { if (!filter || !resourceId || !scenarioId || !teamId || !expenditureCategoryId) { return null; } var actuals = this.getResourceActuals(filter, resourceId, forDataChange); if (!actuals || !actuals.Scenarios) { return null; } var allocationsByScenario = actuals.Scenarios[scenarioId]; if (!allocationsByScenario || !allocationsByScenario.Teams) { return null; } var actualsByTeam = allocationsByScenario.Teams[teamId]; if (!actualsByTeam || !actualsByTeam.Expenditures) { return null; } return actualsByTeam.Expenditures[expenditureCategoryId]; }, getResourceAllocations4Scenario: function (filter, resourceId, scenarioId, teamId, expenditureCategoryId, forDataChange) { if (!filter || !resourceId || !scenarioId || !teamId || !expenditureCategoryId) { return null; } var allocationsByEC = this.getResourceAllocationsExpenditure(filter, resourceId, scenarioId, teamId, expenditureCategoryId, forDataChange); if (!allocationsByEC) { return null; } return allocationsByEC.Allocations; }, getResourceActuals4Scenario: function (filter, resourceId, scenarioId, teamId, expenditureCategoryId, forDataChange) { if (!filter || !resourceId || !scenarioId || !teamId || !expenditureCategoryId) { return null; } var actualsByEC = this.getResourceActualsExpenditure(filter, resourceId, scenarioId, teamId, expenditureCategoryId, forDataChange); if (!actualsByEC) { return null; } return actualsByEC.Allocations; }, getResourcesByProject: function (filter, projectId, expenditureCategoryId, teamIds) { var resources = []; if (!filter || !projectId || !expenditureCategoryId || !teamIds || !teamIds.length) { return resources; } var project = this.getProjectById(filter, projectId); if (!project) { console.error('There is no project with id = ' + projectId); return resources; } if (!project.Teams) { return resources; } for (var i = 0; i < teamIds.length; i++) { var processingTeamId = teamIds[i]; if (processingTeamId) { var teamInProject = project.Teams[processingTeamId]; if (teamInProject.Resources) { resources = union(resources, teamInProject.Resources[expenditureCategoryId]); } } } return resources; }, getExpendituresExistInPendingTeams: function (filter, projectId) { if (!filter || !projectId) return; var result = {}; var project = this.getProjectById(filter, projectId); if (project && project.Teams) { for (var teamId in project.Teams) { var team = project.Teams[teamId]; if (team && ((team.Type == this.TeamType.Pending) || (team.Type == this.TeamType.SavedPending)) && team.Expenditures2Display) { for (var expCatId in team.Expenditures2Display) { if (!(expCatId in result)) { result[expCatId] = true; } } } } } return result; }, getNonProjectTimeResourceAllocations: function (nptItemId, weekEndingMs) { if (!nptItemId || !weekEndingMs) return null; var nptByResources = teamInfoService.getNonProjectTimeItemsById(nptItemId); if (!nptByResources) return null; var result = {}; for (var resourceId in nptByResources) { var nptItem = nptByResources[resourceId]; if (nptItem && nptItem.Allocations && (String(weekEndingMs) in nptItem.Allocations)) result[resourceId] = nptItem.Allocations[String(weekEndingMs)]; }; return result; }, getTeamSaveModel: function (filter, scenarioId, expenditureCategoryId, teamId) { if (!filter || !scenarioId || !expenditureCategoryId || !teamId) { return; } var model = this.getActivityCalendarSaveModel(filter); if (!(scenarioId in model.Scenarios)) { model.Scenarios[scenarioId] = { Expenditures: {} }; } var scenario = model.Scenarios[scenarioId]; if (!(expenditureCategoryId in scenario.Expenditures)) { scenario.Expenditures[expenditureCategoryId] = { Teams: {} }; } var category = scenario.Expenditures[expenditureCategoryId]; if (!(teamId in category.Teams)) { category.Teams[teamId] = { Resources: {} }; } return category.Teams[teamId]; }, getResourceSaveModel: function (filter, scenarioId, expenditureCategoryId, teamId, resourceId) { if (!filter || !scenarioId || !expenditureCategoryId || !teamId || !resourceId) { return; } var teamSaveModel = this.getTeamSaveModel(filter, scenarioId, expenditureCategoryId, teamId); if (!teamSaveModel) { throw 'teamSaveModel is null for scenarioId = ' + scenarioId + ', expenditureCategoryId = ' + expenditureCategoryId + ', teamId = ' + teamId; } if (!(resourceId in teamSaveModel.Resources)) { teamSaveModel.Resources[resourceId] = { IsRemoved: false, ForecastAllocations: {}, ActualAllocations: {}, }; } return teamSaveModel.Resources[resourceId]; }, getNonProjectTimeSaveModel: function (filter, nptItemId) { if (!filter || !nptItemId) { return; } var model = this.getActivityCalendarSaveModel(filter); if (!(nptItemId in model.NonProjectTime)) { model.NonProjectTime[nptItemId] = { Resources: {}, TeamWideNptAllocations: {} }; } var nptItemSaveModel = model.NonProjectTime[nptItemId]; return nptItemSaveModel; }, changeScenarioUnassignedNeedValue: function (filter, scenarioId, expCatId, weekEndingMs, deltaValue) { if (!filter || !scenarioId || !expCatId || !weekEndingMs || !deltaValue) return; var expCatAllocations = this.getUnassignedNeedAllocations4Scenario(filter, scenarioId, true); if (expCatAllocations && (expCatId in expCatAllocations)) { var expCatAllocations = expCatAllocations[expCatId]; if (expCatAllocations && expCatAllocations.Allocations && (weekEndingMs in expCatAllocations.Allocations)) { expCatAllocations.Allocations[weekEndingMs] = expCatAllocations.Allocations[weekEndingMs] - deltaValue; } } }, checkResourceIsAssignedToProject: function (filter, projectId, expenditureCategoryId, teamIds, resourceId) { if (!filter || !projectId || !expenditureCategoryId || !teamIds || !teamIds.length || !resourceId) { throw 'Some parameteres are invalid'; } var project = this.getProjectById(filter, projectId); if (!project) { throw 'Project with id = ' + projectId + ' does not exist'; } if (!project.Teams) { return false; } var isAssigned = false; for (var i = 0; i < teamIds.length; i++) { var teamId = teamIds[i]; if (!teamId) { continue; } var teamInProject = project.Teams[teamId]; if (!teamInProject || !teamInProject.Resources) { continue; } isAssigned = !!teamInProject.Resources[expenditureCategoryId] && teamInProject.Resources[expenditureCategoryId].indexOf(resourceId) >= 0; if (isAssigned) { break; } } return isAssigned; }, assignResource: function (filter, projectId, expenditureCategoryId, teamId, resourceId) { if (!filter || !projectId || !expenditureCategoryId || !teamId || !resourceId) { return; } var project = this.getProjectById(filter, projectId); if (!project) { console.error('There is no project with id = ' + projectId); return; } if (!project.ActiveScenario) { console.error('There is no active scenario for project with id = ' + projectId); return; } this.assignResourceOnProject(filter, projectId, expenditureCategoryId, teamId, resourceId); this.createResourceAllocations(filter, project.ActiveScenario.Id, expenditureCategoryId, teamId, resourceId); }, assignResourceOnProject: function (filter, projectId, expenditureCategoryId, teamId, resourceId) { if (!filter || !projectId || !expenditureCategoryId || !teamId || !resourceId) { return; } var project = this.getProjectById(filter, projectId, true); if (!project) { console.error('There is no project with id = ' + projectId); return; } if (!project.Teams) { project.Teams = {}; } if (!project.Teams[teamId]) { project.Teams[teamId] = {}; } var teamInProject = project.Teams[teamId]; if (!teamInProject.Resources) { teamInProject.Resources = {}; } if (!teamInProject.Resources[expenditureCategoryId]) { teamInProject.Resources[expenditureCategoryId] = []; } if (teamInProject.Resources[expenditureCategoryId].indexOf(resourceId) < 0) { teamInProject.Resources[expenditureCategoryId].push(resourceId); } }, createResourceAllocations: function (filter, scenarioId, expenditureCategoryId, teamId, resourceId) { if (!filter || !scenarioId || !expenditureCategoryId || !teamId || !resourceId) { return; } var calendar = this.getActivityCalendarFromCache(filter, true); if (!calendar) { return; } if (!calendar.ResourceAllocations) { calendar.ResourceAllocations = {}; } if (!calendar.ResourceAllocations[resourceId]) { calendar.ResourceAllocations[resourceId] = {}; } var resource = calendar.ResourceAllocations[resourceId]; if (!resource.Scenarios) { resource.Scenarios = {}; } if (!resource.Scenarios[scenarioId]) { resource.Scenarios[scenarioId] = {}; } var scenario = resource.Scenarios[scenarioId]; if (!scenario.Teams) { scenario.Teams = {}; } if (!scenario.Teams[teamId]) { scenario.Teams[teamId] = {}; } var team = scenario.Teams[teamId]; if (!team.Expenditures) { team.Expenditures = {}; } if (!team.Expenditures[expenditureCategoryId]) { team.Expenditures[expenditureCategoryId] = {}; } var ec = team.Expenditures[expenditureCategoryId]; if (!ec.Allocations) { ec.Allocations = {}; } }, removeResource: function (filter, projectId, expenditureCategoryId, teamId, resourceId) { if (!filter || !projectId || !expenditureCategoryId || !teamId || !resourceId) { return; } var project = this.getProjectById(filter, projectId); if (!project) { console.error('There is no project with id = ' + projectId); return; } if (!project.ActiveScenario) { console.error('There is no active scenario for project with id = ' + projectId); return; } this.removeResourceFromProject(filter, projectId, expenditureCategoryId, teamId, resourceId); this.removeResourceAllocations(filter, project.ActiveScenario.Id, expenditureCategoryId, teamId, resourceId); this.markResourceAsRemovedInSaveModel(filter, project.ActiveScenario.Id, expenditureCategoryId, teamId, resourceId); }, removeResourceFromProject: function (filter, projectId, expenditureCategoryId, teamId, resourceId) { if (!filter || !projectId || !expenditureCategoryId || !teamId || !resourceId) { return; } var project = this.getProjectById(filter, projectId, true); if (!project || !project.Teams) { return; } var teamInProject = project.Teams[teamId]; if (!teamInProject || !teamInProject.Resources) { return; } var resources = teamInProject.Resources[expenditureCategoryId]; if (resources && resources.length) { var resourceIndex = resources.indexOf(resourceId); if (resourceIndex >= 0) { resources.splice(resourceIndex, 1); } } }, removeResourceAllocations: function (filter, scenarioId, expenditureCategoryId, teamId, resourceId) { if (!filter || !scenarioId || !expenditureCategoryId || !teamId || !resourceId) { return; } var allocationsByEC = this.getResourceAllocationsExpenditure(filter, resourceId, scenarioId, teamId, expenditureCategoryId, true); if (allocationsByEC) { allocationsByEC.Allocations = {}; } }, changeResourceValue: function (filter, projectId, expenditureCategoryId, teamId, resourceId, weekEndingMs, value, isActuals) { if (!filter || !projectId || !expenditureCategoryId || !teamId || !resourceId || !weekEndingMs) { return; } value = parseFloat(value) || 0; if (value < 0) { throw 'Value must be a positive number'; } var project = this.getProjectById(filter, projectId); if (!project || !project.ActiveScenario) { throw 'There is no project with id = ' + projectId; } var allocations = null; if (!isActuals) { // Update for forecast value allocations = this.getResourceAllocations4Scenario(filter, resourceId, project.ActiveScenario.Id, teamId, expenditureCategoryId, true); if (!allocations) { // for the following case: // resource is related to a few teams that work on some project // resource has allocations only for one of them // user selects "Group by Project" mode and change some value for the resource // as a total range by all teams is available we have no inited allocations and assignments for this resource // so we need to assign resource this.assignResource(filter, projectId, expenditureCategoryId, teamId, resourceId); allocations = this.getResourceAllocations4Scenario(filter, resourceId, project.ActiveScenario.Id, teamId, expenditureCategoryId, true); if (!allocations) { throw 'Error in assign resource logic has been occurred'; } } } else { // Update for actual value allocations = this.getResourceActuals4Scenario(filter, resourceId, project.ActiveScenario.Id, teamId, expenditureCategoryId, true); if (!allocations) { // for the following case: // resource is related to a few teams that work on some project // resource has allocations only for one of them // user selects "Group by Project" mode and change some value for the resource // as a total range by all teams is available we have no inited allocations and assignments for this resource // so we need to assign resource this.createResourceActuals4Scenario(filter, expenditureCategoryId, teamId, project.ActiveScenario.Id, resourceId); allocations = this.getResourceActuals4Scenario(filter, resourceId, project.ActiveScenario.Id, teamId, expenditureCategoryId, true); if (!allocations) { throw 'Error in creating resource actuals logic has been occurred'; } } } var oldValue = allocations[weekEndingMs] || 0; var deltaValue = value - oldValue; allocations[weekEndingMs] = value; if (!isActuals) { teamInfoService.changeResourceValue(teamId, expenditureCategoryId, resourceId, weekEndingMs, deltaValue, true); this.changeScenarioUnassignedNeedValue(filter, project.ActiveScenario.Id, expenditureCategoryId, weekEndingMs, deltaValue); } this.changeResourceValueInSaveModel(filter, project.ActiveScenario.Id, expenditureCategoryId, teamId, resourceId, weekEndingMs, value, isActuals); }, changeResourceValueInSaveModel: function (filter, scenarioId, expenditureCategoryId, teamId, resourceId, weekEndingMs, value, isActual) { if (!filter || !scenarioId || !expenditureCategoryId || !teamId || !resourceId || !weekEndingMs) { return; } value = parseFloat(value) || 0; if (value < 0) { throw 'Value must be a positive number'; } var resource = this.getResourceSaveModel(filter, scenarioId, expenditureCategoryId, teamId, resourceId); if (!resource) { throw 'Save model for resource ' + resourceId + ' does not exist'; } // as we edit resource it is not deleted // example: user has deleted resource (IsRemoved = true) and then assign resource back again (we need set IsRemoved flag to false) resource.IsRemoved = false; if (isActual) { resource.ActualAllocations[weekEndingMs] = value; } else { resource.ForecastAllocations[weekEndingMs] = value; } }, assignTeam: function (filter, projectId, teamId, extendedModel) { if (!filter || !projectId || !teamId) { return; } var project = this.getProjectById(filter, projectId, true); if (!project) { console.error('assignTeam: there is no project with id = ' + projectId); return; } if (!project.ActiveScenario || !project.ActiveScenario.Id) { console.error('assignTeam: there is no active scenario for project with id = ' + projectId); return; } if (!project.Teams) { project.Teams = {}; } if (!project.Teams[teamId]) { project.Teams[teamId] = angular.extend({}, { Type: this.TeamType.Pending, Resources: {}, }, extendedModel); } else { project.Teams[teamId] = angular.extend(project.Teams[teamId], { Type: this.TeamType.SavedPending }, extendedModel); // if team is already assigned we should keep original team allocations to have ability to restore them if team is removed from sandbox if (extendedModel.Expenditures2Display) { var affectedECs = Object.keys(extendedModel.Expenditures2Display); for (var i = 0; i < affectedECs.length; i++) { this.createTeamAllocationsBackup(filter, project.ActiveScenario.Id, teamId, affectedECs[i]); } } } }, createTeamAllocations: function (filter, projectId, teamId, weekEndings) { if (!filter || !projectId || !teamId || !weekEndings || !weekEndings.length) { return; } var calendar = this.getActivityCalendarFromCache(filter, true) || {}; var project = this.getProjectById(filter, projectId); if (!calendar || !project || !project.ActiveScenario) { return; } var scenarioId = project.ActiveScenario.Id; var needAllocations = this.getNeedAllocations4Scenario(filter, scenarioId); var team = teamInfoService.getById(teamId); if (!needAllocations || !team) { return; } var categoriesInScenario = Object.keys(needAllocations); var categoriesInTeam = Object.keys(team.ExpCategories || {}); if (!(teamId in calendar.TeamAllocations)) { calendar.TeamAllocations[teamId] = { Scenarios: {} }; } if (!(scenarioId in calendar.TeamAllocations[teamId].Scenarios)) { calendar.TeamAllocations[teamId].Scenarios[scenarioId] = { Expenditures: {} }; } var teamAllocations4Scenario = calendar.TeamAllocations[teamId].Scenarios[scenarioId]; for (var i = 0; i < categoriesInScenario.length; i++) { var expenditureCategoryId = categoriesInScenario[i]; var isSuperEC = dataSources.isSuperEC(expenditureCategoryId); if (isSuperEC || categoriesInTeam.indexOf(expenditureCategoryId) >= 0) { // create new expenditure under team only if didn't create yet if (!teamAllocations4Scenario.Expenditures[expenditureCategoryId]) { teamAllocations4Scenario.Expenditures[expenditureCategoryId] = { Allocations: {} }; } for (var weekIndex = 0; weekIndex < weekEndings.length; weekIndex++) { // fill week with zero only if value does not exist if (!teamAllocations4Scenario.Expenditures[expenditureCategoryId].Allocations[weekEndings[weekIndex]]) { teamAllocations4Scenario.Expenditures[expenditureCategoryId].Allocations[weekEndings[weekIndex]] = 0; this.changeTeamValueInSaveModel(filter, scenarioId, expenditureCategoryId, teamId, weekEndings[weekIndex], 0); } } } } }, changeTeamValue: function (filter, projectId, expenditureCategoryId, teamId, weekEndingMs, value) { if (!filter || !projectId || !expenditureCategoryId || !teamId || !weekEndingMs) { return; } value = parseFloat(value) || 0; if (value < 0) { throw 'Value must be non-negative number'; } var project = this.getProjectById(filter, projectId); if (!project || !project.ActiveScenario) { throw 'There is no project with id = ' + projectId; } var allocations = this.getECTeamAllocations(filter, teamId, expenditureCategoryId, project.ActiveScenario.Id, true); if (!allocations) { var teamModel = { Id: teamId }; this.assignTeam(filter, projectId, expenditureCategoryId, teamId, teamModel); allocations = this.getECTeamAllocations(filter, teamId, expenditureCategoryId, project.ActiveScenario.Id, true); if (!allocations) { throw 'Error in team assign logic has been occurred'; } } var oldValue = allocations[weekEndingMs] || 0; var deltaValue = value - oldValue; allocations[weekEndingMs] = value; teamInfoService.changeTeamValue(teamId, expenditureCategoryId, weekEndingMs, deltaValue, true); this.changeTeamValueInSaveModel(filter, project.ActiveScenario.Id, expenditureCategoryId, teamId, weekEndingMs, value); }, changeTeamValueInSaveModel: function (filter, scenarioId, expenditureCategoryId, teamId, weekEndingMs, value) { if (!filter || !scenarioId || !expenditureCategoryId || !teamId || !weekEndingMs) { return; } value = parseFloat(value) || 0; if (value < 0) { throw 'Value must be non-negative number'; } var teamModel = this.getTeamSaveModel(filter, scenarioId, expenditureCategoryId, teamId); if (!teamModel) { throw 'Save model for team ' + teamId + ' does not exist'; } if (!teamModel.Allocations) { teamModel.Allocations = {}; } teamModel.Allocations[weekEndingMs] = value; }, removeTeam: function (filter, projectId, teamId) { if (!filter || !projectId || !teamId) { return; } var project = this.getProjectById(filter, projectId, true); if (!project) { console.error('There is no project with id = ' + projectId); return; } if (!project.Teams || !(teamId in project.Teams)) { console.error('There is no team with id = ' + teamId + ' for project with id = ' + projectId); return; } if (!project.ActiveScenario) { console.error('There is no active scenario for project with id = ' + projectId); return; } var projectTeam = project.Teams[teamId]; var projectTeamType = (projectTeam.Type || this.TeamType.Saved); switch (projectTeamType) { case this.TeamType.Pending: // if it is newly assigned team we should delete it at all this.removeTeamFromProject(filter, projectId, teamId); this.removeTeamAllocations(filter, project.ActiveScenario.Id, teamId); break; case this.TeamType.SavedPending: // if it is already assigned team we should rollback changes and delete created backup projectTeam.Type = this.TeamType.Saved; projectTeam.Expenditures2Display = null; this.removeTeamAllocationsBackup(filter, project.ActiveScenario.Id, teamId); break; } this.removeTeamChangesFromSaveModel(filter, project.ActiveScenario.Id, teamId); }, removeTeamFromProject: function (filter, projectId, teamId) { if (!filter || !projectId || !teamId) { return; } var project = this.getProjectById(filter, projectId, true); if (!project || !project.Teams) { return; } if (project.Teams[teamId]) { delete project.Teams[teamId]; } }, removeTeamAllocations: function (filter, scenarioId, teamId, expCatId) { if (!filter || !scenarioId || !teamId) { return; } var allTeamAllocations = this.getTeamAllocations(filter, teamId, true); if (allTeamAllocations.Scenarios && (scenarioId in allTeamAllocations.Scenarios)) { var scenario = allTeamAllocations.Scenarios[scenarioId]; if (scenario.Expenditures) { var expCatsToReview = expCatId ? [expCatId] : Object.keys(scenario.Expenditures); for (var index = 0; index < expCatsToReview.length; index++) { var currentExpCatId = expCatsToReview[index]; if (currentExpCatId in scenario.Expenditures) { delete scenario.Expenditures[expCatId]; } } if (Object.keys(scenario.Expenditures).length < 1) { delete allTeamAllocations.Scenarios[scenarioId]; } } } }, removeTeamChangesFromSaveModel: function (filter, scenarioId, teamId, expCatId) { if (!filter || !scenarioId || !teamId) { return; } var model = this.getActivityCalendarSaveModel(filter); if (scenarioId in model.Scenarios) { var scenarioSaveModel = model.Scenarios[scenarioId]; if (scenarioSaveModel.Expenditures) { var expCatsToReview = expCatId ? [expCatId] : Object.keys(scenarioSaveModel.Expenditures); for (var index = 0; index < expCatsToReview.length; index++) { var currentExpCatId = expCatsToReview[index]; if (currentExpCatId in scenarioSaveModel.Expenditures) { var expCatSaveModel = scenarioSaveModel.Expenditures[currentExpCatId]; if (teamId in expCatSaveModel.Teams) { delete expCatSaveModel.Teams[teamId]; } if (Object.keys(expCatSaveModel.Teams).length < 1) { delete scenarioSaveModel.Expenditures[currentExpCatId]; } } } } if (Object.keys(scenarioSaveModel.Expenditures).length < 1) { delete model.Scenarios[scenarioId]; } } }, changeNptResourceValueInSaveModel: function (filter, nptItemId, resourceId, weekEndingMs, value) { if (!filter || !nptItemId || !resourceId || !weekEndingMs) { return; } value = parseFloat(value) || 0; if (value < 0) { throw 'Value must be a positive number'; } var nptSaveModel = this.getNonProjectTimeSaveModel(filter, nptItemId); if (!nptSaveModel) { throw 'Save model for non-project time does not exist'; } if (!(resourceId in nptSaveModel.Resources)) { nptSaveModel.Resources[resourceId] = { Allocations: {} }; } var nptResourceAllocations = nptSaveModel.Resources[resourceId].Allocations; nptResourceAllocations[String(weekEndingMs)] = value; }, changeTeamWideNptValueInSaveModel: function (filter, nptItemId, weekEndingMs, value) { if (!filter || !nptItemId || !weekEndingMs) { return; } value = parseFloat(value) || 0; if (value < 0) { throw 'Value must be a positive number'; } var nptSaveModel = this.getNonProjectTimeSaveModel(filter, nptItemId); if (!nptSaveModel) { throw 'Save model for non-project time does not exist'; } if (!nptSaveModel.TeamWideNptAllocations) { nptSaveModel.TeamWideNptAllocations = {}; } nptSaveModel.TeamWideNptAllocations[String(weekEndingMs)] = value; }, markResourceAsRemovedInSaveModel: function (filter, scenarioId, expenditureCategoryId, teamId, resourceId) { if (!filter || !scenarioId || !expenditureCategoryId || !teamId || !resourceId) { return; } var resource = this.getResourceSaveModel(filter, scenarioId, expenditureCategoryId, teamId, resourceId); if (!resource) { throw 'Save model for resource ' + resourceId + ' does not exist'; } resource.IsRemoved = true; resource.ForecastAllocations = {}; resource.ActualAllocations = {}; }, changeNptResourceValue: function (filter, nptItemId, resourceId, expenditureCategoryId, weekEndingMs, value) { if (!filter || !nptItemId || !resourceId || !weekEndingMs) { return; } value = parseFloat(value) || 0; if (value < 0) { throw 'Value must be a positive number'; } if (teamInfoService.setNonProjectTimeResourceValue(nptItemId, resourceId, expenditureCategoryId, weekEndingMs, value)) { this.changeNptResourceValueInSaveModel(filter, nptItemId, resourceId, weekEndingMs, value); } else { throw "Unable to update resource's non-project time allocation: resource or allocation not found"; } }, changeTeamWideNptValue: function (filter, nptItemId, resourceId, expenditureCategoryId, weekEndingMs, value) { if (!filter || !nptItemId || !resourceId || !weekEndingMs) { return; } value = parseFloat(value) || 0; if (value < 0) { throw 'Value must be a positive number'; } if (teamInfoService.setNonProjectTimeResourceValue(nptItemId, resourceId, expenditureCategoryId, weekEndingMs, value)) { this.changeTeamWideNptValueInSaveModel(filter, nptItemId, weekEndingMs, value); } else { throw "Unable to update team-wide non-project time allocation: allocation not found"; } }, loadACFilterOptionsAsync: function (url, data, forceLoadFromServer) { if (acFilterOptions && !forceLoadFromServer) { var deferrer = $q.defer(); deferrer.resolve(acFilterOptions); return deferrer.promise; } var request = getAntiXSRFRequest(url, data); var promise = $http(request). then(function (response) { acFilterOptions = response.data; return response.data; }, function (response) { // throw exception through the pipe throw 'An error occurred while loading calendar filter options'; }); return promise; }, createResourceActuals4Scenario: function (filter, expenditureCategoryId, teamId, scenarioId, resourceId) { if (!filter || !resourceId || !scenarioId || !teamId || !expenditureCategoryId) { return; } var calendar = this.getActivityCalendarFromCache(filter); if (!calendar) throw "Unable to create resource actuals: Calendar data model is empty"; if (!calendar.ResourceActuals) calendar.ResourceActuals = {}; if (!(resourceId in calendar.ResourceActuals)) calendar.ResourceActuals[resourceId] = {}; var resourceData = calendar.ResourceActuals[resourceId]; if (!resourceData.Scenarios) resourceData.Scenarios = {}; if (!(scenarioId in resourceData.Scenarios)) resourceData.Scenarios[scenarioId] = {}; var scenarioData = resourceData.Scenarios[scenarioId]; if (!scenarioData.Teams) scenarioData.Teams = {}; if (!(teamId in scenarioData.Teams)) scenarioData.Teams[teamId] = {}; var teamData = scenarioData.Teams[teamId]; if (!teamData.Expenditures) teamData.Expenditures = {}; if (!(expenditureCategoryId in teamData.Expenditures)) teamData.Expenditures[expenditureCategoryId] = {}; var expData = teamData.Expenditures[expenditureCategoryId]; if (!expData.Allocations) expData.Allocations = {}; }, saveChanges: function (model) { if (!model) { throw 'Data model for save is invalid'; } var request = getAntiXSRFRequest('/CapacityManagement/SaveChanges', model); var promise = $http(request). then(function (response) { return true; }, function (response) { // throw exception through the pipe throw 'An error occurred while saving calendar data changes'; }); return promise; }, cacheKeysEqual: function (filter1, filter2) { var cacheKey1 = getCacheKey(filter1, false); var cacheKey2 = getCacheKey(filter2, false); return cacheKey1 === cacheKey2; }, invalidateCache: function (filter) { if (!filter) { return; } var cacheKey = getCacheKey(filter, false); if (!cacheKey) { return; } this.invalidatePreFilteredCache(filter); delete activityCalendarCache[cacheKey]; delete activityCalendarSaveModelCache[cacheKey]; }, isDataChanged: function (filter) { if (!filter) { return false; } var cacheKey = getCacheKey(filter, false); if (!cacheKey) { return false; } var i = 0; var len = WorkFlowActions.length; for (; i < len; i++) { if (WorkFlowActions[i].HasChanges) return true; } var cache = activityCalendarSaveModelCache[cacheKey]; var changed = false; if (cache && ((!!cache.NonProjectTime && !!Object.keys(cache.NonProjectTime).length) || (!!cache.Scenarios && !!Object.keys(cache.Scenarios).length))) { changed = true; } return changed; }, getProjectsFromSaveModel: function (filter) { var calendar = this.getActivityCalendarFromCache(filter); if (!calendar || !calendar.Projects) { throw 'Overallocation cannot be checked, because there is no data in the cache'; } var saveModel = this.getActivityCalendarSaveModel(filter); if (!saveModel || !saveModel.Scenarios || !Object.keys(saveModel.Scenarios).length) { return false; } var getResourcesChangedWeekEndingsFn = function (resources) { var result = []; if (!resources) { return result; } for (var resourceId in resources) { var resource = resources[resourceId]; if (!resource || !resource.ForecastAllocations) { continue; } var resourceWeekEndings = Object.keys(resource.ForecastAllocations); result = union(result, resourceWeekEndings); } return result; }; var getTeamsChangedWeekEndingsFn = function (teams) { var result = []; if (!teams) { return result; } for (var teamId in teams) { var team = teams[teamId]; if (!team || !team.Allocations) { continue; } var teamWeekEndings = Object.keys(team.Allocations); result = union(result, teamWeekEndings); } return result; }; var result = []; for (var projectId in calendar.Projects) { var project = calendar.Projects[projectId]; if (!project || !project.ActiveScenario) { continue; } var scenarioId = project.ActiveScenario.Id; var scenarioInSaveModel = saveModel.Scenarios[scenarioId]; if (!scenarioInSaveModel || !scenarioInSaveModel.Expenditures) { continue; } var projResultItem = { ScenarioId: scenarioId, Name: project.Name, IsBottomUp: project.ActiveScenario.IsBottomUp, IsOverallocated: false }; var projectIsOverallocated = false; for (var expenditureCategoryId in scenarioInSaveModel.Expenditures) { // it does not make sense to check super EC for overallocation, because this kind of ECs always rollup from resource allocations var isSuperEC = dataSources.isSuperEC(expenditureCategoryId); if (isSuperEC) { continue; } var category = scenarioInSaveModel.Expenditures[expenditureCategoryId]; if (!category || !category.Teams) { continue; } for (var teamId in category.Teams) { var team = category.Teams[teamId]; var resourcesInProject = this.getResourcesByProject(filter, projectId, expenditureCategoryId, [teamId]); // Check for resources var changedWeekEndings = getResourcesChangedWeekEndingsFn(team.Resources); if (changedWeekEndings && changedWeekEndings.length) { for (var weekIndex = 0; weekIndex < changedWeekEndings.length; weekIndex++) { var weekEndingMs = changedWeekEndings[weekIndex]; var teamNeed = this.getTeamAllocations4Scenario4Week(filter, scenarioId, [teamId], expenditureCategoryId, weekEndingMs); var resourceAllocation = this.getTotalResourcesAllocations4WeekEnding(filter, resourcesInProject, scenarioId, teamId, expenditureCategoryId, weekEndingMs); if (roundService.roundQuantity(teamNeed || 0) < roundService.roundQuantity(resourceAllocation || 0)) { projectIsOverallocated = true; break; } } } if (projectIsOverallocated) break; } if (projectIsOverallocated) break; // Check for teams var changedWeekEndings = getTeamsChangedWeekEndingsFn(category.Teams); if (changedWeekEndings && changedWeekEndings.length) { var teamIds = Object.keys(category.Teams); for (var weekIndex = 0; weekIndex < changedWeekEndings.length; weekIndex++) { var weekEndingMs = changedWeekEndings[weekIndex]; var expCatNeed = this.getNeed4WeekEnding(filter, scenarioId, expenditureCategoryId, weekEndingMs); var allocatedToTeams = this.getTeamAllocations4Scenario4Week(filter, scenarioId, teamIds, expenditureCategoryId, weekEndingMs); if (roundService.roundQuantity(expCatNeed || 0) < roundService.roundQuantity(allocatedToTeams || 0)) { projectIsOverallocated = true; break; } } } if (projectIsOverallocated) break; } projResultItem.IsOverallocated = projectIsOverallocated; result.push(projResultItem); } return result; }, getChangedProjectsWithSuperECs: function (filter) { var projects = []; if (!this.isDataChanged(filter)) { return projects; } var calendar = this.getActivityCalendarFromCache(filter); var saveModel = this.getActivityCalendarSaveModel(filter); if (!calendar || !saveModel || !calendar.Projects) { return projects; } // it is pretty fast to go through all projects and check them in the save model as we call it only once for (var projectId in calendar.Projects) { var project = calendar.Projects[projectId]; if (!project || !project.ActiveScenario || !(project.ActiveScenario.Id in saveModel.Scenarios)) { continue; } var scenarioInSaveModel = saveModel.Scenarios[project.ActiveScenario.Id]; if (!scenarioInSaveModel || !scenarioInSaveModel.Expenditures) { continue; } for (var expenditureCategoryId in scenarioInSaveModel.Expenditures) { if (dataSources.isSuperEC(expenditureCategoryId)) { projects.push({ ProjectId: projectId, ProjectName: project.Name }); break; } } } return projects; }, getResourcesWithAssignedProjects: function (filter, resources) { var calendar = this.getActivityCalendarFromCache(filter, false); if (!calendar || !calendar.Projects || !calendar.ResourceAllocations) { return null; } var requestedResources = (resources && resources.length) ? resources : teamInfoService.getAllResources(); if (!requestedResources || !requestedResources.length) { return null; } var map = this.getScenarioToProjectMap(filter); var result = {}; for (var resourceIndex = 0; resourceIndex < requestedResources.length; resourceIndex++) { var resourceId = requestedResources[resourceIndex]; var resourceAllocations = calendar.ResourceAllocations[resourceId]; var resourceActuals = calendar.ResourceActuals ? calendar.ResourceActuals[resourceId] : null; var resourceHasNptAllocations = teamInfoService.hasResourceNonProjectTimeAllocated(resourceId); if ((!resourceAllocations || !resourceAllocations.Scenarios) && !resourceHasNptAllocations) { continue; } // Get resource info to fill Name and EC data var resourceInfo = dataSources.getResourceById(resourceId); var resourceModel = { Id: resourceId, Name: (resourceInfo && resourceInfo.FirstName && resourceInfo.LastName) ? resourceInfo.FirstName + ' ' + resourceInfo.LastName : null, OwnExpenditureCategoryId: resourceInfo ? resourceInfo.ExpenditureCategoryId : null, Projects: {}, }; if (resourceAllocations && resourceAllocations.Scenarios) { for (var scenarioId in resourceAllocations.Scenarios) { var projectId = map[scenarioId]; var project = calendar.Projects[projectId]; if (!project || !project.ActiveScenario || !project.Teams) { continue; } var projectModel = { ProjectId: project.ProjectId, PartId: project.PartId, ParentProjectId: project.ParentProjectId, ParentName: project.ParentName, ExpenditureCategoryId: [], Name: project.Name, Color: project.Color, TypeName: project.TypeName, DeadlineMs: project.DeadlineMs, Priority: project.Priority, ReadOnly: project.ReadOnly, ActiveScenario: { Id: project.ActiveScenario.Id, Name: project.ActiveScenario.Name, IsBottomUp: project.ActiveScenario.IsBottomUp, StartDate: project.ActiveScenario.StartDate, EndDate: project.ActiveScenario.EndDate }, InactiveScenarios: project.InactiveScenarios ? angular.copy(project.InactiveScenarios) : [], IsMultipleECs: false, Teams: [], Allocations: {}, Actuals: {}, }; var scenario = resourceAllocations.Scenarios[scenarioId]; if (scenario && scenario.Teams) { var targetTeamIds = Object.keys(project.Teams); for (var teamIndex = 0; teamIndex < targetTeamIds.length; teamIndex++) { var teamId = targetTeamIds[teamIndex]; var resourceItem = teamInfoService.getResourceByTeam(teamId, null, resourceId); if (resourceItem) { var filteredTeams = !!resourceItem.Teams ? $filter('filter')(resourceItem.Teams, { TeamId: teamId }) : []; projectModel.Teams.push({ TeamId: teamId, ReadOnly: filteredTeams.length > 0 ? filteredTeams[0].ReadOnly : false, StartDate: resourceItem.StartDate, EndDate: resourceItem.EndDate }); var teamInScenario = scenario.Teams[teamId]; if (teamInScenario && teamInScenario.Expenditures) { for (var expenditureCategoryId in teamInScenario.Expenditures) { var category = teamInScenario.Expenditures[expenditureCategoryId]; if (category && category.Allocations) { projectModel.Allocations = calculateDistributionService.mergeAllocations(projectModel.Allocations, category.Allocations); if (projectModel.ExpenditureCategoryId.indexOf(expenditureCategoryId) < 0) { projectModel.ExpenditureCategoryId.push(expenditureCategoryId); } } if (resourceActuals && resourceActuals.Scenarios && resourceActuals.Scenarios[scenarioId] && resourceActuals.Scenarios[scenarioId].Teams && resourceActuals.Scenarios[scenarioId].Teams[teamId] && resourceActuals.Scenarios[scenarioId].Teams[teamId].Expenditures && resourceActuals.Scenarios[scenarioId].Teams[teamId].Expenditures[expenditureCategoryId] && resourceActuals.Scenarios[scenarioId].Teams[teamId].Expenditures[expenditureCategoryId].Allocations) { var actuals = resourceActuals.Scenarios[scenarioId].Teams[teamId].Expenditures[expenditureCategoryId].Allocations; projectModel.Actuals = calculateDistributionService.mergeAllocations(projectModel.Actuals, actuals); } } } } } } projectModel.IsMultipleECs = projectModel.ExpenditureCategoryId.length > 1; resourceModel.Projects[projectId] = projectModel; } } if ((resourceModel.Projects && Object.keys(resourceModel.Projects).length > 0) || resourceHasNptAllocations) { result[resourceId] = resourceModel; } } return result; }, isResourceOverAllocated: function (resourceId, weekEndingMs) { if (!resourceId) { throw 'resourceId is null or undefined'; } var capacity = roundService.roundQuantity(teamInfoService.getResourceCapacity(resourceId, weekEndingMs)); var allocated = roundService.roundQuantity(teamInfoService.getResourceSummaryAllocatedHrs(resourceId, weekEndingMs, true)); var isOverAllocated = allocated > capacity; return isOverAllocated; }, getProjectRolesInScenario: function (filter, scenarioId, teamIds) { if (!filter || !scenarioId || !teamIds || !angular.isArray(teamIds)) return; var result = {}; for (var tIndex = 0; tIndex < teamIds.length; tIndex++) { var teamId = teamIds[tIndex]; var teamAllocations = this.getTeamAllocations(filter, teamId); if (teamAllocations && teamAllocations.Scenarios && (scenarioId in teamAllocations.Scenarios)) { var scenarioData = teamAllocations.Scenarios[scenarioId]; if (scenarioData && scenarioData.Expenditures) { for (var expCatId in scenarioData.Expenditures) { if (!(expCatId in result) && (dataSources.isSuperEC(expCatId))) { result[expCatId] = true; } } } } } return Object.keys(result); }, getWorkFlowActions: function () { return WorkFlowActions; }, createTeamAllocationsBackup: function (filter, scenarioId, teamId, expenditureCategoryId) { if (!filter || !scenarioId || !teamId || !expenditureCategoryId) { return; } var calendar = this.getActivityCalendarFromCache(filter, true) || {}; if (!calendar.TeamAllocations || !(teamId in calendar.TeamAllocations)) { return; } var taInScenario = (calendar.TeamAllocations[teamId].Scenarios || {})[scenarioId] || {}; if (!taInScenario.Expenditures || !(expenditureCategoryId in taInScenario.Expenditures)) { return; } if (!calendar.BackupTeamAllocations) { calendar.BackupTeamAllocations = {}; } if (!(teamId in calendar.BackupTeamAllocations)) { calendar.BackupTeamAllocations[teamId] = { Scenarios: {} }; } var teamBackup = calendar.BackupTeamAllocations[teamId]; if (!(scenarioId in teamBackup.Scenarios)) { teamBackup.Scenarios[scenarioId] = { Expenditures: {} }; } teamBackup.Scenarios[scenarioId].Expenditures[expenditureCategoryId] = angular.copy(taInScenario.Expenditures[expenditureCategoryId]); }, getTeamAllocationsBackup: function (filter, scenarioId, teamId, expenditureCategoryId) { if (!filter || !scenarioId || !teamId || !expenditureCategoryId) { return null; } var calendar = this.getActivityCalendarFromCache(filter, true) || {}; if (!calendar.BackupTeamAllocations) { return null; } if (!(teamId in calendar.BackupTeamAllocations)) { return null; } var teamBackup = calendar.BackupTeamAllocations[teamId]; if (!(scenarioId in teamBackup.Scenarios)) { return null; } var scenarioBackup = teamBackup.Scenarios[scenarioId]; if (!(expenditureCategoryId in scenarioBackup.Expenditures)) { return null; } return scenarioBackup.Expenditures[expenditureCategoryId].Allocations; }, removeTeamAllocationsBackup: function (filter, scenarioId, teamId, expenditureCategoryId) { if (!filter || !scenarioId || !teamId) { return; } var calendar = this.getActivityCalendarFromCache(filter) || {}; if (!calendar.BackupTeamAllocations) { return; } if (!(teamId in calendar.BackupTeamAllocations)) { return; } var teamBackup = calendar.BackupTeamAllocations[teamId]; if (!(scenarioId in teamBackup.Scenarios)) { return; } var scenarioBackup = teamBackup.Scenarios[scenarioId]; var requiredECs = expenditureCategoryId ? [expenditureCategoryId] : Object.keys(scenarioBackup.Expenditures); for (var i = 0; i < requiredECs.length; i++) { delete scenarioBackup.Expenditures[requiredECs[i]]; } if (Object.keys(scenarioBackup.Expenditures).length <= 0) { delete teamBackup.Scenarios[scenarioId]; } if (Object.keys(teamBackup.Scenarios).length <= 0) { delete calendar.BackupTeamAllocations[teamId]; } }, filterCategoriesVsTeams: function (expCats, teams) { if (!expCats || !expCats.length) return expCats; if (!teams || !teams.length) return []; var resultExpCatsIndexed = {}; for (var tIndex = 0; tIndex < teams.length; tIndex++) { var teamExpCatsFiltered = teamInfoService.getExpenditureCategoriesInTeam(teams[tIndex], expCats); if (teamExpCatsFiltered && angular.isArray(teamExpCatsFiltered)) { for (var eIndex = 0; eIndex < teamExpCatsFiltered.length; eIndex++) { var expCatId = teamExpCatsFiltered[eIndex]; if (!(expCatId in resultExpCatsIndexed)) { resultExpCatsIndexed[expCatId] = true; } } } } var result = Object.keys(resultExpCatsIndexed); return result; }, isClientSideFilteringUsed: function (filter) { if (!filter) return; // Data is always filtered by project roles return true; }, convertToDictionary: function (arrayOfScalarItems) { if (!arrayOfScalarItems || !angular.isArray(arrayOfScalarItems)) return null; var result = {} var itemsCount = arrayOfScalarItems.length; for (var index = 0; index < itemsCount; index++) { var value = arrayOfScalarItems[index]; if (!(value in result)) { result[value] = true; } } return result; }, createAvailableExpendituresCache: function (filter) { if (!this.isClientSideFilteringUsed(filter)) return; this.availableExpendituresCache = {}; var costCentersIndexed = null; var projectRolesIndexed = {}; if (filter.CostCenters && angular.isArray(filter.CostCenters) && filter.CostCenters.length) { // Reorganise Cost Centers list to check fast an EC fits selected cost centers costCentersIndexed = this.convertToDictionary(filter.CostCenters); } if (filter.ProjectRoles && angular.isArray(filter.ProjectRoles) && filter.ProjectRoles.length) { // Reorganise selected Project Roles to perform fast checks projectRolesIndexed = this.convertToDictionary(filter.ProjectRoles); } // Create cached list of expenditures, that fit selected cost centers, for fast filtering AC cached data var expCats = dataSources.getExpenditures(); if (expCats) { for (var expCatId in expCats) { var expCatItem = expCats[expCatId]; var isProjectRole = !expCatItem.AllowResourceAssignment; var selectedByUserInFilter = expCatId in projectRolesIndexed; var hasNonZeroAllocations = false; if (expCatItem != null) { // Check the EC fits filter by Cost Centers (if no any Cost Center selected, EC fits filter) var fitsFilter = !costCentersIndexed || (expCatItem.CreditId && (expCatItem.CreditId in costCentersIndexed)); if (fitsFilter && isProjectRole) { // If EC is a Project Role, check it has non-zero team allocations hasNonZeroAllocations = teamInfoService.expenditureCategoryHasNonZeroTeamAllocations(expCatId); fitsFilter = hasNonZeroAllocations; if (!fitsFilter) { // Project Role has no team allocations, check if it selected in the Project Roles filter fitsFilter = selectedByUserInFilter; } } if (fitsFilter) { // EC fits filter. This data struct is used in getAlwaysVisibleProjectRolesByClientSideFilter method this.availableExpendituresCache[expCatId] = { IsProjectRole: isProjectRole, HasNonZeroAllocations: hasNonZeroAllocations, SelectedByUser: selectedByUserInFilter }; } } } } }, getAlwaysVisibleProjectRolesByClientSideFilter: function () { var result = []; if (this.availableExpendituresCache) { // Client-side filtering is used for (var expCatId in this.availableExpendituresCache) { var expCatInfo = this.availableExpendituresCache[expCatId]; if (expCatInfo && expCatInfo.IsProjectRole && expCatInfo.SelectedByUser) { // EC is Project Role and should be always visible (inspite of even zero allocations), // because it was selected by user in client-side filter of Project Roles result.push(expCatId); } } } return result; }, createActivityCalendarPreFilteredCache: function (filter) { if (!filter) throw "createActivityCalendarPreFilteredCache: incoming filter not specified"; console.time("createActivityCalendarPreFilteredCache"); if (!this.isClientSideFilteringUsed(filter)) { // Remove filtered cache, if exists this.invalidatePreFilteredCache(filter, false); return; } // We need to create fast-access expenditures list, that fit selected Cost Centers // And create prefiltered calendar data cache. // sourceCacheKey equals prefilteredCacheKey, when slient-side filters are empty (no items selected) this.createAvailableExpendituresCache(filter); // Create prefiltered data var calendarSourceData = this.getActivityCalendarFromCache(filter, true); var filtered = angular.copy(calendarSourceData); this.filterCalendarNeedAllocations(filtered.NeedAllocations); this.filterCalendarResourceAllocations(filtered.ResourceAllocations); this.filterCalendarTeamAllocations(filtered.TeamAllocations, filtered.RestData); this.filterCalendarUnassignedNeed(filtered.UnassignedNeed); if (filtered.BackupTeamAllocations) { delete filtered.BackupTeamAllocations; } var prefilteredCacheKey = getCacheKey(filter, true); activityCalendarCache[prefilteredCacheKey] = filtered; console.timeEnd("createActivityCalendarPreFilteredCache"); }, invalidatePreFilteredCache: function (filter, forceRecreate) { var prefilteredCacheKey = getCacheKey(filter, true); if (prefilteredCacheKey in activityCalendarCache) { delete activityCalendarCache[prefilteredCacheKey]; } teamInfoService.resetFilter(); if (forceRecreate) { // Get activity calendar data from cache to force recreate it, if prefiltered cached used // in current display&filter mode. Trying to get prefiltered cache, which is not currently // exist, will force instant recreating it. getActivityCalendarFromCache(filter, false); } }, filterCalendarNeedAllocations: function (needAllocations) { if (!needAllocations) return; var scenarioIds = Object.keys(needAllocations); var scenariosCount = scenarioIds.length; for (var sIndex = 0; sIndex < scenariosCount; sIndex++) { var scenarioId = scenarioIds[sIndex]; var scenarioData = needAllocations[scenarioId]; if (scenarioData && scenarioData.Expenditures) { var expCatIds = Object.keys(scenarioData.Expenditures); var expCatsCount = expCatIds.length; for (var eIndex = 0; eIndex < expCatsCount; eIndex++) { var expCatId = expCatIds[eIndex]; if (!(expCatId in this.availableExpendituresCache)) { // Remove EC, because it is not fit available expenditures list delete needAllocations[scenarioId].Expenditures[expCatId]; } } if (!Object.keys(scenarioData.Expenditures).length) { // Remove scenario at all, because it has no longer expenditures, that fit filter delete needAllocations[scenarioId]; } } } }, filterCalendarResourceAllocations: function (resourceAllocations) { if (!resourceAllocations) return; var resourceIds = Object.keys(resourceAllocations); var resourcesCount = resourceIds.length; for (var rIndex = 0; rIndex < resourcesCount; rIndex++) { var resourceId = resourceIds[rIndex]; var resourceData = resourceAllocations[resourceId]; if (resourceData && resourceData.Scenarios) { var scenarioIds = Object.keys(resourceData.Scenarios); var scenariosCount = scenarioIds.length; for (var sIndex = 0; sIndex < scenariosCount; sIndex++) { var scenarioId = scenarioIds[sIndex]; var scenarioData = resourceData.Scenarios[scenarioId]; if (scenarioData && scenarioData.Teams) { var teamIds = Object.keys(scenarioData.Teams); var teamsCount = teamIds.length; for (var tIndex = 0; tIndex < teamsCount; tIndex++) { var teamId = teamIds[tIndex]; var teamData = scenarioData.Teams[teamId]; if (teamData && teamData.Expenditures) { var expCatIds = Object.keys(teamData.Expenditures); var expCatsCount = expCatIds.length; for (var eIndex = 0; eIndex < expCatsCount; eIndex++) { var expCatId = expCatIds[eIndex]; if (!(expCatId in this.availableExpendituresCache)) { // Remove EC, because it is not fit available expenditures list delete resourceAllocations[resourceId].Scenarios[scenarioId].Teams[teamId].Expenditures[expCatId]; } } if (!Object.keys(teamData.Expenditures).length) { delete resourceAllocations[resourceId].Scenarios[scenarioId].Teams[teamId]; } } } if (!Object.keys(scenarioData.Teams).length) { delete resourceAllocations[resourceId].Scenarios[scenarioId]; } } } if (!Object.keys(resourceData.Scenarios).length) { delete resourceAllocations[resourceId]; } } } }, filterCalendarTeamAllocations: function (teamAllocations) { if (!teamAllocations) return; var teamIds = Object.keys(teamAllocations); var teamsCount = teamIds.length; for (var tIndex = 0; tIndex < teamsCount; tIndex++) { var teamId = teamIds[tIndex]; var teamData = teamAllocations[teamId]; if (teamData && teamData.Scenarios) { var scenarioIds = Object.keys(teamData.Scenarios); var scenariosCount = scenarioIds.length; for (var sIndex = 0; sIndex < scenariosCount; sIndex++) { var scenarioId = scenarioIds[sIndex]; var scenarioData = teamData.Scenarios[scenarioId]; if (scenarioData && scenarioData.Expenditures) { var expCatIds = Object.keys(scenarioData.Expenditures); var expCatsCount = expCatIds.length; for (var eIndex = 0; eIndex < expCatsCount; eIndex++) { var expCatId = expCatIds[eIndex]; if (!(expCatId in this.availableExpendituresCache)) { // Remove EC, because it is not fit available expenditures list delete teamAllocations[teamId].Scenarios[scenarioId].Expenditures[expCatId]; } } if (!Object.keys(scenarioData.Expenditures).length) { delete teamAllocations[teamId].Scenarios[scenarioId]; } } } if (!Object.keys(teamData.Scenarios).length) { delete teamAllocations[teamId]; } } } }, filterCalendarUnassignedNeed: function (unassignedNeed) { if (!unassignedNeed) return; var scenarioIds = Object.keys(unassignedNeed); var scenariosCount = scenarioIds.length; for (var sIndex = 0; sIndex < scenariosCount; sIndex++) { var scenarioId = scenarioIds[sIndex]; var scenarioData = unassignedNeed[scenarioId]; if (scenarioData && scenarioData.Expenditures) { var expCatIds = Object.keys(scenarioData.Expenditures); var expCatsCount = expCatIds.length; for (var eIndex = 0; eIndex < expCatsCount; eIndex++) { var expCatId = expCatIds[eIndex]; if (!(expCatId in this.availableExpendituresCache)) { // Remove EC, because it is not fit available expenditures list delete unassignedNeed[scenarioId].Expenditures[expCatId]; } } if (!Object.keys(scenarioData.Expenditures).length) { // Remove scenario at all, because it has no longer expenditures, that fit filter delete unassignedNeed[scenarioId]; } } } }, }; return service; }]); enVisageServices.factory('noteService', ['$q', 'dataSources', function ($q, dataSources) { var NoteTypeScenario = 2; var NoteTypeProject = 1; var NoteTypeExpenditureCat = 3; var serviceContainer = { getNotesForScenario: function (ScenarioId) { return dataSources.loadNotes(null, ScenarioId, NoteTypeScenario); }, getNotesForProject: function (ProjectId) { return dataSources.loadNotes(null, ProjectId, NoteTypeProject); }, getNotesForEC: function (ScenarioId, ExpenditureCategoryId) { return dataSources.loadNotes(ScenarioId, ExpenditureCategoryId, NoteTypeExpenditureCat); }, getNote: function (NoteId) { return dataSources.LoadNote(NoteId); }, EditNote: function (note) { return dataSources.EditNote(note); }, RemoveNote: function (NoteId) { return dataSources.RemoveNote(NoteId); }, CacheData: function (Note) { return dataSources.CacheNote(Note); }, LoadFromCache: function (ParentId) { return dataSources.LoadFromCache(ParentId); }, ClearCache: function () { return dataSources.ClearCache(); }, RemoveFromCache: function (note) { return dataSources.RemoveFromCache(note); } } return serviceContainer; }]); enVisageServices.filter('sortObjectsBy', ['$filter', function ($filter) { return function (items, field, reverse) { var filtered = []; angular.forEach(items, function (item) { filtered.push(item); }); return $filter('orderBy')(filtered, field, reverse); }; }]);