'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 = '