EnVisageOnline/Main/Source/EnVisage/Scripts/Angular/Controllers/ActivityCalendarControllers/activityCalendarByCostCente...

1072 lines
47 KiB
JavaScript

'use strict';
app
.controller('activityCalendarByCostCenterController', ['$scope', '$q', 'activityCalendarService', 'teamInfoService', 'activityCalendarUIService', 'dataSources', 'hoursResourcesConverter', '$filter', function ($scope, $q, activityCalendarService, teamInfoService, activityCalendarUIService, dataSources, hoursResourcesConverter, $filter) {
var commonErrorMessage = 'An error occurred while processing your request. Please, try again later.';
var calendarDataUrl = '/CapacityManagement/GetActivityCalendar';
var nonProjectTimeCache = {}; // cache for non-project time data
var nonProjectTimeCategoryCache = {}; // cache for non-project time categories data
var costCenterExpendituresCache = {}; // cache for ECs to cost center links. Key: costCenterId
$scope.ViewModel = {
DisplayMode: null,
Header: null,
Rows: null,
TotalRow: null,
RemainingCapacityRow: null,
NonProjectTimeTotalRow: null,
DataLoaded: false
};
$scope.Filter = null;
/* Event listeners */
$scope.$on('ac.rebuild-calendar', rebuildCalendarHandler);
$scope.$on('ac.options-changed', optionsChangedHandler);
/* Methods for interaction with view */
$scope.init = function (filter, displayMode) {
rebuildCalendarHandler(null, filter, displayMode);
};
$scope.toggleMonth = function (monthIndex) {
$scope.ViewModel.Header.toggleMonth(monthIndex);
};
$scope.toggleRow = function (uiRow, $event) {
hideRedundantPopovers();
if ($($event.target).parents('.menuGroup').length > 0)
return;
// to-do: we should keep collapsed state in data-model items between possible future postbacks
var dataModel = activityCalendarService.dataItemStub || {};
activityCalendarUIService.toggleRow(uiRow, dataModel);
};
$scope.ToggleStatus = function ($event, projectId, scenario) {
var btn = $($event.target).closest('.popover-warning');
if (btn.length == 1) {
activityCalendarUIService.toggleStatus(btn, projectId, scenario)
.then(function (result) {
if (!!result)
rebuildCalendarHandler($event, $scope.Filter, $scope.ViewModel.DisplayMode, true);
})
.then(null, function () {
showErrorModal(null, 'An error occurred while toggling scenario status', '000009');
});
}
};
$scope.AddScenario = function ($event, projectId) {
activityCalendarUIService.openCreateScenarioWz(projectId);
};
$scope.formatPeopleResourceOption = function (result, container, query, escapeMarkup) {
return activityCalendarUIService.formatPeopleResourceOption(result.element);
};
$scope.assignResource = function (projectId, expCatRow, resourceId, teamId) {
if (!projectId || !expCatRow || !resourceId) {
return;
}
var rowToAddResource = expCatRow;
if (teamId) {
for (var teamKey in expCatRow.Children) {
if (teamId != expCatRow.Children[teamKey].Id)
continue;
rowToAddResource = expCatRow.Children[teamKey];
}
}
activityCalendarUIService.assignResource($scope.Filter, $scope.ViewModel.Header, projectId, expCatRow, resourceId, rowToAddResource);
dataChanged();
};
$scope.assignTeam = function (projectRow, expCatRow) {
if (!projectRow || !expCatRow || !expCatRow.TeamToAssignId) {
return;
}
// prepare extendModel to keep new added team info
var extendedModel = {
Expenditures2Display: {}
};
if (projectRow.Children) {
for (var ecIndex = 0; ecIndex < projectRow.Children.length; ecIndex++) {
var ecRow = projectRow.Children[ecIndex];
if (ecRow && ecRow.AvailableTeams) {
for (var tIndex = 0; tIndex < ecRow.AvailableTeams.length; tIndex++) {
if (ecRow.AvailableTeams[tIndex].Id == expCatRow.TeamToAssignId) {
extendedModel.Expenditures2Display[ecRow.Id] = ecRow.Id;
break;
}
}
}
}
}
// assign new team to DAL model
activityCalendarUIService.assignTeam($scope.Filter, $scope.ViewModel.Header, projectRow.Id, expCatRow.TeamToAssignId, extendedModel);
// add new team row to each EC of the project if it is available for assignment
if (projectRow.Children) {
for (var ecIndex = 0; ecIndex < projectRow.Children.length; ecIndex++) {
var ecRow = projectRow.Children[ecIndex];
if (ecRow && ecRow.AvailableTeams) {
for (var tIndex = 0; tIndex < ecRow.AvailableTeams.length; tIndex++) {
if (ecRow.AvailableTeams[tIndex].Id == expCatRow.TeamToAssignId) {
createAndFillNewEcTeamRow(projectRow, ecRow, expCatRow.TeamToAssignId);
// expand ec row
if (ecRow.Collapsed)
activityCalendarUIService.toggleRow(ecRow, {}, false);
break;
}
}
}
}
}
activityCalendarUIService.refreshProjectAvailableTeams($scope.Filter, projectRow);
expCatRow.TeamToAssignId = null;
dataChanged();
};
$scope.checkResourceValue = function (projectRow, expCatRow, resRow, $index, $data, teamRow) {
if (!projectRow || !expCatRow || !resRow) {
return;
}
var filter = $scope.Filter,
header = $scope.ViewModel.Header,
isUOMHours = $scope.ViewModel.DisplayMode.IsUOMHours,
isAvgMode = $scope.ViewModel.DisplayMode.IsAvgMode(),
parentRow = teamRow || expCatRow || {};
var result = activityCalendarUIService.checkResourceValue(filter, header, projectRow.Id, parentRow, resRow.Id, resRow, isUOMHours, isAvgMode, $index, $data, false);
if (typeof result === 'object' && angular.isArray(result) && result.length > 0) {
triggerResourceChanged(result);
recalculateResourceAvailability(resRow.Id);
refreshResourceStyles(resRow.Id, result);
activityCalendarUIService.refreshProjectStyles($scope.ViewModel.Rows, $scope.ViewModel.Header, $scope.Filter, projectRow, result);
dataChanged();
return false;
}
return result;
};
$scope.checkTeamValueUnassigned = function (projectRow, expCatRow, teamRow, $index, $data) {
if (!projectRow || !expCatRow || !teamRow) {
return;
}
var filter = $scope.Filter,
header = $scope.ViewModel.Header,
isUOMHours = $scope.ViewModel.DisplayMode.IsUOMHours,
isAvgMode = $scope.ViewModel.DisplayMode.IsAvgMode(),
startDate = projectRow.ActiveScenario.StartDate,
endDate = projectRow.ActiveScenario.EndDate;
var result = activityCalendarUIService.checkTeamValue(filter, header, projectRow.Id, expCatRow, teamRow.Id, teamRow, isUOMHours, isAvgMode, $index, $data);
onChangeTeamDataUnassigned(filter, header, projectRow, expCatRow, teamRow, startDate, endDate, isUOMHours, isAvgMode, result);
return false;
};
$scope.checkResourceGrandTotalValue = function (projectRow, expCatRow, resRow, $data, teamRow) {
if (!projectRow || !projectRow.ActiveScenario || !expCatRow || !resRow) {
return;
}
var filter = $scope.Filter,
header = $scope.ViewModel.Header,
isUOMHours = $scope.ViewModel.DisplayMode.IsUOMHours,
isAvgMode = $scope.ViewModel.DisplayMode.IsAvgMode(),
parentRow = teamRow || expCatRow || {};
var result = activityCalendarUIService.checkResourceGrandTotalValue(filter, header, projectRow.Id, projectRow.ActiveScenario.StartDate, projectRow.ActiveScenario.EndDate,
parentRow, resRow.Id, resRow, isUOMHours, isAvgMode, $data, false);
if (typeof result === 'object' && angular.isArray(result) && result.length > 0) {
triggerResourceChanged(result);
recalculateResourceAvailability(resRow.Id);
refreshResourceStyles(resRow.Id, result);
activityCalendarUIService.refreshProjectStyles($scope.ViewModel.Rows, $scope.ViewModel.Header, $scope.Filter, projectRow, result);
dataChanged();
return false;
}
return result;
};
$scope.checkTeamGrandTotalValueUnassigned = function (projectRow, expCatRow, teamRow, $data) {
if (!projectRow || !projectRow.ActiveScenario || !expCatRow || !teamRow) {
return;
}
var filter = $scope.Filter,
header = $scope.ViewModel.Header,
isUOMHours = $scope.ViewModel.DisplayMode.IsUOMHours,
isAvgMode = $scope.ViewModel.DisplayMode.IsAvgMode(),
startDate = projectRow.ActiveScenario.StartDate,
endDate = projectRow.ActiveScenario.EndDate;
var result = activityCalendarUIService.checkTeamGrandTotalValue(filter, header, projectRow.Id, startDate, endDate, expCatRow, teamRow.Id, teamRow, isUOMHours, isAvgMode, $data);
onChangeTeamDataUnassigned(filter, header, projectRow, expCatRow, teamRow, startDate, endDate, isUOMHours, isAvgMode, result);
return false;
};
$scope.takeRemaining = function (projectRow, expCatRow, resRow, teamRow) {
if (!projectRow || !projectRow.ActiveScenario || !expCatRow || !resRow) {
return;
}
var filter = $scope.Filter,
header = $scope.ViewModel.Header,
isUOMHours = $scope.ViewModel.DisplayMode.IsUOMHours,
isAvgMode = $scope.ViewModel.DisplayMode.IsAvgMode(),
parentRow = teamRow || expCatRow || {};
var result = activityCalendarUIService.takeRemainingCapacity(filter, header, projectRow.Id, projectRow.ActiveScenario.StartDate, projectRow.ActiveScenario.EndDate,
parentRow, resRow.Id, resRow, isUOMHours, isAvgMode);
if (typeof result === 'object' && angular.isArray(result) && result.length > 0) {
triggerResourceChanged(result);
recalculateResourceAvailability(resRow.Id);
refreshResourceStyles(resRow.Id, result);
activityCalendarUIService.refreshProjectStyles($scope.ViewModel.Rows, $scope.ViewModel.Header, $scope.Filter, projectRow, result);
dataChanged();
}
};
$scope.takeAll = function (projectRow, expCatRow, resRow, teamRow) {
if (!projectRow || !projectRow.ActiveScenario || !expCatRow || !resRow) {
return;
}
var filter = $scope.Filter,
header = $scope.ViewModel.Header,
isUOMHours = $scope.ViewModel.DisplayMode.IsUOMHours,
isAvgMode = $scope.ViewModel.DisplayMode.IsAvgMode(),
parentRow = teamRow || expCatRow || {};
var result = activityCalendarUIService.takeFullCapacity(filter, header, projectRow.Id, projectRow.ActiveScenario.StartDate, projectRow.ActiveScenario.EndDate,
parentRow, resRow.Id, resRow, isUOMHours, isAvgMode);
if (typeof result === 'object' && angular.isArray(result) && result.length > 0) {
triggerResourceChanged(result);
recalculateResourceAvailability(resRow.Id);
refreshResourceStyles(resRow.Id, result);
activityCalendarUIService.refreshProjectStyles($scope.ViewModel.Rows, $scope.ViewModel.Header, $scope.Filter, projectRow, result);
dataChanged();
}
};
$scope.zeroResource = function (projectRow, expCatRow, resRow, teamRow) {
if (!projectRow || !projectRow.ActiveScenario || !expCatRow || !resRow) {
return;
}
bootbox.confirm({
message: "Are you sure you want to fill this resource quantities with 0s?",
callback: function (result) {
$scope.$apply(function () {
if (result) {
var filter = $scope.Filter,
header = $scope.ViewModel.Header,
isUOMHours = $scope.ViewModel.DisplayMode.IsUOMHours,
isAvgMode = $scope.ViewModel.DisplayMode.IsAvgMode(),
parentRow = teamRow || expCatRow || {};
var changedCells = activityCalendarUIService.zeroResource(filter, header, projectRow.Id, projectRow.ActiveScenario.StartDate, projectRow.ActiveScenario.EndDate,
parentRow, resRow.Id, resRow, isUOMHours, isAvgMode);
if (typeof changedCells === 'object' && angular.isArray(changedCells) && changedCells.length > 0) {
triggerResourceChanged(changedCells);
recalculateResourceAvailability(resRow.Id);
refreshResourceStyles(resRow.Id, changedCells);
activityCalendarUIService.refreshProjectStyles($scope.ViewModel.Rows, $scope.ViewModel.Header, $scope.Filter, projectRow, changedCells);
dataChanged();
}
}
});
},
className: "bootbox-sm"
});
};
$scope.removeResource = function (projectRow, expCatRow, teamRow, resRow, $index) {
if (!projectRow || !projectRow.ActiveScenario || !expCatRow || !resRow) {
return;
}
bootbox.confirm({
message: "Are you sure you want to remove this resource?",
callback: function (result) {
$scope.$apply(function () {
if (result) {
var filter = $scope.Filter,
header = $scope.ViewModel.Header,
isUOMHours = $scope.ViewModel.DisplayMode.IsUOMHours,
isAvgMode = $scope.ViewModel.DisplayMode.IsAvgMode(),
expCatId = (teamRow && teamRow.ExpenditureCategoryId) || expCatRow.Id,
parentRow = teamRow || expCatRow;
var changedCells = activityCalendarUIService.cleanAndRemoveResource(filter, header, projectRow.Id, projectRow.ActiveScenario.StartDate,
projectRow.ActiveScenario.EndDate, expCatId, parentRow, resRow.Id, resRow, isUOMHours, isAvgMode, $index);
if (typeof changedCells === 'object' && angular.isArray(changedCells) && changedCells.length > 0) {
triggerResourceChanged(changedCells);
recalculateResourceAvailability(resRow.Id);
refreshResourceStyles(resRow.Id, changedCells);
activityCalendarUIService.refreshProjectStyles($scope.ViewModel.Rows, $scope.ViewModel.Header, $scope.Filter, projectRow, changedCells);
dataChanged();
}
}
});
},
className: "bootbox-sm"
});
};
$scope.checkNptResourceValue = function (nptCatRow, nptItemRow, nptResourceRow, $index, $data) {
if (!nptCatRow || !nptItemRow || !nptResourceRow)
return;
var filter = $scope.Filter,
header = $scope.ViewModel.Header,
isUOMHours = $scope.ViewModel.DisplayMode.IsUOMHours,
isAvgMode = $scope.ViewModel.DisplayMode.IsAvgMode(),
nptTotalRow = $scope.ViewModel.NonProjectTimeTotalRow;
var rollupRows = [];
rollupRows.push(nptTotalRow);
rollupRows.push(nptCatRow);
rollupRows.push(nptItemRow);
rollupRows.push($scope.ViewModel.TotalRow);
var result = activityCalendarUIService.checkNptResourceValue(filter, header, $scope.ViewModel.RemainingCapacityRow,
rollupRows, nptCatRow, nptItemRow, nptResourceRow, isUOMHours, isAvgMode, $index, $data);
// if result is not false it is incorrect one and we do not need to recalculate availability in this case
if (typeof result === 'object' && angular.isArray(result) && result.length > 0) {
triggerNptResourceChanged(result);
recalculateResourceAvailability(nptResourceRow.Id);
dataChanged();
return false;
}
return result;
};
$scope.checkTeamWideNptValue = function (nptCatRow, nptItemRow, $index, $data) {
if (!nptCatRow || !nptItemRow)
return;
var filter = $scope.Filter,
header = $scope.ViewModel.Header,
isUOMHours = $scope.ViewModel.DisplayMode.IsUOMHours,
isAvgMode = $scope.ViewModel.DisplayMode.IsAvgMode(),
nptTotalRow = $scope.ViewModel.NonProjectTimeTotalRow;
var rollupRows = [];
rollupRows.push(nptTotalRow);
rollupRows.push(nptCatRow);
rollupRows.push(nptItemRow);
rollupRows.push($scope.ViewModel.TotalRow);
var result = activityCalendarUIService.checkTeamWideNptValue(filter, header, $scope.ViewModel.RemainingCapacityRow,
rollupRows, nptCatRow, nptItemRow, isUOMHours, isAvgMode, $index, $data);
// if result is not false it is incorrect one and we do not need to recalculate availability in this case
if (typeof result === 'object' && angular.isArray(result) && result.length > 0) {
triggerNptResourceChanged(result);
// Recalculate resource availability for all related resources
for (var i = 0; i < result.length; i++) {
recalculateResourceAvailability(result[i].ResourceId);
}
dataChanged();
return false;
}
return result;
};
$scope.takeTeamRemainingUnassigned = function (projectRow, expCatRow, teamRow) {
if (!projectRow || !projectRow.ActiveScenario || !expCatRow || !teamRow) {
return;
}
var filter = $scope.Filter,
header = $scope.ViewModel.Header,
isUOMHours = $scope.ViewModel.DisplayMode.IsUOMHours,
isAvgMode = $scope.ViewModel.DisplayMode.IsAvgMode(),
startDate = projectRow.ActiveScenario.StartDate,
endDate = projectRow.ActiveScenario.EndDate;
var result = activityCalendarUIService.takeTeamRemainingCapacity(filter, header, projectRow.Id, startDate, endDate,
expCatRow, teamRow, isUOMHours, isAvgMode);
onChangeTeamDataUnassigned(filter, header, projectRow, expCatRow, teamRow, startDate, endDate, isUOMHours, isAvgMode, result);
};
$scope.takeTeamAllUnassigned = function (projectRow, expCatRow, teamRow) {
if (!projectRow || !projectRow.ActiveScenario || !expCatRow || !teamRow)
return;
var filter = $scope.Filter,
header = $scope.ViewModel.Header,
isUOMHours = $scope.ViewModel.DisplayMode.IsUOMHours,
isAvgMode = $scope.ViewModel.DisplayMode.IsAvgMode(),
startDate = projectRow.ActiveScenario.StartDate,
endDate = projectRow.ActiveScenario.EndDate;
var result = activityCalendarUIService.takeTeamFullCapacity(filter, header, projectRow.Id, startDate, endDate,
expCatRow, teamRow, isUOMHours, isAvgMode);
onChangeTeamDataUnassigned(filter, header, projectRow, expCatRow, teamRow, startDate, endDate, isUOMHours, isAvgMode, result);
};
$scope.zeroTeamUnassigned = function (projectRow, expCatRow, teamRow) {
if (!projectRow || !projectRow.ActiveScenario || !expCatRow || !teamRow) {
return;
}
bootbox.confirm({
message: "Are you sure you want to fill this Team allocations with 0s?",
callback: function (result) {
$scope.$apply(function () {
if (result) {
var filter = $scope.Filter,
header = $scope.ViewModel.Header,
isUOMHours = $scope.ViewModel.DisplayMode.IsUOMHours,
isAvgMode = $scope.ViewModel.DisplayMode.IsAvgMode(),
startDate = projectRow.ActiveScenario.StartDate,
endDate = projectRow.ActiveScenario.EndDate;
var changedCells = activityCalendarUIService.zeroTeam(filter, header, projectRow.Id, startDate,
endDate, expCatRow, teamRow, isUOMHours, isAvgMode);
onChangeTeamDataUnassigned(filter, header, projectRow, expCatRow, teamRow, startDate, endDate, isUOMHours, isAvgMode, changedCells);
}
});
},
className: "bootbox-sm"
});
};
$scope.removeTeamUnassigned = function (prjRow, expCatRow, teamRow, $index) {
if (!prjRow || !prjRow.ActiveScenario || !expCatRow || !teamRow) {
return;
}
bootbox.confirm({
message: "Are you sure you want to remove this team?",
callback: function (result) {
$scope.$apply(function () {
if (result) {
var filter = $scope.Filter,
header = $scope.ViewModel.Header,
isUOMHours = $scope.ViewModel.DisplayMode.IsUOMHours,
isAvgMode = $scope.ViewModel.DisplayMode.IsAvgMode(),
teamId = teamRow.Id,
startDate = prjRow.ActiveScenario.StartDate,
endDate = prjRow.ActiveScenario.EndDate;
var changedCellsByExpCats = activityCalendarUIService.cleanAndRemoveTeam(filter, header, prjRow, teamId, startDate,
endDate, isUOMHours, isAvgMode);
if ((typeof changedCellsByExpCats === 'object') && (Object.keys(changedCellsByExpCats).length > 0)) {
for (var expCatId in changedCellsByExpCats) {
var currentExpCatRow = changedCellsByExpCats[expCatId].ExpCatRow;
var changedCells = changedCellsByExpCats[expCatId].ChangedCells;
onChangeTeamDataUnassigned(filter, header, prjRow, currentExpCatRow, teamRow, startDate, endDate, isUOMHours, isAvgMode, changedCells);
}
}
activityCalendarUIService.refreshProjectAvailableTeams($scope.Filter, prjRow);
}
});
},
className: "bootbox-sm"
});
};
$scope.watchKeyInput = function (t) {
activityCalendarUIService.watchKeyInput(t);
};
$scope.onTxtBlur = function (t) {
activityCalendarUIService.onTxtBlur(t);
};
/* Event handlers */
function rebuildCalendarHandler(event, filter, displayMode, forceDataReload, successCallback) {
// if filter is changed we need to invalidate cache
if ($scope.Filter && !activityCalendarService.cacheKeysEqual($scope.Filter, filter)) {
activityCalendarService.invalidateCache($scope.Filter);
}
$scope.ViewModel.DataLoaded = false;
$scope.ViewModel.DisplayMode = angular.copy(displayMode || {});
$scope.Filter = angular.copy(filter || {});
blockUI();
hoursResourcesConverter.load(forceDataReload).then(function () {
var loadCalendarTask = activityCalendarService.getActivityCalendar(calendarDataUrl, filter, forceDataReload);
var nptCategories = dataSources.getNonProjectTimeCategories();
var loadCostCentersTask = dataSources.loadCostCenters();
var cachedTemplates = activityCalendarUIService.cacheTemplates();
var dateRangeTask = dataSources.loadAvailableDateRange();
var allTasks = [loadCalendarTask, nptCategories, loadCostCentersTask, dateRangeTask].concat(cachedTemplates);
$q.all(allTasks)
.then(function (asyncResults) {
var calendar = asyncResults[0];
if (calendar.Teams) {
var teamIds = Object.keys(calendar.Teams);
var promise = dataSources.loadResourcesByTeamIds(teamIds)
.then(function (result) {
return asyncResults;
});
return promise;
} else {
return asyncResults;
}
})
.then(function (asyncResults) {
var calendar = asyncResults[0];
nonProjectTimeCategoryCache = asyncResults[1];
createViewModel(calendar);
initBottomPart();
toggleParts();
toggleSortBy();
toggleHoursResourcesMode();
toggleBarsValuesMode();
toggleMonths();
$scope.$emit('ac.workflowactionsloaded');
unblockUI();
if (typeof successCallback === 'function') {
successCallback(calendar);
}
if (angular.isObject(calendar))
$scope.ViewModel.DataLoaded = true;
})
.then(null, function () {
unblockUI();
showErrorModal('Oops!', commonErrorMessage);
});
}).then(null, function () {
unblockUI();
showErrorModal('Oops!', commonErrorMessage);
});
};
function optionsChangedHandler(event, displayMode, key, newValue) {
//console.log(key);
$scope.ViewModel.DisplayMode = displayMode || {};
switch (key) {
case 'uomMode':
toggleHoursResourcesMode();
$scope.$broadcast('changeUOMMode', newValue);
break;
case 'capBarMode':
toggleBarsValuesMode();
break;
case 'defaultView':
toggleMonths();
break;
case 'sortBy':
case 'sortOrder':
toggleSortBy();
break;
case 'showParts':
toggleParts();
toggleSortBy();
toggleHoursResourcesMode();
break;
case 'capacityView':
toggleCapacityType();
$scope.$broadcast('capacityViewChanged', newValue);
break;
default:
break;
};
};
function dataChanged() {
$scope.$emit('ac.calendarDataChanged');
};
function onChangeTeamDataUnassigned(filter, header, projectRow, expCatRow, teamRow, startDate, endDate, isUOMHours, isAvgMode, changedCells) {
if (typeof changedCells === 'object' && angular.isArray(changedCells) && changedCells.length > 0) {
// Find out, if some categories where added to teams at bottom part
var needRecreateRows = activityCalendarUIService.categoryRowsWereAdded(changedCells);
if (needRecreateRows)
// Categories where added, we need to redraw all rows in bottom part
refreshBottomPart();
else
triggerTeamChanged(changedCells);
activityCalendarUIService.updateTotalRows(changedCells, teamRow, expCatRow, projectRow.Id, $scope.ViewModel.NoTeamRow,
$scope.ViewModel.Rows, $scope.ViewModel.Header, $scope.Filter, $scope.ViewModel.DisplayMode);
activityCalendarUIService.refreshProjectStyles($scope.ViewModel.NoTeamRow.Children, $scope.ViewModel.Header, $scope.Filter, projectRow, changedCells);
dataChanged();
}
};
/* Methods for preparing data */
function getDataModel(projects, noTeamProjects, filteredByView) {
if (!projects) {
return null;
}
costCenterExpendituresCache = {};
var filterExpendituresByTeams = activityCalendarUIService.isFilteredByTeams($scope.Filter);
var filteredByTeamsOrViews = activityCalendarUIService.isFilteredByTeams($scope.Filter);
var allocationMode = $scope.Filter.EntityType == activityCalendarUIService.filterEntityType.company ? activityCalendarUIService.allocationMode.needAllocation :
activityCalendarUIService.allocationMode.teamAllocation;
var result = {};
for (var projectId in projects) {
var extendedProjectModel = angular.extend({}, projects[projectId], {
AllocationMode: allocationMode
});
// get all expenditures with resources in project and split them between cost centers they relate to
var expenditures = activityCalendarUIService.getExpendituresWithResources4Project(extendedProjectModel, null, null, $scope.ViewModel.Header, $scope.Filter, true, filterExpendituresByTeams, filteredByView);
if (expenditures && Object.keys(expenditures).length > 0) {
for (var ecKey in expenditures) {
var expenditure = expenditures[ecKey];
var costCenter = dataSources.getCostCenterByExpenditureId(expenditure.Id);
if (costCenter) {
if (!(costCenter.Id in result)) {
result[costCenter.Id] = {
Id: costCenter.Id,
Name: costCenter.Name,
AllocationMode: allocationMode,
Projects: {}
};
}
// Fill costCenter to expenditures links cache
if (!(costCenter.Id in costCenterExpendituresCache)) {
costCenterExpendituresCache[costCenter.Id] = [];
}
if (costCenterExpendituresCache[costCenter.Id].indexOf(expenditure.Id) < 0) {
costCenterExpendituresCache[costCenter.Id].push(expenditure.Id);
}
if (!(projectId in result[costCenter.Id].Projects)) {
result[costCenter.Id].Projects[projectId]= {
Project: extendedProjectModel,
Expenditures: {},
};
}
result[costCenter.Id].Projects[projectId].Expenditures[ecKey]= expenditures[ecKey];
}
}
}
if (noTeamProjects) {
// put project to No Team section if there are any unassigned project need in this project
var noTeamProject = angular.extend({}, extendedProjectModel,
{ AllocationMode: activityCalendarUIService.allocationMode.remainingNeedAllocation });
// load data for project expenditures
var projectNoTeamsExpenditures = activityCalendarUIService.getExpenditures4Project(noTeamProject, null, $scope.Filter.ProjectRoles || [],
$scope.ViewModel.Header, $scope.Filter, true, false);
var ecKeys = Object.keys(projectNoTeamsExpenditures);
for (var i = 0; i < ecKeys.length; i++) {
if (projectNoTeamsExpenditures[ecKeys[i]].AllowResourceAssignment)
delete projectNoTeamsExpenditures[ecKeys[i]];
}
// remove rows where nothing to assign or to display
var projectNoTeamsExpendituresNonEmpty =
activityCalendarUIService.removeEmptyUnassignedExpenditures(projectId, projectNoTeamsExpenditures, $scope.Filter);
if (projectNoTeamsExpendituresNonEmpty && (Object.keys(projectNoTeamsExpendituresNonEmpty).length > 0)) {
noTeamProjects[projectId] = {
Project: noTeamProject,
Expenditures: projectNoTeamsExpendituresNonEmpty
};
}
}
}
return result;
};
/* Methods for creating view models */
function createViewModel(model) {
var projectExpenditures = {}; // cache for aggregated ECs
var noTeamProjects = {};
createHeaderViewModel(model.WeekEndings);
createRowsViewModel(model, projectExpenditures, noTeamProjects);
fillViewModelWithData(model, projectExpenditures, noTeamProjects);
projectExpenditures = null;
noTeamProjects = null;
};
function createHeaderViewModel(weekEndings) {
$scope.ViewModel.Header = (new GridHeader(weekEndings || {}).create());
};
function createRowsViewModel(model, projectExpenditures, noTeamProjects) {
$scope.ViewModel.Rows = [];
$scope.ViewModel.NoTeamRow = null;
if (!model || !model.Projects) {
return;
}
var filteredByView = $scope.Filter.EntityType == activityCalendarUIService.filterEntityType.view;
var costCenterModels = getDataModel(model.Projects, noTeamProjects, filteredByView);
for (var costCenterId in costCenterModels) {
projectExpenditures[costCenterId] = costCenterModels[costCenterId];
}
if (projectExpenditures && Object.keys(projectExpenditures).length) {
// sort cost centers by name
var sortedCostCenters = $filter('sortObjectsBy')(projectExpenditures, 'Name', false);
for (var i = 0; i < sortedCostCenters.length; i++) {
var costCenterData = sortedCostCenters[i];
var costCenterViewModel = activityCalendarUIService.createViewModel4CostCenter(costCenterData, $scope.Filter);
if (costCenterViewModel) {
setTooltips(costCenterViewModel);
$scope.ViewModel.Rows.push(costCenterViewModel);
}
}
}
// Create non-project time rows
nonProjectTimeCache = activityCalendarUIService.getNonProjectTimeDataModel(nonProjectTimeCategoryCache, $scope.DisplayMode.GroupBy.Value);
createNonProjectTimeRowModels();
// Create project rows for No Team block
if (noTeamProjects) {
$scope.ViewModel.NoTeamRow = activityCalendarUIService.createViewModel4TotalRow('No Team');
$scope.ViewModel.NoTeamRow.AllocationMode = activityCalendarUIService.allocationMode.remainingNeedAllocation;
$scope.ViewModel.NoTeamRow.Children = activityCalendarUIService.createViewModel4Projects(noTeamProjects);
for (var pIndex = 0; pIndex < $scope.ViewModel.NoTeamRow.Children.length; pIndex++) {
var projectRow = $scope.ViewModel.NoTeamRow.Children[pIndex];
projectRow.getTooltipContent = $scope.getTooltipProjectUnassignedContent;
if (projectRow.Children) {
for (var ecIndex = 0; ecIndex < projectRow.Children.length; ecIndex++) {
var ecRow = projectRow.Children[ecIndex];
if (ecRow) {
ecRow.getTooltipContent = $scope.getTooltipEcUnassignedContent;
ecRow.AvailableTeams = activityCalendarUIService.getAvailableTeams(projectRow.Id, ecRow.Id);
activityCalendarUIService.setNoTeamRowTemplates(ecRow);
}
}
}
}
}
// Create total rows
createTotalRowModels();
};
function setTooltips(costCenterRow) {
if (!costCenterRow || !costCenterRow.Children)
return;
for (var pIndex = 0; pIndex < costCenterRow.Children.length; pIndex++) {
var projectRow = costCenterRow.Children[pIndex];
if (projectRow && projectRow.Children) {
for (var ecIndex = 0; ecIndex < projectRow.Children.length; ecIndex++) {
var expCatRow = projectRow.Children[ecIndex];
if (expCatRow) {
expCatRow.getTooltipContent = $scope.getTooltipContent;
if (expCatRow.Children && expCatRow.Children.length) {
for (var teamKey in expCatRow.Children) { // set tooltips for child team rows
var teamRow = expCatRow.Children[teamKey];
if (teamRow.RowType != 'Team') // if child is not a team (e.g. resource) then do not set tooltips
break;
if (teamRow) {
teamRow.getTooltipContent = $scope.getTooltipContent;
}
}
}
}
}
}
}
};
function createNonProjectTimeRowModels() {
$scope.ViewModel.NonProjectTimeTotalRow = activityCalendarUIService.createViewModel4NptTotalRow('Non-Project Time', nonProjectTimeCache[C_EMPTY_GUID]);
};
function createTotalRowModels() {
$scope.ViewModel.TotalRow = activityCalendarUIService.createViewModel4TotalRow('Total');
$scope.ViewModel.RemainingCapacityRow = activityCalendarUIService.createViewModel4TotalRow('Remaining Capacity');
};
function getTotalRows() {
var rows = [];
if ($scope.ViewModel.NonProjectTimeTotalRow) {
rows.push($scope.ViewModel.NonProjectTimeTotalRow);
}
if ($scope.ViewModel.TotalRow) {
rows.push($scope.ViewModel.TotalRow);
}
if ($scope.ViewModel.RemainingCapacityRow) {
rows.push($scope.ViewModel.RemainingCapacityRow);
}
return rows;
};
function getResourceRows(resourceId) {
var result = activityCalendarUIService.getResourceRows($scope.ViewModel.Rows, resourceId);
return result;
};
/* Root methods for creating view models and filling them with data */
function fillViewModelWithData(model, projectExpenditures, noTeamProjects) {
if (!$scope.ViewModel.Rows) {
return;
}
var filteredByView = $scope.Filter.EntityType == activityCalendarUIService.filterEntityType.view;
for (var i = 0; i < $scope.ViewModel.Rows.length; i++) {
var costCenterRow = $scope.ViewModel.Rows[i];
var costCenterData = projectExpenditures[costCenterRow.Id] || {};
for (var j = 0; j < costCenterRow.Children.length; j++) {
var project = costCenterRow.Children[j];
var projectData = (costCenterData.Projects || {})[project.Id] || {};
activityCalendarUIService.fillProjectRowWithData(costCenterRow, project, projectData.Expenditures, $scope.ViewModel.Header, $scope.Filter, filteredByView);
}
}
// Fill NPT-rows with data and clear NPT caches. Caches not need more
fillNonProjectTimeRowsWithData();
nonProjectTimeCache = {};
nonProjectTimeCategoryCache = {};
// Fill No Team section
if ($scope.ViewModel.NoTeamRow && $scope.ViewModel.NoTeamRow.Children && $scope.ViewModel.NoTeamRow.Children.length) {
var noTeamProject = noTeamProjects || {};
for (var j = 0; j < $scope.ViewModel.NoTeamRow.Children.length; j++) {
var project = $scope.ViewModel.NoTeamRow.Children[j];
var projectData = (noTeamProject || {})[project.Id] || {};
activityCalendarUIService.fillProjectRowWithData(null, project, projectData.Expenditures, $scope.ViewModel.Header, $scope.Filter);
if (project.Children) {
for (var ecIndex = 0; ecIndex < project.Children.length; ecIndex++) {
var ecRow = project.Children[ecIndex];
ecRow.RemainingCapacityValues = angular.extend([], ecRow.RemainingNeedHoursValues);
}
}
// add additional ec-team rows for recently added teams
if (projectData && projectData.Project && projectData.Project.Teams) {
angular.forEach(projectData.Project.Teams, function (pTeamCache, teamKey) {
// we should care only about pending (newly assigned)/saved pending (assigned multiple times) teams
var type = pTeamCache.Type || activityCalendarService.TeamType.Saved;
if (type !== activityCalendarService.TeamType.Saved && pTeamCache.Expenditures2Display && project.Children) {
for (var ecIndex = 0; ecIndex < project.Children.length; ecIndex++) {
var ecRow = project.Children[ecIndex];
if (ecRow.Id in pTeamCache.Expenditures2Display) {
createAndFillNewEcTeamRow(project, ecRow, teamKey);
// expand project row
if (project.Collapsed)
activityCalendarUIService.toggleRow(project, {}, false);
// expand ec row
if (ecRow.Collapsed)
activityCalendarUIService.toggleRow(ecRow, {}, false);
}
}
}
});
}
activityCalendarUIService.refreshProjectAvailableTeams($scope.Filter, project);
}
activityCalendarUIService.fillTotalRowData($scope.ViewModel.NoTeamRow.Children, $scope.ViewModel.NoTeamRow, $scope.ViewModel.Header);
}
// Fill total rows with data
fillTotalRowsWithData();
};
function fillNonProjectTimeRowsWithData() {
activityCalendarUIService.fillTotalNptRowData($scope.ViewModel.NonProjectTimeTotalRow, nonProjectTimeCache[C_EMPTY_GUID], $scope.ViewModel.Header);
};
function fillTotalRowsWithData() {
var rowsForTotal = angular.copy($scope.ViewModel.Rows) || [];
rowsForTotal.push(angular.copy($scope.ViewModel.NonProjectTimeTotalRow));
if ($scope.ViewModel.NoTeamRow && $scope.Filter.EntityType !== activityCalendarUIService.filterEntityType.company) {
rowsForTotal = rowsForTotal.concat(angular.copy($scope.ViewModel.NoTeamRow));
}
activityCalendarUIService.fillTotalRowData(rowsForTotal, $scope.ViewModel.TotalRow, $scope.ViewModel.Header);
activityCalendarUIService.fillRemainingCapacityRowData($scope.ViewModel.TotalRow, $scope.ViewModel.RemainingCapacityRow, $scope.ViewModel.Header, $scope.ViewModel.DisplayMode.IsCapacityModeActuals.Value);
};
function createAndFillNewEcTeamRow(projectRow, expCatRow, teamId) {
var teamInfo = teamInfoService.getById(teamId);
activityCalendarUIService.createAndFillNewEcTeamRow(projectRow, expCatRow, teamId, teamInfo,
$scope.ViewModel.Header, $scope.ViewModel.DisplayMode, $scope.Filter);
};
/* Methods for toggling display modes */
function toggleHoursResourcesMode() {
var totalRows = getTotalRows();
activityCalendarUIService.toggleGridSource($scope.ViewModel.Rows, $scope.ViewModel.DisplayMode.IsUOMHours);
activityCalendarUIService.toggleGridSource(totalRows, $scope.ViewModel.DisplayMode.IsUOMHours);
if ($scope.ViewModel.NoTeamRow) {
activityCalendarUIService.toggleGridSource([$scope.ViewModel.NoTeamRow], $scope.ViewModel.DisplayMode.IsUOMHours);
}
if ($scope.ViewModel.DisplayMode.IsAvgMode()) {
activityCalendarUIService.applyAvgMode($scope.ViewModel.Rows, $scope.ViewModel.Header);
activityCalendarUIService.applyAvgMode(totalRows, $scope.ViewModel.Header);
if ($scope.ViewModel.NoTeamRow) {
activityCalendarUIService.applyAvgMode([$scope.ViewModel.NoTeamRow], $scope.ViewModel.Header);
}
}
};
function toggleBarsValuesMode() {
if (!$scope.ViewModel.Rows || !$scope.ViewModel.Rows.length) {
return;
}
for (var i = 0; i < $scope.ViewModel.Rows.length; i++) {
var projects = $scope.ViewModel.Rows[i].Children;
if (projects) {
activityCalendarUIService.toggleBarsValuesMode(projects, $scope.ViewModel.DisplayMode.IsBarMode);
}
}
if ($scope.ViewModel.NoTeamRow && $scope.ViewModel.NoTeamRow.Children) {
activityCalendarUIService.toggleBarsValuesMode($scope.ViewModel.NoTeamRow.Children, $scope.ViewModel.DisplayMode.IsBarMode);
}
};
function toggleMonths() {
if ($scope.ViewModel.DisplayMode.IsViewModeMonth) {
$scope.ViewModel.Header.collapseMonthes();
}
else {
$scope.ViewModel.Header.expandMonthes();
}
};
function toggleParts() {
if (!$scope.ViewModel || !$scope.ViewModel.Rows)
return;
for (var i = 0; i < $scope.ViewModel.Rows.length; i++) {
var costCenterRow = $scope.ViewModel.Rows[i];
if (costCenterRow.Children && costCenterRow.Children.length > 0) {
costCenterRow.Children = activityCalendarUIService.toggleParts(costCenterRow.Children, $scope.ViewModel.DisplayMode.ShowParts.Value);
var costCenterExpenditures = costCenterExpendituresCache[costCenterRow.Id];
activityCalendarUIService.updateProjectStyles($scope.ViewModel.Header, $scope.Filter, costCenterRow.Children, null, costCenterExpenditures);
}
}
if ($scope.ViewModel.NoTeamRow && $scope.ViewModel.NoTeamRow.Children && $scope.ViewModel.NoTeamRow.Children.length) {
$scope.ViewModel.NoTeamRow.Children = activityCalendarUIService.toggleParts($scope.ViewModel.NoTeamRow.Children, $scope.ViewModel.DisplayMode.ShowParts.Value);
activityCalendarUIService.updateProjectStyles($scope.ViewModel.Header, $scope.Filter, $scope.ViewModel.NoTeamRow.Children);
}
};
function toggleSortBy() {
if (!$scope.ViewModel || !$scope.ViewModel.Rows)
return;
for (var i = 0; i < $scope.ViewModel.Rows.length; i++) {
var costCenterRow = $scope.ViewModel.Rows[i];
if (costCenterRow.Children && costCenterRow.Children.length > 0) {
costCenterRow.Children = activityCalendarUIService.toggleSortBy(costCenterRow.Children, $scope.ViewModel.DisplayMode.SortBy, $scope.ViewModel.DisplayMode.SortOrder);
}
}
if ($scope.ViewModel.NoTeamRow && $scope.ViewModel.NoTeamRow.Children && $scope.ViewModel.NoTeamRow.Children.length) {
$scope.ViewModel.NoTeamRow.Children = activityCalendarUIService.toggleSortBy($scope.ViewModel.NoTeamRow.Children, $scope.ViewModel.DisplayMode.SortBy, $scope.ViewModel.DisplayMode.SortOrder);
}
};
function toggleCapacityType() {
// reset total row values
createTotalRowModels();
// recalculate total row UI data
fillTotalRowsWithData();
toggleHoursResourcesMode();
};
function recalculateResourceAvailability(resourceId) {
if (!resourceId || !$scope.ViewModel.Rows) {
return;
}
for (var rowIndex = 0; rowIndex < $scope.ViewModel.Rows.length; rowIndex++) {
var costCenterRow = $scope.ViewModel.Rows[rowIndex];
if (costCenterRow && costCenterRow.Children) {
activityCalendarUIService.recalculateResourceAvailability($scope.Filter, $scope.ViewModel.Header, costCenterRow.Children, resourceId);
}
}
};
function initBottomPart() {
// need to pass new objects for preventing using single instances of objects inside current and other controllers
$scope.$broadcast('rebindTeamInfo', {
Header: $scope.ViewModel.Header,
DisplayMode: activityCalendarUIService.castDisplayModeIntoTeamInfoMode($scope.ViewModel.DisplayMode),
});
};
function refreshBottomPart() {
$scope.$broadcast('teaminfo.recreateRows');
};
function triggerResourceChanged(data) {
if (typeof data !== 'object' || !angular.isArray(data)) {
return;
}
for (var i = 0; i < data.length; i++) {
$scope.$broadcast('resourceValueChanged', data[i]);
}
};
function triggerTeamChanged(data) {
if (typeof data !== 'object' || !angular.isArray(data)) {
return;
}
for (var i = 0; i < data.length; i++) {
$scope.$broadcast('teamValueChanged', data[i]);
}
};
function triggerNptResourceChanged(data) {
if (typeof data !== 'object' || !angular.isArray(data)) {
return;
}
for (var i = 0; i < data.length; i++) {
$scope.$broadcast('resourceNonProjectTimeChanged', data[i]);
}
};
function refreshResourceStyles(resourceId, data) {
if (!resourceId || typeof data !== 'object' || !angular.isArray(data)) {
return;
}
var resourceRows = getResourceRows(resourceId);
if (!resourceRows || !resourceRows.length) {
return;
}
activityCalendarUIService.updateResourceStyles($scope.ViewModel.Header, resourceId, resourceRows, data);
};
/* ===== ToolTips display and Management ==== */
$scope.getTooltipContent = function (opts) {
var tooltip = 'No data available';
var units = ' hours';
if (!$scope.DisplayMode.IsUOMHours)
units = ' resources';
if (!opts || !opts.header || !opts.projectId || !opts.expCatId)
return tooltip;
var header = opts.header;
var projectId = opts.projectId;
var expCatId = opts.expCatId;
var teamId = opts.teamId;
var teamAllocations = activityCalendarUIService.getTeamAllocations4Display($scope.Filter,
$scope.ViewModel.DisplayMode, $scope.ViewModel.Header, header, projectId, expCatId, teamId);
if (teamAllocations && (teamAllocations.length > 0)) {
tooltip = 'Allocated to Teams:';
for (var index = 0; index < teamAllocations.length; index++) {
var teamInfo = teamAllocations[index];
tooltip += ('<br/>' + teamInfo.Name + ': ' + teamInfo.Need + units);
}
}
else {
tooltip = 'No Team Allocations present';
}
return tooltip;
};
$scope.getTooltipProjectUnassignedContent = function (opts) {
return activityCalendarUIService.getTooltipProjectUnassignedContent(opts, $scope.ViewModel.DisplayMode, $scope.Filter, $scope.ViewModel.Header, $scope.ViewModel.NoTeamRow.Children);
};
$scope.getTooltipEcUnassignedContent = function (opts) {
return activityCalendarUIService.getTooltipEcUnassignedContent(opts, $scope.ViewModel.DisplayMode, $scope.Filter, $scope.ViewModel.Header, $scope.ViewModel.NoTeamRow.Children);
};
}]);