1449 lines
63 KiB
JavaScript
1449 lines
63 KiB
JavaScript
'use strict';
|
|
|
|
app
|
|
.controller('activityCalendarByResController', ['$scope', '$q', '$filter', '$timeout', 'activityCalendarService', 'teamInfoService', 'activityCalendarUIService', 'dataSources', 'hoursResourcesConverter', 'roundService', 'calculateDistributionService', function ($scope, $q, $filter, $timeout, activityCalendarService, teamInfoService, activityCalendarUIService, dataSources, hoursResourcesConverter, roundService, calculateDistributionService) {
|
|
//console.log('activityCalendarByResController started');
|
|
var commonErrorMessage = 'An error occurred while processing your request. Please, try again later.';
|
|
var calendarDataUrl = '/CapacityManagement/GetActivityCalendar';
|
|
var projectExpendituresCache = {}; // cache for aggregated ECs by project for "group by resources" mode
|
|
var unassignedProjectsCache = {}; // cache for projects for unassigned section
|
|
var nonProjectTimeCache = {}; // cache for non-project time data
|
|
var nonProjectTimeCategoryCache = {}; // cache for non-project time categories data
|
|
|
|
$scope.ViewModel = {
|
|
DisplayMode: null,
|
|
Header: null,
|
|
Rows: null,
|
|
UnassignedRow: null,
|
|
TotalRow: null,
|
|
RemainingCapacityRow: null,
|
|
NonProjectTimeTotalRows: null,
|
|
DataLoaded: false
|
|
};
|
|
$scope.Filter = null;
|
|
|
|
/* Event listeners */
|
|
$scope.$on('ac.rebuild-calendar', rebuildCalendarHandler);
|
|
$scope.$on('ac.options-changed', optionsChangedHandler);
|
|
$scope.$on('teamBoard.plannedCapacityChanged', function (eventData) {
|
|
// move it to bottom part and handle it there
|
|
console.log('teamBoard.plannedCapacityChanged event raised');
|
|
});
|
|
$scope.$on('resourceEdited', function (eventData) {
|
|
rebuildCalendarHandler(eventData, $scope.Filter, $scope.ViewModel.DisplayMode, true);
|
|
});
|
|
|
|
/* 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) {
|
|
if ($event && ($($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);
|
|
rowToAddResource.ResourceToAssignId = null;
|
|
dataChanged();
|
|
};
|
|
$scope.checkResourceValueTopPart = function (projectRow, resourceRow, $index, $data) {
|
|
if (!projectRow || !projectRow.ExpenditureCategoryId || !projectRow.ExpenditureCategoryId.length || projectRow.IsMultipleECs || !resourceRow) {
|
|
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.checkResourceValue(filter, header, projectRow.Id, { Id: projectRow.ExpenditureCategoryId[0] }, resourceRow.Id, projectRow, isUOMHours, isAvgMode, $index, $data);
|
|
onChangeResourceData(filter, header, projectRow.Id, projectRow.ExpenditureCategoryId[0], null, resourceRow.Id, startDate, endDate, isUOMHours, isAvgMode, result, false);
|
|
return false;
|
|
};
|
|
$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(),
|
|
startDate = projectRow.ActiveScenario.StartDate,
|
|
endDate = projectRow.ActiveScenario.EndDate,
|
|
parentRow = teamRow || expCatRow || {},
|
|
teamId = teamRow && teamRow.Id;
|
|
|
|
var result = activityCalendarUIService.checkResourceValue(filter, header, projectRow.Id, parentRow, resRow.Id, resRow, isUOMHours, isAvgMode, $index, $data, false);
|
|
onChangeResourceData(filter, header, projectRow.Id, expCatRow.Id, teamId, resRow.Id, startDate, endDate, isUOMHours, isAvgMode, result, true);
|
|
return false;
|
|
};
|
|
$scope.checkResourceGrandTotalValueTopPart = function (row, resource, $data) {
|
|
if (!row || !row.ExpenditureCategoryId || !row.ExpenditureCategoryId.length || row.IsMultipleECs || !row.ActiveScenario || !resource) {
|
|
return;
|
|
}
|
|
|
|
var filter = $scope.Filter,
|
|
header = $scope.ViewModel.Header,
|
|
isUOMHours = $scope.ViewModel.DisplayMode.IsUOMHours,
|
|
isAvgMode = $scope.ViewModel.DisplayMode.IsAvgMode(),
|
|
startDate = row.ActiveScenario.StartDate,
|
|
endDate = row.ActiveScenario.EndDate;
|
|
|
|
var result = activityCalendarUIService.checkResourceGrandTotalValue(filter, header, row.Id, startDate, endDate, { Id: row.ExpenditureCategoryId[0] }, resource.Id, row, isUOMHours, isAvgMode, $data, false);
|
|
onChangeResourceData(filter, header, row.Id, row.ExpenditureCategoryId[0], null, resource.Id, startDate, endDate, isUOMHours, isAvgMode, result, false);
|
|
return false;
|
|
};
|
|
$scope.checkResourceGrandTotalValue = function (projectRow, expCatRow, resRow, $data, teamRow) {
|
|
if (!projectRow || !projectRow || !resRow) {
|
|
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,
|
|
parentRow = teamRow || expCatRow || {},
|
|
teamId = teamRow && teamRow.Id;
|
|
|
|
var result = activityCalendarUIService.checkResourceGrandTotalValue(filter, header, projectRow.Id, startDate, endDate, parentRow, resRow.Id, resRow, isUOMHours, isAvgMode, $data, false);
|
|
onChangeResourceData(filter, header, projectRow.Id, expCatRow.Id, teamId, resRow.Id, startDate, endDate, isUOMHours, isAvgMode, result, true);
|
|
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(),
|
|
startDate = projectRow.ActiveScenario.StartDate,
|
|
endDate = projectRow.ActiveScenario.EndDate,
|
|
parentRow = teamRow || expCatRow || {},
|
|
teamId = teamRow && teamRow.Id;
|
|
|
|
var result = activityCalendarUIService.takeRemainingCapacity(filter, header, projectRow.Id, startDate, endDate,
|
|
parentRow, resRow.Id, resRow, isUOMHours, isAvgMode);
|
|
onChangeResourceData(filter, header, projectRow.Id, expCatRow.Id, teamId, resRow.Id, startDate, endDate, isUOMHours, isAvgMode, result, true);
|
|
};
|
|
$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(),
|
|
startDate = projectRow.ActiveScenario.StartDate,
|
|
endDate = projectRow.ActiveScenario.EndDate,
|
|
parentRow = teamRow || expCatRow || {},
|
|
teamId = teamRow && teamRow.Id;
|
|
|
|
var result = activityCalendarUIService.takeFullCapacity(filter, header, projectRow.Id, startDate, endDate,
|
|
parentRow, resRow.Id, resRow, isUOMHours, isAvgMode);
|
|
onChangeResourceData(filter, header, projectRow.Id, expCatRow.Id, teamId, resRow.Id, startDate, endDate, isUOMHours, isAvgMode, result, true);
|
|
};
|
|
$scope.zeroResource = function (projectRow, expCatRow, resRow, teamRow) {
|
|
if (!projectRow || !projectRow.ActiveScenario || !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(),
|
|
projectId = projectRow.Id,
|
|
startDate = projectRow.ActiveScenario.StartDate,
|
|
endDate = projectRow.ActiveScenario.EndDate,
|
|
expCatRowParam = teamRow || expCatRow || {},
|
|
teamId = teamRow && teamRow.Id;
|
|
|
|
// if this is a project row from top part
|
|
if (typeof projectRow.ExpenditureCategoryId === 'object' && angular.isArray(projectRow.ExpenditureCategoryId)) {
|
|
// if there are any expenditures for the selected resource then let's update data for those expenditures
|
|
if (projectRow.ExpenditureCategoryId.length > 0) {
|
|
for (var i = 0; i < projectRow.ExpenditureCategoryId.length; i++) {
|
|
expCatRowParam = { Id: projectRow.ExpenditureCategoryId[i] };
|
|
var changedCells = activityCalendarUIService.zeroResource(filter, header, projectId, startDate,
|
|
endDate, expCatRowParam, resRow.Id, projectRow, isUOMHours, isAvgMode);
|
|
onChangeResourceData(filter, header, projectId, projectRow.ExpenditureCategoryId[0], teamId, resRow.Id, startDate, endDate, isUOMHours, isAvgMode, changedCells, false);
|
|
}
|
|
}
|
|
} else { // if this is a resource row from unassigned part
|
|
var changedCells = activityCalendarUIService.zeroResource(filter, header, projectId, startDate,
|
|
endDate, expCatRowParam, resRow.Id, resRow, isUOMHours, isAvgMode);
|
|
onChangeResourceData(filter, header, projectId, expCatRow.Id, teamId, resRow.Id, startDate, endDate, isUOMHours, isAvgMode, changedCells, true);
|
|
}
|
|
}
|
|
});
|
|
},
|
|
className: "bootbox-sm"
|
|
});
|
|
};
|
|
$scope.removeResource = function (prjRow, expCatRow, teamRow, resRow, $index) {
|
|
if (!prjRow || !prjRow.ActiveScenario || !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(),
|
|
projectId = prjRow.Id,
|
|
startDate = prjRow.ActiveScenario.StartDate,
|
|
endDate = prjRow.ActiveScenario.EndDate,
|
|
parentRow = teamRow || expCatRow || {},
|
|
expCatId = expCatRow && expCatRow.Id,
|
|
cellsToUpdate = [],
|
|
teamId = teamRow && teamRow.Id
|
|
|
|
// if this is a project row from top part
|
|
if (typeof prjRow.ExpenditureCategoryId === 'object' && angular.isArray(prjRow.ExpenditureCategoryId)) {
|
|
// if there are any expenditures for the selected resource then let's update data for those expenditures
|
|
if (prjRow.ExpenditureCategoryId.length > 0) {
|
|
var projUnassignedRow = null,
|
|
unassignedResIndex = -1;
|
|
if ($scope.ViewModel.UnassignedRow && $scope.ViewModel.UnassignedRow.Children) {
|
|
for (var index = 0; index < $scope.ViewModel.UnassignedRow.Children.length; index++) {
|
|
var pRow = $scope.ViewModel.UnassignedRow.Children[index];
|
|
if (pRow && pRow.Id && pRow.Id == projectId) {
|
|
projUnassignedRow = pRow;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
for (var i = 0; i < prjRow.ExpenditureCategoryId.length; i++) {
|
|
parentRow = {};
|
|
expCatId = prjRow.ExpenditureCategoryId[i];
|
|
// Get corresponding row for EC in Unassigned block
|
|
if (projUnassignedRow && projUnassignedRow.Children && projUnassignedRow.Children.length) {
|
|
for (var ecIndex = 0; ecIndex < projUnassignedRow.Children.length; ecIndex++) {
|
|
var unassignedEcRow = projUnassignedRow.Children[ecIndex];
|
|
if (unassignedEcRow && unassignedEcRow.Id && unassignedEcRow.Id == prjRow.ExpenditureCategoryId[i]) {
|
|
parentRow = unassignedEcRow;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
// remove resource from project and recalculate available resources for exp cat row from related EC row in Unassigned block
|
|
// pass parentRow.Children.length as $index to avoid removing of unexisting resources from unassigned EC row
|
|
var changedCells = activityCalendarUIService.cleanAndRemoveResource(filter, header, projectId, startDate,
|
|
endDate, expCatId, parentRow, resRow.Id, prjRow, isUOMHours, isAvgMode, (parentRow.Children || []).length);
|
|
if (typeof changedCells === 'object' && angular.isArray(changedCells) && changedCells.length > 0) {
|
|
cellsToUpdate = cellsToUpdate.concat(changedCells);
|
|
}
|
|
}
|
|
for (var idx = 0; idx < resRow.Children.length; idx++) {
|
|
var child = resRow.Children[idx];
|
|
if (child.Id == prjRow.Id) {
|
|
resRow.Children.splice(idx, 1);
|
|
break;
|
|
}
|
|
if (child.Children && child.Children.length) {
|
|
for (var idx2 = 0; idx2 < child.Children.length; idx2++) {
|
|
if (child.Children[idx2].Id == prjRow.Id) {
|
|
child.Children.splice(idx2, 1);
|
|
break;
|
|
}
|
|
}
|
|
// if master project does not have parts any more then remove master project as well
|
|
if (child.Children.length == 0)
|
|
resRow.Children.splice(idx, 1);
|
|
}
|
|
}
|
|
var expenditureCategoryId = (!prjRow.IsMultipleECs && prjRow.ExpenditureCategoryId && prjRow.ExpenditureCategoryId.length) ? prjRow.ExpenditureCategoryId[0] : resRow.OwnExpenditureCategoryId;
|
|
onChangeResourceData(filter, header, projectId, expenditureCategoryId, teamId, resRow.Id, startDate, endDate, isUOMHours, isAvgMode, cellsToUpdate, false);
|
|
}
|
|
} else { // if this is a resource row from unassigned part
|
|
var changedCells = activityCalendarUIService.cleanAndRemoveResource(filter, header, projectId, startDate,
|
|
endDate, expCatId, parentRow, resRow.Id, resRow, isUOMHours, isAvgMode, $index);
|
|
if (typeof changedCells === 'object' && angular.isArray(changedCells) && changedCells.length > 0) {
|
|
cellsToUpdate = cellsToUpdate.concat(changedCells);
|
|
}
|
|
onChangeResourceData(filter, header, projectId, expCatRow.Id, teamId, resRow.Id, startDate, endDate, isUOMHours, isAvgMode, cellsToUpdate, true);
|
|
}
|
|
}
|
|
});
|
|
},
|
|
className: "bootbox-sm"
|
|
});
|
|
};
|
|
$scope.checkNptResourceValue = function (topResourceRow, nptTotalRow, nptCatRow, nptItemRow, nptResourceRow, $index, $data) {
|
|
if (!topResourceRow || !nptTotalRow || !nptCatRow || !nptItemRow || !nptResourceRow)
|
|
return;
|
|
|
|
var filter = $scope.Filter,
|
|
header = $scope.ViewModel.Header,
|
|
isUOMHours = $scope.ViewModel.DisplayMode.IsUOMHours,
|
|
isAvgMode = $scope.ViewModel.DisplayMode.IsAvgMode();
|
|
|
|
var rollupRows = [];
|
|
rollupRows.push(topResourceRow);
|
|
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);
|
|
dataChanged();
|
|
return false;
|
|
}
|
|
|
|
return result;
|
|
};
|
|
$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 cachedTemplates = activityCalendarUIService.cacheTemplates();
|
|
var dateRangeTask = dataSources.loadAvailableDateRange();
|
|
var allTasks = [loadCalendarTask, nptCategories, 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);
|
|
fillViewModelWithData(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');
|
|
};
|
|
|
|
/* Methods for preparing data */
|
|
function removeEmptyUnassignedExpenditures(projectExpCatsCache, allocationMode) {
|
|
if (!projectExpCatsCache)
|
|
return;
|
|
|
|
var result = {};
|
|
|
|
for (var expCatId in projectExpCatsCache) {
|
|
var expCatItem = projectExpCatsCache[expCatId];
|
|
var allocationData = expCatItem.UnassignedNeed;
|
|
if (allocationMode == activityCalendarUIService.allocationMode.remainingTeamAllocation)
|
|
allocationData = expCatItem.RemainingTeamAllocations;
|
|
if (expCatItem && !calculateDistributionService.isEmptyAllocations(allocationData, true)) {
|
|
result[expCatId] = expCatItem;
|
|
}
|
|
}
|
|
|
|
if (result && !Object.keys(result).length) {
|
|
// No EC with Unassigned Need was found
|
|
result = undefined;
|
|
}
|
|
|
|
// Remove empty teams (with no available resources to assign)
|
|
var teamsFilteringResult = {};
|
|
|
|
for (var expId in result) {
|
|
var exp = result[expId];
|
|
exp.Resources = {}; // do not show resources already assigned to the team because they are in the top part
|
|
|
|
switch ($scope.Filter.EntityType) {
|
|
case activityCalendarUIService.filterEntityType.team:
|
|
// AC displayed for a single Team
|
|
if (exp.AvailableResources && Object.keys(exp.AvailableResources).length > 0) {
|
|
teamsFilteringResult[expId] = exp;
|
|
}
|
|
break;
|
|
|
|
case activityCalendarUIService.filterEntityType.view:
|
|
// AC displayed for view View
|
|
if (exp.Teams && Object.keys(exp.Teams).length) {
|
|
var availableForAssignTeams = {};
|
|
|
|
for (var teamId in exp.Teams) {
|
|
var team = exp.Teams[teamId];
|
|
team.Resources = {}; // do not show resources already assigned to the team because they are in the top part
|
|
|
|
if (team.AvailableResources && Object.keys(team.AvailableResources).length > 0)
|
|
availableForAssignTeams[teamId] = team;
|
|
}
|
|
exp.Teams = availableForAssignTeams;
|
|
|
|
if (exp.Teams && Object.keys(exp.Teams).length) {
|
|
teamsFilteringResult[expId] = exp;
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
teamsFilteringResult[expId] = exp;
|
|
break;
|
|
}
|
|
}
|
|
result = teamsFilteringResult;
|
|
|
|
if (result && !Object.keys(result).length) {
|
|
// No ECs in project after filtering
|
|
result = undefined;
|
|
}
|
|
|
|
return result;
|
|
};
|
|
function getDataModel4UnassignedProjects(filteredByView) {
|
|
var result = {};
|
|
|
|
var projects = activityCalendarService.getProjects($scope.Filter);
|
|
if (!projects) {
|
|
return result;
|
|
}
|
|
|
|
var allocationMode = $scope.Filter.EntityType == activityCalendarUIService.filterEntityType.company ? activityCalendarUIService.allocationMode.unassignedNeedAllocation :
|
|
activityCalendarUIService.allocationMode.remainingTeamAllocation;
|
|
for (var projectId in projects) {
|
|
var extendedProjectModel = angular.extend({}, projects[projectId], {
|
|
AllocationMode: allocationMode
|
|
});
|
|
var projectModel = getDataModel4SingleUnassignedProject(extendedProjectModel, filteredByView);
|
|
|
|
if (projectModel) {
|
|
result[projectId] = projectModel;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
};
|
|
function getDataModel4SingleUnassignedProject(project, filteredByView) {
|
|
if (!project)
|
|
return null;
|
|
|
|
var result = null;
|
|
var projectUnassignedExpenditures = activityCalendarUIService.getExpendituresWithResources4Project(project, null, null, $scope.ViewModel.Header, $scope.Filter, true, false/*will be filtered in controller*/, filteredByView);
|
|
var projectUnassignedExpendituresNonEmpty = removeEmptyUnassignedExpenditures(projectUnassignedExpenditures, project.AllocationMode);
|
|
|
|
if (projectUnassignedExpendituresNonEmpty && (Object.keys(projectUnassignedExpendituresNonEmpty).length > 0)) {
|
|
result = {
|
|
Project: project,
|
|
Expenditures: projectUnassignedExpendituresNonEmpty
|
|
};
|
|
}
|
|
return result;
|
|
};
|
|
|
|
/* Methods for creating view models */
|
|
function createViewModel(model) {
|
|
var startDate = new Date().getTime();
|
|
|
|
createHeaderViewModel(model.WeekEndings);
|
|
createRowsViewModel();
|
|
|
|
var endDate = new Date().getTime();
|
|
//console.log('creating view model: ' + (endDate - startDate) + ' ms');
|
|
};
|
|
function createHeaderViewModel(weekEndings) {
|
|
$scope.ViewModel.Header = (new GridHeader(weekEndings || {}).create());
|
|
};
|
|
function createRowsViewModel() {
|
|
$scope.ViewModel.Rows = [];
|
|
$scope.ViewModel.NonProjectTimeTotalRows = [];
|
|
var filteredByView = $scope.Filter.EntityType == activityCalendarUIService.filterEntityType.view;
|
|
|
|
projectExpendituresCache = activityCalendarService.getResourcesWithAssignedProjects($scope.Filter);
|
|
unassignedProjectsCache = getDataModel4UnassignedProjects(filteredByView);
|
|
nonProjectTimeCache = activityCalendarUIService.getNonProjectTimeDataModel(nonProjectTimeCategoryCache, $scope.DisplayMode.GroupBy.Value);
|
|
|
|
// Set Team-wide npts read-only for current view, because they are displayed partially
|
|
activityCalendarUIService.setTeamWideNptItemsReadOnly(nonProjectTimeCache);
|
|
|
|
if (projectExpendituresCache && (Object.keys(projectExpendituresCache).length > 0)) {
|
|
var sortedResources = $filter('sortObjectsBy')(projectExpendituresCache, 'Name', false);
|
|
for (var resourceIndex = 0; resourceIndex < sortedResources.length; resourceIndex++) {
|
|
var resource = sortedResources[resourceIndex];
|
|
if (resource) {
|
|
var resourceModel = activityCalendarUIService.createViewModel4ResourceBasic(resource);
|
|
|
|
if (resource.Projects) {
|
|
resourceModel.OwnExpenditureCategoryId = resource.OwnExpenditureCategoryId;
|
|
resourceModel.VisibleCellsCount = $scope.ViewModel.Header.TotalWeeks;
|
|
resourceModel.Children = [];
|
|
|
|
for (var projectId in resource.Projects) {
|
|
var project = resource.Projects[projectId];
|
|
var projectModel = activityCalendarUIService.createViewModel4Project(project, null, $scope.Filter);
|
|
var projectEditableModel = angular.extend({}, projectModel, activityCalendarUIService.createViewModelEditableBasic());
|
|
if (projectEditableModel) {
|
|
projectEditableModel.IsMultipleECs = project.IsMultipleECs;
|
|
projectEditableModel.ExpenditureCategoryId = project.ExpenditureCategoryId;
|
|
projectEditableModel.Teams = [];
|
|
|
|
resourceModel.Children.push(projectEditableModel);
|
|
}
|
|
}
|
|
}
|
|
|
|
var resourceNptData = (nonProjectTimeCache && (resource.Id in nonProjectTimeCache)) ?
|
|
nonProjectTimeCache[resource.Id] : null;
|
|
|
|
var resourceNptModel = activityCalendarUIService.createViewModel4NptTotalRow('Non-Project Time', resourceNptData);
|
|
resourceModel.NonProjectTime = resourceNptModel;
|
|
$scope.ViewModel.NonProjectTimeTotalRows.push(resourceNptModel);
|
|
|
|
setTopPartCustomRowTemplates(resourceModel);
|
|
setTooltips4ResourceRowTop(resourceModel);
|
|
$scope.ViewModel.Rows.push(resourceModel);
|
|
}
|
|
}
|
|
}
|
|
|
|
$scope.ViewModel.UnassignedRow = activityCalendarUIService.createViewModel4TotalRow('Unassigned');
|
|
|
|
if (unassignedProjectsCache && Object.keys(unassignedProjectsCache).length > 0) {
|
|
$scope.ViewModel.UnassignedRow.Children = activityCalendarUIService.createViewModel4Projects(unassignedProjectsCache, $scope.Filter, filteredByView);
|
|
setTooltips4UnassignedRows($scope.ViewModel.UnassignedRow.Children);
|
|
}
|
|
|
|
// Create total rows
|
|
createTotalRowModels();
|
|
};
|
|
function setTopPartCustomRowTemplates(resourceModel) {
|
|
if (!resourceModel)
|
|
return;
|
|
|
|
resourceModel.Templates = {
|
|
Main: activityCalendarUIService.viewRowTemplates.resourceGbrRowTemplate,
|
|
Numbers: activityCalendarUIService.viewRowTemplates.resourceGbrRowNumbersTemplate
|
|
};
|
|
|
|
if (resourceModel.Children) {
|
|
for (var index = 0; index < resourceModel.Children.length; index++) {
|
|
var projectRow = resourceModel.Children[index];
|
|
projectRow.Templates = {
|
|
Main: activityCalendarUIService.viewRowTemplates.projectGbrRowTemplate,
|
|
Numbers: activityCalendarUIService.viewRowTemplates.projectGbrRowNumbersTemplate
|
|
};
|
|
}
|
|
}
|
|
};
|
|
function setTooltips4ResourceRowTop(resourceRow) {
|
|
if (!resourceRow)
|
|
return;
|
|
|
|
resourceRow.getTooltipContent = $scope.getResourceTopTooltipContent;
|
|
};
|
|
function setTooltips4UnassignedRows(rows) {
|
|
if (!rows || !angular.isArray(rows))
|
|
return;
|
|
|
|
for (var index = 0; index < rows.length; index++) {
|
|
var row = rows[index];
|
|
if (row) {
|
|
if (row.RowType && (row.RowType == 'Project') && row.IsMaster) {
|
|
// Tooltips for parts of the master project
|
|
setTooltips4UnassignedRows(row.Children)
|
|
}
|
|
else {
|
|
// Tooltips for simple project row (without parts), EC and Team rows
|
|
setTooltips4UnassignedRow(row);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
function setTooltips4UnassignedRow(row) {
|
|
if (!row)
|
|
return;
|
|
|
|
if (row.RowType) {
|
|
switch (row.RowType) {
|
|
case 'Project':
|
|
case 'ExpCat':
|
|
case 'Team':
|
|
row.getTooltipContent = $scope.getTooltipUnassignedContent;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (row.Children && angular.isArray(row.Children)) {
|
|
setTooltips4UnassignedRows(row.Children);
|
|
}
|
|
};
|
|
function createTotalRowModels() {
|
|
$scope.ViewModel.TotalRow = activityCalendarUIService.createViewModel4TotalRow('Total');
|
|
$scope.ViewModel.RemainingCapacityRow = activityCalendarUIService.createViewModel4TotalRow('Remaining Capacity');
|
|
};
|
|
function getTotalRows() {
|
|
var rows = [];
|
|
|
|
if ($scope.ViewModel.TotalRow) {
|
|
rows.push($scope.ViewModel.TotalRow);
|
|
}
|
|
if ($scope.ViewModel.RemainingCapacityRow) {
|
|
rows.push($scope.ViewModel.RemainingCapacityRow);
|
|
}
|
|
|
|
return rows;
|
|
};
|
|
function getResourceRowById(resourceId) {
|
|
if (!resourceId || !$scope.ViewModel.Rows || !$scope.ViewModel.Rows.length)
|
|
return null;
|
|
|
|
var foundRow = null;
|
|
for (var index = 0; index < $scope.ViewModel.Rows.length; index++) {
|
|
var row = $scope.ViewModel.Rows[index];
|
|
|
|
if (row && row.Id && (row.Id == resourceId)) {
|
|
foundRow = row;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return foundRow;
|
|
}
|
|
|
|
/* Root methods for creating view models and filling them with data */
|
|
function fillViewModelWithData(model) {
|
|
var startDate = new Date().getTime();
|
|
|
|
if (!$scope.ViewModel.Rows) {
|
|
return;
|
|
}
|
|
|
|
for (var i = 0; i < $scope.ViewModel.Rows.length; i++) {
|
|
var resourceRow = $scope.ViewModel.Rows[i];
|
|
if (resourceRow && resourceRow.Children) {
|
|
var resourceData = projectExpendituresCache[resourceRow.Id];
|
|
for (var projectIndex = 0; projectIndex < resourceRow.Children.length; projectIndex++) {
|
|
var projectRow = resourceRow.Children[projectIndex];
|
|
var resourceRowData = resourceData.Projects[projectRow.Id];
|
|
var expenditureCategoryId = (!projectRow.IsMultipleECs && projectRow.ExpenditureCategoryId && projectRow.ExpenditureCategoryId.length) ? projectRow.ExpenditureCategoryId[0] : resourceRow.OwnExpenditureCategoryId;
|
|
|
|
activityCalendarUIService.fillResourceRowWithData($scope.ViewModel.Header, null, expenditureCategoryId,
|
|
resourceRow.Id, projectRow, resourceRowData, [resourceRow], projectRow.ActiveScenario.StartDate,
|
|
projectRow.ActiveScenario.EndDate, false, projectRow.DisableResourceEdit);
|
|
}
|
|
|
|
var resourceNptData = (resourceRow.NonProjectTime && (resourceRow.Id in nonProjectTimeCache)) ?
|
|
nonProjectTimeCache[resourceRow.Id] : null;
|
|
activityCalendarUIService.fillTotalNptRowData(resourceRow.NonProjectTime, resourceNptData, $scope.ViewModel.Header, [resourceRow]);
|
|
}
|
|
|
|
activityCalendarUIService.updateResourceStylesOnEntireRange($scope.ViewModel.Header, resourceRow.Id, resourceRow);
|
|
}
|
|
var filteredByView = $scope.Filter.EntityType == activityCalendarUIService.filterEntityType.view;
|
|
if ($scope.ViewModel.UnassignedRow && $scope.ViewModel.UnassignedRow.Children && $scope.ViewModel.UnassignedRow.Children.length) {
|
|
for (var j = 0; j < $scope.ViewModel.UnassignedRow.Children.length; j++) {
|
|
var project = $scope.ViewModel.UnassignedRow.Children[j];
|
|
var projectData = (unassignedProjectsCache || {})[project.Id] || {};
|
|
activityCalendarUIService.fillProjectRowWithData(null, project, projectData.Expenditures, $scope.ViewModel.Header, $scope.Filter, filteredByView);
|
|
if (project.Children) {
|
|
for (var k = 0; k < project.Children.length; k++) {
|
|
var ecRow = project.Children[k];
|
|
ecRow.RemainingCapacityValues = angular.extend([], ecRow.RemainingTAHoursValues);
|
|
if (ecRow.Children) {
|
|
for (var l = 0; l < ecRow.Children.length; l++) {
|
|
if (ecRow.Children[l].RowType == 'Team') {
|
|
var teamRow = ecRow.Children[l];
|
|
teamRow.RemainingCapacityValues = angular.extend([], teamRow.RemainingTAHoursValues);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
activityCalendarUIService.fillTotalRowData($scope.ViewModel.UnassignedRow.Children, $scope.ViewModel.UnassignedRow, $scope.ViewModel.Header);
|
|
}
|
|
|
|
// Fill NPT-rows with data and clear NPT caches. Caches not need more
|
|
nonProjectTimeCache = {};
|
|
nonProjectTimeCategoryCache = {};
|
|
|
|
// Fill total rows with data
|
|
fillTotalRowsWithData();
|
|
|
|
var endDate = new Date().getTime();
|
|
//console.log('filling model with data: ' + (endDate - startDate) + ' ms');
|
|
};
|
|
function fillTotalRowsWithData() {
|
|
var startDate = new Date().getTime();
|
|
var rowsForTotal = ($scope.ViewModel.UnassignedRow && $scope.ViewModel.UnassignedRow.Children) ? angular.copy($scope.ViewModel.UnassignedRow.Children) : [];
|
|
|
|
if ($scope.ViewModel.Rows && ($scope.ViewModel.Rows.length > 0)) {
|
|
rowsForTotal = rowsForTotal.concat(angular.copy($scope.ViewModel.Rows));
|
|
}
|
|
|
|
activityCalendarUIService.fillTotalRowData(rowsForTotal, $scope.ViewModel.TotalRow, $scope.ViewModel.Header);
|
|
activityCalendarUIService.fillRemainingCapacityRowData($scope.ViewModel.TotalRow, $scope.ViewModel.RemainingCapacityRow, $scope.ViewModel.Header, $scope.ViewModel.DisplayMode.IsCapacityModeActuals.Value);
|
|
var endDate = new Date().getTime();
|
|
console.log('filling total rows with data: ' + (endDate - startDate) + ' ms');
|
|
};
|
|
|
|
/* Methods for toggling display modes */
|
|
function toggleHoursResourcesMode() {
|
|
var totalRows = getTotalRows();
|
|
|
|
activityCalendarUIService.toggleGridSource($scope.ViewModel.NonProjectTimeTotalRows, $scope.ViewModel.DisplayMode.IsUOMHours);
|
|
activityCalendarUIService.toggleGridSource($scope.ViewModel.Rows, $scope.ViewModel.DisplayMode.IsUOMHours);
|
|
|
|
if ($scope.ViewModel.UnassignedRow) {
|
|
activityCalendarUIService.toggleGridSource([$scope.ViewModel.UnassignedRow], $scope.ViewModel.DisplayMode.IsUOMHours);
|
|
}
|
|
activityCalendarUIService.toggleGridSource(totalRows, $scope.ViewModel.DisplayMode.IsUOMHours);
|
|
|
|
|
|
if ($scope.ViewModel.DisplayMode.IsAvgMode()) {
|
|
activityCalendarUIService.applyAvgMode($scope.ViewModel.NonProjectTimeTotalRows, $scope.ViewModel.Header);
|
|
activityCalendarUIService.applyAvgMode($scope.ViewModel.Rows, $scope.ViewModel.Header);
|
|
|
|
if ($scope.ViewModel.UnassignedRow) {
|
|
activityCalendarUIService.applyAvgMode([$scope.ViewModel.UnassignedRow], $scope.ViewModel.Header);
|
|
}
|
|
activityCalendarUIService.applyAvgMode(totalRows, $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.UnassignedRow && $scope.ViewModel.UnassignedRow.Children) {
|
|
activityCalendarUIService.toggleBarsValuesMode($scope.ViewModel.UnassignedRow.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;
|
|
|
|
var customProjectTemplates = {
|
|
Main: activityCalendarUIService.viewRowTemplates.projectGbrRowTemplate,
|
|
Numbers: activityCalendarUIService.viewRowTemplates.projectGbrRowNumbersTemplate
|
|
};
|
|
|
|
for (var i = 0; i < $scope.ViewModel.Rows.length; i++) {
|
|
var resource = $scope.ViewModel.Rows[i];
|
|
if (resource.Children && resource.Children.length > 0) {
|
|
resource.Children = activityCalendarUIService.toggleParts(resource.Children, $scope.ViewModel.DisplayMode.ShowParts.Value, 2, customProjectTemplates);
|
|
activityCalendarUIService.toggleRow(resource, {}, true);
|
|
}
|
|
}
|
|
|
|
if ($scope.ViewModel.UnassignedRow && $scope.ViewModel.UnassignedRow.Children && $scope.ViewModel.UnassignedRow.Children.length) {
|
|
$scope.ViewModel.UnassignedRow.Children = activityCalendarUIService.toggleParts($scope.ViewModel.UnassignedRow.Children, $scope.ViewModel.DisplayMode.ShowParts.Value);
|
|
activityCalendarUIService.updateProjectStyles($scope.ViewModel.Header, $scope.Filter, $scope.ViewModel.UnassignedRow.Children);
|
|
}
|
|
};
|
|
function toggleSortBy() {
|
|
if (!$scope.ViewModel || !$scope.ViewModel.Rows) {
|
|
return;
|
|
}
|
|
|
|
for (var i = 0; i < $scope.ViewModel.Rows.length; i++) {
|
|
var resource = $scope.ViewModel.Rows[i];
|
|
if (resource.Children && resource.Children.length > 0) {
|
|
resource.Children = activityCalendarUIService.toggleSortBy(resource.Children, $scope.ViewModel.DisplayMode.SortBy, $scope.ViewModel.DisplayMode.SortOrder);
|
|
}
|
|
}
|
|
|
|
if ($scope.ViewModel.UnassignedRow && $scope.ViewModel.UnassignedRow.Children && $scope.ViewModel.UnassignedRow.Children.length) {
|
|
$scope.ViewModel.UnassignedRow.Children = activityCalendarUIService.toggleSortBy($scope.ViewModel.UnassignedRow.Children, $scope.ViewModel.DisplayMode.SortBy, $scope.ViewModel.DisplayMode.SortOrder);
|
|
}
|
|
};
|
|
function toggleCapacityType() {
|
|
// reset total row values
|
|
createTotalRowModels();
|
|
// recalculate total row UI data
|
|
fillTotalRowsWithData();
|
|
toggleHoursResourcesMode();
|
|
};
|
|
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 onChangeResourceData(filter, header, projectId, expCatId, teamId, resourceId, startDate, endDate, isUOMHours, isAvgMode, changedCells, fromUnassignedBlock) {
|
|
if (typeof changedCells === 'object' && angular.isArray(changedCells) && changedCells.length > 0) {
|
|
var resRow = getResourceRowById(resourceId);
|
|
triggerResourceChanged(changedCells);
|
|
recalculateResourceAvailability(resourceId);
|
|
updateTotalRows(changedCells, resRow, expCatId, projectId, teamId);
|
|
refreshResourceStyles(resourceId, resRow, changedCells);
|
|
|
|
if (fromUnassignedBlock) {
|
|
refreshProjectStyles(projectId, changedCells);
|
|
}
|
|
|
|
dataChanged();
|
|
}
|
|
}
|
|
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 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, resourceRow, data) {
|
|
if (!resourceId || typeof data !== 'object' || !angular.isArray(data)) {
|
|
return;
|
|
}
|
|
var resourceRows = [];
|
|
if (resourceRow && typeof resourceRow === 'object') {
|
|
resourceRows = [resourceRow];
|
|
if (resourceRow && resourceRow.Children) {
|
|
for (var prIndex = 0; prIndex < resourceRow.Children.length; prIndex++) {
|
|
resourceRows.push(resourceRow.Children[prIndex]);
|
|
}
|
|
}
|
|
}
|
|
|
|
var projectRows = [];
|
|
|
|
if ($scope.ViewModel.UnassignedRow && $scope.ViewModel.UnassignedRow.Children) {
|
|
// Get project rows to iterate by
|
|
for (var pIndex = 0; pIndex < $scope.ViewModel.UnassignedRow.Children.length; pIndex++) {
|
|
var pRow = $scope.ViewModel.UnassignedRow.Children[pIndex];
|
|
|
|
if (pRow && pRow.Children && pRow.Children.length) {
|
|
if (pRow.IsMaster) {
|
|
// Found row is master project
|
|
for (var partIndex = 0; partIndex < pRow.Children.length; partIndex++) {
|
|
projectRows.push(pRow.Children[partIndex]);
|
|
}
|
|
}
|
|
else {
|
|
projectRows.push(pRow);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (var pIndex = 0; pIndex < projectRows.length; pIndex++) {
|
|
var pRow = projectRows[pIndex];
|
|
|
|
if (pRow && pRow.Children) {
|
|
for (var ecIndex = 0; ecIndex < pRow.Children.length; ecIndex++) {
|
|
var ecRow = pRow.Children[ecIndex];
|
|
if (ecRow && ecRow.Children) {
|
|
for (var trIndex = 0; trIndex < ecRow.Children.length; trIndex++) {
|
|
// Under EC there may be Team row as well as resource Row
|
|
var teamOrResourceRow = ecRow.Children[trIndex];
|
|
|
|
if (teamOrResourceRow) {
|
|
if ((teamOrResourceRow.RowType == 'Team') && teamOrResourceRow.Children) {
|
|
for (var tIndex = 0; tIndex < teamOrResourceRow.Children.length; tIndex++) {
|
|
var resourceRow = teamOrResourceRow.Children[tIndex];
|
|
|
|
if (resourceRow.RowType == 'Resource' && (resourceRow.Id == resourceId))
|
|
resourceRows.push(resourceRow);
|
|
}
|
|
}
|
|
|
|
if (teamOrResourceRow.RowType == 'Resource' && (teamOrResourceRow.Id == resourceId))
|
|
resourceRows.push(teamOrResourceRow);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
activityCalendarUIService.updateResourceStyles($scope.ViewModel.Header, resourceId, resourceRows, data);
|
|
};
|
|
function refreshProjectStyles(projectId, data) {
|
|
if (!projectId || typeof data !== 'object' || !angular.isArray(data)) {
|
|
return;
|
|
}
|
|
|
|
var foundUnassignedRows = activityCalendarUIService.getProjectRows($scope.ViewModel.UnassignedRow.Children, projectId);
|
|
var rowToUpdateStylesIn = null;
|
|
|
|
if (foundUnassignedRows) {
|
|
if (foundUnassignedRows.projectRow) {
|
|
var rowToUpdateStylesIn = foundUnassignedRows.projectRow;
|
|
}
|
|
|
|
if (foundUnassignedRows.masterProjectRow) {
|
|
var rowToUpdateStylesIn = foundUnassignedRows.masterProjectRow;
|
|
}
|
|
|
|
activityCalendarUIService.updateProjectStyles($scope.ViewModel.Header, $scope.Filter, [rowToUpdateStylesIn], data);
|
|
}
|
|
};
|
|
function updateTotalRows(changedCells, resourceRow, expCatId, projectId, teamId) {
|
|
if (typeof changedCells !== 'object' || !angular.isArray(changedCells) || !expCatId || !projectId) {
|
|
return;
|
|
}
|
|
// gather rows to rollup changes
|
|
var unassignedRowsToUpdate = getUnassignedRowsToUpdate(projectId, expCatId, teamId) || [];
|
|
var assignedRowsToUpdate = resourceRow ? getAssignedRowsToUpdate(resourceRow, projectId) : [];
|
|
|
|
if ((assignedRowsToUpdate.length + unassignedRowsToUpdate.length) < 1) {
|
|
return;
|
|
}
|
|
|
|
var header = $scope.ViewModel.Header;
|
|
var isUOMHours = $scope.ViewModel.DisplayMode.IsUOMHours;
|
|
var isAvgMode = $scope.ViewModel.DisplayMode.IsAvgMode();
|
|
var project = activityCalendarService.getProjectById($scope.Filter, projectId);
|
|
var weekEndings = changedCells.map(function (cell) { return cell.WeekEnding; });
|
|
|
|
var remainingNeed = {}
|
|
var allocationMode = $scope.Filter.EntityType == activityCalendarUIService.filterEntityType.company ? activityCalendarUIService.allocationMode.unassignedNeedAllocation :
|
|
activityCalendarUIService.allocationMode.remainingTeamAllocation;
|
|
if (allocationMode == activityCalendarUIService.allocationMode.unassignedNeedAllocation)
|
|
remainingNeed = activityCalendarService.getUnassignedNeedAllocations4Scenario($scope.Filter, project.ActiveScenario.Id) || {};
|
|
else
|
|
remainingNeed = activityCalendarService.getRemainingTeamAllocations4Scenario($scope.Filter, Object.keys(project.Teams || {}), projectId, project.ActiveScenario.Id) || {};
|
|
|
|
for (var i = 0; i < changedCells.length; i++) {
|
|
var cell = changedCells[i];
|
|
var month = header.Months[cell.MonthIndex];
|
|
var monthWeeksCount = month.Childs.length;
|
|
|
|
var newRemainingNeedValue = ((remainingNeed[expCatId] || {}).Allocations || {})[cell.WeekEnding] || 0;
|
|
var rollupRemainingNeedDeltaHours = cell.DeltaHoursValue || 0;
|
|
|
|
if (rollupRemainingNeedDeltaHours < 0) {
|
|
rollupRemainingNeedDeltaHours = (newRemainingNeedValue > 0) ?
|
|
-Math.min(Math.abs(rollupRemainingNeedDeltaHours), newRemainingNeedValue) : 0;
|
|
}
|
|
|
|
var rollupRemainingNeedDeltaResources = hoursResourcesConverter.convertToResources(expCatId, rollupRemainingNeedDeltaHours);
|
|
|
|
// team allocations delta values
|
|
var rollupTeamAllocationDeltaHours = cell.DeltaHoursValue || 0;
|
|
var rollupTeamAllocationDeltaResources = hoursResourcesConverter.convertToResources(expCatId, rollupTeamAllocationDeltaHours);
|
|
var rollupValues = null;
|
|
|
|
// rollup rows under no team section
|
|
for (var rowIndex = 0; rowIndex < unassignedRowsToUpdate.length; rowIndex++) {
|
|
var row = unassignedRowsToUpdate[rowIndex];
|
|
rollupValues = activityCalendarUIService.rollupRemainingRow(row, -(rollupRemainingNeedDeltaHours || 0), -(rollupRemainingNeedDeltaResources || 0), false, cell.WeekIndex, month.SelfIndexInWeeks);
|
|
setCellsValues4Row(row, cell.WeekIndex, month.SelfIndexInWeeks, isUOMHours, isAvgMode, monthWeeksCount);
|
|
}
|
|
// rollup already assigned rows in the top part
|
|
for (var rowIndex = 0; rowIndex < assignedRowsToUpdate.length; rowIndex++) {
|
|
rollupValues = activityCalendarUIService.rollupRemainingRow(assignedRowsToUpdate[rowIndex], (rollupTeamAllocationDeltaHours || 0), (rollupTeamAllocationDeltaResources || 0), false, cell.WeekIndex, month.SelfIndexInWeeks);
|
|
setCellsValues4Row(assignedRowsToUpdate[rowIndex], cell.WeekIndex, month.SelfIndexInWeeks, isUOMHours, isAvgMode, monthWeeksCount);
|
|
}
|
|
}
|
|
};
|
|
// set Cells values based on new values and apply AVG mode if necessary
|
|
function setCellsValues4Row(rowObject, weekCellIndex, monthCellIndex, isUOMHours, isAvgMode, monthWeeksCount) {
|
|
var row = rowObject.Row;
|
|
var collectionNames = activityCalendarUIService.getRowCollectionNames(rowObject);
|
|
// recursively set Cells properties for al nested rows
|
|
if (rowObject.Children && rowObject.Children.length > 0) {
|
|
for (var i = 0; i < rowObject.Children.length; i++) {
|
|
setCellsValues4Row(rowObject.Children[i], weekCellIndex, monthCellIndex, isUOMHours, isAvgMode, monthWeeksCount);
|
|
}
|
|
}
|
|
if (isUOMHours) {
|
|
row.Cells[weekCellIndex] = row[collectionNames.hoursCollectionName][weekCellIndex];
|
|
row.Cells[monthCellIndex] = row[collectionNames.hoursCollectionName][monthCellIndex];
|
|
row.TotalValue = row[collectionNames.hoursTotal];
|
|
}
|
|
else {
|
|
row.Cells[weekCellIndex] = row[collectionNames.resourcesCollectionName][weekCellIndex];
|
|
row.Cells[monthCellIndex] = isAvgMode ?
|
|
monthWeeksCount == 0 ? 0 : roundService.roundQuantity(row[collectionNames.resourcesCollectionName][monthCellIndex] / monthWeeksCount) :
|
|
row[collectionNames.resourcesCollectionName][monthCellIndex];
|
|
row.TotalValue = isAvgMode ?
|
|
row.VisibleCellsCount == 0 ? 0 : roundService.roundQuantity(row[collectionNames.resourceTotal] / row.VisibleCellsCount) :
|
|
row[collectionNames.resourceTotal];
|
|
}
|
|
}
|
|
function getAssignedRowsToUpdate(resourceRow, projectId) {
|
|
if (!resourceRow || !projectId)
|
|
return [];
|
|
|
|
var result = [];
|
|
var resourceRowInfo = {
|
|
Row: resourceRow,
|
|
Children: []
|
|
};
|
|
|
|
var totalRow = {
|
|
Row: $scope.ViewModel.TotalRow,
|
|
Children: [resourceRowInfo]
|
|
};
|
|
|
|
// Make rows tree from total row to include total row in rollup process
|
|
result.push(totalRow);
|
|
|
|
if (resourceRow.Children) {
|
|
var foundUnassignedRows = activityCalendarUIService.getProjectRows(resourceRow.Children, projectId);
|
|
|
|
if (foundUnassignedRows && foundUnassignedRows.masterProjectRow) {
|
|
var masterProjectRowInfo = {
|
|
Row: foundUnassignedRows.masterProjectRow,
|
|
Children: []
|
|
};
|
|
resourceRowInfo.Children.push(masterProjectRowInfo);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
};
|
|
function getUnassignedRowsToUpdate(projectId, expCatId, teamId) {
|
|
if (!projectId || !expCatId || !$scope.ViewModel.UnassignedRow || !$scope.ViewModel.UnassignedRow.Children)
|
|
return [];
|
|
|
|
// Get corresponding row for unassigned project
|
|
var result = [];
|
|
var foundUnassignedRows = findUnassignedRows(projectId, expCatId, teamId); // activityCalendarUIService.getProjectRows($scope.ViewModel.UnassignedRow.Children, projectId);
|
|
|
|
if (foundUnassignedRows && foundUnassignedRows.projectRow) {
|
|
var projectRow = foundUnassignedRows.projectRow;
|
|
var masterProjectRow = foundUnassignedRows.masterProjectRow;
|
|
|
|
var unassignedRow = {
|
|
Row: $scope.ViewModel.UnassignedRow,
|
|
Children: []
|
|
};
|
|
|
|
var totalRow = {
|
|
Row: $scope.ViewModel.TotalRow,
|
|
Children: [unassignedRow]
|
|
};
|
|
|
|
// Make rows tree from total row to include total row in rollup process
|
|
result.push(totalRow);
|
|
|
|
var pRow = {
|
|
Row: projectRow,
|
|
Children: []
|
|
};
|
|
|
|
if (masterProjectRow) {
|
|
var mpRow = {
|
|
Row: masterProjectRow,
|
|
Children: []
|
|
};
|
|
mpRow.Children.push(pRow);
|
|
unassignedRow.Children.push(mpRow);
|
|
}
|
|
else {
|
|
unassignedRow.Children.push(pRow);
|
|
}
|
|
|
|
if (foundUnassignedRows.expCatRow) {
|
|
var ecRow = {
|
|
Row: foundUnassignedRows.expCatRow,
|
|
Children: []
|
|
}
|
|
pRow.Children.push(ecRow);
|
|
|
|
if (foundUnassignedRows.teamRows && angular.isArray(foundUnassignedRows.teamRows)) {
|
|
for (var index = 0; index < foundUnassignedRows.teamRows.length; index++) {
|
|
ecRow.Children.push({
|
|
Row: foundUnassignedRows.teamRows[index]
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
function findUnassignedRows(projectId, expCatId, teamId) {
|
|
if (!projectId || !$scope.ViewModel.UnassignedRow || !$scope.ViewModel.UnassignedRow.Children || !$scope.ViewModel.UnassignedRow.Children.length) {
|
|
return null;
|
|
}
|
|
|
|
var result = {
|
|
masterProjectRow: null,
|
|
projectRow: null,
|
|
expCatRow: null,
|
|
teamRows: null
|
|
};
|
|
|
|
// Find project rows (master & part)
|
|
var projectRowsInfo = activityCalendarUIService.getProjectRows($scope.ViewModel.UnassignedRow.Children, projectId);
|
|
result.masterProjectRow = projectRowsInfo && projectRowsInfo.masterProjectRow ? projectRowsInfo.masterProjectRow : null;
|
|
result.projectRow = projectRowsInfo && projectRowsInfo.projectRow ? projectRowsInfo.projectRow : null;
|
|
|
|
if (expCatId && result.projectRow && result.projectRow.Children) {
|
|
var foundExpCatRow = activityCalendarUIService.findRowInCollection(result.projectRow.Children, expCatId);
|
|
if (foundExpCatRow.RowType && (foundExpCatRow.RowType == 'ExpCat')) {
|
|
result.expCatRow = foundExpCatRow;
|
|
|
|
if (foundExpCatRow && foundExpCatRow.Children) {
|
|
result.teamRows = [];
|
|
|
|
if (teamId) {
|
|
var foundTeamRow = activityCalendarUIService.findRowInCollection(foundExpCatRow.Children, teamId);
|
|
if (foundTeamRow && foundTeamRow.RowType && (foundTeamRow.RowType == 'Team')) {
|
|
result.teamRows.push(foundTeamRow);
|
|
}
|
|
}
|
|
else {
|
|
// Get all child team rows
|
|
for (var tKey in foundExpCatRow.Children) {
|
|
var teamRow = foundExpCatRow.Children[tKey];
|
|
if (teamRow && teamRow.RowType && (teamRow.RowType == 'Team')) {
|
|
result.teamRows.push(teamRow);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
};
|
|
function recalculateResourceAvailability(resourceId) {
|
|
if (!resourceId) {
|
|
return;
|
|
}
|
|
|
|
activityCalendarUIService.recalculateResourceAvailability($scope.Filter, $scope.ViewModel.Header, $scope.ViewModel.UnassignedRow.Children, resourceId);
|
|
};
|
|
|
|
/* ===== ToolTips display ==== */
|
|
$scope.getResourceTopTooltipContent = function (opts) {
|
|
var tooltip = 'No data available';
|
|
var units = ' hours';
|
|
|
|
if (!$scope.DisplayMode.IsUOMHours)
|
|
units = ' resources';
|
|
|
|
if (!opts || !opts.header || !opts.resourceId)
|
|
return tooltip;
|
|
|
|
var header = opts.header;
|
|
var resourceId = opts.resourceId;
|
|
var expCatId = opts.expCatId;
|
|
|
|
var allocationsInfo = getResourceAllocationsInfo(header, resourceId, expCatId);
|
|
|
|
if (allocationsInfo) {
|
|
tooltip = 'Allocated to Projects: ' + String(allocationsInfo.ScenarioAllocations ? allocationsInfo.ScenarioAllocations : 0) + units;
|
|
tooltip += ('<br/>NPT Allocations: ' + String(allocationsInfo.NptAllocations ? allocationsInfo.NptAllocations : 0) + units);
|
|
}
|
|
else {
|
|
tooltip = 'No Allocations present';
|
|
}
|
|
|
|
return tooltip;
|
|
};
|
|
$scope.getTooltipUnassignedContent = function (opts) {
|
|
var tooltip = 'No data available';
|
|
var units = $scope.DisplayMode.IsUOMHours ? ' hours' : ' resources';
|
|
|
|
if (!opts || !opts.header || !opts.projectId)
|
|
return tooltip;
|
|
|
|
var header = opts.header;
|
|
var projectId = opts.projectId;
|
|
var expCatId = opts.expCatId;
|
|
var teamId = opts.teamId;
|
|
|
|
var rows = findUnassignedRows(projectId, expCatId);
|
|
if (!rows || !rows.projectRow)
|
|
return tooltip;
|
|
|
|
var projectRow = rows.projectRow;
|
|
var expCatRows = rows.expCatRow && rows.expCatRow.Id ? [rows.expCatRow.Id] : null;
|
|
var allocationModeCalculated = activityCalendarUIService.getAllocationMode(projectRow);
|
|
var scenarioId = projectRow.ActiveScenario ? projectRow.ActiveScenario.Id : null;
|
|
|
|
if (!scenarioId) {
|
|
return tooltip;
|
|
}
|
|
|
|
if (allocationModeCalculated == activityCalendarUIService.allocationMode.unassignedNeedAllocation) {
|
|
// Business Unit filtering mode. Unassigned rows display Project Need minus Resource Allocations
|
|
var expCatInfo = activityCalendarUIService.getExpCatUnassignedNeed($scope.Filter, $scope.ViewModel.DisplayMode,
|
|
$scope.ViewModel.Header, header, projectId, scenarioId, expCatRows, true);
|
|
|
|
if (expCatInfo) {
|
|
var unassignedNeed = roundService.roundQuantity((expCatInfo.Need || 0) - (expCatInfo.ResourceAllocations || 0));
|
|
tooltip =
|
|
'Category Need: ' + String(expCatInfo.Need || 0) + units + '<br/>' +
|
|
'Allocated to Resources: ' + String(expCatInfo.ResourceAllocations || 0) + units + '<br/>' +
|
|
'Unassigned Need: ' + String(unassignedNeed) + units;
|
|
}
|
|
}
|
|
|
|
if (allocationModeCalculated == activityCalendarUIService.allocationMode.remainingTeamAllocation) {
|
|
// View or Team filtering mode. Unassigned rows display Team Allocations minus Resource Allocations
|
|
var teamIds = teamId ? [teamId] : null;
|
|
var teamAndResourceAllocations =
|
|
activityCalendarUIService.getExpCatRemainingTeamAllocations($scope.Filter, $scope.ViewModel.DisplayMode,
|
|
$scope.ViewModel.Header, header, projectId, allocationModeCalculated, expCatRows, teamIds);
|
|
|
|
tooltip =
|
|
'Team Allocations: ' + String(teamAndResourceAllocations.TeamAllocations || 0) + units + '<br/>' +
|
|
'Allocated to Resources: ' + String(teamAndResourceAllocations.ResourceAllocations || 0) + units;
|
|
|
|
if (angular.isNumber(teamAndResourceAllocations.TeamAllocations) && angular.isNumber(teamAndResourceAllocations.ResourceAllocations)) {
|
|
var remainingAllocations = roundService.roundQuantity(Math.abs(teamAndResourceAllocations.TeamAllocations - teamAndResourceAllocations.ResourceAllocations));
|
|
|
|
if (teamAndResourceAllocations.TeamAllocations > teamAndResourceAllocations.ResourceAllocations) {
|
|
tooltip += ('<br/>Underallocation: ' + String(remainingAllocations) + units);
|
|
}
|
|
|
|
if (teamAndResourceAllocations.ResourceAllocations > teamAndResourceAllocations.TeamAllocations) {
|
|
tooltip += ('<br/>Overallocation: ' + String(remainingAllocations) + units);
|
|
}
|
|
}
|
|
}
|
|
|
|
return tooltip;
|
|
};
|
|
function getResourceAllocationsInfo(calendarDate, resourceId, expCatId) {
|
|
if (!calendarDate || !resourceId || !expCatId)
|
|
return null;
|
|
|
|
var result = {
|
|
ScenarioAllocations: 0,
|
|
NptAllocations: 0
|
|
};
|
|
|
|
var weekendings = [];
|
|
var scenarioAllocations = 0;
|
|
var nptAllocations = 0;
|
|
|
|
if (calendarDate.DataType == Header.DataType.Week) {
|
|
// Get allocations for a single weekending
|
|
weekendings.push(calendarDate.Milliseconds);
|
|
}
|
|
|
|
if (calendarDate.DataType == Header.DataType.Month) {
|
|
// Get summary allocations for month
|
|
weekendings = $scope.ViewModel.Header.getWeeksInMonth(calendarDate.ParentIndex);
|
|
}
|
|
|
|
// Get sum of resource allocations in all scenarios
|
|
var scenarioValuesCount = weekendings.length;
|
|
var nptValuesCount = 0;
|
|
var resourceAllocations = activityCalendarService.getResourceAllocations($scope.Filter, resourceId, false);
|
|
|
|
for (var wIndex = 0; wIndex < weekendings.length; wIndex++) {
|
|
var we = weekendings[wIndex];
|
|
|
|
if (resourceAllocations && resourceAllocations.Scenarios) {
|
|
for (var scenarioId in resourceAllocations.Scenarios) {
|
|
var scenarioData = resourceAllocations.Scenarios[scenarioId];
|
|
|
|
if (scenarioData && scenarioData.Teams) {
|
|
for (var teamId in scenarioData.Teams) {
|
|
var teamData = scenarioData.Teams[teamId];
|
|
|
|
if (teamData && teamData.Expenditures) {
|
|
for (var expCatInTeamId in teamData.Expenditures) {
|
|
var expCatData = teamData.Expenditures[expCatInTeamId];
|
|
|
|
if (expCatData && expCatData.Allocations && (we in expCatData.Allocations)) {
|
|
if ($scope.DisplayMode.IsUOMHours) {
|
|
scenarioAllocations += roundService.roundQuantity(expCatData.Allocations[we]);
|
|
}
|
|
else {
|
|
scenarioAllocations += roundService.roundQuantity(hoursResourcesConverter.convertToResources(expCatId, expCatData.Allocations[we]));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Get sum of NPT allocations
|
|
var resourceNptData = teamInfoService.getResourceSummaryNptAllocations(resourceId, weekendings);
|
|
if (resourceNptData) {
|
|
for (var wIndex = 0; wIndex < weekendings.length; wIndex++) {
|
|
var we = weekendings[wIndex];
|
|
var nptValue = resourceNptData[we];
|
|
|
|
if (angular.isNumber(nptValue)) {
|
|
if ($scope.DisplayMode.IsUOMHours) {
|
|
nptAllocations += roundService.roundQuantity(nptValue);
|
|
}
|
|
else {
|
|
nptAllocations += roundService.roundQuantity(hoursResourcesConverter.convertToResources(expCatId, nptValue));
|
|
}
|
|
nptValuesCount++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($scope.ViewModel.DisplayMode.ShowAvgTotals && !$scope.ViewModel.DisplayMode.IsUOMHours) {
|
|
result.ScenarioAllocations = (scenarioValuesCount > 0) ? roundService.roundQuantity(scenarioAllocations / scenarioValuesCount) : 0;
|
|
result.NptAllocations = (nptValuesCount > 0) ? roundService.roundQuantity(nptAllocations / nptValuesCount) : 0;
|
|
}
|
|
else {
|
|
result.ScenarioAllocations = roundService.roundQuantity(scenarioAllocations);
|
|
result.NptAllocations = roundService.roundQuantity(nptAllocations);
|
|
}
|
|
|
|
return result;
|
|
};
|
|
}]); |