'use strict';
app
.controller('activityCalendarByTeamController', ['$scope', '$q', 'activityCalendarService', 'teamInfoService', 'activityCalendarUIService', 'dataSources', 'hoursResourcesConverter', '$filter', 'roundService', 'calculateDistributionService', function ($scope, $q, activityCalendarService, teamInfoService, activityCalendarUIService, dataSources, hoursResourcesConverter, $filter, roundService, calculateDistributionService) {
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 teams" mode
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,
NoTeamRow: 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);
/* 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;
}
}
}
}
}
refreshProjectAvailableTeams(projectRow);
expCatRow.TeamToAssignId = null;
dataChanged();
};
function refreshProjectAvailableTeams(projectRow) {
// we need to refresh list with available teams for each expenditure category under certain project
if (projectRow.Children) {
for (var ecIndex = 0; ecIndex < projectRow.Children.length; ecIndex++) {
var ecRow = projectRow.Children[ecIndex];
if (ecRow) {
ecRow.AvailableTeams = getAvailableTeams(projectRow.Id, ecRow.Id);
}
}
}
};
$scope.checkResourceValue = function (projectRow, expCatRow, resRow, $index, $data) {
if (!projectRow || !expCatRow || !resRow) {
return;
}
var filter = $scope.Filter,
header = $scope.ViewModel.Header,
isUOMHours = $scope.ViewModel.DisplayMode.IsUOMHours,
isAvgMode = $scope.ViewModel.DisplayMode.IsAvgMode(),
parentRow = 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);
refreshProjectStyles(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) {
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 = 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);
refreshProjectStyles(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) {
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 = 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);
refreshProjectStyles(projectRow, result);
dataChanged();
}
};
$scope.takeAll = function (projectRow, expCatRow, resRow) {
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 = 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);
refreshProjectStyles(projectRow, result);
dataChanged();
}
};
$scope.zeroResource = function (projectRow, expCatRow, resRow) {
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 = 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);
refreshProjectStyles(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 = 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);
refreshProjectStyles(projectRow, changedCells);
dataChanged();
}
}
});
},
className: "bootbox-sm"
});
};
$scope.checkNptResourceValue = function (teamRow, nptTotalRow, nptCatRow, nptItemRow, nptResourceRow, $index, $data) {
if (!teamRow || !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(nptTotalRow);
rollupRows.push(teamRow);
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 (teamRow, nptTotalRow, nptCatRow, nptItemRow, $index, $data) {
if (!teamRow || !nptTotalRow || !nptCatRow || !nptItemRow)
return;
var filter = $scope.Filter,
header = $scope.ViewModel.Header,
isUOMHours = $scope.ViewModel.DisplayMode.IsUOMHours,
isAvgMode = $scope.ViewModel.DisplayMode.IsAvgMode();
var rollupRows = [];
rollupRows.push(nptTotalRow);
rollupRows.push(companyRow);
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);
}
}
refreshProjectAvailableTeams(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 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) {
$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);
updateTotalRows(changedCells, teamRow, expCatRow, projectRow.Id);
refreshNoTeamProjectStyles(projectRow, changedCells);
dataChanged();
}
};
function updateTotalRows(changedCells, teamRow, expCatRow, projectId) {
if (typeof changedCells !== 'object' || !angular.isArray(changedCells) || !expCatRow || !projectId || !teamRow) {
return;
}
var expenditureCategoryId = expCatRow.Id;
// gather rows to rollup changes
var unassignedRowsToUpdate = getUnassignedRowsToUpdate(projectId, expenditureCategoryId) || [];
var assignedRowsToUpdate = getAssignedRowsToUpdate(teamRow.Id, projectId, expenditureCategoryId) || [];
if ((assignedRowsToUpdate.length <= 0) && (unassignedRowsToUpdate.length <= 0)) {
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 = activityCalendarService.getRemainingNeedAllocations4Scenario($scope.Filter, Object.keys(project.Teams), project.ActiveScenario.Id, [expenditureCategoryId], weekEndings) || {};
var expCatCollectionNames = activityCalendarUIService.getCollectionNames4AllocationMode(expCatRow.AllocationMode);
for (var i = 0; i < changedCells.length; i++) {
var cell = changedCells[i];
var month = header.Months[cell.MonthIndex];
var monthWeeksCount = month.Childs.length;
// decrease values in related rows by provided delta value
var oldRemainingNeedValue = expCatRow[expCatCollectionNames.hoursCollectionName][cell.WeekIndex] || 0;
// get delta value for exp cat row
var newRemainingNeedValue = ((remainingNeed[expenditureCategoryId] || {}).Allocations || {})[cell.WeekEnding] || 0;
var rollupRemainingNeedDeltaHours = oldRemainingNeedValue - newRemainingNeedValue;
var rollupRemainingNeedDeltaResources = hoursResourcesConverter.convertToResources(expenditureCategoryId, rollupRemainingNeedDeltaHours);
// team allocations delta values
var rollupTeamAllocationDeltaHours = cell.DeltaHoursValue || 0;
var rollupTeamAllocationDeltaResources = hoursResourcesConverter.convertToResources(expenditureCategoryId, rollupTeamAllocationDeltaHours);
// rollup rows under no team section
for (var rowIndex = 0; rowIndex < unassignedRowsToUpdate.length; rowIndex++) {
var row = unassignedRowsToUpdate[rowIndex];
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++) {
var rollupValues = activityCalendarUIService.rollupRemainingRow(assignedRowsToUpdate[rowIndex], (rollupTeamAllocationDeltaHours || 0), (rollupTeamAllocationDeltaResources || 0), false, cell.WeekIndex, month.SelfIndexInWeeks);
setCellsValues4Row(assignedRowsToUpdate[rowIndex], cell.WeekIndex, month.SelfIndexInWeeks, isUOMHours, isAvgMode, monthWeeksCount);
var assignedProjectItems = assignedRowsToUpdate[rowIndex].Children;
if (assignedProjectItems && angular.isArray(assignedProjectItems)) {
for (var projectIndex = 0; projectIndex < assignedProjectItems.length; projectIndex++) {
// refresh original project styles
var assignedProjectItem = assignedProjectItems[projectIndex];
var assignedProjectRow = assignedProjectItem.Row;
if (assignedProjectItem.updateCellStyles) {
refreshProjectStyles(assignedProjectRow, changedCells);
}
// change remaining capacity value for original category in the top part
if (assignedProjectItem.Children && angular.isArray(assignedProjectItem.Children)) {
for (var ecIndex = 0; ecIndex < assignedProjectItem.Children.length; ecIndex++) {
var assignedECRow = assignedProjectItem.Children[ecIndex].Row;
if (assignedECRow.RemainingCapacityValues) {
assignedECRow.RemainingCapacityValues[cell.WeekIndex] += rollupValues[0];
assignedECRow.RemainingCapacityValues[month.SelfIndexInWeeks] += rollupValues[0];
}
}
}
}
}
}
}
};
function getUnassignedRowsToUpdate(projectId, expCatId) {
if (!projectId || !expCatId || !$scope.ViewModel.NoTeamRow || !$scope.ViewModel.NoTeamRow.Children)
return [];
// Get corresponding row for unassigned project
var result = [];
var foundRowsToRollup = findUnassignedRows(projectId, expCatId);
var result = [];
var masterProjectRow = null;
if (foundRowsToRollup) {
if (foundRowsToRollup.expCatRow) {
var expCatRow = {
Row: foundRowsToRollup.expCatRow,
Children: [],
};
if (foundRowsToRollup.masterProjectRow) {
masterProjectRow = {
Row: foundRowsToRollup.masterProjectRow,
Children: [],
};
}
if (foundRowsToRollup.projectRow) {
var projectRow = {
Row: foundRowsToRollup.projectRow,
Children: [],
};
var unassignedRow = {
Row: $scope.ViewModel.NoTeamRow,
Children: []
};
projectRow.Children.push(expCatRow);
if (masterProjectRow) {
masterProjectRow.Children.push(projectRow)
unassignedRow.Children.push(masterProjectRow);
}
else {
unassignedRow.Children.push(projectRow);
}
result.push(unassignedRow);
}
}
}
return result;
};
function getAssignedRowsToUpdate(teamId, projectId, expCatId) {
if (!teamId || !projectId || !expCatId || !$scope.ViewModel.Rows || !$scope.ViewModel.Rows.length) {
return [];
}
var foundRowsToRollup = findAssignedRows(teamId, projectId, expCatId);
var result = [];
var masterProjectRow = null;
if (foundRowsToRollup) {
if (foundRowsToRollup.expCatRow) {
var expCatRow = {
Row: foundRowsToRollup.expCatRow,
Children: [],
};
if (foundRowsToRollup.masterProjectRow) {
masterProjectRow = {
updateCellStyles: false, // No cell highlighting update needed (master project rows have no highlighting)
Row: foundRowsToRollup.masterProjectRow,
Children: [],
};
}
if (foundRowsToRollup.projectRow) {
var projectRow = {
updateCellStyles: true, // Cell highlighting update needed
Row: foundRowsToRollup.projectRow,
Children: [],
};
if (foundRowsToRollup.teamRow) {
var teamRow = {
Row: foundRowsToRollup.teamRow,
Children: [],
};
projectRow.Children.push(expCatRow);
if (masterProjectRow) {
masterProjectRow.Children.push(projectRow)
teamRow.Children.push(masterProjectRow);
}
else {
teamRow.Children.push(projectRow);
}
result.push(teamRow);
}
}
}
}
return result;
};
function findAssignedRows(teamId, projectId, expCatId) {
if (!teamId || !projectId || !expCatId || !$scope.ViewModel.Rows || !$scope.ViewModel.Rows.length)
return null;
var result = {
teamRow: null,
masterProjectRow: null,
projectRow: null,
expCatRow: null
};
result.teamRow = activityCalendarUIService.findRowInCollection($scope.ViewModel.Rows, teamId);
if (result.teamRow && angular.isArray(result.teamRow.Children)) {
// Get project rows (master & part)
var projectRowsInfo = activityCalendarUIService.getProjectRows(result.teamRow.Children, projectId);
result.masterProjectRow = projectRowsInfo && projectRowsInfo.masterProjectRow ? projectRowsInfo.masterProjectRow : null;
result.projectRow = projectRowsInfo && projectRowsInfo.projectRow ? projectRowsInfo.projectRow : null;
if (result.projectRow && result.projectRow.Children) {
result.expCatRow = activityCalendarUIService.findRowInCollection(result.projectRow.Children, expCatId);
}
}
return result;
};
function findUnassignedRows(projectId, expCatId) {
if (!projectId || !expCatId || !$scope.ViewModel.NoTeamRow || !$scope.ViewModel.NoTeamRow.Children ||
!$scope.ViewModel.NoTeamRow.Children.length) {
return null;
}
var result = {
masterProjectRow: null,
projectRow: null,
expCatRow: null
};
// Find project rows (master & part)
var projectRowsInfo = activityCalendarUIService.getProjectRows($scope.ViewModel.NoTeamRow.Children, projectId);
result.masterProjectRow = projectRowsInfo && projectRowsInfo.masterProjectRow ? projectRowsInfo.masterProjectRow : null;
result.projectRow = projectRowsInfo && projectRowsInfo.projectRow ? projectRowsInfo.projectRow : null;
if (result.projectRow && result.projectRow.Children) {
result.expCatRow = activityCalendarUIService.findRowInCollection(result.projectRow.Children, expCatId);
}
return result;
};
// 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];
}
}
/* Methods for preparing data */
function removeEmptyUnassignedExpenditures(projectId, projectExpCatsCache) {
if (!projectExpCatsCache)
return;
var result = {};
var ec2TeamsExisting = {};
var expCatsInPendingTeams = activityCalendarService.getExpendituresExistInPendingTeams($scope.Filter, projectId);
for (var expCatId in projectExpCatsCache) {
// check if there is any team that contains category
if (ec2TeamsExisting[expCatId] == undefined) {
ec2TeamsExisting[expCatId] = teamInfoService.teamsWithExpenditureCategoryExist(expCatId);
}
var expCatItem = projectExpCatsCache[expCatId];
if (expCatItem && ec2TeamsExisting[expCatId] &&
((expCatsInPendingTeams && (expCatId in expCatsInPendingTeams) && expCatsInPendingTeams[expCatId]) ||
!calculateDistributionService.isEmptyAllocations(expCatItem.RemainingNeed))) {
// Display EC, if it has realated teams and (it has recently assigned teams or has unallocated need)
result[expCatId] = expCatItem;
}
}
if (Object.keys(result).length < 1) {
// No EC with Remaining Need was found
result = undefined;
}
return result;
};
function getDataModel(projects) {
if (!projects) {
return null;
}
var filterExpendituresByTeams = activityCalendarUIService.isFilteredByTeams($scope.Filter);
var result = {
NoTeam: {
Id: '',
Name: 'No Team',
Projects: {}
}
};
for (var projectId in projects) {
var project = projects[projectId];
if (project.Teams && Object.keys(project.Teams).length) {
for (var teamId in project.Teams) {
// skip newly assigned teams
var type = project.Teams[teamId].Type || activityCalendarService.TeamType.Saved;
if (type === activityCalendarService.TeamType.Pending) {
continue;
}
if (!(teamId in result)) {
var sourceTeam = teamInfoService.getById(teamId);
if (sourceTeam) {
result[teamId] = {
Id: teamId,
Name: sourceTeam.Name,
Projects: {}
};
}
}
// if for some reasons teamInfoService.getById got null
if (!(teamId in result)) {
console.error('There is no team with id = ' + teamId);
continue;
}
var projectExpenditures = activityCalendarUIService.getExpendituresWithResources4Project(project, [teamId], null, $scope.ViewModel.Header, $scope.Filter, false, filterExpendituresByTeams);
if (projectExpenditures && Object.keys(projectExpenditures).length) {
result[teamId].Projects[projectId] = {
Project: project,
Expenditures: projectExpenditures
};
}
}
}
var noTeamProject = angular.extend({}, project, { AllocationMode: activityCalendarUIService.allocationMode.remainingNeedAllocation });
var projectNoTeamsExpenditures = activityCalendarUIService.getExpenditures4Project(noTeamProject, null, null,
$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 && ($.inArray(ecKeys[i], $scope.Filter.ProjectRoles || []) < 0))
delete projectNoTeamsExpenditures[ecKeys[i]];
}
var projectNoTeamsExpendituresNonEmpty = removeEmptyUnassignedExpenditures(projectId, projectNoTeamsExpenditures);
if (projectNoTeamsExpendituresNonEmpty && (Object.keys(projectNoTeamsExpendituresNonEmpty).length > 0)) {
result['NoTeam'].Projects[projectId] = {
Project: noTeamProject,
Expenditures: projectNoTeamsExpendituresNonEmpty
};
}
}
// Add teams without projects to display NPT
var teams = teamInfoService.getAll();
if (teams) {
for (var teamId in teams) {
if (!(teamId in result)) {
var sourceTeam = teamInfoService.getById(teamId);
if (sourceTeam) {
result[teamId] = {
Id: teamId,
Name: sourceTeam.Name,
Projects: {}
};
}
}
}
}
return result;
};
function getDataModel4NewTeam(teamId, projectId, expCatId) {
if (!teamId || !projectId || !expCatId) {
return null;
}
var project = activityCalendarService.getProjectById($scope.Filter, projectId);
if (!project || !project.Teams || !(teamId in project.Teams)) {
return null;
}
var expCats = activityCalendarUIService.getExpendituresWithResources4Project(project, [teamId], [expCatId], $scope.ViewModel.Header, $scope.Filter, true, false);
if (expCats && expCats[expCatId])
return expCats[expCatId];
else
return null;
};
/* Methods for creating view models */
function createViewModel(model) {
createHeaderViewModel(model.WeekEndings);
createRowsViewModel(model);
};
function createHeaderViewModel(weekEndings) {
$scope.ViewModel.Header = (new GridHeader(weekEndings || {}).create());
};
function createRowsViewModel(model) {
$scope.ViewModel.Rows = [];
$scope.ViewModel.NonProjectTimeTotalRows = [];
if (!model || !model.Projects) {
return;
}
projectExpendituresCache = getDataModel(model.Projects);
nonProjectTimeCache = activityCalendarUIService.getNonProjectTimeDataModel(nonProjectTimeCategoryCache, $scope.DisplayMode.GroupBy.Value);
// Set Team-wide npts read-only for current view, because they displayed partially
activityCalendarUIService.setTeamWideNptItemsReadOnly(nonProjectTimeCache);
if (projectExpendituresCache && Object.keys(projectExpendituresCache).length) {
// sort teams by name
var sortedTeams = $filter('sortObjectsBy')(projectExpendituresCache, 'Name', false);
for (var i = 0; i < sortedTeams.length; i++) {
var teamData = sortedTeams[i];
if (!teamData.Id) { // No Team section
continue;
}
var teamViewModel = activityCalendarUIService.createViewModel4Team(teamData);
if (teamViewModel) {
setTooltips4TopPartRows(teamViewModel);
var teamNptData = (nonProjectTimeCache && (teamData.Id in nonProjectTimeCache)) ?
nonProjectTimeCache[teamData.Id] : null;
var teamNptModel = activityCalendarUIService.createViewModel4NptTotalRow('Non-Project Time', teamNptData);
teamViewModel.NonProjectTime = teamNptModel;
$scope.ViewModel.NonProjectTimeTotalRows.push(teamNptModel);
$scope.ViewModel.Rows.push(teamViewModel);
}
}
if ('NoTeam' in projectExpendituresCache && projectExpendituresCache['NoTeam'].Projects) {
$scope.ViewModel.NoTeamRow = activityCalendarUIService.createViewModel4TotalRow('No Team');
$scope.ViewModel.NoTeamRow.AllocationMode = activityCalendarUIService.allocationMode.remainingNeedAllocation;
$scope.ViewModel.NoTeamRow.Children = activityCalendarUIService.createViewModel4Projects(projectExpendituresCache['NoTeam'].Projects);
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 = getAvailableTeams(projectRow.Id, ecRow.Id);
setCustomRowTemplates(ecRow);
}
}
}
}
}
}
// Create total rows
createTotalRowModels();
};
function setCustomRowTemplates(expCatRow) {
if (!expCatRow)
return;
expCatRow.Templates = {
Main: activityCalendarUIService.viewRowTemplates.expCatUnallocatedRowTemplate,
Numbers: activityCalendarUIService.viewRowTemplates.expCatUnallocatedRowNumbersTemplate
};
if (expCatRow.Children) {
for (var tIndex = 0; tIndex < expCatRow.Children.length; tIndex) {
var teamRow = expCatRow.Children[tIndex];
teamRow.Templates = {
Main: activityCalendarUIService.viewRowTemplates.teamUnallocatedRowTemplate,
Numbers: activityCalendarUIService.viewRowTemplates.teamUnallocatedRowNumbersTemplate
};
}
}
};
function setTooltips4TopPartRows(teamRow) {
if (!teamRow || !teamRow.Children)
return;
for (var pIndex = 0; pIndex < teamRow.Children.length; pIndex++) {
var projectRow = teamRow.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.getTooltipExpCatTopContent;
}
}
}
}
};
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 getResourceRows(resourceId) {
var result = activityCalendarUIService.getResourceRows($scope.ViewModel.Rows, resourceId);
return result;
};
function getAvailableTeams(projectId, expenditureCategoryId) {
if (!projectId || !expenditureCategoryId) {
return null;
}
var availableTeams = activityCalendarService.getTeamsAvailable4Assign($scope.Filter, projectId, expenditureCategoryId);
var availableTeamsViewModel = activityCalendarUIService.createViewModel4AvailableTeams(availableTeams);
if (!availableTeamsViewModel || !Object.keys(availableTeamsViewModel).length) {
return null;
}
return $filter('sortObjectsBy')(availableTeamsViewModel, 'Name', false);
};
/* Root methods for creating view models and filling them with data */
function fillViewModelWithData(model) {
if (!$scope.ViewModel.Rows) {
return;
}
for (var i = 0; i < $scope.ViewModel.Rows.length; i++) {
var teamRow = $scope.ViewModel.Rows[i];
var projectExpenditures = projectExpendituresCache[teamRow.Id] || {};
for (var j = 0; j < teamRow.Children.length; j++) {
var projectRow = teamRow.Children[j];
var projectData = (projectExpenditures.Projects || {})[projectRow.Id] || {};
activityCalendarUIService.fillProjectRowWithData(teamRow, projectRow, projectData.Expenditures, $scope.ViewModel.Header, $scope.Filter);
}
if (teamRow.NonProjectTime) {
var teamNptData = (teamRow.Id in nonProjectTimeCache) ? nonProjectTimeCache[teamRow.Id] : null;
activityCalendarUIService.fillTotalNptRowData(teamRow.NonProjectTime, teamNptData, $scope.ViewModel.Header, [teamRow]);
}
}
if ($scope.ViewModel.NoTeamRow && $scope.ViewModel.NoTeamRow.Children && $scope.ViewModel.NoTeamRow.Children.length) {
var projectExpenditures = projectExpendituresCache['NoTeam'] || {};
for (var j = 0; j < $scope.ViewModel.NoTeamRow.Children.length; j++) {
var project = $scope.ViewModel.NoTeamRow.Children[j];
var projectData = (projectExpenditures.Projects || {})[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);
}
}
}
});
}
refreshProjectAvailableTeams(project);
}
activityCalendarUIService.fillTotalRowData($scope.ViewModel.NoTeamRow.Children, $scope.ViewModel.NoTeamRow, $scope.ViewModel.Header);
}
// Clear NPT caches. Caches not need more
nonProjectTimeCache = {};
nonProjectTimeCategoryCache = {};
// Fill total rows with data
fillTotalRowsWithData();
};
function fillTotalRowsWithData() {
var rowsForTotal = ($scope.ViewModel.Rows) ? angular.copy($scope.ViewModel.Rows) : [];
if ($scope.ViewModel.NoTeamRow) {
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) {
// create a UI row model and fill it with data
var team = teamInfoService.getById(teamId);
var ecTeamData = getDataModel4NewTeam(teamId, projectRow.Id, expCatRow.Id);
ecTeamData = angular.extend({}, ecTeamData, {
Id: team.Id,
ExpenditureCategoryId: ecTeamData.Id,
Name: team.Name,
Initialized: expCatRow.Initialized,
Show: expCatRow.Show,
Level: expCatRow.Level + 1
});
var newTeamRow = activityCalendarUIService.createViewModel4ECTeam(ecTeamData);
activityCalendarUIService.fillECTeamRowWithData($scope.ViewModel.Header, newTeamRow, expCatRow, ecTeamData, projectRow.Id, projectRow.ActiveScenario.StartDate, projectRow.ActiveScenario.EndDate, projectRow.ReadOnly);
activityCalendarUIService.toggleGridSource([newTeamRow], $scope.ViewModel.DisplayMode.IsUOMHours);
if ($scope.ViewModel.DisplayMode.IsAvgMode()) {
activityCalendarUIService.applyAvgMode([newTeamRow], $scope.ViewModel.Header);
}
// Remove highlighting classes for new team row (they are automatically set in toggleGridSource
if (newTeamRow && newTeamRow.CSSClass && angular.isArray(newTeamRow.CSSClass)) {
for (var index = 0; index < newTeamRow.CSSClass.length; index++) {
newTeamRow.CSSClass[index] = '';
}
}
// add new team row to EC row's children and resort it
if (!expCatRow.Children)
expCatRow.Children = [];
expCatRow.Children.push(newTeamRow);
expCatRow.Children = activityCalendarUIService.toggleSortBy(expCatRow.Children, $scope.ViewModel.DisplayMode.SortBy, $scope.ViewModel.DisplayMode.SortOrder);
};
/* 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.NoTeamRow) {
activityCalendarUIService.toggleGridSource([$scope.ViewModel.NoTeamRow], $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.NoTeamRow) {
activityCalendarUIService.applyAvgMode([$scope.ViewModel.NoTeamRow], $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.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 team = $scope.ViewModel.Rows[i];
if (team.Children && team.Children.length > 0) {
team.Children = activityCalendarUIService.toggleParts(team.Children, $scope.ViewModel.DisplayMode.ShowParts.Value);
activityCalendarUIService.updateProjectStyles($scope.ViewModel.Header, $scope.Filter, team.Children, null, null, [team.Id]);
}
}
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 team = $scope.ViewModel.Rows[i];
if (team.Children && team.Children.length > 0) {
team.Children = activityCalendarUIService.toggleSortBy(team.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 teamIndex = 0; teamIndex < $scope.ViewModel.Rows.length; teamIndex++) {
var teamRow = $scope.ViewModel.Rows[teamIndex];
if (teamRow && teamRow.Children) {
activityCalendarUIService.recalculateResourceAvailability($scope.Filter, $scope.ViewModel.Header, teamRow.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);
};
function refreshProjectStyles(projectRow, data) {
if (!projectRow || typeof data !== 'object' || !angular.isArray(data)) {
return;
}
var rowToUpdateStylesIn = projectRow;
if (projectRow.Level && (projectRow.Level > 1)) {
// For master project perform style updates for entire master project
for (var i = 0; i < $scope.ViewModel.Rows.length; i++) {
var team = $scope.ViewModel.Rows[i];
if (team.Children && team.Children.length) {
var projectRowsInfo = activityCalendarUIService.getProjectRows(team.Children, projectRow.Id);
if (projectRowsInfo && projectRowsInfo.masterProjectRow && projectRowsInfo.projectRow &&
(projectRowsInfo.projectRow.ParentTeamId == projectRow.ParentTeamId)) {
rowToUpdateStylesIn = projectRowsInfo.masterProjectRow;
break;
}
}
}
}
activityCalendarUIService.updateProjectStyles($scope.ViewModel.Header, $scope.Filter, [rowToUpdateStylesIn], data, null, [projectRow.ParentTeamId]);
};
function refreshNoTeamProjectStyles(projectRow, data) {
if (!projectRow || typeof data !== 'object' || !angular.isArray(data)) {
return;
}
var rowToUpdateStylesIn = projectRow;
if (projectRow.Level && (projectRow.Level > 1)) {
// For master project perform style updates for entire master project
var projectRowsInfo = activityCalendarUIService.getProjectRows($scope.ViewModel.NoTeamRow.Children, projectRow.Id);
if (projectRowsInfo && projectRowsInfo.masterProjectRow) {
rowToUpdateStylesIn = projectRowsInfo.masterProjectRow;
}
}
activityCalendarUIService.updateProjectStyles($scope.ViewModel.Header, $scope.Filter, [rowToUpdateStylesIn], data);
};
/* ===== ToolTips display ==== */
$scope.getTooltipExpCatTopContent = 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 += ('
' + teamInfo.Name + ': ' + teamInfo.Need + units);
}
}
else {
tooltip = 'No Team Allocations present';
}
return tooltip;
};
$scope.getTooltipEcUnassignedContent = function (opts) {
var tooltip = 'No data available';
var units = $scope.DisplayMode.IsUOMHours ? ' hours' : ' resources';
if (!opts || !opts.header || !opts.projectId || !opts.expCatId)
return tooltip;
var header = opts.header;
var projectId = opts.projectId;
var expCatId = opts.expCatId;
var rows = findUnassignedRows(projectId, expCatId);
if (!rows || !rows.projectRow || !rows.expCatRow)
return tooltip;
var projectRow = rows.projectRow;
var expCatRow = rows.expCatRow;
var scenarioId = projectRow.ActiveScenario ? projectRow.ActiveScenario.Id : null;
if (!projectId || !scenarioId) {
return tooltip;
}
var expCatInfo = activityCalendarUIService.getExpCatUnassignedNeed($scope.Filter, $scope.ViewModel.DisplayMode,
$scope.ViewModel.Header, header, projectId, scenarioId, [expCatRow.Id], false);
if (expCatInfo) {
tooltip =
'Category Need: ' + String(expCatInfo.Need || 0) + units + '
' +
'Allocated to Teams in View: ' + String(expCatInfo.AllocatedToTeamsInView || 0) + units + '
' +
'Allocated to Teams out of View: ' + String(expCatInfo.AllocatedToTeamsOutOfView || 0) + units;
if (expCatInfo.Underallocation) {
if (expCatInfo.Underallocation > 0) {
tooltip += ('
Underallocation: ' + String(expCatInfo.Underallocation) + units);
}
else {
if (expCatInfo.Underallocation < 0) {
tooltip += ('
Overallocation: ' + String(-expCatInfo.Underallocation) + units);
}
}
}
}
return tooltip;
};
$scope.getTooltipProjectUnassignedContent = 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 projectNeed = 0;
var projectAllocatedInView = 0;
var projectAllocatedOutOfView = 0;
var projectRowsInfo = activityCalendarUIService.getProjectRows($scope.ViewModel.NoTeamRow.Children, projectId);
var projectRow = projectRowsInfo && projectRowsInfo.projectRow ? projectRowsInfo.projectRow : null;
if (!projectRow)
return tooltip;
var scenarioId = projectRow.ActiveScenario ? projectRow.ActiveScenario.Id : null;
if (!scenarioId) {
return tooltip;
}
if (projectRow.Children && projectRow.Children.length) {
for (var ecIndex = 0; ecIndex < projectRow.Children.length; ecIndex++) {
var expCatRow = projectRow.Children[ecIndex];
if (expCatRow) {
var expCatSummaryInfo = activityCalendarUIService.getExpCatUnassignedNeed($scope.Filter, $scope.ViewModel.DisplayMode,
$scope.ViewModel.Header, header, projectId, scenarioId, [expCatRow.Id], false);
if (expCatSummaryInfo) {
projectNeed += (expCatSummaryInfo.Need || 0);
projectAllocatedInView += (expCatSummaryInfo.AllocatedToTeamsInView || 0);
projectAllocatedOutOfView += (expCatSummaryInfo.AllocatedToTeamsOutOfView || 0);
}
}
}
}
projectNeed = roundService.roundQuantity(projectNeed);
projectAllocatedInView = roundService.roundQuantity(projectAllocatedInView);
projectAllocatedOutOfView = roundService.roundQuantity(projectAllocatedOutOfView);
tooltip =
'Project Need: ' + String(projectNeed) + units + '
' +
'Allocated to Teams in View: ' + String(projectAllocatedInView) + units + '
' +
'Allocated to Teams out of View: ' + String(projectAllocatedOutOfView) + units;
return tooltip;
};
}]);