5662 lines
185 KiB
JavaScript
5662 lines
185 KiB
JavaScript
'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 = '<div class="row">' +
|
|
'<div class="col-sm-12">' + resource.name + '</div>' +
|
|
'</div>';
|
|
else
|
|
optionHtml = '<div class="row">' +
|
|
'<div class="col-sm-5">' + resource.name + '</div>' +
|
|
'<div class="col-sm-2">' +
|
|
'<span ' + (avgAvailability < 0 ? 'style="color: red;"' : '') + '>' + avgAvailability + '%</span>' +
|
|
'</div>' +
|
|
'<div class="col-sm-5">' +
|
|
'(<span ' + (minAvailability < 0 ? 'style="color: red;"' : '') + '>' + minAvailability + '%</span> - ' +
|
|
'<span ' + (maxAvailability < 0 ? 'style="color: red;"' : '') + '>' + maxAvailability + '%</span>)' +
|
|
'</div>' +
|
|
'</div>';
|
|
|
|
return optionHtml;
|
|
},
|
|
getAssignableTeamOptionHtml: function (team) {
|
|
if (!team)
|
|
throw 'team parameter for formatTeam4SelectOption cannot be null';
|
|
|
|
|
|
var optionHtml = '<div class="row">' +
|
|
'<div class="col-sm-12">' + team.name + '</div>' +
|
|
'</div>';
|
|
|
|
return optionHtml;
|
|
},
|
|
extendResourceModelWithAttributes: function (resource, teamId) {
|
|
return teamInfoService.extendResourceModelWithAttributes(resource, teamId);
|
|
},
|
|
};
|
|
return service;
|
|
}]);
|
|
enVisageServices.factory('activityCalendarService', ['$q', '$http', '$filter', 'teamInfoService', 'dataSources', 'calculateDistributionService', 'roundService', function ($q, $http, $filter, teamInfoService, dataSources, calculateDistributionService, roundService) {
|
|
var activityCalendarCache = {};
|
|
var WorkFlowActions = [];
|
|
var availableExpendituresCache = {};
|
|
var activityCalendarSaveModelCache = {};
|
|
var acFilterOptions;
|
|
var getCacheKey = function (filter, useClientSideFiltering) {
|
|
if (!filter) {
|
|
return null;
|
|
}
|
|
|
|
// unique filter combination contains the following fields: EntityType, EntityId, StartDate, EndDate
|
|
// so we need to create unique key to store activity calendar data, because we need it to switch display modes: Group by teams, group by resources, group by projects
|
|
var startDateMs = filter.StartDate ? (new Date(filter.StartDate)).getTime() : 0;
|
|
var endDateMs = filter.EndDate ? (new Date(filter.EndDate)).getTime() : 0;
|
|
var cacheKey = filter.EntityType + '-' + filter.EntityId + '-' + startDateMs + '-' + endDateMs;
|
|
|
|
if (useClientSideFiltering)
|
|
cacheKey = cacheKey + '#filtered';
|
|
|
|
return cacheKey;
|
|
};
|
|
var service = {
|
|
TeamType: {
|
|
// team is already assigned for project
|
|
Saved: 0,
|
|
// team is new for project
|
|
Pending: 1,
|
|
// team is already assigned for project, but it assigned again
|
|
SavedPending: 2,
|
|
},
|
|
getActivityCalendar: function (url, filter, forceDataReload) {
|
|
if (!filter) {
|
|
throw 'Filter object is invalid';
|
|
}
|
|
|
|
if (!filter.EntityId) {
|
|
throw 'EntityId field is invalid';
|
|
}
|
|
|
|
var loadDataFromServer = forceDataReload;
|
|
var that = this;
|
|
var sourceCacheKey = getCacheKey(filter, false);
|
|
|
|
if (!forceDataReload) {
|
|
if (sourceCacheKey in activityCalendarCache) {
|
|
// Create prefiltered data set and cache it. Apply filtering to Resource part of the AC
|
|
this.createActivityCalendarPreFilteredCache(filter);
|
|
|
|
if (this.isClientSideFilteringUsed(filter)) {
|
|
teamInfoService.applyFilter(this.availableExpendituresCache);
|
|
}
|
|
else {
|
|
teamInfoService.resetFilter();
|
|
}
|
|
|
|
var deferrer = $q.defer();
|
|
deferrer.resolve(activityCalendarCache[sourceCacheKey]);
|
|
return deferrer.promise;
|
|
}
|
|
else {
|
|
loadDataFromServer = true;
|
|
}
|
|
}
|
|
|
|
if (loadDataFromServer) {
|
|
console.debug("getActivityCalendar query"); // DEBUG
|
|
console.time("getActivityCalendar"); // DEBUG
|
|
var request = getAntiXSRFRequest(url, filter);
|
|
var promise = $http(request)
|
|
.then(function (response) {
|
|
console.timeEnd("getActivityCalendar"); // DEBUG
|
|
// we need teams only once to init teamInfoService, we do not need to store this collection in the cache
|
|
activityCalendarCache[sourceCacheKey] = angular.extend({}, response.data, { CostCenters: null, Teams: null });
|
|
WorkFlowActions = response.data.WorkFlowActions;
|
|
|
|
// We need to clear save model cache if calendar has been reloaded
|
|
delete activityCalendarSaveModelCache[sourceCacheKey];
|
|
|
|
// Create prefiltered data set and cache it
|
|
teamInfoService.init(response.data.Teams, response.data.NeedAllocations);
|
|
that.createActivityCalendarPreFilteredCache(filter);
|
|
|
|
if (that.isClientSideFilteringUsed(filter)) {
|
|
teamInfoService.applyFilter(that.availableExpendituresCache);
|
|
}
|
|
else {
|
|
teamInfoService.resetFilter();
|
|
}
|
|
|
|
return response.data;
|
|
}, function (response) {
|
|
throw 'An error occurred while loading activity calendar /CapacityManagement/GetActivityCalendar';
|
|
});
|
|
}
|
|
|
|
return promise;
|
|
},
|
|
getActivityCalendarFromCache: function (filter, forDataChange) {
|
|
if (!filter) {
|
|
return null;
|
|
}
|
|
|
|
var result = null;
|
|
var useFilteredCache = !forDataChange && this.isClientSideFilteringUsed(filter);
|
|
var cacheKey = getCacheKey(filter, useFilteredCache);
|
|
|
|
if (cacheKey in activityCalendarCache) {
|
|
// Return source or pre-filtered data from cache according to forDataChange value
|
|
result = activityCalendarCache[cacheKey];
|
|
}
|
|
else {
|
|
if (!useFilteredCache) {
|
|
// Source calendar data not found in cache
|
|
throw 'There is no loaded calendar with cacheKey = ' + cacheKey;
|
|
}
|
|
else {
|
|
// Pre-filtered data copy not found in cache. Create this data and store in cache
|
|
this.createActivityCalendarPreFilteredCache(filter);
|
|
|
|
if (this.isClientSideFilteringUsed(filter))
|
|
teamInfoService.applyFilter(this.availableExpendituresCache);
|
|
else
|
|
teamInfoService.resetFilter();
|
|
|
|
if (!(cacheKey in activityCalendarCache)) {
|
|
throw 'Error creating pre-filtered data cache';
|
|
}
|
|
else {
|
|
result = activityCalendarCache[cacheKey];
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
return result;
|
|
},
|
|
getActivityCalendarSaveModel: function (filter) {
|
|
if (!filter) {
|
|
return null;
|
|
}
|
|
|
|
var cacheKey = getCacheKey(filter, false);
|
|
if (cacheKey in activityCalendarSaveModelCache)
|
|
return activityCalendarSaveModelCache[cacheKey];
|
|
|
|
activityCalendarSaveModelCache[cacheKey] = {
|
|
Scenarios: {},
|
|
NonProjectTime: {},
|
|
};
|
|
|
|
return activityCalendarSaveModelCache[cacheKey];
|
|
},
|
|
getResourceDetailsRights: function () {
|
|
var request = getAntiXSRFRequest('/CapacityManagement/GetResourceDetailsRights');
|
|
var promise = $http(request)
|
|
.then(function (response) {
|
|
return response.data;
|
|
}, function (response) {
|
|
throw 'An error occurred while loading resource details rights /CapacityManagement/GetResourceDetailsRights';
|
|
});
|
|
|
|
return promise;
|
|
},
|
|
getProjects: function (filter) {
|
|
// Return data as is, because no filtering ever applied to .Project collection
|
|
return (this.getActivityCalendarFromCache(filter, true) || {}).Projects;
|
|
},
|
|
getScenarioToProjectMap: function (filter) {
|
|
var calendar = this.getActivityCalendarFromCache(filter, true);
|
|
if (!calendar || !calendar.Projects) {
|
|
return null;
|
|
}
|
|
|
|
var map = {};
|
|
for (var projectId in calendar.Projects) {
|
|
var project = calendar.Projects[projectId];
|
|
if (project && project.ActiveScenario && project.ActiveScenario.Id) {
|
|
map[project.ActiveScenario.Id] = projectId;
|
|
}
|
|
}
|
|
|
|
return map;
|
|
},
|
|
getProjectById: function (filter, projectId, forDataChange) {
|
|
if (!filter || !projectId) {
|
|
return null;
|
|
}
|
|
|
|
// Return data as is, because no filtering ever applied to .Project collection
|
|
var calendar = this.getActivityCalendarFromCache(filter, forDataChange);
|
|
if (!calendar || !calendar.Projects) {
|
|
return null;
|
|
}
|
|
|
|
return calendar.Projects[projectId];
|
|
},
|
|
getTeamsAvailable4Assign: function (filter, projectId, expenditureCategoryId) {
|
|
var availableTeams = {};
|
|
if (!filter || !projectId || !expenditureCategoryId) {
|
|
return availableTeams;
|
|
}
|
|
|
|
var project = this.getProjectById(filter, projectId);
|
|
if (!project) {
|
|
return availableTeams;
|
|
}
|
|
|
|
var teamsWithEC = teamInfoService.getTeamsByExpenditureCategoryId(expenditureCategoryId, true);
|
|
if (!teamsWithEC || !Object.keys(teamsWithEC).length) {
|
|
return availableTeams;
|
|
}
|
|
|
|
for (var teamId in teamsWithEC) {
|
|
// skip teams already added in the No Team section
|
|
if (project.Teams && (teamId in project.Teams) && (project.Teams[teamId].Type || this.TeamType.Saved) != this.TeamType.Saved) {
|
|
continue;
|
|
}
|
|
|
|
var availableTeam = teamsWithEC[teamId];
|
|
if (availableTeam.AccessLevel !== 1)
|
|
continue;
|
|
var availableTeamModel = {
|
|
Id: availableTeam.Id,
|
|
Name: availableTeam.Name,
|
|
};
|
|
|
|
availableTeams[teamId] = availableTeamModel;
|
|
}
|
|
|
|
return availableTeams;
|
|
},
|
|
getTeamAllocations: function (filter, teamId, forDataChange) {
|
|
if (!filter || !teamId) {
|
|
return null;
|
|
}
|
|
|
|
var calendar = this.getActivityCalendarFromCache(filter, forDataChange) || {};
|
|
var teamAllocations = (calendar.TeamAllocations || {})[teamId];
|
|
|
|
return teamAllocations;
|
|
},
|
|
getNeedAllocations4Scenario: function (filter, scenarioId) {
|
|
if (!filter || !scenarioId) {
|
|
return null;
|
|
}
|
|
|
|
var calendar = this.getActivityCalendarFromCache(filter, false) || {};
|
|
var needAllocations = (calendar.NeedAllocations || {})[scenarioId];
|
|
|
|
return (needAllocations || {}).Expenditures;
|
|
},
|
|
getRestTeamAllocations4Scenario: function (filter, scenarioId) {
|
|
if (!filter || !scenarioId) {
|
|
return null;
|
|
}
|
|
|
|
var calendar = this.getActivityCalendarFromCache(filter, false) || {};
|
|
var restData = (calendar.RestData || {});
|
|
var restAllocations = (restData.TeamAllocations || {})[scenarioId];
|
|
|
|
return (restAllocations || {}).Expenditures;
|
|
},
|
|
getRemainingNeedAllocations4Scenario: function (filter, visibleTeams, scenarioId, expenditureCategories, weekEndings) {
|
|
if (!filter || !scenarioId) {
|
|
return null;
|
|
}
|
|
|
|
var result = {};
|
|
var teamAllocations = this.getTeamAllocations4Scenario(filter, visibleTeams, scenarioId, expenditureCategories, weekEndings) || {};
|
|
var needAllocations = this.getNeedAllocations4Scenario(filter, scenarioId) || {};
|
|
var restAllocations = this.getRestTeamAllocations4Scenario(filter, scenarioId) || {};
|
|
var requiredECs = (expenditureCategories && expenditureCategories.length) ? expenditureCategories : Object.keys(needAllocations || {});
|
|
|
|
for (var ecIndex = 0; ecIndex < requiredECs.length; ecIndex++) {
|
|
var expenditureCategoryId = requiredECs[ecIndex];
|
|
var needAllocations4EC = needAllocations[expenditureCategoryId] || {};
|
|
var teamAllocations4EC = teamAllocations[expenditureCategoryId] || {};
|
|
var restAllocations4EC = restAllocations[expenditureCategoryId] || {};
|
|
if (!needAllocations4EC.Allocations) {
|
|
continue;
|
|
}
|
|
var weeks = (weekEndings && weekEndings.length) ? weekEndings : Object.keys(needAllocations4EC.Allocations);
|
|
|
|
result[expenditureCategoryId] = {
|
|
Allocations: {}
|
|
};
|
|
|
|
for (var weekIndex = 0; weekIndex < weeks.length; weekIndex++) {
|
|
var needAllocations4Week = (needAllocations4EC.Allocations || {})[weeks[weekIndex]] || 0;
|
|
var teamAllocations4Week = (teamAllocations4EC.Allocations || {})[weeks[weekIndex]] || 0;
|
|
var restAllocations4Week = (restAllocations4EC.Allocations || {})[weeks[weekIndex]] || 0;
|
|
|
|
result[expenditureCategoryId].Allocations[weeks[weekIndex]] = roundService.roundQuantity(needAllocations4Week - teamAllocations4Week - restAllocations4Week);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
},
|
|
getRemainingTeamAllocations4Scenario: function (filter, teamIds, projectId, scenarioId, expenditureCategories, weekEndings) {
|
|
if (!filter || !scenarioId || !teamIds || !teamIds.length) {
|
|
return null;
|
|
}
|
|
|
|
var scenarioAllocations = {};
|
|
for (var i = 0; i < teamIds.length; i++) {
|
|
var teamKey = teamIds[i];
|
|
var teamAllocations = this.getTeamAllocations(filter, teamKey);
|
|
if (!teamAllocations) {
|
|
continue;
|
|
}
|
|
|
|
var scenario = (teamAllocations.Scenarios || {})[scenarioId];
|
|
if (!scenario || !scenario.Expenditures) {
|
|
continue;
|
|
}
|
|
|
|
var requiredECs = (expenditureCategories && expenditureCategories.length) ? expenditureCategories : Object.keys(scenario.Expenditures);
|
|
for (var ecIndex = 0; ecIndex < requiredECs.length; ecIndex++) {
|
|
var expenditureCategoryId = requiredECs[ecIndex];
|
|
var ecExistsInSource = (scenario.Expenditures[expenditureCategoryId] && scenario.Expenditures[expenditureCategoryId].Allocations);
|
|
if (!ecExistsInSource) {
|
|
continue;
|
|
}
|
|
|
|
if (!(expenditureCategoryId in scenarioAllocations)) {
|
|
scenarioAllocations[expenditureCategoryId] = {
|
|
Allocations: {},
|
|
Teams: {}
|
|
};
|
|
}
|
|
var scenarioECTeamAllocations = angular.copy(scenario.Expenditures[expenditureCategoryId].Allocations || {});
|
|
var resources = this.getResourcesByProject(filter, projectId, expenditureCategoryId, [teamKey]);
|
|
var resourceAllocations = {};
|
|
var remainingTeamAllocations = {};
|
|
for (var resKey in resources) {
|
|
resourceAllocations[resKey] = this.getResourceAllocations4Scenario(filter, resources[resKey], scenarioId, teamKey, expenditureCategoryId);
|
|
}
|
|
var weeks = (weekEndings && weekEndings.length) ? weekEndings : Object.keys(scenarioECTeamAllocations);
|
|
for (var weekIndex = 0; weekIndex < weeks.length; weekIndex++) {
|
|
var teamAllocations4Week = scenarioECTeamAllocations[weeks[weekIndex]] || 0;
|
|
var resAllocations4Week = 0;
|
|
for (var resKey in resourceAllocations) {
|
|
resAllocations4Week += resourceAllocations[resKey][weeks[weekIndex]] || 0;
|
|
}
|
|
remainingTeamAllocations[weeks[weekIndex]] = roundService.roundQuantity(teamAllocations4Week - resAllocations4Week);
|
|
}
|
|
|
|
scenarioAllocations[expenditureCategoryId].Teams[teamKey] = {
|
|
Allocations: remainingTeamAllocations
|
|
}
|
|
scenarioAllocations[expenditureCategoryId].Allocations = calculateDistributionService.mergeAllocations(scenarioAllocations[expenditureCategoryId].Allocations, remainingTeamAllocations, weekEndings);
|
|
}
|
|
}
|
|
|
|
return scenarioAllocations;
|
|
},
|
|
getUnassignedNeedAllocations4Scenario: function (filter, scenarioId, forDataChange) {
|
|
if (!filter || !scenarioId) {
|
|
return null;
|
|
}
|
|
|
|
var result = null;
|
|
var calendar = this.getActivityCalendarFromCache(filter, forDataChange) || {};
|
|
var needAllocations = (calendar.UnassignedNeed || {})[scenarioId];
|
|
|
|
result = needAllocations ? needAllocations.Expenditures : null;
|
|
return result;
|
|
},
|
|
getTeamAllocations4Scenario: function (filter, teamIds, scenarioId, expenditureCategories, weekEndings) {
|
|
if (!filter || !scenarioId || !teamIds || !teamIds.length) {
|
|
return null;
|
|
}
|
|
|
|
var scenarioAllocations = {};
|
|
for (var i = 0; i < teamIds.length; i++) {
|
|
var teamAllocations = this.getTeamAllocations(filter, teamIds[i]);
|
|
if (!teamAllocations) {
|
|
continue;
|
|
}
|
|
|
|
var scenario = (teamAllocations.Scenarios || {})[scenarioId];
|
|
if (!scenario || !scenario.Expenditures) {
|
|
continue;
|
|
}
|
|
|
|
var requiredECs = (expenditureCategories && angular.isArray(expenditureCategories)) ? expenditureCategories : Object.keys(scenario.Expenditures);
|
|
for (var ecIndex = 0; ecIndex < requiredECs.length; ecIndex++) {
|
|
var expenditureCategoryId = requiredECs[ecIndex];
|
|
var ecExistsInSource = (scenario.Expenditures[expenditureCategoryId] && scenario.Expenditures[expenditureCategoryId].Allocations);
|
|
if (!ecExistsInSource) {
|
|
continue;
|
|
}
|
|
|
|
if (!(expenditureCategoryId in scenarioAllocations)) {
|
|
scenarioAllocations[expenditureCategoryId] = {
|
|
Allocations: {},
|
|
Teams: {}
|
|
};
|
|
}
|
|
var scenarioECTeamAllocations = angular.copy(scenario.Expenditures[expenditureCategoryId].Allocations);
|
|
scenarioAllocations[expenditureCategoryId].Teams[teamIds[i]] = {
|
|
Allocations: scenarioECTeamAllocations
|
|
}
|
|
scenarioAllocations[expenditureCategoryId].Allocations = calculateDistributionService.mergeAllocations(scenarioAllocations[expenditureCategoryId].Allocations, scenarioECTeamAllocations, weekEndings);
|
|
}
|
|
}
|
|
|
|
return scenarioAllocations;
|
|
},
|
|
getECTeamAllocations: function (filter, teamId, expCatId, scenarioId, forDataChange) {
|
|
if (!filter || !scenarioId || !teamId || !expCatId) {
|
|
return null;
|
|
}
|
|
|
|
var teamAllocations = this.getTeamAllocations(filter, [teamId], forDataChange);
|
|
if (!teamAllocations) {
|
|
return null;
|
|
}
|
|
|
|
var scenario = teamAllocations.Scenarios[scenarioId];
|
|
if (!scenario || !scenario.Expenditures) {
|
|
return null;
|
|
}
|
|
var ecTeamAllocations = scenario.Expenditures[expCatId];
|
|
if (!ecTeamAllocations)
|
|
return null;
|
|
|
|
return ecTeamAllocations.Allocations;
|
|
},
|
|
getTeamAllocations4Scenario4Week: function (filter, scenarioId, teamIds, expenditureCategoryId, weekEndingMs, addAllocationsForProjectRoles) {
|
|
if (!filter || !scenarioId || !teamIds || !teamIds.length || !weekEndingMs) {
|
|
return 0;
|
|
}
|
|
|
|
var result = undefined;
|
|
var expCats = expenditureCategoryId ? [expenditureCategoryId] : undefined;
|
|
|
|
if (expCats && addAllocationsForProjectRoles) {
|
|
// If method is called for specified EC, and it is necessary to add all scenario project roles in calculations
|
|
var projectRoles = this.getProjectRolesInScenario(filter, scenarioId, teamIds);
|
|
if (projectRoles && angular.isArray(projectRoles)) {
|
|
for (var pIndex = 0; pIndex < projectRoles.length; pIndex++) {
|
|
var projectRoleId = projectRoles[pIndex];
|
|
|
|
if (expCats.indexOf(projectRoleId) < 0) {
|
|
expCats.push(projectRoleId);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var scenarioTeamAllocations = this.getTeamAllocations4Scenario(filter, teamIds, scenarioId, expCats);
|
|
if (!scenarioTeamAllocations) {
|
|
return result;
|
|
}
|
|
|
|
if (!expCats || !angular.isArray(expCats))
|
|
expCats = Object.keys(scenarioTeamAllocations);
|
|
|
|
for (var eIndex = 0; eIndex < expCats.length; eIndex++) {
|
|
var expCatId = expCats[eIndex];
|
|
|
|
if (expCatId in scenarioTeamAllocations) {
|
|
var category = scenarioTeamAllocations[expCatId];
|
|
|
|
if (category && category.Allocations && (weekEndingMs in category.Allocations)) {
|
|
if (!angular.isNumber(result)) {
|
|
result = category.Allocations[weekEndingMs];
|
|
}
|
|
else {
|
|
result += category.Allocations[weekEndingMs];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
},
|
|
getTeamAllocationsSummary4Week: function (filter, teamIds, expenditureCategoryId, weekEndingMs, addAllocationsForProjectRoles) {
|
|
if (!filter || !teamIds || !teamIds.length) {
|
|
return null;
|
|
}
|
|
|
|
var calendar = this.getActivityCalendarFromCache(filter, false);
|
|
if (!calendar)
|
|
return null;
|
|
|
|
var result = {};
|
|
for (var index = 0; index < teamIds.length; index++) {
|
|
result[teamIds[index]] = {};
|
|
}
|
|
|
|
if (!calendar.TeamAllocations)
|
|
return result;
|
|
|
|
for (var tIndex = 0; tIndex < teamIds.length; tIndex++) {
|
|
var teamId = teamIds[tIndex];
|
|
|
|
if (teamId in calendar.TeamAllocations) {
|
|
var teamAllocations = calendar.TeamAllocations[teamId];
|
|
|
|
if (teamAllocations && teamAllocations.Scenarios) {
|
|
for (var scenarioId in teamAllocations.Scenarios) {
|
|
var scenarioTeamAllocations = teamAllocations.Scenarios[scenarioId];
|
|
|
|
if (scenarioTeamAllocations && scenarioTeamAllocations.Expenditures) {
|
|
var expCats = null;
|
|
if (expenditureCategoryId) {
|
|
if (addAllocationsForProjectRoles) {
|
|
// Create list of ECs to take in calculations: specified EC & all project roles in scenario
|
|
expCats = this.getProjectRolesInScenario(filter, scenarioId, [teamId]);
|
|
|
|
if (!expCats || !angular.isArray(expCats)) {
|
|
expCats = [];
|
|
}
|
|
expCats.push(expenditureCategoryId);
|
|
}
|
|
}
|
|
else {
|
|
// We'll perform calculations for all ECs in scenario, since EC was not specified in method parameters
|
|
expCats = Object.keys(scenarioTeamAllocations.Expenditures);
|
|
}
|
|
|
|
for (var eIndex = 0; eIndex < expCats.length; eIndex++) {
|
|
var expCatId = expCats[eIndex];
|
|
var expCatAllocations = scenarioTeamAllocations.Expenditures[expCatId];
|
|
|
|
if (expCatAllocations && expCatAllocations.Allocations) {
|
|
var weeks = weekEndingMs && angular.isArray(weekEndingMs) ? weekEndingMs : Object.keys(expCatAllocations.Allocations);
|
|
|
|
for (var wIndex = 0; wIndex < weeks.length; wIndex++) {
|
|
var we = Number(weeks[wIndex]);
|
|
|
|
if (we in expCatAllocations.Allocations) {
|
|
var value = expCatAllocations.Allocations[we];
|
|
|
|
if (angular.isNumber(value) && !isNaN(value)) {
|
|
if (!(we in result[teamIds])) {
|
|
result[teamIds][we] = 0;
|
|
}
|
|
|
|
result[teamIds][we] += Number(value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
},
|
|
getNeed4WeekEnding: function (filter, scenarioId, expenditureCategoryId, weekEndingMs) {
|
|
if (!filter || !scenarioId || !expenditureCategoryId || !weekEndingMs) {
|
|
return 0;
|
|
}
|
|
|
|
var scenarioNeed = this.getNeedAllocations4Scenario(filter, scenarioId);
|
|
if (!scenarioNeed) {
|
|
return 0;
|
|
}
|
|
|
|
var category = scenarioNeed[expenditureCategoryId];
|
|
if (!category || !category.Allocations) {
|
|
return undefined;
|
|
}
|
|
|
|
return category.Allocations[weekEndingMs];
|
|
},
|
|
getResourceAllocations: function (filter, resourceId, forDataChange) {
|
|
if (!filter || !resourceId) {
|
|
return null;
|
|
}
|
|
|
|
var calendar = this.getActivityCalendarFromCache(filter, forDataChange) || {};
|
|
var resourceAllocations = (calendar.ResourceAllocations || {})[resourceId];
|
|
|
|
return resourceAllocations;
|
|
},
|
|
getResourceActuals: function (filter, resourceId, forDataChange) {
|
|
if (!filter || !resourceId) {
|
|
return null;
|
|
}
|
|
|
|
var calendar = this.getActivityCalendarFromCache(filter, forDataChange) || {};
|
|
var resourceActuals = (calendar.ResourceActuals || {})[resourceId];
|
|
|
|
return resourceActuals;
|
|
},
|
|
getResourceAllocationsExpenditure: function (filter, resourceId, scenarioId, teamId, expenditureCategoryId, forDataChange) {
|
|
if (!filter || !resourceId || !scenarioId || !teamId || !expenditureCategoryId) {
|
|
return null;
|
|
}
|
|
|
|
var allocations = this.getResourceAllocations(filter, resourceId, forDataChange);
|
|
if (!allocations || !allocations.Scenarios) {
|
|
return null;
|
|
}
|
|
|
|
var allocationsByScenario = allocations.Scenarios[scenarioId];
|
|
if (!allocationsByScenario || !allocationsByScenario.Teams) {
|
|
return null;
|
|
}
|
|
|
|
var allocationsByTeam = allocationsByScenario.Teams[teamId];
|
|
if (!allocationsByTeam || !allocationsByTeam.Expenditures) {
|
|
return null;
|
|
}
|
|
|
|
return allocationsByTeam.Expenditures[expenditureCategoryId];
|
|
},
|
|
getTotalResourcesAllocations4WeekEnding: function (filter, resources, scenarioId, teamId, expenditureCategoryId, weekEndingMs) {
|
|
if (!filter || !resources || !resources.length || !scenarioId || !teamId || !expenditureCategoryId || !weekEndingMs) {
|
|
return 0;
|
|
}
|
|
|
|
var result = 0;
|
|
for (var i = 0; i < resources.length; i++) {
|
|
var category = this.getResourceAllocationsExpenditure(filter, resources[i], scenarioId, teamId, expenditureCategoryId) || {};
|
|
var allocations = category.Allocations || {};
|
|
|
|
result += (allocations[weekEndingMs] || 0);
|
|
}
|
|
|
|
return result;
|
|
},
|
|
getResourceActualsExpenditure: function (filter, resourceId, scenarioId, teamId, expenditureCategoryId, forDataChange) {
|
|
if (!filter || !resourceId || !scenarioId || !teamId || !expenditureCategoryId) {
|
|
return null;
|
|
}
|
|
|
|
var actuals = this.getResourceActuals(filter, resourceId, forDataChange);
|
|
if (!actuals || !actuals.Scenarios) {
|
|
return null;
|
|
}
|
|
|
|
var allocationsByScenario = actuals.Scenarios[scenarioId];
|
|
if (!allocationsByScenario || !allocationsByScenario.Teams) {
|
|
return null;
|
|
}
|
|
|
|
var actualsByTeam = allocationsByScenario.Teams[teamId];
|
|
if (!actualsByTeam || !actualsByTeam.Expenditures) {
|
|
return null;
|
|
}
|
|
|
|
return actualsByTeam.Expenditures[expenditureCategoryId];
|
|
},
|
|
getResourceAllocations4Scenario: function (filter, resourceId, scenarioId, teamId, expenditureCategoryId, forDataChange) {
|
|
if (!filter || !resourceId || !scenarioId || !teamId || !expenditureCategoryId) {
|
|
return null;
|
|
}
|
|
|
|
var allocationsByEC = this.getResourceAllocationsExpenditure(filter, resourceId, scenarioId, teamId, expenditureCategoryId, forDataChange);
|
|
if (!allocationsByEC) {
|
|
return null;
|
|
}
|
|
|
|
return allocationsByEC.Allocations;
|
|
},
|
|
getResourceActuals4Scenario: function (filter, resourceId, scenarioId, teamId, expenditureCategoryId, forDataChange) {
|
|
if (!filter || !resourceId || !scenarioId || !teamId || !expenditureCategoryId) {
|
|
return null;
|
|
}
|
|
|
|
var actualsByEC = this.getResourceActualsExpenditure(filter, resourceId, scenarioId, teamId, expenditureCategoryId, forDataChange);
|
|
if (!actualsByEC) {
|
|
return null;
|
|
}
|
|
|
|
return actualsByEC.Allocations;
|
|
},
|
|
getResourcesByProject: function (filter, projectId, expenditureCategoryId, teamIds) {
|
|
var resources = [];
|
|
if (!filter || !projectId || !expenditureCategoryId || !teamIds || !teamIds.length) {
|
|
return resources;
|
|
}
|
|
|
|
var project = this.getProjectById(filter, projectId);
|
|
if (!project) {
|
|
console.error('There is no project with id = ' + projectId);
|
|
return resources;
|
|
}
|
|
|
|
if (!project.Teams) {
|
|
return resources;
|
|
}
|
|
|
|
for (var i = 0; i < teamIds.length; i++) {
|
|
var processingTeamId = teamIds[i];
|
|
if (processingTeamId) {
|
|
var teamInProject = project.Teams[processingTeamId];
|
|
if (teamInProject.Resources) {
|
|
resources = union(resources, teamInProject.Resources[expenditureCategoryId]);
|
|
}
|
|
}
|
|
}
|
|
|
|
return resources;
|
|
},
|
|
getExpendituresExistInPendingTeams: function (filter, projectId) {
|
|
if (!filter || !projectId)
|
|
return;
|
|
|
|
var result = {};
|
|
var project = this.getProjectById(filter, projectId);
|
|
|
|
if (project && project.Teams) {
|
|
for (var teamId in project.Teams) {
|
|
var team = project.Teams[teamId];
|
|
|
|
if (team && ((team.Type == this.TeamType.Pending) || (team.Type == this.TeamType.SavedPending)) && team.Expenditures2Display) {
|
|
for (var expCatId in team.Expenditures2Display) {
|
|
if (!(expCatId in result)) {
|
|
result[expCatId] = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
},
|
|
getNonProjectTimeResourceAllocations: function (nptItemId, weekEndingMs) {
|
|
if (!nptItemId || !weekEndingMs)
|
|
return null;
|
|
|
|
var nptByResources = teamInfoService.getNonProjectTimeItemsById(nptItemId);
|
|
if (!nptByResources)
|
|
return null;
|
|
|
|
var result = {};
|
|
for (var resourceId in nptByResources) {
|
|
var nptItem = nptByResources[resourceId];
|
|
|
|
if (nptItem && nptItem.Allocations && (String(weekEndingMs) in nptItem.Allocations))
|
|
result[resourceId] = nptItem.Allocations[String(weekEndingMs)];
|
|
};
|
|
|
|
return result;
|
|
},
|
|
getTeamSaveModel: function (filter, scenarioId, expenditureCategoryId, teamId) {
|
|
if (!filter || !scenarioId || !expenditureCategoryId || !teamId) {
|
|
return;
|
|
}
|
|
|
|
var model = this.getActivityCalendarSaveModel(filter);
|
|
if (!(scenarioId in model.Scenarios)) {
|
|
model.Scenarios[scenarioId] = {
|
|
Expenditures: {}
|
|
};
|
|
}
|
|
|
|
var scenario = model.Scenarios[scenarioId];
|
|
if (!(expenditureCategoryId in scenario.Expenditures)) {
|
|
scenario.Expenditures[expenditureCategoryId] = {
|
|
Teams: {}
|
|
};
|
|
}
|
|
|
|
var category = scenario.Expenditures[expenditureCategoryId];
|
|
if (!(teamId in category.Teams)) {
|
|
category.Teams[teamId] = {
|
|
Resources: {}
|
|
};
|
|
}
|
|
|
|
return category.Teams[teamId];
|
|
},
|
|
getResourceSaveModel: function (filter, scenarioId, expenditureCategoryId, teamId, resourceId) {
|
|
if (!filter || !scenarioId || !expenditureCategoryId || !teamId || !resourceId) {
|
|
return;
|
|
}
|
|
|
|
var teamSaveModel = this.getTeamSaveModel(filter, scenarioId, expenditureCategoryId, teamId);
|
|
if (!teamSaveModel) {
|
|
throw 'teamSaveModel is null for scenarioId = ' + scenarioId + ', expenditureCategoryId = ' + expenditureCategoryId + ', teamId = ' + teamId;
|
|
}
|
|
|
|
if (!(resourceId in teamSaveModel.Resources)) {
|
|
teamSaveModel.Resources[resourceId] = {
|
|
IsRemoved: false,
|
|
ForecastAllocations: {},
|
|
ActualAllocations: {},
|
|
};
|
|
}
|
|
|
|
return teamSaveModel.Resources[resourceId];
|
|
},
|
|
getNonProjectTimeSaveModel: function (filter, nptItemId) {
|
|
if (!filter || !nptItemId) {
|
|
return;
|
|
}
|
|
|
|
var model = this.getActivityCalendarSaveModel(filter);
|
|
|
|
if (!(nptItemId in model.NonProjectTime)) {
|
|
model.NonProjectTime[nptItemId] = {
|
|
Resources: {},
|
|
TeamWideNptAllocations: {}
|
|
};
|
|
}
|
|
|
|
var nptItemSaveModel = model.NonProjectTime[nptItemId];
|
|
return nptItemSaveModel;
|
|
},
|
|
changeScenarioUnassignedNeedValue: function (filter, scenarioId, expCatId, weekEndingMs, deltaValue) {
|
|
if (!filter || !scenarioId || !expCatId || !weekEndingMs || !deltaValue)
|
|
return;
|
|
|
|
var expCatAllocations = this.getUnassignedNeedAllocations4Scenario(filter, scenarioId, true);
|
|
if (expCatAllocations && (expCatId in expCatAllocations)) {
|
|
var expCatAllocations = expCatAllocations[expCatId];
|
|
|
|
if (expCatAllocations && expCatAllocations.Allocations && (weekEndingMs in expCatAllocations.Allocations)) {
|
|
expCatAllocations.Allocations[weekEndingMs] = expCatAllocations.Allocations[weekEndingMs] - deltaValue;
|
|
}
|
|
}
|
|
},
|
|
checkResourceIsAssignedToProject: function (filter, projectId, expenditureCategoryId, teamIds, resourceId) {
|
|
if (!filter || !projectId || !expenditureCategoryId || !teamIds || !teamIds.length || !resourceId) {
|
|
throw 'Some parameteres are invalid';
|
|
}
|
|
|
|
var project = this.getProjectById(filter, projectId);
|
|
if (!project) {
|
|
throw 'Project with id = ' + projectId + ' does not exist';
|
|
}
|
|
|
|
if (!project.Teams) {
|
|
return false;
|
|
}
|
|
|
|
var isAssigned = false;
|
|
for (var i = 0; i < teamIds.length; i++) {
|
|
var teamId = teamIds[i];
|
|
if (!teamId) {
|
|
continue;
|
|
}
|
|
|
|
var teamInProject = project.Teams[teamId];
|
|
if (!teamInProject || !teamInProject.Resources) {
|
|
continue;
|
|
}
|
|
|
|
isAssigned = !!teamInProject.Resources[expenditureCategoryId] && teamInProject.Resources[expenditureCategoryId].indexOf(resourceId) >= 0;
|
|
if (isAssigned) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return isAssigned;
|
|
},
|
|
assignResource: function (filter, projectId, expenditureCategoryId, teamId, resourceId) {
|
|
if (!filter || !projectId || !expenditureCategoryId || !teamId || !resourceId) {
|
|
return;
|
|
}
|
|
|
|
var project = this.getProjectById(filter, projectId);
|
|
if (!project) {
|
|
console.error('There is no project with id = ' + projectId);
|
|
return;
|
|
}
|
|
|
|
if (!project.ActiveScenario) {
|
|
console.error('There is no active scenario for project with id = ' + projectId);
|
|
return;
|
|
}
|
|
|
|
this.assignResourceOnProject(filter, projectId, expenditureCategoryId, teamId, resourceId);
|
|
this.createResourceAllocations(filter, project.ActiveScenario.Id, expenditureCategoryId, teamId, resourceId);
|
|
},
|
|
assignResourceOnProject: function (filter, projectId, expenditureCategoryId, teamId, resourceId) {
|
|
if (!filter || !projectId || !expenditureCategoryId || !teamId || !resourceId) {
|
|
return;
|
|
}
|
|
|
|
var project = this.getProjectById(filter, projectId, true);
|
|
if (!project) {
|
|
console.error('There is no project with id = ' + projectId);
|
|
return;
|
|
}
|
|
|
|
if (!project.Teams) {
|
|
project.Teams = {};
|
|
}
|
|
|
|
if (!project.Teams[teamId]) {
|
|
project.Teams[teamId] = {};
|
|
}
|
|
|
|
var teamInProject = project.Teams[teamId];
|
|
if (!teamInProject.Resources) {
|
|
teamInProject.Resources = {};
|
|
}
|
|
|
|
if (!teamInProject.Resources[expenditureCategoryId]) {
|
|
teamInProject.Resources[expenditureCategoryId] = [];
|
|
}
|
|
|
|
if (teamInProject.Resources[expenditureCategoryId].indexOf(resourceId) < 0) {
|
|
teamInProject.Resources[expenditureCategoryId].push(resourceId);
|
|
}
|
|
},
|
|
createResourceAllocations: function (filter, scenarioId, expenditureCategoryId, teamId, resourceId) {
|
|
if (!filter || !scenarioId || !expenditureCategoryId || !teamId || !resourceId) {
|
|
return;
|
|
}
|
|
|
|
var calendar = this.getActivityCalendarFromCache(filter, true);
|
|
if (!calendar) {
|
|
return;
|
|
}
|
|
|
|
if (!calendar.ResourceAllocations) {
|
|
calendar.ResourceAllocations = {};
|
|
}
|
|
if (!calendar.ResourceAllocations[resourceId]) {
|
|
calendar.ResourceAllocations[resourceId] = {};
|
|
}
|
|
|
|
var resource = calendar.ResourceAllocations[resourceId];
|
|
if (!resource.Scenarios) {
|
|
resource.Scenarios = {};
|
|
}
|
|
if (!resource.Scenarios[scenarioId]) {
|
|
resource.Scenarios[scenarioId] = {};
|
|
}
|
|
|
|
var scenario = resource.Scenarios[scenarioId];
|
|
if (!scenario.Teams) {
|
|
scenario.Teams = {};
|
|
}
|
|
if (!scenario.Teams[teamId]) {
|
|
scenario.Teams[teamId] = {};
|
|
}
|
|
|
|
var team = scenario.Teams[teamId];
|
|
if (!team.Expenditures) {
|
|
team.Expenditures = {};
|
|
}
|
|
if (!team.Expenditures[expenditureCategoryId]) {
|
|
team.Expenditures[expenditureCategoryId] = {};
|
|
}
|
|
|
|
var ec = team.Expenditures[expenditureCategoryId];
|
|
if (!ec.Allocations) {
|
|
ec.Allocations = {};
|
|
}
|
|
},
|
|
removeResource: function (filter, projectId, expenditureCategoryId, teamId, resourceId) {
|
|
if (!filter || !projectId || !expenditureCategoryId || !teamId || !resourceId) {
|
|
return;
|
|
}
|
|
|
|
var project = this.getProjectById(filter, projectId);
|
|
if (!project) {
|
|
console.error('There is no project with id = ' + projectId);
|
|
return;
|
|
}
|
|
|
|
if (!project.ActiveScenario) {
|
|
console.error('There is no active scenario for project with id = ' + projectId);
|
|
return;
|
|
}
|
|
|
|
this.removeResourceFromProject(filter, projectId, expenditureCategoryId, teamId, resourceId);
|
|
this.removeResourceAllocations(filter, project.ActiveScenario.Id, expenditureCategoryId, teamId, resourceId);
|
|
this.markResourceAsRemovedInSaveModel(filter, project.ActiveScenario.Id, expenditureCategoryId, teamId, resourceId);
|
|
},
|
|
removeResourceFromProject: function (filter, projectId, expenditureCategoryId, teamId, resourceId) {
|
|
if (!filter || !projectId || !expenditureCategoryId || !teamId || !resourceId) {
|
|
return;
|
|
}
|
|
|
|
var project = this.getProjectById(filter, projectId, true);
|
|
if (!project || !project.Teams) {
|
|
return;
|
|
}
|
|
|
|
var teamInProject = project.Teams[teamId];
|
|
if (!teamInProject || !teamInProject.Resources) {
|
|
return;
|
|
}
|
|
|
|
var resources = teamInProject.Resources[expenditureCategoryId];
|
|
if (resources && resources.length) {
|
|
var resourceIndex = resources.indexOf(resourceId);
|
|
if (resourceIndex >= 0) {
|
|
resources.splice(resourceIndex, 1);
|
|
}
|
|
}
|
|
},
|
|
removeResourceAllocations: function (filter, scenarioId, expenditureCategoryId, teamId, resourceId) {
|
|
if (!filter || !scenarioId || !expenditureCategoryId || !teamId || !resourceId) {
|
|
return;
|
|
}
|
|
|
|
var allocationsByEC = this.getResourceAllocationsExpenditure(filter, resourceId, scenarioId, teamId, expenditureCategoryId, true);
|
|
if (allocationsByEC) {
|
|
allocationsByEC.Allocations = {};
|
|
}
|
|
},
|
|
changeResourceValue: function (filter, projectId, expenditureCategoryId, teamId, resourceId, weekEndingMs, value, isActuals) {
|
|
if (!filter || !projectId || !expenditureCategoryId || !teamId || !resourceId || !weekEndingMs) {
|
|
return;
|
|
}
|
|
|
|
value = parseFloat(value) || 0;
|
|
if (value < 0) {
|
|
throw 'Value must be a positive number';
|
|
}
|
|
|
|
var project = this.getProjectById(filter, projectId);
|
|
if (!project || !project.ActiveScenario) {
|
|
throw 'There is no project with id = ' + projectId;
|
|
}
|
|
|
|
var allocations = null;
|
|
if (!isActuals) {
|
|
// Update for forecast value
|
|
allocations = this.getResourceAllocations4Scenario(filter, resourceId, project.ActiveScenario.Id, teamId, expenditureCategoryId, true);
|
|
if (!allocations) {
|
|
// for the following case:
|
|
// resource is related to a few teams that work on some project
|
|
// resource has allocations only for one of them
|
|
// user selects "Group by Project" mode and change some value for the resource
|
|
// as a total range by all teams is available we have no inited allocations and assignments for this resource
|
|
// so we need to assign resource
|
|
this.assignResource(filter, projectId, expenditureCategoryId, teamId, resourceId);
|
|
allocations = this.getResourceAllocations4Scenario(filter, resourceId, project.ActiveScenario.Id, teamId, expenditureCategoryId, true);
|
|
if (!allocations) {
|
|
throw 'Error in assign resource logic has been occurred';
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
// Update for actual value
|
|
allocations = this.getResourceActuals4Scenario(filter, resourceId, project.ActiveScenario.Id, teamId, expenditureCategoryId, true);
|
|
if (!allocations) {
|
|
// for the following case:
|
|
// resource is related to a few teams that work on some project
|
|
// resource has allocations only for one of them
|
|
// user selects "Group by Project" mode and change some value for the resource
|
|
// as a total range by all teams is available we have no inited allocations and assignments for this resource
|
|
// so we need to assign resource
|
|
this.createResourceActuals4Scenario(filter, expenditureCategoryId, teamId, project.ActiveScenario.Id, resourceId);
|
|
allocations = this.getResourceActuals4Scenario(filter, resourceId, project.ActiveScenario.Id, teamId, expenditureCategoryId, true);
|
|
if (!allocations) {
|
|
throw 'Error in creating resource actuals logic has been occurred';
|
|
}
|
|
}
|
|
}
|
|
|
|
var oldValue = allocations[weekEndingMs] || 0;
|
|
var deltaValue = value - oldValue;
|
|
allocations[weekEndingMs] = value;
|
|
|
|
if (!isActuals) {
|
|
teamInfoService.changeResourceValue(teamId, expenditureCategoryId, resourceId, weekEndingMs, deltaValue, true);
|
|
this.changeScenarioUnassignedNeedValue(filter, project.ActiveScenario.Id, expenditureCategoryId, weekEndingMs, deltaValue);
|
|
}
|
|
|
|
this.changeResourceValueInSaveModel(filter, project.ActiveScenario.Id, expenditureCategoryId, teamId, resourceId, weekEndingMs, value, isActuals);
|
|
},
|
|
changeResourceValueInSaveModel: function (filter, scenarioId, expenditureCategoryId, teamId, resourceId, weekEndingMs, value, isActual) {
|
|
if (!filter || !scenarioId || !expenditureCategoryId || !teamId || !resourceId || !weekEndingMs) {
|
|
return;
|
|
}
|
|
|
|
value = parseFloat(value) || 0;
|
|
if (value < 0) {
|
|
throw 'Value must be a positive number';
|
|
}
|
|
|
|
var resource = this.getResourceSaveModel(filter, scenarioId, expenditureCategoryId, teamId, resourceId);
|
|
if (!resource) {
|
|
throw 'Save model for resource ' + resourceId + ' does not exist';
|
|
}
|
|
|
|
// as we edit resource it is not deleted
|
|
// example: user has deleted resource (IsRemoved = true) and then assign resource back again (we need set IsRemoved flag to false)
|
|
resource.IsRemoved = false;
|
|
if (isActual) {
|
|
resource.ActualAllocations[weekEndingMs] = value;
|
|
}
|
|
else {
|
|
resource.ForecastAllocations[weekEndingMs] = value;
|
|
}
|
|
},
|
|
assignTeam: function (filter, projectId, teamId, extendedModel) {
|
|
if (!filter || !projectId || !teamId) {
|
|
return;
|
|
}
|
|
|
|
var project = this.getProjectById(filter, projectId, true);
|
|
if (!project) {
|
|
console.error('assignTeam: there is no project with id = ' + projectId);
|
|
return;
|
|
}
|
|
|
|
if (!project.ActiveScenario || !project.ActiveScenario.Id) {
|
|
console.error('assignTeam: there is no active scenario for project with id = ' + projectId);
|
|
return;
|
|
}
|
|
|
|
if (!project.Teams) {
|
|
project.Teams = {};
|
|
}
|
|
|
|
if (!project.Teams[teamId]) {
|
|
project.Teams[teamId] = angular.extend({}, {
|
|
Type: this.TeamType.Pending,
|
|
Resources: {},
|
|
}, extendedModel);
|
|
}
|
|
else {
|
|
project.Teams[teamId] = angular.extend(project.Teams[teamId], {
|
|
Type: this.TeamType.SavedPending
|
|
}, extendedModel);
|
|
|
|
// if team is already assigned we should keep original team allocations to have ability to restore them if team is removed from sandbox
|
|
if (extendedModel.Expenditures2Display) {
|
|
var affectedECs = Object.keys(extendedModel.Expenditures2Display);
|
|
for (var i = 0; i < affectedECs.length; i++) {
|
|
this.createTeamAllocationsBackup(filter, project.ActiveScenario.Id, teamId, affectedECs[i]);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
createTeamAllocations: function (filter, projectId, teamId, weekEndings) {
|
|
if (!filter || !projectId || !teamId || !weekEndings || !weekEndings.length) {
|
|
return;
|
|
}
|
|
|
|
var calendar = this.getActivityCalendarFromCache(filter, true) || {};
|
|
var project = this.getProjectById(filter, projectId);
|
|
if (!calendar || !project || !project.ActiveScenario) {
|
|
return;
|
|
}
|
|
|
|
var scenarioId = project.ActiveScenario.Id;
|
|
var needAllocations = this.getNeedAllocations4Scenario(filter, scenarioId);
|
|
var team = teamInfoService.getById(teamId);
|
|
if (!needAllocations || !team) {
|
|
return;
|
|
}
|
|
|
|
var categoriesInScenario = Object.keys(needAllocations);
|
|
var categoriesInTeam = Object.keys(team.ExpCategories || {});
|
|
|
|
if (!(teamId in calendar.TeamAllocations)) {
|
|
calendar.TeamAllocations[teamId] = {
|
|
Scenarios: {}
|
|
};
|
|
}
|
|
|
|
if (!(scenarioId in calendar.TeamAllocations[teamId].Scenarios)) {
|
|
calendar.TeamAllocations[teamId].Scenarios[scenarioId] = {
|
|
Expenditures: {}
|
|
};
|
|
}
|
|
|
|
var teamAllocations4Scenario = calendar.TeamAllocations[teamId].Scenarios[scenarioId];
|
|
for (var i = 0; i < categoriesInScenario.length; i++) {
|
|
var expenditureCategoryId = categoriesInScenario[i];
|
|
var isSuperEC = dataSources.isSuperEC(expenditureCategoryId);
|
|
if (isSuperEC || categoriesInTeam.indexOf(expenditureCategoryId) >= 0) {
|
|
// create new expenditure under team only if didn't create yet
|
|
if (!teamAllocations4Scenario.Expenditures[expenditureCategoryId]) {
|
|
teamAllocations4Scenario.Expenditures[expenditureCategoryId] = {
|
|
Allocations: {}
|
|
};
|
|
}
|
|
for (var weekIndex = 0; weekIndex < weekEndings.length; weekIndex++) {
|
|
// fill week with zero only if value does not exist
|
|
if (!teamAllocations4Scenario.Expenditures[expenditureCategoryId].Allocations[weekEndings[weekIndex]]) {
|
|
teamAllocations4Scenario.Expenditures[expenditureCategoryId].Allocations[weekEndings[weekIndex]] = 0;
|
|
this.changeTeamValueInSaveModel(filter, scenarioId, expenditureCategoryId, teamId, weekEndings[weekIndex], 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
changeTeamValue: function (filter, projectId, expenditureCategoryId, teamId, weekEndingMs, value) {
|
|
if (!filter || !projectId || !expenditureCategoryId || !teamId || !weekEndingMs) {
|
|
return;
|
|
}
|
|
|
|
value = parseFloat(value) || 0;
|
|
if (value < 0) {
|
|
throw 'Value must be non-negative number';
|
|
}
|
|
|
|
var project = this.getProjectById(filter, projectId);
|
|
if (!project || !project.ActiveScenario) {
|
|
throw 'There is no project with id = ' + projectId;
|
|
}
|
|
|
|
var allocations = this.getECTeamAllocations(filter, teamId, expenditureCategoryId, project.ActiveScenario.Id, true);
|
|
if (!allocations) {
|
|
var teamModel = {
|
|
Id: teamId
|
|
};
|
|
|
|
this.assignTeam(filter, projectId, expenditureCategoryId, teamId, teamModel);
|
|
allocations = this.getECTeamAllocations(filter, teamId, expenditureCategoryId, project.ActiveScenario.Id, true);
|
|
if (!allocations) {
|
|
throw 'Error in team assign logic has been occurred';
|
|
}
|
|
}
|
|
|
|
var oldValue = allocations[weekEndingMs] || 0;
|
|
var deltaValue = value - oldValue;
|
|
allocations[weekEndingMs] = value;
|
|
|
|
teamInfoService.changeTeamValue(teamId, expenditureCategoryId, weekEndingMs, deltaValue, true);
|
|
this.changeTeamValueInSaveModel(filter, project.ActiveScenario.Id, expenditureCategoryId, teamId, weekEndingMs, value);
|
|
},
|
|
changeTeamValueInSaveModel: function (filter, scenarioId, expenditureCategoryId, teamId, weekEndingMs, value) {
|
|
if (!filter || !scenarioId || !expenditureCategoryId || !teamId || !weekEndingMs) {
|
|
return;
|
|
}
|
|
|
|
value = parseFloat(value) || 0;
|
|
if (value < 0) {
|
|
throw 'Value must be non-negative number';
|
|
}
|
|
|
|
var teamModel = this.getTeamSaveModel(filter, scenarioId, expenditureCategoryId, teamId);
|
|
if (!teamModel) {
|
|
throw 'Save model for team ' + teamId + ' does not exist';
|
|
}
|
|
|
|
if (!teamModel.Allocations) {
|
|
teamModel.Allocations = {};
|
|
}
|
|
|
|
teamModel.Allocations[weekEndingMs] = value;
|
|
},
|
|
removeTeam: function (filter, projectId, teamId) {
|
|
if (!filter || !projectId || !teamId) {
|
|
return;
|
|
}
|
|
|
|
var project = this.getProjectById(filter, projectId, true);
|
|
if (!project) {
|
|
console.error('There is no project with id = ' + projectId);
|
|
return;
|
|
}
|
|
|
|
if (!project.Teams || !(teamId in project.Teams)) {
|
|
console.error('There is no team with id = ' + teamId + ' for project with id = ' + projectId);
|
|
return;
|
|
}
|
|
|
|
if (!project.ActiveScenario) {
|
|
console.error('There is no active scenario for project with id = ' + projectId);
|
|
return;
|
|
}
|
|
|
|
var projectTeam = project.Teams[teamId];
|
|
var projectTeamType = (projectTeam.Type || this.TeamType.Saved);
|
|
switch (projectTeamType) {
|
|
case this.TeamType.Pending: // if it is newly assigned team we should delete it at all
|
|
this.removeTeamFromProject(filter, projectId, teamId);
|
|
this.removeTeamAllocations(filter, project.ActiveScenario.Id, teamId);
|
|
break;
|
|
case this.TeamType.SavedPending: // if it is already assigned team we should rollback changes and delete created backup
|
|
projectTeam.Type = this.TeamType.Saved;
|
|
projectTeam.Expenditures2Display = null;
|
|
this.removeTeamAllocationsBackup(filter, project.ActiveScenario.Id, teamId);
|
|
break;
|
|
}
|
|
|
|
this.removeTeamChangesFromSaveModel(filter, project.ActiveScenario.Id, teamId);
|
|
},
|
|
removeTeamFromProject: function (filter, projectId, teamId) {
|
|
if (!filter || !projectId || !teamId) {
|
|
return;
|
|
}
|
|
|
|
var project = this.getProjectById(filter, projectId, true);
|
|
if (!project || !project.Teams) {
|
|
return;
|
|
}
|
|
|
|
if (project.Teams[teamId]) {
|
|
delete project.Teams[teamId];
|
|
}
|
|
},
|
|
removeTeamAllocations: function (filter, scenarioId, teamId, expCatId) {
|
|
if (!filter || !scenarioId || !teamId) {
|
|
return;
|
|
}
|
|
|
|
var allTeamAllocations = this.getTeamAllocations(filter, teamId, true);
|
|
if (allTeamAllocations.Scenarios && (scenarioId in allTeamAllocations.Scenarios)) {
|
|
var scenario = allTeamAllocations.Scenarios[scenarioId];
|
|
|
|
if (scenario.Expenditures) {
|
|
var expCatsToReview = expCatId ? [expCatId] : Object.keys(scenario.Expenditures);
|
|
|
|
for (var index = 0; index < expCatsToReview.length; index++) {
|
|
var currentExpCatId = expCatsToReview[index];
|
|
|
|
if (currentExpCatId in scenario.Expenditures) {
|
|
delete scenario.Expenditures[expCatId];
|
|
}
|
|
}
|
|
|
|
if (Object.keys(scenario.Expenditures).length < 1) {
|
|
delete allTeamAllocations.Scenarios[scenarioId];
|
|
}
|
|
}
|
|
}
|
|
},
|
|
removeTeamChangesFromSaveModel: function (filter, scenarioId, teamId, expCatId) {
|
|
if (!filter || !scenarioId || !teamId) {
|
|
return;
|
|
}
|
|
|
|
var model = this.getActivityCalendarSaveModel(filter);
|
|
|
|
if (scenarioId in model.Scenarios) {
|
|
var scenarioSaveModel = model.Scenarios[scenarioId];
|
|
|
|
if (scenarioSaveModel.Expenditures) {
|
|
var expCatsToReview = expCatId ? [expCatId] : Object.keys(scenarioSaveModel.Expenditures);
|
|
|
|
for (var index = 0; index < expCatsToReview.length; index++) {
|
|
var currentExpCatId = expCatsToReview[index];
|
|
|
|
if (currentExpCatId in scenarioSaveModel.Expenditures) {
|
|
var expCatSaveModel = scenarioSaveModel.Expenditures[currentExpCatId];
|
|
|
|
if (teamId in expCatSaveModel.Teams) {
|
|
delete expCatSaveModel.Teams[teamId];
|
|
}
|
|
|
|
if (Object.keys(expCatSaveModel.Teams).length < 1) {
|
|
delete scenarioSaveModel.Expenditures[currentExpCatId];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Object.keys(scenarioSaveModel.Expenditures).length < 1) {
|
|
delete model.Scenarios[scenarioId];
|
|
}
|
|
}
|
|
},
|
|
changeNptResourceValueInSaveModel: function (filter, nptItemId, resourceId, weekEndingMs, value) {
|
|
if (!filter || !nptItemId || !resourceId || !weekEndingMs) {
|
|
return;
|
|
}
|
|
|
|
value = parseFloat(value) || 0;
|
|
if (value < 0) {
|
|
throw 'Value must be a positive number';
|
|
}
|
|
|
|
var nptSaveModel = this.getNonProjectTimeSaveModel(filter, nptItemId);
|
|
if (!nptSaveModel) {
|
|
throw 'Save model for non-project time does not exist';
|
|
}
|
|
|
|
if (!(resourceId in nptSaveModel.Resources)) {
|
|
nptSaveModel.Resources[resourceId] = {
|
|
Allocations: {}
|
|
};
|
|
}
|
|
|
|
var nptResourceAllocations = nptSaveModel.Resources[resourceId].Allocations;
|
|
nptResourceAllocations[String(weekEndingMs)] = value;
|
|
},
|
|
changeTeamWideNptValueInSaveModel: function (filter, nptItemId, weekEndingMs, value) {
|
|
if (!filter || !nptItemId || !weekEndingMs) {
|
|
return;
|
|
}
|
|
|
|
value = parseFloat(value) || 0;
|
|
if (value < 0) {
|
|
throw 'Value must be a positive number';
|
|
}
|
|
|
|
var nptSaveModel = this.getNonProjectTimeSaveModel(filter, nptItemId);
|
|
if (!nptSaveModel) {
|
|
throw 'Save model for non-project time does not exist';
|
|
}
|
|
|
|
if (!nptSaveModel.TeamWideNptAllocations) {
|
|
nptSaveModel.TeamWideNptAllocations = {};
|
|
}
|
|
|
|
nptSaveModel.TeamWideNptAllocations[String(weekEndingMs)] = value;
|
|
},
|
|
markResourceAsRemovedInSaveModel: function (filter, scenarioId, expenditureCategoryId, teamId, resourceId) {
|
|
if (!filter || !scenarioId || !expenditureCategoryId || !teamId || !resourceId) {
|
|
return;
|
|
}
|
|
|
|
var resource = this.getResourceSaveModel(filter, scenarioId, expenditureCategoryId, teamId, resourceId);
|
|
if (!resource) {
|
|
throw 'Save model for resource ' + resourceId + ' does not exist';
|
|
}
|
|
|
|
resource.IsRemoved = true;
|
|
resource.ForecastAllocations = {};
|
|
resource.ActualAllocations = {};
|
|
},
|
|
changeNptResourceValue: function (filter, nptItemId, resourceId, expenditureCategoryId, weekEndingMs, value) {
|
|
if (!filter || !nptItemId || !resourceId || !weekEndingMs) {
|
|
return;
|
|
}
|
|
|
|
value = parseFloat(value) || 0;
|
|
if (value < 0) {
|
|
throw 'Value must be a positive number';
|
|
}
|
|
|
|
if (teamInfoService.setNonProjectTimeResourceValue(nptItemId, resourceId, expenditureCategoryId, weekEndingMs, value)) {
|
|
this.changeNptResourceValueInSaveModel(filter, nptItemId, resourceId, weekEndingMs, value);
|
|
}
|
|
else {
|
|
throw "Unable to update resource's non-project time allocation: resource or allocation not found";
|
|
}
|
|
},
|
|
changeTeamWideNptValue: function (filter, nptItemId, resourceId, expenditureCategoryId, weekEndingMs, value) {
|
|
if (!filter || !nptItemId || !resourceId || !weekEndingMs) {
|
|
return;
|
|
}
|
|
|
|
value = parseFloat(value) || 0;
|
|
if (value < 0) {
|
|
throw 'Value must be a positive number';
|
|
}
|
|
|
|
if (teamInfoService.setNonProjectTimeResourceValue(nptItemId, resourceId, expenditureCategoryId, weekEndingMs, value)) {
|
|
this.changeTeamWideNptValueInSaveModel(filter, nptItemId, weekEndingMs, value);
|
|
}
|
|
else {
|
|
throw "Unable to update team-wide non-project time allocation: allocation not found";
|
|
}
|
|
},
|
|
loadACFilterOptionsAsync: function (url, data, forceLoadFromServer) {
|
|
if (acFilterOptions && !forceLoadFromServer) {
|
|
var deferrer = $q.defer();
|
|
deferrer.resolve(acFilterOptions);
|
|
return deferrer.promise;
|
|
}
|
|
|
|
var request = getAntiXSRFRequest(url, data);
|
|
var promise = $http(request).
|
|
then(function (response) {
|
|
acFilterOptions = response.data;
|
|
return response.data;
|
|
}, function (response) {
|
|
// throw exception through the pipe
|
|
throw 'An error occurred while loading calendar filter options';
|
|
});
|
|
return promise;
|
|
},
|
|
createResourceActuals4Scenario: function (filter, expenditureCategoryId, teamId, scenarioId, resourceId) {
|
|
if (!filter || !resourceId || !scenarioId || !teamId || !expenditureCategoryId) {
|
|
return;
|
|
}
|
|
|
|
var calendar = this.getActivityCalendarFromCache(filter);
|
|
if (!calendar)
|
|
throw "Unable to create resource actuals: Calendar data model is empty";
|
|
|
|
if (!calendar.ResourceActuals)
|
|
calendar.ResourceActuals = {};
|
|
|
|
if (!(resourceId in calendar.ResourceActuals))
|
|
calendar.ResourceActuals[resourceId] = {};
|
|
|
|
var resourceData = calendar.ResourceActuals[resourceId];
|
|
|
|
if (!resourceData.Scenarios)
|
|
resourceData.Scenarios = {};
|
|
|
|
if (!(scenarioId in resourceData.Scenarios))
|
|
resourceData.Scenarios[scenarioId] = {};
|
|
|
|
var scenarioData = resourceData.Scenarios[scenarioId];
|
|
|
|
if (!scenarioData.Teams)
|
|
scenarioData.Teams = {};
|
|
|
|
if (!(teamId in scenarioData.Teams))
|
|
scenarioData.Teams[teamId] = {};
|
|
|
|
var teamData = scenarioData.Teams[teamId];
|
|
|
|
if (!teamData.Expenditures)
|
|
teamData.Expenditures = {};
|
|
|
|
if (!(expenditureCategoryId in teamData.Expenditures))
|
|
teamData.Expenditures[expenditureCategoryId] = {};
|
|
|
|
var expData = teamData.Expenditures[expenditureCategoryId];
|
|
|
|
if (!expData.Allocations)
|
|
expData.Allocations = {};
|
|
},
|
|
saveChanges: function (model) {
|
|
if (!model) {
|
|
throw 'Data model for save is invalid';
|
|
}
|
|
|
|
var request = getAntiXSRFRequest('/CapacityManagement/SaveChanges', model);
|
|
var promise = $http(request).
|
|
then(function (response) {
|
|
return true;
|
|
}, function (response) {
|
|
// throw exception through the pipe
|
|
throw 'An error occurred while saving calendar data changes';
|
|
});
|
|
return promise;
|
|
},
|
|
cacheKeysEqual: function (filter1, filter2) {
|
|
var cacheKey1 = getCacheKey(filter1, false);
|
|
var cacheKey2 = getCacheKey(filter2, false);
|
|
|
|
return cacheKey1 === cacheKey2;
|
|
},
|
|
invalidateCache: function (filter) {
|
|
if (!filter) {
|
|
return;
|
|
}
|
|
|
|
var cacheKey = getCacheKey(filter, false);
|
|
if (!cacheKey) {
|
|
return;
|
|
}
|
|
|
|
this.invalidatePreFilteredCache(filter);
|
|
delete activityCalendarCache[cacheKey];
|
|
delete activityCalendarSaveModelCache[cacheKey];
|
|
},
|
|
isDataChanged: function (filter) {
|
|
if (!filter) {
|
|
return false;
|
|
}
|
|
|
|
var cacheKey = getCacheKey(filter, false);
|
|
if (!cacheKey) {
|
|
return false;
|
|
}
|
|
var i = 0;
|
|
var len = WorkFlowActions.length;
|
|
for (; i < len; i++) {
|
|
if (WorkFlowActions[i].HasChanges)
|
|
return true;
|
|
}
|
|
var cache = activityCalendarSaveModelCache[cacheKey];
|
|
var changed = false;
|
|
|
|
if (cache && ((!!cache.NonProjectTime && !!Object.keys(cache.NonProjectTime).length) ||
|
|
(!!cache.Scenarios && !!Object.keys(cache.Scenarios).length))) {
|
|
changed = true;
|
|
}
|
|
|
|
return changed;
|
|
},
|
|
getProjectsFromSaveModel: function (filter) {
|
|
var calendar = this.getActivityCalendarFromCache(filter);
|
|
if (!calendar || !calendar.Projects) {
|
|
throw 'Overallocation cannot be checked, because there is no data in the cache';
|
|
}
|
|
|
|
var saveModel = this.getActivityCalendarSaveModel(filter);
|
|
if (!saveModel || !saveModel.Scenarios || !Object.keys(saveModel.Scenarios).length) {
|
|
return false;
|
|
}
|
|
|
|
var getResourcesChangedWeekEndingsFn = function (resources) {
|
|
var result = [];
|
|
|
|
if (!resources) {
|
|
return result;
|
|
}
|
|
|
|
for (var resourceId in resources) {
|
|
var resource = resources[resourceId];
|
|
if (!resource || !resource.ForecastAllocations) {
|
|
continue;
|
|
}
|
|
var resourceWeekEndings = Object.keys(resource.ForecastAllocations);
|
|
result = union(result, resourceWeekEndings);
|
|
}
|
|
|
|
return result;
|
|
};
|
|
var getTeamsChangedWeekEndingsFn = function (teams) {
|
|
var result = [];
|
|
|
|
if (!teams) {
|
|
return result;
|
|
}
|
|
|
|
for (var teamId in teams) {
|
|
var team = teams[teamId];
|
|
if (!team || !team.Allocations) {
|
|
continue;
|
|
}
|
|
var teamWeekEndings = Object.keys(team.Allocations);
|
|
result = union(result, teamWeekEndings);
|
|
}
|
|
|
|
return result;
|
|
};
|
|
var result = [];
|
|
for (var projectId in calendar.Projects) {
|
|
var project = calendar.Projects[projectId];
|
|
if (!project || !project.ActiveScenario) {
|
|
continue;
|
|
}
|
|
|
|
var scenarioId = project.ActiveScenario.Id;
|
|
var scenarioInSaveModel = saveModel.Scenarios[scenarioId];
|
|
if (!scenarioInSaveModel || !scenarioInSaveModel.Expenditures) {
|
|
continue;
|
|
}
|
|
|
|
var projResultItem = {
|
|
ScenarioId: scenarioId,
|
|
Name: project.Name,
|
|
IsBottomUp: project.ActiveScenario.IsBottomUp,
|
|
IsOverallocated: false
|
|
};
|
|
var projectIsOverallocated = false;
|
|
for (var expenditureCategoryId in scenarioInSaveModel.Expenditures) {
|
|
// it does not make sense to check super EC for overallocation, because this kind of ECs always rollup from resource allocations
|
|
var isSuperEC = dataSources.isSuperEC(expenditureCategoryId);
|
|
if (isSuperEC) {
|
|
continue;
|
|
}
|
|
|
|
var category = scenarioInSaveModel.Expenditures[expenditureCategoryId];
|
|
if (!category || !category.Teams) {
|
|
continue;
|
|
}
|
|
|
|
for (var teamId in category.Teams) {
|
|
var team = category.Teams[teamId];
|
|
var resourcesInProject = this.getResourcesByProject(filter, projectId, expenditureCategoryId, [teamId]);
|
|
|
|
// Check for resources
|
|
var changedWeekEndings = getResourcesChangedWeekEndingsFn(team.Resources);
|
|
if (changedWeekEndings && changedWeekEndings.length) {
|
|
for (var weekIndex = 0; weekIndex < changedWeekEndings.length; weekIndex++) {
|
|
var weekEndingMs = changedWeekEndings[weekIndex];
|
|
var teamNeed = this.getTeamAllocations4Scenario4Week(filter, scenarioId, [teamId], expenditureCategoryId, weekEndingMs);
|
|
var resourceAllocation = this.getTotalResourcesAllocations4WeekEnding(filter, resourcesInProject, scenarioId, teamId, expenditureCategoryId, weekEndingMs);
|
|
|
|
if (roundService.roundQuantity(teamNeed || 0) < roundService.roundQuantity(resourceAllocation || 0)) {
|
|
projectIsOverallocated = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (projectIsOverallocated)
|
|
break;
|
|
}
|
|
if (projectIsOverallocated)
|
|
break;
|
|
|
|
// Check for teams
|
|
var changedWeekEndings = getTeamsChangedWeekEndingsFn(category.Teams);
|
|
if (changedWeekEndings && changedWeekEndings.length) {
|
|
var teamIds = Object.keys(category.Teams);
|
|
|
|
for (var weekIndex = 0; weekIndex < changedWeekEndings.length; weekIndex++) {
|
|
var weekEndingMs = changedWeekEndings[weekIndex];
|
|
var expCatNeed = this.getNeed4WeekEnding(filter, scenarioId, expenditureCategoryId, weekEndingMs);
|
|
var allocatedToTeams = this.getTeamAllocations4Scenario4Week(filter, scenarioId, teamIds, expenditureCategoryId, weekEndingMs);
|
|
if (roundService.roundQuantity(expCatNeed || 0) < roundService.roundQuantity(allocatedToTeams || 0)) {
|
|
projectIsOverallocated = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (projectIsOverallocated)
|
|
break;
|
|
}
|
|
projResultItem.IsOverallocated = projectIsOverallocated;
|
|
result.push(projResultItem);
|
|
}
|
|
|
|
return result;
|
|
},
|
|
getChangedProjectsWithSuperECs: function (filter) {
|
|
var projects = [];
|
|
|
|
if (!this.isDataChanged(filter)) {
|
|
return projects;
|
|
}
|
|
|
|
var calendar = this.getActivityCalendarFromCache(filter);
|
|
var saveModel = this.getActivityCalendarSaveModel(filter);
|
|
if (!calendar || !saveModel || !calendar.Projects) {
|
|
return projects;
|
|
}
|
|
|
|
// it is pretty fast to go through all projects and check them in the save model as we call it only once
|
|
for (var projectId in calendar.Projects) {
|
|
var project = calendar.Projects[projectId];
|
|
if (!project || !project.ActiveScenario || !(project.ActiveScenario.Id in saveModel.Scenarios)) {
|
|
continue;
|
|
}
|
|
|
|
var scenarioInSaveModel = saveModel.Scenarios[project.ActiveScenario.Id];
|
|
if (!scenarioInSaveModel || !scenarioInSaveModel.Expenditures) {
|
|
continue;
|
|
}
|
|
|
|
for (var expenditureCategoryId in scenarioInSaveModel.Expenditures) {
|
|
if (dataSources.isSuperEC(expenditureCategoryId)) {
|
|
projects.push({
|
|
ProjectId: projectId,
|
|
ProjectName: project.Name
|
|
});
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return projects;
|
|
},
|
|
getResourcesWithAssignedProjects: function (filter, resources) {
|
|
var calendar = this.getActivityCalendarFromCache(filter, false);
|
|
if (!calendar || !calendar.Projects || !calendar.ResourceAllocations) {
|
|
return null;
|
|
}
|
|
|
|
var requestedResources = (resources && resources.length) ? resources : teamInfoService.getAllResources();
|
|
if (!requestedResources || !requestedResources.length) {
|
|
return null;
|
|
}
|
|
|
|
var map = this.getScenarioToProjectMap(filter);
|
|
var result = {};
|
|
|
|
for (var resourceIndex = 0; resourceIndex < requestedResources.length; resourceIndex++) {
|
|
var resourceId = requestedResources[resourceIndex];
|
|
var resourceAllocations = calendar.ResourceAllocations[resourceId];
|
|
var resourceActuals = calendar.ResourceActuals ? calendar.ResourceActuals[resourceId] : null;
|
|
var resourceHasNptAllocations = teamInfoService.hasResourceNonProjectTimeAllocated(resourceId);
|
|
|
|
if ((!resourceAllocations || !resourceAllocations.Scenarios) && !resourceHasNptAllocations) {
|
|
continue;
|
|
}
|
|
|
|
// Get resource info to fill Name and EC data
|
|
var resourceInfo = dataSources.getResourceById(resourceId);
|
|
var resourceModel = {
|
|
Id: resourceId,
|
|
Name: (resourceInfo && resourceInfo.FirstName && resourceInfo.LastName) ?
|
|
resourceInfo.FirstName + ' ' + resourceInfo.LastName : null,
|
|
OwnExpenditureCategoryId: resourceInfo ? resourceInfo.ExpenditureCategoryId : null,
|
|
Projects: {},
|
|
};
|
|
|
|
if (resourceAllocations && resourceAllocations.Scenarios) {
|
|
for (var scenarioId in resourceAllocations.Scenarios) {
|
|
var projectId = map[scenarioId];
|
|
var project = calendar.Projects[projectId];
|
|
if (!project || !project.ActiveScenario || !project.Teams) {
|
|
continue;
|
|
}
|
|
|
|
var projectModel = {
|
|
ProjectId: project.ProjectId,
|
|
PartId: project.PartId,
|
|
ParentProjectId: project.ParentProjectId,
|
|
ParentName: project.ParentName,
|
|
ExpenditureCategoryId: [],
|
|
Name: project.Name,
|
|
Color: project.Color,
|
|
TypeName: project.TypeName,
|
|
DeadlineMs: project.DeadlineMs,
|
|
Priority: project.Priority,
|
|
ReadOnly: project.ReadOnly,
|
|
ActiveScenario: {
|
|
Id: project.ActiveScenario.Id,
|
|
Name: project.ActiveScenario.Name,
|
|
IsBottomUp: project.ActiveScenario.IsBottomUp,
|
|
StartDate: project.ActiveScenario.StartDate,
|
|
EndDate: project.ActiveScenario.EndDate
|
|
},
|
|
InactiveScenarios: project.InactiveScenarios ? angular.copy(project.InactiveScenarios) : [],
|
|
IsMultipleECs: false,
|
|
Teams: [],
|
|
Allocations: {},
|
|
Actuals: {},
|
|
};
|
|
|
|
var scenario = resourceAllocations.Scenarios[scenarioId];
|
|
if (scenario && scenario.Teams) {
|
|
var targetTeamIds = Object.keys(project.Teams);
|
|
|
|
for (var teamIndex = 0; teamIndex < targetTeamIds.length; teamIndex++) {
|
|
var teamId = targetTeamIds[teamIndex];
|
|
var resourceItem = teamInfoService.getResourceByTeam(teamId, null, resourceId);
|
|
if (resourceItem) {
|
|
var filteredTeams = !!resourceItem.Teams ? $filter('filter')(resourceItem.Teams, { TeamId: teamId }) : [];
|
|
|
|
projectModel.Teams.push({
|
|
TeamId: teamId,
|
|
ReadOnly: filteredTeams.length > 0 ? filteredTeams[0].ReadOnly : false,
|
|
StartDate: resourceItem.StartDate,
|
|
EndDate: resourceItem.EndDate
|
|
});
|
|
|
|
var teamInScenario = scenario.Teams[teamId];
|
|
if (teamInScenario && teamInScenario.Expenditures) {
|
|
for (var expenditureCategoryId in teamInScenario.Expenditures) {
|
|
var category = teamInScenario.Expenditures[expenditureCategoryId];
|
|
if (category && category.Allocations) {
|
|
projectModel.Allocations = calculateDistributionService.mergeAllocations(projectModel.Allocations, category.Allocations);
|
|
if (projectModel.ExpenditureCategoryId.indexOf(expenditureCategoryId) < 0) {
|
|
projectModel.ExpenditureCategoryId.push(expenditureCategoryId);
|
|
}
|
|
}
|
|
|
|
if (resourceActuals && resourceActuals.Scenarios && resourceActuals.Scenarios[scenarioId] &&
|
|
resourceActuals.Scenarios[scenarioId].Teams && resourceActuals.Scenarios[scenarioId].Teams[teamId] &&
|
|
resourceActuals.Scenarios[scenarioId].Teams[teamId].Expenditures &&
|
|
resourceActuals.Scenarios[scenarioId].Teams[teamId].Expenditures[expenditureCategoryId] &&
|
|
resourceActuals.Scenarios[scenarioId].Teams[teamId].Expenditures[expenditureCategoryId].Allocations) {
|
|
|
|
var actuals = resourceActuals.Scenarios[scenarioId].Teams[teamId].Expenditures[expenditureCategoryId].Allocations;
|
|
projectModel.Actuals = calculateDistributionService.mergeAllocations(projectModel.Actuals, actuals);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
projectModel.IsMultipleECs = projectModel.ExpenditureCategoryId.length > 1;
|
|
resourceModel.Projects[projectId] = projectModel;
|
|
}
|
|
}
|
|
|
|
if ((resourceModel.Projects && Object.keys(resourceModel.Projects).length > 0) || resourceHasNptAllocations) {
|
|
result[resourceId] = resourceModel;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
},
|
|
isResourceOverAllocated: function (resourceId, weekEndingMs) {
|
|
if (!resourceId) {
|
|
throw 'resourceId is null or undefined';
|
|
}
|
|
|
|
var capacity = roundService.roundQuantity(teamInfoService.getResourceCapacity(resourceId, weekEndingMs));
|
|
var allocated = roundService.roundQuantity(teamInfoService.getResourceSummaryAllocatedHrs(resourceId, weekEndingMs, true));
|
|
var isOverAllocated = allocated > capacity;
|
|
|
|
return isOverAllocated;
|
|
},
|
|
getProjectRolesInScenario: function (filter, scenarioId, teamIds) {
|
|
if (!filter || !scenarioId || !teamIds || !angular.isArray(teamIds))
|
|
return;
|
|
|
|
var result = {};
|
|
for (var tIndex = 0; tIndex < teamIds.length; tIndex++) {
|
|
var teamId = teamIds[tIndex];
|
|
var teamAllocations = this.getTeamAllocations(filter, teamId);
|
|
|
|
if (teamAllocations && teamAllocations.Scenarios && (scenarioId in teamAllocations.Scenarios)) {
|
|
var scenarioData = teamAllocations.Scenarios[scenarioId];
|
|
|
|
if (scenarioData && scenarioData.Expenditures) {
|
|
for (var expCatId in scenarioData.Expenditures) {
|
|
if (!(expCatId in result) && (dataSources.isSuperEC(expCatId))) {
|
|
result[expCatId] = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return Object.keys(result);
|
|
},
|
|
getWorkFlowActions: function () {
|
|
return WorkFlowActions;
|
|
},
|
|
createTeamAllocationsBackup: function (filter, scenarioId, teamId, expenditureCategoryId) {
|
|
if (!filter || !scenarioId || !teamId || !expenditureCategoryId) {
|
|
return;
|
|
}
|
|
|
|
var calendar = this.getActivityCalendarFromCache(filter, true) || {};
|
|
if (!calendar.TeamAllocations || !(teamId in calendar.TeamAllocations)) {
|
|
return;
|
|
}
|
|
|
|
var taInScenario = (calendar.TeamAllocations[teamId].Scenarios || {})[scenarioId] || {};
|
|
if (!taInScenario.Expenditures || !(expenditureCategoryId in taInScenario.Expenditures)) {
|
|
return;
|
|
}
|
|
|
|
if (!calendar.BackupTeamAllocations) {
|
|
calendar.BackupTeamAllocations = {};
|
|
}
|
|
|
|
if (!(teamId in calendar.BackupTeamAllocations)) {
|
|
calendar.BackupTeamAllocations[teamId] = {
|
|
Scenarios: {}
|
|
};
|
|
}
|
|
|
|
var teamBackup = calendar.BackupTeamAllocations[teamId];
|
|
if (!(scenarioId in teamBackup.Scenarios)) {
|
|
teamBackup.Scenarios[scenarioId] = {
|
|
Expenditures: {}
|
|
};
|
|
}
|
|
|
|
teamBackup.Scenarios[scenarioId].Expenditures[expenditureCategoryId] = angular.copy(taInScenario.Expenditures[expenditureCategoryId]);
|
|
},
|
|
getTeamAllocationsBackup: function (filter, scenarioId, teamId, expenditureCategoryId) {
|
|
if (!filter || !scenarioId || !teamId || !expenditureCategoryId) {
|
|
return null;
|
|
}
|
|
|
|
var calendar = this.getActivityCalendarFromCache(filter, true) || {};
|
|
if (!calendar.BackupTeamAllocations) {
|
|
return null;
|
|
}
|
|
|
|
if (!(teamId in calendar.BackupTeamAllocations)) {
|
|
return null;
|
|
}
|
|
|
|
var teamBackup = calendar.BackupTeamAllocations[teamId];
|
|
if (!(scenarioId in teamBackup.Scenarios)) {
|
|
return null;
|
|
}
|
|
|
|
var scenarioBackup = teamBackup.Scenarios[scenarioId];
|
|
if (!(expenditureCategoryId in scenarioBackup.Expenditures)) {
|
|
return null;
|
|
}
|
|
|
|
return scenarioBackup.Expenditures[expenditureCategoryId].Allocations;
|
|
},
|
|
removeTeamAllocationsBackup: function (filter, scenarioId, teamId, expenditureCategoryId) {
|
|
if (!filter || !scenarioId || !teamId) {
|
|
return;
|
|
}
|
|
|
|
var calendar = this.getActivityCalendarFromCache(filter) || {};
|
|
if (!calendar.BackupTeamAllocations) {
|
|
return;
|
|
}
|
|
|
|
if (!(teamId in calendar.BackupTeamAllocations)) {
|
|
return;
|
|
}
|
|
|
|
var teamBackup = calendar.BackupTeamAllocations[teamId];
|
|
if (!(scenarioId in teamBackup.Scenarios)) {
|
|
return;
|
|
}
|
|
|
|
var scenarioBackup = teamBackup.Scenarios[scenarioId];
|
|
var requiredECs = expenditureCategoryId ? [expenditureCategoryId] : Object.keys(scenarioBackup.Expenditures);
|
|
|
|
for (var i = 0; i < requiredECs.length; i++) {
|
|
delete scenarioBackup.Expenditures[requiredECs[i]];
|
|
}
|
|
if (Object.keys(scenarioBackup.Expenditures).length <= 0) {
|
|
delete teamBackup.Scenarios[scenarioId];
|
|
}
|
|
if (Object.keys(teamBackup.Scenarios).length <= 0) {
|
|
delete calendar.BackupTeamAllocations[teamId];
|
|
}
|
|
},
|
|
filterCategoriesVsTeams: function (expCats, teams) {
|
|
if (!expCats || !expCats.length)
|
|
return expCats;
|
|
|
|
if (!teams || !teams.length)
|
|
return [];
|
|
|
|
var resultExpCatsIndexed = {};
|
|
for (var tIndex = 0; tIndex < teams.length; tIndex++) {
|
|
var teamExpCatsFiltered = teamInfoService.getExpenditureCategoriesInTeam(teams[tIndex], expCats);
|
|
|
|
if (teamExpCatsFiltered && angular.isArray(teamExpCatsFiltered)) {
|
|
for (var eIndex = 0; eIndex < teamExpCatsFiltered.length; eIndex++) {
|
|
var expCatId = teamExpCatsFiltered[eIndex];
|
|
|
|
if (!(expCatId in resultExpCatsIndexed)) {
|
|
resultExpCatsIndexed[expCatId] = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var result = Object.keys(resultExpCatsIndexed);
|
|
return result;
|
|
},
|
|
isClientSideFilteringUsed: function (filter) {
|
|
if (!filter)
|
|
return;
|
|
|
|
// Data is always filtered by project roles
|
|
return true;
|
|
},
|
|
convertToDictionary: function (arrayOfScalarItems) {
|
|
if (!arrayOfScalarItems || !angular.isArray(arrayOfScalarItems))
|
|
return null;
|
|
|
|
var result = {}
|
|
var itemsCount = arrayOfScalarItems.length;
|
|
for (var index = 0; index < itemsCount; index++) {
|
|
var value = arrayOfScalarItems[index];
|
|
|
|
if (!(value in result)) {
|
|
result[value] = true;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
},
|
|
createAvailableExpendituresCache: function (filter) {
|
|
if (!this.isClientSideFilteringUsed(filter))
|
|
return;
|
|
|
|
this.availableExpendituresCache = {};
|
|
var costCentersIndexed = null;
|
|
var projectRolesIndexed = {};
|
|
|
|
if (filter.CostCenters && angular.isArray(filter.CostCenters) && filter.CostCenters.length) {
|
|
// Reorganise Cost Centers list to check fast an EC fits selected cost centers
|
|
costCentersIndexed = this.convertToDictionary(filter.CostCenters);
|
|
}
|
|
|
|
if (filter.ProjectRoles && angular.isArray(filter.ProjectRoles) && filter.ProjectRoles.length) {
|
|
// Reorganise selected Project Roles to perform fast checks
|
|
projectRolesIndexed = this.convertToDictionary(filter.ProjectRoles);
|
|
}
|
|
|
|
// Create cached list of expenditures, that fit selected cost centers, for fast filtering AC cached data
|
|
var expCats = dataSources.getExpenditures();
|
|
if (expCats) {
|
|
for (var expCatId in expCats) {
|
|
var expCatItem = expCats[expCatId];
|
|
var isProjectRole = !expCatItem.AllowResourceAssignment;
|
|
var selectedByUserInFilter = expCatId in projectRolesIndexed;
|
|
var hasNonZeroAllocations = false;
|
|
|
|
if (expCatItem != null) {
|
|
// Check the EC fits filter by Cost Centers (if no any Cost Center selected, EC fits filter)
|
|
var fitsFilter = !costCentersIndexed || (expCatItem.CreditId && (expCatItem.CreditId in costCentersIndexed));
|
|
|
|
if (fitsFilter && isProjectRole) {
|
|
// If EC is a Project Role, check it has non-zero team allocations
|
|
hasNonZeroAllocations = teamInfoService.expenditureCategoryHasNonZeroTeamAllocations(expCatId);
|
|
fitsFilter = hasNonZeroAllocations;
|
|
|
|
if (!fitsFilter) {
|
|
// Project Role has no team allocations, check if it selected in the Project Roles filter
|
|
fitsFilter = selectedByUserInFilter;
|
|
}
|
|
}
|
|
|
|
if (fitsFilter) {
|
|
// EC fits filter. This data struct is used in getAlwaysVisibleProjectRolesByClientSideFilter method
|
|
this.availableExpendituresCache[expCatId] = {
|
|
IsProjectRole: isProjectRole,
|
|
HasNonZeroAllocations: hasNonZeroAllocations,
|
|
SelectedByUser: selectedByUserInFilter
|
|
};
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
getAlwaysVisibleProjectRolesByClientSideFilter: function () {
|
|
var result = [];
|
|
|
|
if (this.availableExpendituresCache) {
|
|
// Client-side filtering is used
|
|
for (var expCatId in this.availableExpendituresCache) {
|
|
var expCatInfo = this.availableExpendituresCache[expCatId];
|
|
|
|
if (expCatInfo && expCatInfo.IsProjectRole && expCatInfo.SelectedByUser) {
|
|
// EC is Project Role and should be always visible (inspite of even zero allocations),
|
|
// because it was selected by user in client-side filter of Project Roles
|
|
result.push(expCatId);
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
},
|
|
createActivityCalendarPreFilteredCache: function (filter) {
|
|
if (!filter)
|
|
throw "createActivityCalendarPreFilteredCache: incoming filter not specified";
|
|
|
|
console.time("createActivityCalendarPreFilteredCache");
|
|
|
|
if (!this.isClientSideFilteringUsed(filter)) {
|
|
// Remove filtered cache, if exists
|
|
this.invalidatePreFilteredCache(filter, false);
|
|
return;
|
|
}
|
|
|
|
// We need to create fast-access expenditures list, that fit selected Cost Centers
|
|
// And create prefiltered calendar data cache.
|
|
// sourceCacheKey equals prefilteredCacheKey, when slient-side filters are empty (no items selected)
|
|
this.createAvailableExpendituresCache(filter);
|
|
|
|
// Create prefiltered data
|
|
var calendarSourceData = this.getActivityCalendarFromCache(filter, true);
|
|
var filtered = angular.copy(calendarSourceData);
|
|
|
|
this.filterCalendarNeedAllocations(filtered.NeedAllocations);
|
|
this.filterCalendarResourceAllocations(filtered.ResourceAllocations);
|
|
this.filterCalendarTeamAllocations(filtered.TeamAllocations, filtered.RestData);
|
|
this.filterCalendarUnassignedNeed(filtered.UnassignedNeed);
|
|
|
|
if (filtered.BackupTeamAllocations) {
|
|
delete filtered.BackupTeamAllocations;
|
|
}
|
|
|
|
var prefilteredCacheKey = getCacheKey(filter, true);
|
|
activityCalendarCache[prefilteredCacheKey] = filtered;
|
|
|
|
console.timeEnd("createActivityCalendarPreFilteredCache");
|
|
},
|
|
invalidatePreFilteredCache: function (filter, forceRecreate) {
|
|
var prefilteredCacheKey = getCacheKey(filter, true);
|
|
|
|
if (prefilteredCacheKey in activityCalendarCache) {
|
|
delete activityCalendarCache[prefilteredCacheKey];
|
|
}
|
|
teamInfoService.resetFilter();
|
|
|
|
if (forceRecreate) {
|
|
// Get activity calendar data from cache to force recreate it, if prefiltered cached used
|
|
// in current display&filter mode. Trying to get prefiltered cache, which is not currently
|
|
// exist, will force instant recreating it.
|
|
getActivityCalendarFromCache(filter, false);
|
|
}
|
|
},
|
|
filterCalendarNeedAllocations: function (needAllocations) {
|
|
if (!needAllocations)
|
|
return;
|
|
|
|
var scenarioIds = Object.keys(needAllocations);
|
|
var scenariosCount = scenarioIds.length;
|
|
|
|
for (var sIndex = 0; sIndex < scenariosCount; sIndex++) {
|
|
var scenarioId = scenarioIds[sIndex];
|
|
var scenarioData = needAllocations[scenarioId];
|
|
|
|
if (scenarioData && scenarioData.Expenditures) {
|
|
var expCatIds = Object.keys(scenarioData.Expenditures);
|
|
var expCatsCount = expCatIds.length;
|
|
|
|
for (var eIndex = 0; eIndex < expCatsCount; eIndex++) {
|
|
var expCatId = expCatIds[eIndex];
|
|
|
|
if (!(expCatId in this.availableExpendituresCache)) {
|
|
// Remove EC, because it is not fit available expenditures list
|
|
delete needAllocations[scenarioId].Expenditures[expCatId];
|
|
}
|
|
}
|
|
|
|
if (!Object.keys(scenarioData.Expenditures).length) {
|
|
// Remove scenario at all, because it has no longer expenditures, that fit filter
|
|
delete needAllocations[scenarioId];
|
|
}
|
|
}
|
|
}
|
|
},
|
|
filterCalendarResourceAllocations: function (resourceAllocations) {
|
|
if (!resourceAllocations)
|
|
return;
|
|
|
|
var resourceIds = Object.keys(resourceAllocations);
|
|
var resourcesCount = resourceIds.length;
|
|
|
|
for (var rIndex = 0; rIndex < resourcesCount; rIndex++) {
|
|
var resourceId = resourceIds[rIndex];
|
|
var resourceData = resourceAllocations[resourceId];
|
|
|
|
if (resourceData && resourceData.Scenarios) {
|
|
var scenarioIds = Object.keys(resourceData.Scenarios);
|
|
var scenariosCount = scenarioIds.length;
|
|
|
|
for (var sIndex = 0; sIndex < scenariosCount; sIndex++) {
|
|
var scenarioId = scenarioIds[sIndex];
|
|
var scenarioData = resourceData.Scenarios[scenarioId];
|
|
|
|
if (scenarioData && scenarioData.Teams) {
|
|
var teamIds = Object.keys(scenarioData.Teams);
|
|
var teamsCount = teamIds.length;
|
|
|
|
for (var tIndex = 0; tIndex < teamsCount; tIndex++) {
|
|
var teamId = teamIds[tIndex];
|
|
var teamData = scenarioData.Teams[teamId];
|
|
|
|
if (teamData && teamData.Expenditures) {
|
|
var expCatIds = Object.keys(teamData.Expenditures);
|
|
var expCatsCount = expCatIds.length;
|
|
|
|
for (var eIndex = 0; eIndex < expCatsCount; eIndex++) {
|
|
var expCatId = expCatIds[eIndex];
|
|
|
|
if (!(expCatId in this.availableExpendituresCache)) {
|
|
// Remove EC, because it is not fit available expenditures list
|
|
delete resourceAllocations[resourceId].Scenarios[scenarioId].Teams[teamId].Expenditures[expCatId];
|
|
}
|
|
}
|
|
|
|
if (!Object.keys(teamData.Expenditures).length) {
|
|
delete resourceAllocations[resourceId].Scenarios[scenarioId].Teams[teamId];
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!Object.keys(scenarioData.Teams).length) {
|
|
delete resourceAllocations[resourceId].Scenarios[scenarioId];
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!Object.keys(resourceData.Scenarios).length) {
|
|
delete resourceAllocations[resourceId];
|
|
}
|
|
}
|
|
}
|
|
},
|
|
filterCalendarTeamAllocations: function (teamAllocations) {
|
|
if (!teamAllocations)
|
|
return;
|
|
|
|
var teamIds = Object.keys(teamAllocations);
|
|
var teamsCount = teamIds.length;
|
|
|
|
for (var tIndex = 0; tIndex < teamsCount; tIndex++) {
|
|
var teamId = teamIds[tIndex];
|
|
var teamData = teamAllocations[teamId];
|
|
|
|
if (teamData && teamData.Scenarios) {
|
|
var scenarioIds = Object.keys(teamData.Scenarios);
|
|
var scenariosCount = scenarioIds.length;
|
|
|
|
for (var sIndex = 0; sIndex < scenariosCount; sIndex++) {
|
|
var scenarioId = scenarioIds[sIndex];
|
|
var scenarioData = teamData.Scenarios[scenarioId];
|
|
|
|
if (scenarioData && scenarioData.Expenditures) {
|
|
var expCatIds = Object.keys(scenarioData.Expenditures);
|
|
var expCatsCount = expCatIds.length;
|
|
|
|
for (var eIndex = 0; eIndex < expCatsCount; eIndex++) {
|
|
var expCatId = expCatIds[eIndex];
|
|
|
|
if (!(expCatId in this.availableExpendituresCache)) {
|
|
// Remove EC, because it is not fit available expenditures list
|
|
delete teamAllocations[teamId].Scenarios[scenarioId].Expenditures[expCatId];
|
|
}
|
|
}
|
|
|
|
if (!Object.keys(scenarioData.Expenditures).length) {
|
|
delete teamAllocations[teamId].Scenarios[scenarioId];
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!Object.keys(teamData.Scenarios).length) {
|
|
delete teamAllocations[teamId];
|
|
}
|
|
}
|
|
}
|
|
},
|
|
filterCalendarUnassignedNeed: function (unassignedNeed) {
|
|
if (!unassignedNeed)
|
|
return;
|
|
|
|
var scenarioIds = Object.keys(unassignedNeed);
|
|
var scenariosCount = scenarioIds.length;
|
|
|
|
for (var sIndex = 0; sIndex < scenariosCount; sIndex++) {
|
|
var scenarioId = scenarioIds[sIndex];
|
|
var scenarioData = unassignedNeed[scenarioId];
|
|
|
|
if (scenarioData && scenarioData.Expenditures) {
|
|
var expCatIds = Object.keys(scenarioData.Expenditures);
|
|
var expCatsCount = expCatIds.length;
|
|
|
|
for (var eIndex = 0; eIndex < expCatsCount; eIndex++) {
|
|
var expCatId = expCatIds[eIndex];
|
|
|
|
if (!(expCatId in this.availableExpendituresCache)) {
|
|
// Remove EC, because it is not fit available expenditures list
|
|
delete unassignedNeed[scenarioId].Expenditures[expCatId];
|
|
}
|
|
}
|
|
|
|
if (!Object.keys(scenarioData.Expenditures).length) {
|
|
// Remove scenario at all, because it has no longer expenditures, that fit filter
|
|
delete unassignedNeed[scenarioId];
|
|
}
|
|
}
|
|
}
|
|
},
|
|
};
|
|
|
|
return service;
|
|
}]);
|
|
enVisageServices.factory('noteService', ['$q', 'dataSources', function ($q, dataSources) {
|
|
var NoteTypeScenario = 2;
|
|
var NoteTypeProject = 1;
|
|
var NoteTypeExpenditureCat = 3;
|
|
var serviceContainer = {
|
|
getNotesForScenario: function (ScenarioId) {
|
|
return dataSources.loadNotes(null, ScenarioId, NoteTypeScenario);
|
|
},
|
|
getNotesForProject: function (ProjectId) {
|
|
return dataSources.loadNotes(null, ProjectId, NoteTypeProject);
|
|
},
|
|
getNotesForEC: function (ScenarioId, ExpenditureCategoryId) {
|
|
return dataSources.loadNotes(ScenarioId, ExpenditureCategoryId, NoteTypeExpenditureCat);
|
|
},
|
|
getNote: function (NoteId) {
|
|
return dataSources.LoadNote(NoteId);
|
|
},
|
|
EditNote: function (note) {
|
|
return dataSources.EditNote(note);
|
|
},
|
|
RemoveNote: function (NoteId) {
|
|
return dataSources.RemoveNote(NoteId);
|
|
},
|
|
CacheData: function (Note) {
|
|
return dataSources.CacheNote(Note);
|
|
},
|
|
LoadFromCache: function (ParentId) {
|
|
return dataSources.LoadFromCache(ParentId);
|
|
},
|
|
ClearCache: function () {
|
|
return dataSources.ClearCache();
|
|
},
|
|
RemoveFromCache: function (note) {
|
|
return dataSources.RemoveFromCache(note);
|
|
}
|
|
|
|
}
|
|
return serviceContainer;
|
|
}]);
|
|
enVisageServices.filter('sortObjectsBy', ['$filter', function ($filter) {
|
|
return function (items, field, reverse) {
|
|
var filtered = [];
|
|
angular.forEach(items, function (item) {
|
|
filtered.push(item);
|
|
});
|
|
return $filter('orderBy')(filtered, field, reverse);
|
|
};
|
|
}]); |