1594 lines
76 KiB
JavaScript
1594 lines
76 KiB
JavaScript
'use strict';
|
|
|
|
app.controller('scenarioBottomUpController', ['$scope', '$timeout', '$q', 'scenarioDetailsService', 'teamInfoService', 'hoursResourcesConverter', 'roundService', 'cellHighlightingService', 'dataSources', '$rootScope', function ($scope, $timeout, $q, scenarioDetailsService, teamInfoService, hoursResourcesConverter, roundService, cellHighlightingService, dataSources, $rootScope) {
|
|
var collapsedIcon = 'fa-plus-square',
|
|
nonCollapsedIcon = 'fa-minus-square';
|
|
|
|
var C_HEADER_DATA_TYPE_ORDINAL = 1;
|
|
var C_HEADER_DATA_TYPE_TOTALS = 100;
|
|
|
|
var C_CALENDAR_VIEW_MODE_FORECAST = "F";
|
|
var C_CALENDAR_VIEW_MODE_ACTUALS = "A";
|
|
|
|
$scope.ViewModel = {
|
|
ScenarioId: null,
|
|
MonthHeaders: null,
|
|
WeekHeaders: null,
|
|
CalendarFilter: null,
|
|
ColspanValues: {},
|
|
Teams: null,
|
|
Total: {
|
|
Expanded: null,
|
|
CollapsedClass: collapsedIcon,
|
|
},
|
|
};
|
|
$scope.$on('refreshScenarioDetailsGrid', refreshScenarioDetailsGridHandler);
|
|
$scope.$on('refreshTableMode', refreshTableModeHandler);
|
|
$scope.$on('refreshUOMMode', refreshUOMModeHandler);
|
|
$scope.$on('refreshActualsMode', refreshActualsModeHandler);
|
|
$scope.$on('applyGridFilter', applyGridFilterHandler);
|
|
|
|
$scope.toggleTotalRow = function () {
|
|
$scope.ViewModel.Total.Expanded = !$scope.ViewModel.Total.Expanded;
|
|
$scope.ViewModel.Total.CollapsedClass = $scope.ViewModel.Total.Expanded ? nonCollapsedIcon : collapsedIcon;
|
|
};
|
|
$scope.assignResource = function (row, $event) {
|
|
if (!row || !row.AvailableResources || !row.ResourceToAssignId)
|
|
return;
|
|
|
|
var resource = row.AvailableResources[row.ResourceToAssignId];
|
|
if (!resource)
|
|
return;
|
|
|
|
if (scenarioDetailsService.isExpenditureExists($scope.ViewModel.ScenarioId, resource.expenditureCategoryId)) {
|
|
blockUI();
|
|
assignResourceAsync(resource, $event)
|
|
.then(function () {
|
|
// we need to refresh IsEditable flag for total row after new resource was assigned because it may has read only weeks
|
|
checkGrandTotalIsEditable();
|
|
unblockUI();
|
|
})
|
|
.then(null, function () {
|
|
unblockUI();
|
|
showErrorModal('Oops!', $scope.CommonErrorMessage);
|
|
});
|
|
}
|
|
else {
|
|
$scope.$emit('addNewExpenditureCategory', {
|
|
expenditureCategoryId: resource.expenditureCategoryId,
|
|
callback: function () {
|
|
return assignResourceAsync(resource, $event)
|
|
.then(null, function () {
|
|
showErrorModal('Oops!', $scope.CommonErrorMessage);
|
|
});
|
|
}
|
|
});
|
|
}
|
|
};
|
|
$scope.checkResourceValue = function (resource, colIndex, value) {
|
|
var newValue = roundService.roundQuantity(value);
|
|
if (isNaN(newValue))
|
|
newValue = 0;
|
|
|
|
if (newValue < 0) {
|
|
return "Value should not be less than zero";
|
|
}
|
|
|
|
if (newValue == resource.Cells[colIndex])
|
|
return false;
|
|
|
|
var weekHeader = $scope.ViewModel.WeekHeaders[colIndex];
|
|
if (weekHeader.DataType == C_HEADER_DATA_TYPE_TOTALS) {
|
|
changeResourceMonthValue(resource, colIndex, newValue);
|
|
}
|
|
else {
|
|
changeResourceWeekValue(resource, colIndex, newValue);
|
|
}
|
|
|
|
// we need to refresh css for all month cells even if user changed only one week cell
|
|
refreshResourceMonthCss(resource, weekHeader.MonthHeader);
|
|
|
|
// we need to refresh CanBeDeleted flag after resource total was changed
|
|
checkResourceCanBeDeleted(resource);
|
|
|
|
// notify parent controller about grid was changed
|
|
triggerEventAboutScenarioChanged();
|
|
|
|
//required to be false by xeditable grid
|
|
return false;
|
|
};
|
|
$scope.checkResourceTotalValue = function (resource, value) {
|
|
var newValue = roundService.roundQuantity(value);
|
|
if (isNaN(newValue))
|
|
newValue = 0;
|
|
|
|
if (newValue < 0) {
|
|
return "Value should not be less than zero";
|
|
}
|
|
|
|
if (newValue == resource.TotalValue)
|
|
return false;
|
|
|
|
changeResourceTotalValue(resource, newValue);
|
|
|
|
// we need to refresh css for all resource cells
|
|
refreshResourceCss(resource);
|
|
|
|
// we need to refresh CanBeDeleted flag after resource total was changed
|
|
checkResourceCanBeDeleted(resource);
|
|
|
|
// notify parent controller about grid was changed
|
|
triggerEventAboutScenarioChanged();
|
|
|
|
//required to be false by xeditable grid
|
|
return false;
|
|
};
|
|
$scope.checkGrandTotalValue = function (value) {
|
|
var newValue = roundService.roundQuantity(value);
|
|
if (isNaN(newValue))
|
|
newValue = 0;
|
|
|
|
if (newValue < 0) {
|
|
return "Value should not be less than zero";
|
|
}
|
|
|
|
if (newValue == $scope.ViewModel.Total.TotalValue)
|
|
return false;
|
|
|
|
changeGrandTotalValue(newValue);
|
|
|
|
// we need to refresh css for entire grid
|
|
refreshGridCss();
|
|
|
|
// we need to refresh CanBeDeleted flag for all assigned resources after grand total was changed
|
|
checkResourcesCanBeDeleted();
|
|
|
|
// notify parent controller about grid was changed
|
|
triggerEventAboutScenarioChanged();
|
|
|
|
//required to be false by xeditable grid
|
|
return false;
|
|
};
|
|
$scope.zeroResource = function (resource) {
|
|
if (!resource)
|
|
return;
|
|
bootbox.confirm({
|
|
message: "Are you sure you want fill this resource quantities with 0s?",
|
|
callback: function (result) {
|
|
if (result) {
|
|
$scope.$apply(function () {
|
|
zeroOutResource(resource);
|
|
});
|
|
}
|
|
}
|
|
});
|
|
};
|
|
$scope.removeResource = function (resource, $event) {
|
|
if (!resource || !resource.CanBeDeleted)
|
|
return;
|
|
bootbox.confirm({
|
|
message: "Are you sure you want to remove this resource?",
|
|
callback: function (result) {
|
|
if (result) {
|
|
$scope.$apply(function () {
|
|
blockUI();
|
|
zeroOutResource(resource);
|
|
var data = { DomainId: null, ParentId: resource.Id };
|
|
$rootScope.$broadcast('removeNoteCB', data);
|
|
removeResourceAsync(resource, $event)
|
|
.then(function () {
|
|
var isLastResource = !scenarioDetailsService.checkExpenditureCategoryHasAssignedResources($scope.ViewModel.ScenarioId, resource.ExpenditureCategoryId);
|
|
if (isLastResource) {
|
|
triggerEventAboutExpenditureCategoriesNeedToBeRemoved([resource.ExpenditureCategoryId]);
|
|
}
|
|
else {
|
|
// we need to refresh IsEditable flag for total row after new resource was assigned because it may has read only weeks
|
|
checkGrandTotalIsEditable();
|
|
}
|
|
unblockUI();
|
|
})
|
|
.then(null, function () {
|
|
unblockUI();
|
|
showErrorModal('Oops!', $scope.CommonErrorMessage);
|
|
});
|
|
});
|
|
}
|
|
}
|
|
});
|
|
};
|
|
function zeroOutResource(resource)
|
|
{
|
|
//changeResourceTotalValue(resource, 0);
|
|
var weeks = getVisibleWeeksInCalendar($scope.ViewModel.CalendarFilter.ViewModeName);
|
|
for (var i = 0; i < weeks.length; i++) {
|
|
changeResourceWeekValue(resource, weeks[i], 0);
|
|
}
|
|
// we need to refresh css for all resource cells
|
|
refreshResourceCss(resource);
|
|
|
|
// we need to refresh CanBeDeleted flag after resource total was changed
|
|
checkResourceCanBeDeleted(resource);
|
|
|
|
// notify parent controller about grid was changed
|
|
triggerEventAboutScenarioChanged();
|
|
}
|
|
$scope.removeTeam = function (team) {
|
|
if (!team || !team.CanBeDeleted)
|
|
return;
|
|
bootbox.confirm({
|
|
message: "Are you sure you want to remove this team and all assigned resources from this one?",
|
|
callback: function (result) {
|
|
if (result) {
|
|
$scope.$apply(function () {
|
|
removeTeamInternal(team);
|
|
});
|
|
}
|
|
}
|
|
});
|
|
};
|
|
$scope.watchKeyInput = function (t) {
|
|
|
|
$timeout(function () {
|
|
if (t.$editable.inputEl.select)
|
|
t.$editable.inputEl.select();
|
|
else if (t.$editable.inputEl.setSelectionRange)
|
|
t.$editable.inputEl.setSelectionRange(0, t.$editable.inputEl.val().length);
|
|
}, 3);
|
|
|
|
t.$editable.inputEl.on('keydown', function (e) {
|
|
|
|
if (e.which == 9) { //when tab key is pressed
|
|
e.preventDefault();
|
|
var tab2Cell;
|
|
if (e.shiftKey) { // when shift + tab use with 'onblur' set to 'submit' for automatic submission find the parent of the editable before this one in the markup grab the editable and display it
|
|
tab2Cell = $(this).parentsUntil('table#table').prevAll(":has(.editable:visible):first").find(".editable:visible:last");
|
|
t.$form.$submit();
|
|
$timeout(function () {
|
|
tab2Cell.click();
|
|
}, 0);
|
|
} else { // when just tab use with 'onblur' set to 'submit' for automatic submission find the parent of the editable after this one in the markup grab the editable and display it
|
|
tab2Cell = $(this).parentsUntil('table#table').nextAll(":has(.editable:visible):first").find(".editable:visible:first");
|
|
t.$form.$submit();
|
|
$timeout(function () {
|
|
tab2Cell.click();
|
|
}, 0);
|
|
}
|
|
}
|
|
});
|
|
};
|
|
$scope.onTxtBlur = function (txt) {
|
|
txt.$form.$submit();
|
|
};
|
|
|
|
/* Event handlers */
|
|
function refreshScenarioDetailsGridHandler(event, data) {
|
|
data = data || {};
|
|
|
|
$scope.ViewModel.ScenarioId = data.ScenarioId;
|
|
$scope.ViewModel.MonthHeaders = data.MonthHeaders || [];
|
|
$scope.ViewModel.WeekHeaders = data.WeekHeaders || [];
|
|
$scope.ViewModel.CalendarFilter = data.CalendarFilter || {};
|
|
$scope.ViewModel.ColspanValues[C_CALENDAR_VIEW_MODE_ACTUALS] = getVisibleWeeksInCalendarCount(C_CALENDAR_VIEW_MODE_ACTUALS);
|
|
$scope.ViewModel.ColspanValues[C_CALENDAR_VIEW_MODE_FORECAST] = getVisibleWeeksInCalendarCount(C_CALENDAR_VIEW_MODE_FORECAST);
|
|
|
|
recreateView();
|
|
};
|
|
function recreateView() {
|
|
var scenarioInfo = scenarioDetailsService.getScenarioInfo($scope.ViewModel.ScenarioId);
|
|
if (!scenarioInfo || !scenarioInfo.TeamsInScenario)
|
|
return;
|
|
|
|
blockUI();
|
|
|
|
var loadTeamsTask = teamInfoService.getTeamsById(Object.keys(scenarioInfo.TeamsInScenario));
|
|
var loadExpendituresTask = hoursResourcesConverter.load();
|
|
|
|
$q.all([loadTeamsTask, loadExpendituresTask])
|
|
.then(function (asyncResults) { // asyncResults is array of results from each async method, order corresponds to order of async method calls
|
|
createViewModel(asyncResults[0], scenarioInfo.Expenditures);
|
|
fillViewModelWithData(scenarioInfo.Expenditures);
|
|
checkResourcesCanBeDeleted();
|
|
checkGrandTotalIsEditable();
|
|
refreshGridCss();
|
|
setGridSource();
|
|
initResourceAssignControls();
|
|
unblockUI();
|
|
})
|
|
.then(null, function () {
|
|
unblockUI();
|
|
showErrorModal('Oops!', $scope.CommonErrorMessage);
|
|
});
|
|
};
|
|
function refreshTableModeHandler(event, data) {
|
|
$scope.ViewModel.CalendarFilter.IsTableModeQuantity = data;
|
|
checkGrandTotalIsEditable();
|
|
checkResourcesCanBeDeleted();
|
|
setGridSource();
|
|
};
|
|
function refreshUOMModeHandler(event, data) {
|
|
$scope.ViewModel.CalendarFilter.IsUOMHours = data;
|
|
setGridSource();
|
|
};
|
|
function refreshActualsModeHandler(event, data) {
|
|
$scope.ViewModel.CalendarFilter.ShowActuals = data.ShowActuals;
|
|
$scope.ViewModel.CalendarFilter.ViewModeName = data.ViewModeName;
|
|
|
|
// TODO: review for data recalculation only instead of recreating entire view
|
|
recreateView();
|
|
|
|
// we should always expand total row if user has selected actuals mode
|
|
if ($scope.ViewModel.CalendarFilter.ShowActuals && !$scope.ViewModel.Total.Expanded) {
|
|
$scope.toggleTotalRow();
|
|
}
|
|
};
|
|
function applyGridFilterHandler(event, data) {
|
|
data = data || {};
|
|
|
|
$scope.ViewModel.CalendarFilter.CategoryType = data.CategoryType;
|
|
$scope.ViewModel.CalendarFilter.GLAccount = data.GLAccount;
|
|
$scope.ViewModel.CalendarFilter.CreditDepartment = data.CreditDepartment;
|
|
$scope.ViewModel.CalendarFilter.SelectedExpCats = data.SelectedExpCats;
|
|
|
|
recreateView();
|
|
};
|
|
|
|
/* Root methods for creating view models and filling them with data */
|
|
function createViewModel(teams, expenditures) {
|
|
createTeamsViewModel(teams, expenditures);
|
|
createTotalViewModel(expenditures);
|
|
};
|
|
function fillViewModelWithData(expenditures) {
|
|
if (!expenditures)
|
|
return;
|
|
|
|
angular.forEach($scope.ViewModel.Total.Expenditures, function (categoryRow) {
|
|
|
|
var categoryData = expenditures[categoryRow.Id];
|
|
|
|
fillExpenditureViewModelWithData(categoryRow, categoryData);
|
|
|
|
if (categoryData.Teams && Object.keys(categoryData.Teams).length > 0) {
|
|
angular.forEach($scope.ViewModel.Teams, function (teamRow) {
|
|
if (categoryData.Teams[teamRow.Id]) {
|
|
fillTeamViewModelWithData(teamRow, categoryData.Teams[teamRow.Id], categoryData);
|
|
}
|
|
});
|
|
}
|
|
});
|
|
};
|
|
|
|
/* Methods for teams view model */
|
|
function createTeamsViewModel(teams, expenditures) {
|
|
$scope.ViewModel.Teams = {};
|
|
|
|
if (!teams)
|
|
return;
|
|
|
|
var scenarioInfo = scenarioDetailsService.getScenarioInfo($scope.ViewModel.ScenarioId);
|
|
if (!scenarioInfo || !scenarioInfo.TeamsInScenario)
|
|
return;
|
|
|
|
angular.forEach(teams, function (currentTeam, teamId) {
|
|
var teamViewModel = createViewModel4Team(currentTeam, scenarioInfo.TeamsInScenario[teamId], expenditures);
|
|
if (teamViewModel) {
|
|
$scope.ViewModel.Teams[teamId] = teamViewModel;
|
|
}
|
|
});
|
|
};
|
|
function createViewModel4Team(sourceTeam, teamInScenarioInfo, expenditures) {
|
|
if (!sourceTeam || !teamInScenarioInfo)
|
|
return null;
|
|
|
|
var assignedResources = getAssignedResources(sourceTeam.Id, expenditures),
|
|
availableResources = getAvailableResources(sourceTeam);
|
|
|
|
var teamViewModel = {
|
|
Id: sourceTeam.Id,
|
|
Name: sourceTeam.Name,
|
|
IsAccessible: teamInScenarioInfo.IsAccessible,
|
|
CanBeDeleted: teamInScenarioInfo.CanBeDeleted,
|
|
ResourceToAssignId: null,
|
|
AssignedResources: assignedResources,
|
|
AvailableResources: availableResources,
|
|
HasAvailableResources: availableResources && Object.keys(availableResources).length > 0,
|
|
TotalHoursValue: 0, // total value in hours
|
|
TotalResourcesValue: 0, // total value in resources
|
|
TotalValue: 0, // total value for view, it depends on $scope.Viewodel.CalendarFilter (hours, resources)
|
|
QuantityHoursValues: new Array($scope.ViewModel.WeekHeaders.length), // week/month values in hours
|
|
QuantityResourcesValues: new Array($scope.ViewModel.WeekHeaders.length), // week/month values in resources
|
|
Cells: new Array($scope.ViewModel.WeekHeaders.length) // week/month values for view, it depends on $scope.Viewodel.CalendarFilter (hours, resources)
|
|
};
|
|
|
|
return teamViewModel;
|
|
};
|
|
function createViewModel4Resource(expenditureCategory, team, resource) {
|
|
if (!expenditureCategory || !team || !resource)
|
|
return null;
|
|
|
|
var resourceViewModel = {
|
|
Id: resource.Id,
|
|
Name: resource.Name,
|
|
CanBeDeleted: true, // this property changes separately, by default it should be true
|
|
ExpenditureCategoryId: expenditureCategory.ExpenditureCategoryId,
|
|
TeamId: team.Id,
|
|
ReadOnly: new Array($scope.ViewModel.WeekHeaders.length), // week/month values in resources
|
|
TotalHoursValue: 0, // total value in hours
|
|
TotalResourcesValue: 0, // total value in resources
|
|
TotalValue: 0, // total value for view, it depends on $scope.Viewodel.CalendarFilter (hours, resources)
|
|
QuantityHoursValues: new Array($scope.ViewModel.WeekHeaders.length), // week/month values in hours
|
|
QuantityResourcesValues: new Array($scope.ViewModel.WeekHeaders.length), // week/month values in resources
|
|
Cells: new Array($scope.ViewModel.WeekHeaders.length), // week/month values for view, it depends on $scope.Viewodel.CalendarFilter (hours, resources)
|
|
CSSClass: new Array($scope.ViewModel.WeekHeaders.length) // array with css strings for each week/month cell
|
|
};
|
|
|
|
return resourceViewModel;
|
|
};
|
|
function getAssignedResources(sourceTeamId, expenditures) {
|
|
var resourcesModel = {};
|
|
if (!sourceTeamId || !expenditures)
|
|
return resourcesModel;
|
|
|
|
angular.forEach(expenditures, function (category, categoryId) {
|
|
if (category.Teams && category.Teams[sourceTeamId]) {
|
|
var resources = category.Teams[sourceTeamId].Resources;
|
|
if (resources) {
|
|
angular.forEach(resources, function (resource, resourceId) {
|
|
if (!resource.Deleted && checkExpenditureCategory(categoryId)) {
|
|
var resourceExt = teamInfoService.extendResourceModelWithAttributes(resource, sourceTeamId)
|
|
var resourceViewModel = createViewModel4Resource(category, category.Teams[sourceTeamId], resourceExt);
|
|
|
|
if (resourceViewModel)
|
|
resourcesModel[resourceId] = resourceViewModel;
|
|
}
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
return resourcesModel;
|
|
};
|
|
function getAvailableResources(sourceTeam) {
|
|
var resources = {};
|
|
if (!sourceTeam || !sourceTeam.ExpCategories)
|
|
return resources;
|
|
|
|
var weekEndings = $scope.getScenarioWeekEndings();
|
|
angular.forEach(sourceTeam.ExpCategories, function (category, categoryId) {
|
|
if (category.Resources) {
|
|
angular.forEach(category.Resources, function (resource, resourceId) {
|
|
var resourceExt = teamInfoService.extendResourceModelWithAttributes(resource, sourceTeam.Id);
|
|
var isResourceAssigned = scenarioDetailsService.checkResourceAssignedToScenario($scope.ViewModel.ScenarioId, resourceExt.OwnExpenditureCategoryId, sourceTeam.Id, resourceId);
|
|
if (!isResourceAssigned && checkExpenditureCategory(resourceExt.OwnExpenditureCategoryId)) {
|
|
var resourceViewModel = getAvailableResourceViewModel(sourceTeam, resourceExt, weekEndings);
|
|
|
|
if (resourceViewModel)
|
|
resources[resourceId] = resourceViewModel;
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
return resources;
|
|
};
|
|
function getAvailableResourceViewModel(team, resource, weekEndings) {
|
|
if (!team || !resource)
|
|
return null;
|
|
|
|
var resourceCopy = {
|
|
AllocatedCapacity: resource.AllocatedCapacity,
|
|
TotalCapacity: resource.TotalCapacity,
|
|
NonProjectTime: resource.NonProjectTime,
|
|
Teams: []
|
|
};
|
|
|
|
if (resource.Teams) {
|
|
resourceCopy.Teams = angular.copy(resource.Teams);
|
|
resourceCopy.StartDate = resource.Teams.GetMinStartDate();
|
|
resourceCopy.EndDate = resource.Teams.GetMaxEndDate();
|
|
}
|
|
|
|
scenarioDetailsService.recalculateResourceAvailability(resourceCopy, weekEndings);
|
|
|
|
var resourceViewModel = {
|
|
id: resource.Id,
|
|
expenditureCategoryId: resource.OwnExpenditureCategoryId,
|
|
teamId: team.Id,
|
|
name: resource.Name,
|
|
minAvailability: resourceCopy.MinAvailability,
|
|
maxAvailability: resourceCopy.MaxAvailability,
|
|
avgAvailability: resourceCopy.AvgAvailability,
|
|
isVisible: resourceCopy.IsVisible
|
|
};
|
|
|
|
return resourceViewModel;
|
|
};
|
|
|
|
/* Methods for total view model */
|
|
function createTotalViewModel(expenditures) {
|
|
$scope.ViewModel.Total.TotalHoursValue = 0; // total value in hours
|
|
$scope.ViewModel.Total.TotalResourcesValue = 0; // total value in resources
|
|
$scope.ViewModel.Total.TotalCostValue = 0; // total cost
|
|
$scope.ViewModel.Total.TotalValue = 0; // total value for view, it depends on $scope.Viewodel.CalendarFilter (hours, resources, cost)
|
|
$scope.ViewModel.Total.QuantityHoursValues = new Array($scope.ViewModel.WeekHeaders.length); // week/month values in hours
|
|
$scope.ViewModel.Total.QuantityResourcesValues = new Array($scope.ViewModel.WeekHeaders.length); // week/month values in resources
|
|
$scope.ViewModel.Total.CostValues = new Array($scope.ViewModel.WeekHeaders.length); // week/month costs
|
|
$scope.ViewModel.Total.Cells = new Array($scope.ViewModel.WeekHeaders.length); // week/month values for view, it depends on $scope.Viewodel.CalendarFilter (hours, resources, costs)
|
|
$scope.ViewModel.Total.IsEditable = false;
|
|
$scope.ViewModel.Total.Expenditures = {};
|
|
|
|
// we should expand total row if user has selected actuals mode and total row does not have state yet
|
|
if ($scope.ViewModel.CalendarFilter.ShowActuals && $scope.ViewModel.Total.Expanded == null) {
|
|
$scope.toggleTotalRow();
|
|
}
|
|
|
|
refreshTotalExpenditures(expenditures);
|
|
};
|
|
function refreshTotalExpenditures(expenditures) {
|
|
if (!expenditures || !$scope.ViewModel.Total)
|
|
return;
|
|
|
|
$scope.ViewModel.Total.Expenditures = {};
|
|
angular.forEach(expenditures, function (category, categoryId) {
|
|
if (checkExpenditureCategory(categoryId)) {
|
|
var categoryViewModel = createEpxenditureViewModel(category);
|
|
if (categoryViewModel)
|
|
$scope.ViewModel.Total.Expenditures[categoryId] = categoryViewModel;
|
|
}
|
|
});
|
|
};
|
|
function createEpxenditureViewModel(category) {
|
|
if (!category)
|
|
return null;
|
|
|
|
var categoryViewModel = {
|
|
Id: category.ExpenditureCategoryId,
|
|
Name: category.ExpenditureCategoryName,
|
|
|
|
TotalHoursValue: 0, // total value in hours
|
|
TotalResourcesValue: 0, // total value in resources
|
|
TotalCostValue: 0, // total cost
|
|
TotalValue: 0, // total value for view, it depends on $scope.Viewodel.CalendarFilter (hours, resources, cost)
|
|
QuantityHoursValues: new Array($scope.ViewModel.WeekHeaders.length), // week/month values in hours
|
|
QuantityResourcesValues: new Array($scope.ViewModel.WeekHeaders.length), // week/month values in resources
|
|
CostValues: new Array($scope.ViewModel.WeekHeaders.length), // week/month costs
|
|
Cells: new Array($scope.ViewModel.WeekHeaders.length) // week/month values for view, it depends on $scope.Viewodel.CalendarFilter (hours, resources, costs)
|
|
};
|
|
|
|
return categoryViewModel;
|
|
};
|
|
|
|
/* Methods for filling view model with data */
|
|
function fillExpenditureViewModelWithData(row, category) {
|
|
if (!row || !category || !category.Details)
|
|
return;
|
|
|
|
var monthCost = 0,
|
|
monthHoursQuantity = 0,
|
|
monthResourceQuantity = 0;
|
|
|
|
var totalRow = $scope.ViewModel.Total;
|
|
|
|
for (var i = 0; i < $scope.ViewModel.WeekHeaders.length; i++) {
|
|
var header = $scope.ViewModel.WeekHeaders[i];
|
|
if (header.DataType == C_HEADER_DATA_TYPE_ORDINAL) {
|
|
|
|
var isActualsCell = !header.Editable[$scope.ViewModel.CalendarFilter.ViewModeName];
|
|
var scenarioDetail = category.Details[header.Milliseconds];
|
|
|
|
if (isActualsCell) {
|
|
row.CostValues[i] = roundService.roundCost(scenarioDetail.ActualsCost);
|
|
row.QuantityHoursValues[i] = roundService.roundQuantity(scenarioDetail.ActualsQuantity);
|
|
row.QuantityResourcesValues[i] = roundService.roundQuantity(hoursResourcesConverter.convertToResources(category.ExpenditureCategoryId, scenarioDetail.ActualsQuantity));
|
|
} else {
|
|
row.CostValues[i] = roundService.roundCost(scenarioDetail.ForecastCost);
|
|
row.QuantityHoursValues[i] = roundService.roundQuantity(scenarioDetail.ForecastQuantity);
|
|
row.QuantityResourcesValues[i] = roundService.roundQuantity(hoursResourcesConverter.convertToResources(category.ExpenditureCategoryId, scenarioDetail.ForecastQuantity));
|
|
}
|
|
|
|
if (header.Visible[$scope.ViewModel.CalendarFilter.ViewModeName]) {
|
|
monthCost = roundService.roundCost(monthCost + row.CostValues[i]);
|
|
monthHoursQuantity = roundService.roundQuantity(monthHoursQuantity + row.QuantityHoursValues[i]);
|
|
monthResourceQuantity = roundService.roundQuantity(monthResourceQuantity + row.QuantityResourcesValues[i]);
|
|
|
|
row.TotalCostValue = roundService.roundCost(row.TotalCostValue + row.CostValues[i]);
|
|
row.TotalHoursValue = roundService.roundQuantity(row.TotalHoursValue + row.QuantityHoursValues[i]);
|
|
row.TotalResourcesValue = roundService.roundQuantity(row.TotalResourcesValue + row.QuantityResourcesValues[i]);
|
|
}
|
|
}
|
|
else {
|
|
row.CostValues[i] = monthCost;
|
|
row.QuantityHoursValues[i] = monthHoursQuantity;
|
|
row.QuantityResourcesValues[i] = monthResourceQuantity;
|
|
|
|
monthCost = 0;
|
|
monthHoursQuantity = 0;
|
|
monthResourceQuantity = 0;
|
|
}
|
|
|
|
/* Total row updating */
|
|
if (!totalRow.CostValues[i])
|
|
totalRow.CostValues[i] = 0;
|
|
if (!totalRow.QuantityHoursValues[i])
|
|
totalRow.QuantityHoursValues[i] = 0;
|
|
if (!totalRow.QuantityResourcesValues[i])
|
|
totalRow.QuantityResourcesValues[i] = 0;
|
|
|
|
totalRow.CostValues[i] = roundService.roundCost(totalRow.CostValues[i] + row.CostValues[i]);
|
|
totalRow.QuantityHoursValues[i] = roundService.roundQuantity(totalRow.QuantityHoursValues[i] + row.QuantityHoursValues[i]);
|
|
totalRow.QuantityResourcesValues[i] = roundService.roundQuantity(totalRow.QuantityResourcesValues[i] + row.QuantityResourcesValues[i]);
|
|
}
|
|
|
|
totalRow.TotalCostValue = roundService.roundCost(totalRow.TotalCostValue + row.TotalCostValue);
|
|
totalRow.TotalHoursValue = roundService.roundQuantity(totalRow.TotalHoursValue + row.TotalHoursValue);
|
|
totalRow.TotalResourcesValue = roundService.roundQuantity(totalRow.TotalResourcesValue + row.TotalResourcesValue);
|
|
};
|
|
function fillTeamViewModelWithData(row, team, category) {
|
|
if (!row || !team || !category)
|
|
return;
|
|
|
|
var monthHoursQuantity = 0,
|
|
monthResourceQuantity = 0;
|
|
|
|
var totalHoursQuantity = 0,
|
|
totalResourcesQuantity = 0;
|
|
|
|
for (var i = 0; i < $scope.ViewModel.WeekHeaders.length; i++) {
|
|
var header = $scope.ViewModel.WeekHeaders[i];
|
|
|
|
if (!row.QuantityHoursValues[i])
|
|
row.QuantityHoursValues[i] = 0;
|
|
if (!row.QuantityResourcesValues[i])
|
|
row.QuantityResourcesValues[i] = 0;
|
|
|
|
if (header.DataType == C_HEADER_DATA_TYPE_ORDINAL) {
|
|
|
|
var hoursValue = roundService.roundQuantity(team.QuantityValues[header.Milliseconds] || 0),
|
|
resourcesValue = roundService.roundQuantity(hoursResourcesConverter.convertToResources(category.ExpenditureCategoryId, hoursValue));
|
|
|
|
row.QuantityHoursValues[i] = roundService.roundQuantity(row.QuantityHoursValues[i] + hoursValue);
|
|
row.QuantityResourcesValues[i] = roundService.roundQuantity(row.QuantityResourcesValues[i] + resourcesValue);
|
|
|
|
if (header.Visible[$scope.ViewModel.CalendarFilter.ViewModeName]) {
|
|
monthHoursQuantity = roundService.roundQuantity(monthHoursQuantity + hoursValue);
|
|
monthResourceQuantity = roundService.roundQuantity(monthResourceQuantity + resourcesValue);
|
|
|
|
totalHoursQuantity = roundService.roundQuantity(totalHoursQuantity + hoursValue);
|
|
totalResourcesQuantity = roundService.roundQuantity(totalResourcesQuantity + resourcesValue);
|
|
}
|
|
}
|
|
else {
|
|
row.QuantityHoursValues[i] = roundService.roundQuantity(row.QuantityHoursValues[i] + monthHoursQuantity);
|
|
row.QuantityResourcesValues[i] = roundService.roundQuantity(row.QuantityResourcesValues[i] + monthResourceQuantity);
|
|
|
|
monthHoursQuantity = 0;
|
|
monthResourceQuantity = 0;
|
|
}
|
|
}
|
|
|
|
row.TotalHoursValue = roundService.roundQuantity(row.TotalHoursValue + totalHoursQuantity);
|
|
row.TotalResourcesValue = roundService.roundQuantity(row.TotalResourcesValue + totalResourcesQuantity);
|
|
|
|
if (team.Resources && row.AssignedResources) {
|
|
angular.forEach(row.AssignedResources, function (resourceRow) {
|
|
if (team.Resources[resourceRow.Id]) {
|
|
var resourceModel = team.Resources[resourceRow.Id];
|
|
fillResourceViewModelWithData(resourceRow, resourceModel, category);
|
|
}
|
|
});
|
|
}
|
|
};
|
|
function fillResourceViewModelWithData(row, resource, category) {
|
|
if (!row || !resource || !category)
|
|
return;
|
|
|
|
var monthHoursQuantity = 0,
|
|
monthResourceQuantity = 0,
|
|
monthReadOnly = false;
|
|
|
|
for (var i = 0; i < $scope.ViewModel.WeekHeaders.length; i++) {
|
|
var header = $scope.ViewModel.WeekHeaders[i];
|
|
|
|
if (header.DataType == C_HEADER_DATA_TYPE_ORDINAL) {
|
|
|
|
row.QuantityHoursValues[i] = roundService.roundQuantity(resource.QuantityValues[header.Milliseconds] || 0);
|
|
row.QuantityResourcesValues[i] = roundService.roundQuantity(hoursResourcesConverter.convertToResources(category.ExpenditureCategoryId, row.QuantityHoursValues[i]));
|
|
// resource is read only when current week ending out of resource range and in atuals mode as well
|
|
row.ReadOnly[i] = resource.ReadOnly[header.Milliseconds] === true || $scope.ViewModel.CalendarFilter.ShowActuals;
|
|
|
|
if (header.Visible[$scope.ViewModel.CalendarFilter.ViewModeName]) {
|
|
monthHoursQuantity = roundService.roundQuantity(monthHoursQuantity + row.QuantityHoursValues[i]);
|
|
monthResourceQuantity = roundService.roundQuantity(monthResourceQuantity + row.QuantityResourcesValues[i]);
|
|
|
|
// if any week is readonly we need to mark month cell and total one as read only because if we try to zero resource
|
|
// with read only weeks that have assigned values (e.g. wrong data) we can get negative values in editable weeks
|
|
monthReadOnly |= row.ReadOnly[i];
|
|
|
|
row.TotalHoursValue = roundService.roundQuantity(row.TotalHoursValue + row.QuantityHoursValues[i]);
|
|
row.TotalResourcesValue = roundService.roundQuantity(row.TotalResourcesValue + row.QuantityResourcesValues[i]);
|
|
}
|
|
}
|
|
else {
|
|
row.QuantityHoursValues[i] = monthHoursQuantity;
|
|
row.QuantityResourcesValues[i] = monthResourceQuantity;
|
|
row.ReadOnly[i] = monthReadOnly;
|
|
|
|
monthHoursQuantity = 0;
|
|
monthResourceQuantity = 0;
|
|
monthReadOnly = false;
|
|
}
|
|
}
|
|
};
|
|
|
|
/* Methods for changing sources for grid */
|
|
function setGridSource() {
|
|
if (!$scope.ViewModel.CalendarFilter.IsTableModeQuantity) {
|
|
setCostGridSource();
|
|
}
|
|
else {
|
|
setQuantityGridSource();
|
|
}
|
|
|
|
if (isAvgMode()) {
|
|
applyAverageModeToViewModel();
|
|
}
|
|
};
|
|
function applyAverageModeToViewModel() {
|
|
|
|
if ($scope.ViewModel.Teams) {
|
|
angular.forEach($scope.ViewModel.Teams, function (teamRow) {
|
|
applyAverageModeToRow(teamRow);
|
|
|
|
if (teamRow.AssignedResources) {
|
|
angular.forEach(teamRow.AssignedResources, function (resourceRow) {
|
|
applyAverageModeToRow(resourceRow);
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
if ($scope.ViewModel.Total) {
|
|
applyAverageModeToRow($scope.ViewModel.Total);
|
|
|
|
if ($scope.ViewModel.Total.Expenditures) {
|
|
angular.forEach($scope.ViewModel.Total.Expenditures, function (row) {
|
|
applyAverageModeToRow(row);
|
|
});
|
|
}
|
|
}
|
|
};
|
|
function applyAverageModeToRow(row) {
|
|
if (!row || !row.Cells)
|
|
return;
|
|
|
|
var calendarLength = $scope.ViewModel.ColspanValues[$scope.ViewModel.CalendarFilter.ViewModeName];
|
|
angular.forEach($scope.ViewModel.MonthHeaders, function (monthHeader) {
|
|
var monthIndex = monthHeader.WeekHeaders[monthHeader.WeekHeaders.length - 1] + 1,
|
|
monthLength = monthHeader.ColspanValues[$scope.ViewModel.CalendarFilter.ViewModeName];
|
|
|
|
row.Cells[monthIndex] = roundService.roundQuantity(row.Cells[monthIndex] / monthLength);
|
|
});
|
|
row.TotalValue = roundService.roundQuantity(row.TotalValue / calendarLength);
|
|
};
|
|
function setCostGridSource() {
|
|
if ($scope.ViewModel.Total) {
|
|
$scope.ViewModel.Total.Cells = angular.copy($scope.ViewModel.Total.CostValues);
|
|
$scope.ViewModel.Total.TotalValue = $scope.ViewModel.Total.TotalCostValue;
|
|
|
|
if ($scope.ViewModel.Total.Expenditures) {
|
|
angular.forEach($scope.ViewModel.Total.Expenditures, function (row) {
|
|
row.Cells = angular.copy(row.CostValues);
|
|
row.TotalValue = row.TotalCostValue;
|
|
});
|
|
}
|
|
}
|
|
};
|
|
function setQuantityResourceSource(resourceRow) {
|
|
if (!resourceRow)
|
|
return;
|
|
|
|
resourceRow.Cells = angular.copy($scope.ViewModel.CalendarFilter.IsUOMHours ? resourceRow.QuantityHoursValues : resourceRow.QuantityResourcesValues);
|
|
resourceRow.TotalValue = $scope.ViewModel.CalendarFilter.IsUOMHours ? resourceRow.TotalHoursValue : resourceRow.TotalResourcesValue;
|
|
};
|
|
function setQuantityGridSource() {
|
|
if ($scope.ViewModel.Teams) {
|
|
angular.forEach($scope.ViewModel.Teams, function (teamRow) {
|
|
teamRow.Cells = angular.copy($scope.ViewModel.CalendarFilter.IsUOMHours ? teamRow.QuantityHoursValues : teamRow.QuantityResourcesValues);
|
|
teamRow.TotalValue = $scope.ViewModel.CalendarFilter.IsUOMHours ? teamRow.TotalHoursValue : teamRow.TotalResourcesValue;
|
|
|
|
if (teamRow.AssignedResources) {
|
|
angular.forEach(teamRow.AssignedResources, function (resourceRow) {
|
|
setQuantityResourceSource(resourceRow);
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
if ($scope.ViewModel.Total) {
|
|
$scope.ViewModel.Total.Cells = angular.copy($scope.ViewModel.CalendarFilter.IsUOMHours ? $scope.ViewModel.Total.QuantityHoursValues : $scope.ViewModel.Total.QuantityResourcesValues);
|
|
$scope.ViewModel.Total.TotalValue = $scope.ViewModel.CalendarFilter.IsUOMHours ? $scope.ViewModel.Total.TotalHoursValue : $scope.ViewModel.Total.TotalResourcesValue;
|
|
|
|
if ($scope.ViewModel.Total.Expenditures) {
|
|
angular.forEach($scope.ViewModel.Total.Expenditures, function (row) {
|
|
row.Cells = angular.copy($scope.ViewModel.CalendarFilter.IsUOMHours ? row.QuantityHoursValues : row.QuantityResourcesValues);
|
|
row.TotalValue = $scope.ViewModel.CalendarFilter.IsUOMHours ? row.TotalHoursValue : row.TotalResourcesValue;
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
function isAvgMode() {
|
|
return $scope.ViewModel.CalendarFilter.ShowAvgTotals && !$scope.ViewModel.CalendarFilter.IsUOMHours;
|
|
};
|
|
function getVisibleWeeksInMonthCount(monthIndex) {
|
|
if (isNaN(parseInt(monthIndex)))
|
|
throw 'Incorrect value for monthIndex: ' + monthIndex;
|
|
|
|
var parentMonthHeader = $scope.ViewModel.MonthHeaders[monthIndex];
|
|
var weekCount = parentMonthHeader.ColspanValues[$scope.ViewModel.CalendarFilter.ViewModeName];
|
|
|
|
return weekCount;
|
|
};
|
|
function getVisibleWeeksInMonth(monthIndex) {
|
|
if (isNaN(parseInt(monthIndex)))
|
|
throw 'Incorrect value for monthIndex: ' + monthIndex;
|
|
|
|
var weeks = [];
|
|
var parentMonthHeader = $scope.ViewModel.MonthHeaders[monthIndex];
|
|
if (!parentMonthHeader.WeekHeaders || parentMonthHeader.WeekHeaders.length <= 0)
|
|
return weeks;
|
|
|
|
for (var i = 0; i < parentMonthHeader.WeekHeaders.length; i++) {
|
|
var weekIndex = parentMonthHeader.WeekHeaders[i];
|
|
var weekHeader = $scope.ViewModel.WeekHeaders[weekIndex];
|
|
if (weekHeader.Visible[$scope.ViewModel.CalendarFilter.ViewModeName]) {
|
|
weeks.push(weekIndex);
|
|
}
|
|
}
|
|
|
|
return weeks;
|
|
};
|
|
function getVisibleWeeksInCalendarCount(viewMode) {
|
|
var weeksCount = 0;
|
|
if (!viewMode)
|
|
return weeksCount;
|
|
|
|
if (!$scope.ViewModel.MonthHeaders || $scope.ViewModel.MonthHeaders.length <= 0)
|
|
return weeksCount;
|
|
|
|
angular.forEach($scope.ViewModel.MonthHeaders, function (month) {
|
|
weeksCount += (month.ColspanValues[viewMode] || 0);
|
|
});
|
|
|
|
return weeksCount;
|
|
};
|
|
function getVisibleWeeksInCalendar(viewMode) {
|
|
var weeks = [];
|
|
if (!viewMode)
|
|
return weeks;
|
|
|
|
if (!$scope.ViewModel.MonthHeaders || $scope.ViewModel.MonthHeaders.length <= 0)
|
|
return weeks;
|
|
|
|
for (var i = 0; i < $scope.ViewModel.MonthHeaders.length; i++) {
|
|
var visibleWeeksInMonth = getVisibleWeeksInMonth(i);
|
|
if (visibleWeeksInMonth && visibleWeeksInMonth.length > 0) {
|
|
weeks = weeks.concat(visibleWeeksInMonth);
|
|
}
|
|
};
|
|
|
|
return weeks;
|
|
};
|
|
function initResourceAssignControls() {
|
|
$timeout(function () {
|
|
angular.element('[ng-model="row.ResourceToAssignId"]').select2({
|
|
allowClear: true,
|
|
placeholder: 'Select a person',
|
|
dropdownAutoWidth: true,
|
|
dropdownCss: {
|
|
'font-size': '9pt'
|
|
},
|
|
minimumResultsForSearch: 5,
|
|
formatResult: formatPeopleResourceOption
|
|
});
|
|
});
|
|
};
|
|
function assignResourceAsync(resource, $event) {
|
|
var deferrer = $q.defer();
|
|
|
|
scenarioDetailsService.assignResource($scope.ViewModel.ScenarioId, resource.expenditureCategoryId, resource.teamId, resource.id);
|
|
|
|
// it is fast operation because team already exists in the cache for this moment
|
|
teamInfoService.getTeamsById([resource.teamId])
|
|
.then(function (teams) {
|
|
if (teams && teams[resource.teamId]) {
|
|
var categoryInScenario = scenarioDetailsService.getCategoryInScenario($scope.ViewModel.ScenarioId, resource.expenditureCategoryId);
|
|
var teamInScenario = scenarioDetailsService.getTeamInScenario($scope.ViewModel.ScenarioId, resource.expenditureCategoryId, resource.teamId);
|
|
var resourceInScenario = scenarioDetailsService.getResourceInScenario($scope.ViewModel.ScenarioId, resource.expenditureCategoryId, resource.teamId, resource.id);
|
|
var teamViewModel = $scope.ViewModel.Teams[resource.teamId];
|
|
|
|
var resourceModel = teamInfoService.extendResourceModelWithAttributes(resourceInScenario, resource.teamId);
|
|
var resourceViewModel = createViewModel4Resource(categoryInScenario, teamInScenario, resourceInScenario);
|
|
var availableResources = getAvailableResources(teams[resource.teamId]);
|
|
|
|
teamViewModel.AvailableResources = availableResources;
|
|
teamViewModel.HasAvailableResources = availableResources && Object.keys(availableResources).length > 0;
|
|
|
|
fillResourceViewModelWithData(resourceViewModel, resourceModel, categoryInScenario);
|
|
setQuantityResourceSource(resourceViewModel);
|
|
if (!teamViewModel.AssignedResources)
|
|
teamViewModel.AssignedResources = {};
|
|
teamViewModel.AssignedResources[resource.id] = resourceViewModel;
|
|
}
|
|
// refresh control with resources
|
|
var targetId = angular.element($event.currentTarget).attr('target');
|
|
var target = angular.element('#' + targetId);
|
|
refreshSelect2(target);
|
|
|
|
triggerEventAboutScenarioChanged();
|
|
|
|
deferrer.resolve();
|
|
});
|
|
|
|
return deferrer.promise;
|
|
};
|
|
function removeResourceAsync(resource, $event) {
|
|
var deferrer = $q.defer();
|
|
|
|
scenarioDetailsService.removeResource($scope.ViewModel.ScenarioId, resource.ExpenditureCategoryId, resource.TeamId, resource.Id);
|
|
|
|
// it is fast operation because team already exists in the cache for this moment
|
|
teamInfoService.getTeamsById([resource.TeamId])
|
|
.then(function (teams) {
|
|
if (teams && teams[resource.TeamId]) {
|
|
var availableResources = getAvailableResources(teams[resource.TeamId]);
|
|
var teamViewModel = $scope.ViewModel.Teams[resource.TeamId];
|
|
|
|
teamViewModel.AvailableResources = availableResources;
|
|
teamViewModel.HasAvailableResources = availableResources && Object.keys(availableResources).length > 0;
|
|
if (teamViewModel.AssignedResources)
|
|
delete teamViewModel.AssignedResources[resource.Id];
|
|
}
|
|
// refresh control with resources
|
|
var targetId = angular.element($event.currentTarget).attr('target');
|
|
var target = angular.element('#' + targetId);
|
|
refreshSelect2(target);
|
|
|
|
// notify parent controller about grid was changed
|
|
triggerEventAboutScenarioChanged();
|
|
|
|
deferrer.resolve();
|
|
});
|
|
|
|
return deferrer.promise;
|
|
};
|
|
function removeTeamInternal(teamRow) {
|
|
if (!teamRow)
|
|
return;
|
|
|
|
// collection of categories that will be without assigned resources after team removing
|
|
var emptyCategories = [];
|
|
if (teamRow.AssignedResources) {
|
|
angular.forEach(teamRow.AssignedResources, function (resource) {
|
|
// we need to refresh resource, team and category allocation befor remove resource
|
|
changeResourceTotalValue(resource, 0);
|
|
|
|
// remove resource from the DAL object
|
|
scenarioDetailsService.removeResource($scope.ViewModel.ScenarioId, resource.ExpenditureCategoryId, resource.TeamId, resource.Id);
|
|
var data = { DomainId: null, ParentId: resource.Id };
|
|
$rootScope.$broadcast('removeNoteCB', data);
|
|
// if there are no assigned resources in the expenditure category we need to put it to the queue for removing
|
|
var isLastResource = !scenarioDetailsService.checkExpenditureCategoryHasAssignedResources($scope.ViewModel.ScenarioId, resource.ExpenditureCategoryId);
|
|
if (isLastResource) {
|
|
emptyCategories.push(resource.ExpenditureCategoryId);
|
|
}
|
|
});
|
|
}
|
|
|
|
// remove team from the DAL object
|
|
scenarioDetailsService.deleteTeamsFromScenario($scope.ViewModel.ScenarioId, teamRow.Id);
|
|
|
|
// delete team row from the view model
|
|
delete $scope.ViewModel.Teams[teamRow.Id];
|
|
|
|
// trigger event into the parent controller to refresh list of available for assign teams
|
|
triggerEventAboutTeamsNeedToBeRefreshed();
|
|
|
|
// trigger event into the parent controller for removing empty categories
|
|
if (emptyCategories.length > 0) {
|
|
triggerEventAboutExpenditureCategoriesNeedToBeRemoved(emptyCategories);
|
|
}
|
|
};
|
|
function formatPeopleResourceOption(result, container, query, escapeMarkup) {
|
|
var $optionScope = angular.element(result.element).scope();
|
|
/* $optionScope.resource property is declared in the expression in the view: resource in row.AvailableResources track by $index */
|
|
if ($optionScope && $optionScope.resource) {
|
|
return scenarioDetailsService.getAssignableResourceOptionHtml($optionScope.resource);
|
|
}
|
|
};
|
|
function refreshSelect2(obj) {
|
|
$timeout(function () { // You might need this timeout to be sure its run after DOM render.
|
|
obj.trigger("change");
|
|
}, 0, false);
|
|
};
|
|
function triggerEventAboutScenarioChanged() {
|
|
$scope.$emit('scenarioIsChanged');
|
|
};
|
|
function triggerEventAboutExpenditureCategoriesNeedToBeRemoved(categories) {
|
|
$scope.$emit('deleteExpenditureCategories', categories);
|
|
};
|
|
function triggerEventAboutTeamsNeedToBeRefreshed() {
|
|
$scope.$emit('refreshTeamsList');
|
|
};
|
|
function changeGrandTotalValue(newTotal) {
|
|
var assignedResources = getAssignedResourcesFromAllTeams();
|
|
if (!assignedResources || assignedResources.length <= 0)
|
|
return;
|
|
|
|
if (isNaN(parseFloat(newTotal)))
|
|
newTotal = 0;
|
|
|
|
var oldData = getActualData4CurrentMode($scope.ViewModel.Total, null, null) || 0;
|
|
var oldTotal = (oldData ? (oldData.TotalValue || 0) : 0);
|
|
|
|
var distributedNewValue = 0;
|
|
var lastResourceIndex = assignedResources.length - 1;
|
|
|
|
if (oldTotal == 0) {
|
|
var newResourceValue = roundService.roundQuantity(newTotal / assignedResources.length);
|
|
|
|
for (var i = 0; i < assignedResources.length - 1; i++) {
|
|
changeResourceTotalValue(assignedResources[i], newResourceValue);
|
|
distributedNewValue = distributedNewValue + newResourceValue;
|
|
}
|
|
}
|
|
else {
|
|
var isAvg = isAvgMode();
|
|
var calendarLength = getVisibleWeeksInCalendarCount($scope.ViewModel.CalendarFilter.ViewModeName);
|
|
var factor = newTotal / (oldTotal / (isAvg ? calendarLength : 1));
|
|
var distributedOldValue = 0;
|
|
|
|
for (var i = 0; i < assignedResources.length - 1; i++) {
|
|
var oldResourceValue = getActualData4CurrentMode(assignedResources[i], null, null),
|
|
oldTotalValue = (oldResourceValue ? (oldResourceValue.TotalValue || 0) : 0),
|
|
newTotalValue = roundService.roundQuantity(oldTotalValue * factor) / (isAvg ? calendarLength : 1);
|
|
|
|
distributedOldValue = roundService.roundQuantity(distributedOldValue + oldTotalValue);
|
|
// example: scenario has 14 assigned resources, but only 5 of them have values; so we should affect only these resources
|
|
if (distributedOldValue == oldTotal) {
|
|
lastResourceIndex = i;
|
|
break;
|
|
}
|
|
else {
|
|
changeResourceTotalValue(assignedResources[i], newTotalValue);
|
|
distributedNewValue += newTotalValue;
|
|
}
|
|
}
|
|
}
|
|
|
|
var lastResource = assignedResources[lastResourceIndex],
|
|
lastResourceValue = roundService.roundQuantity(newTotal - distributedNewValue);
|
|
|
|
changeResourceTotalValue(lastResource, lastResourceValue);
|
|
};
|
|
function changeResourceTotalValue(resource, newValue) {
|
|
if (!resource)
|
|
return;
|
|
|
|
if (isNaN(parseFloat(newValue)))
|
|
newValue = 0;
|
|
|
|
var weeks = getVisibleWeeksInCalendar($scope.ViewModel.CalendarFilter.ViewModeName);
|
|
if (isAvgMode()) {
|
|
// as we type average value for some period total value will be equal to newTotal * period range
|
|
newValue *= weeks.length;
|
|
}
|
|
var oldData = getActualData4CurrentMode(resource, null, null) || 0;
|
|
var oldValue = (oldData ? (oldData.TotalValue || 0) : 0);
|
|
alignResourceTotal(resource, oldValue, newValue, weeks);
|
|
};
|
|
function changeResourceMonthValue(resource, colIndex, newValue) {
|
|
var weekHeader = $scope.ViewModel.WeekHeaders[colIndex];
|
|
if (!resource || !weekHeader || weekHeader.DataType != C_HEADER_DATA_TYPE_TOTALS)
|
|
return;
|
|
|
|
if (isNaN(parseFloat(newValue)))
|
|
newValue = 0;
|
|
|
|
var weeks = getVisibleWeeksInMonth(weekHeader.MonthHeader);
|
|
if (isAvgMode()) {
|
|
// as we type average value for some period total value will be equal to newTotal * period range
|
|
newValue *= weeks.length;
|
|
}
|
|
var oldData = getActualData4CurrentMode(resource, colIndex, weeks[weeks.length - 1] + 1);
|
|
var oldValue = (oldData ? (oldData.MonthValue || 0) : 0);
|
|
alignResourceTotal(resource, oldValue, newValue, weeks);
|
|
};
|
|
function alignResourceTotal(resource, oldTotal, newTotal, weeks) {
|
|
if (!resource || !weeks || weeks.length <= 0)
|
|
return;
|
|
|
|
oldTotal = roundService.roundQuantity(oldTotal) || 0;
|
|
newTotal = roundService.roundQuantity(newTotal) || 0;
|
|
// commented out for the following case:
|
|
// we have corrupted data with Grand Total = 10, but all resources have 0 in their total row
|
|
// when user types zero in the Grand Total cell oldTotal will be equals to newTotal and ECs and Teams will not be recalculated
|
|
//if (oldTotal == newTotal)
|
|
// return;
|
|
|
|
var editableWeeks = weeks.filter(function (weekIndex) { return !resource.ReadOnly[weekIndex]; });
|
|
if (editableWeeks.length <= 0)
|
|
return;
|
|
|
|
var readOnlyWeeks = weeks.filter(function (weekIndex) { return resource.ReadOnly[weekIndex]; });
|
|
var readOnlyTotal = roundService.roundQuantity(calculateResourceTotalOnRange(resource, readOnlyWeeks));
|
|
|
|
newTotal = roundService.roundQuantity(Math.max(newTotal - readOnlyTotal, 0));
|
|
oldTotal = roundService.roundQuantity(Math.max(oldTotal - readOnlyTotal, 0));
|
|
|
|
// TODO: use calculateDistributionService.alignValues instead and then go through result and call changeResourceWeekValue for each item
|
|
var distributedNewValue = 0;
|
|
var lastWeekIndex = editableWeeks[editableWeeks.length - 1];
|
|
|
|
if (oldTotal == 0) {
|
|
var newWeekValue = roundService.roundQuantity(newTotal / editableWeeks.length);
|
|
|
|
for (var i = 0; i < editableWeeks.length - 1; i++) {
|
|
// Example: month has 4 weeks. User types 0.000002 in the month cell.
|
|
// newWeekValue will be 0.000002 / 4 = 0.0000005 ~ 0.000001
|
|
// if we distribute this value in editableWeeks.length - 1 weeks (3) we get distributedNewValue = 0.000003
|
|
// and -0.000001 in the last cell: newTotal - distributedNewValue = (0.000002 - 0.000003)
|
|
// so we should check over allocation and break distribution
|
|
if (distributedNewValue + newWeekValue <= newTotal) {
|
|
changeResourceWeekValue(resource, editableWeeks[i], newWeekValue);
|
|
distributedNewValue += newWeekValue;
|
|
}
|
|
else {
|
|
lastWeekIndex = editableWeeks[i];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
var factor = newTotal / oldTotal;
|
|
var distributedOldValue = 0;
|
|
|
|
for (var i = 0; i < editableWeeks.length - 1; i++) {
|
|
var oldWeekValue = (resource.Cells[editableWeeks[i]] || 0);
|
|
var newWeekValue = roundService.roundQuantity(oldWeekValue * factor);
|
|
|
|
distributedOldValue += oldWeekValue;
|
|
// example: scenario has 31 weeks, but only 5 of them have values; so we should affect only these weeks
|
|
if (roundService.roundQuantity(distributedOldValue) == oldTotal) {
|
|
lastWeekIndex = editableWeeks[i];
|
|
break;
|
|
}
|
|
else {
|
|
changeResourceWeekValue(resource, editableWeeks[i], newWeekValue);
|
|
distributedNewValue += newWeekValue;
|
|
}
|
|
}
|
|
}
|
|
|
|
var lastWeekValue = roundService.roundQuantity(newTotal - distributedNewValue);
|
|
|
|
changeResourceWeekValue(resource, lastWeekIndex, lastWeekValue);
|
|
};
|
|
function calculateResourceTotalOnRange(resource, weeks) {
|
|
var resourceTotal = 0;
|
|
if (!resource || !resource.Cells || !weeks || !weeks.length)
|
|
return 0;
|
|
|
|
for (var i = 0; i < weeks.length; i++) {
|
|
resourceTotal += (resource.Cells[weeks[i]] || 0);
|
|
}
|
|
|
|
return resourceTotal;
|
|
};
|
|
function changeResourceWeekValue(resource, colIndex, newValue) {
|
|
var weekHeader = $scope.ViewModel.WeekHeaders[colIndex];
|
|
if (!resource || !weekHeader || weekHeader.DataType != C_HEADER_DATA_TYPE_ORDINAL)
|
|
return;
|
|
|
|
if (isNaN(parseFloat(newValue)))
|
|
newValue = 0;
|
|
|
|
var hoursValue = roundService.roundQuantity($scope.ViewModel.CalendarFilter.IsUOMHours ? newValue : hoursResourcesConverter.convertToHours(resource.ExpenditureCategoryId, newValue)),
|
|
deltaHoursValue = roundService.roundQuantity(hoursValue - (resource.QuantityHoursValues[colIndex] || 0));
|
|
|
|
scenarioDetailsService.changeResourceValue($scope.ViewModel.ScenarioId, resource.ExpenditureCategoryId, resource.TeamId, resource.Id, weekHeader.Milliseconds, deltaHoursValue);
|
|
updateResourceValueInViewModel(resource.TeamId, resource.Id, deltaHoursValue, colIndex);
|
|
|
|
recalculateCategoryValue(resource.ExpenditureCategoryId, colIndex);
|
|
recalculateTeamValue(resource.ExpenditureCategoryId, resource.TeamId, colIndex);
|
|
};
|
|
function recalculateCategoryValue(expenditureCategoryId, colIndex) {
|
|
if (!expenditureCategoryId || colIndex < 0)
|
|
return;
|
|
|
|
var weekHeader = $scope.ViewModel.WeekHeaders[colIndex];
|
|
if (!weekHeader || weekHeader.DataType != C_HEADER_DATA_TYPE_ORDINAL)
|
|
return;
|
|
|
|
var oldValue = scenarioDetailsService.getExpenditureCategoryValue($scope.ViewModel.ScenarioId, expenditureCategoryId, weekHeader.Milliseconds) || {},
|
|
oldQuantity = oldValue.ForecastQuantity || 0,
|
|
oldCost = oldValue.ForecastCost || 0,
|
|
newQuantity = calculateExpenditureCategoryValueFromAssignedResources(expenditureCategoryId, colIndex),
|
|
newCost = scenarioDetailsService.getCost($scope.ViewModel.ScenarioId, expenditureCategoryId, weekHeader.Milliseconds, newQuantity) || 0,
|
|
deltaQuantity = roundService.roundQuantity(newQuantity - oldQuantity),
|
|
deltaCost = roundService.roundQuantity(newCost - oldCost);
|
|
|
|
scenarioDetailsService.changeExpenditureCategoryValue($scope.ViewModel.ScenarioId, expenditureCategoryId, weekHeader.Milliseconds, deltaQuantity, deltaCost);
|
|
updateCategoryValueInViewModel(expenditureCategoryId, deltaQuantity, deltaCost, colIndex);
|
|
updateTotalValueInViewModel(deltaQuantity, deltaCost, colIndex);
|
|
};
|
|
function recalculateTeamValue(expenditureCategoryId, teamId, colIndex) {
|
|
var weekHeader = $scope.ViewModel.WeekHeaders[colIndex];
|
|
if (!weekHeader || weekHeader.DataType != C_HEADER_DATA_TYPE_ORDINAL)
|
|
return;
|
|
|
|
var teamData = calculateTeamValuesFromAssignedResources(expenditureCategoryId, teamId, colIndex);
|
|
if (!teamData)
|
|
return;
|
|
|
|
var oldHoursValue = scenarioDetailsService.getTeamValue($scope.ViewModel.ScenarioId, expenditureCategoryId, teamId, weekHeader.Milliseconds) || 0,
|
|
deltaHoursValue = teamData.WeekHoursValueByCategory - oldHoursValue;
|
|
|
|
scenarioDetailsService.changeTeamValue($scope.ViewModel.ScenarioId, expenditureCategoryId, teamId, weekHeader.Milliseconds, deltaHoursValue);
|
|
updateTeamValueInViewModel(teamId, teamData, colIndex);
|
|
};
|
|
function recalculateTotalResourceValue(colIndex) {
|
|
if (!$scope.ViewModel.Total || !$scope.ViewModel.Total.Expenditures)
|
|
return 0;
|
|
|
|
var resourceValue = 0;
|
|
angular.forEach($scope.ViewModel.Total.Expenditures, function (category, categoryId) {
|
|
if (colIndex >= 0) {
|
|
resourceValue += hoursResourcesConverter.convertToResources(categoryId, category.QuantityHoursValues[colIndex] || 0);
|
|
} else {
|
|
resourceValue += hoursResourcesConverter.convertToResources(categoryId, category.TotalHoursValue || 0);
|
|
}
|
|
});
|
|
|
|
return resourceValue;
|
|
};
|
|
function calculateExpenditureCategoryValueFromAssignedResources(expenditureCategoryId, colIndex) {
|
|
if (!expenditureCategoryId)
|
|
return;
|
|
|
|
var assignedResources = getAssignedResourcesFromAllTeams();
|
|
if (!assignedResources || assignedResources.length <= 0)
|
|
return 0;
|
|
|
|
var quantity = 0;
|
|
angular.forEach(assignedResources, function (resource) {
|
|
if (resource.ExpenditureCategoryId == expenditureCategoryId) {
|
|
quantity += (resource.QuantityHoursValues[colIndex] || 0);
|
|
}
|
|
});
|
|
|
|
return quantity;
|
|
};
|
|
function calculateTeamValuesFromAssignedResources(expenditureCategoryId, teamId, colIndex) {
|
|
var value = {
|
|
WeekHoursValue: 0,
|
|
WeekResourcesValue: 0,
|
|
MonthHoursValue: 0,
|
|
MonthResourcesValue: 0,
|
|
TotalHoursValue: 0,
|
|
TotalResourcesValue: 0,
|
|
WeekHoursValueByCategory: 0
|
|
};
|
|
|
|
if (!teamId || colIndex < 0)
|
|
return value;
|
|
|
|
var weekHeader = $scope.ViewModel.WeekHeaders[colIndex],
|
|
monthHeader = $scope.ViewModel.MonthHeaders[weekHeader.MonthHeader],
|
|
monthIndex = monthHeader.WeekHeaders[monthHeader.WeekHeaders.length - 1] + 1;
|
|
|
|
var targetTeam = $scope.ViewModel.Teams[teamId];
|
|
if (!targetTeam || !targetTeam.AssignedResources)
|
|
return value;
|
|
|
|
angular.forEach(targetTeam.AssignedResources, function (resource) {
|
|
value.WeekHoursValue += (resource.QuantityHoursValues[colIndex] || 0);
|
|
value.WeekResourcesValue += hoursResourcesConverter.convertToResources(resource.ExpenditureCategoryId, (resource.QuantityHoursValues[colIndex] || 0));
|
|
value.MonthHoursValue += (resource.QuantityHoursValues[monthIndex] || 0);
|
|
value.MonthResourcesValue += hoursResourcesConverter.convertToResources(resource.ExpenditureCategoryId, (resource.QuantityHoursValues[monthIndex] || 0));
|
|
value.TotalHoursValue += (resource.TotalHoursValue || 0);
|
|
value.TotalResourcesValue += hoursResourcesConverter.convertToResources(resource.ExpenditureCategoryId, (resource.TotalHoursValue || 0));
|
|
if (resource.ExpenditureCategoryId == expenditureCategoryId) {
|
|
value.WeekHoursValueByCategory += (resource.QuantityHoursValues[colIndex] || 0);
|
|
}
|
|
});
|
|
|
|
return value;
|
|
};
|
|
function updateResourceValueInViewModel(teamId, resourceId, deltaHours, colIndex) {
|
|
if (!teamId || !resourceId)
|
|
return;
|
|
|
|
if (isNaN(colIndex = parseInt(colIndex)) || colIndex < 0)
|
|
return;
|
|
|
|
var weekHeader = $scope.ViewModel.WeekHeaders[colIndex],
|
|
monthHeader = $scope.ViewModel.MonthHeaders[weekHeader.MonthHeader],
|
|
monthIndex = monthHeader.WeekHeaders[monthHeader.WeekHeaders.length - 1] + 1,
|
|
monthVisibleWeeks = monthHeader.ColspanValues[$scope.ViewModel.CalendarFilter.ViewModeName],
|
|
calendarVisibleWeeks = $scope.ViewModel.ColspanValues[$scope.ViewModel.CalendarFilter.ViewModeName];
|
|
|
|
// Caution: we should round result of operation because of known javascript math problem: http://www.webdeveloper.com/forum/showthread.php?92612-Basic-(-)-Javascript-math-precision-problem
|
|
// Example: 4.615385 + 6.923077 + 6.923077 + 6.923077 + 4.615384 = 29.999999999999996, but should be 30
|
|
|
|
// update view model values for resource row
|
|
var targetResource = $scope.ViewModel.Teams[teamId].AssignedResources[resourceId];
|
|
|
|
targetResource.QuantityHoursValues[colIndex] = roundService.roundQuantity(targetResource.QuantityHoursValues[colIndex] + (deltaHours || 0));
|
|
targetResource.QuantityHoursValues[monthIndex] = roundService.roundQuantity(targetResource.QuantityHoursValues[monthIndex] + (deltaHours || 0));
|
|
targetResource.TotalHoursValue = roundService.roundQuantity(targetResource.TotalHoursValue + (deltaHours || 0));
|
|
targetResource.QuantityResourcesValues[colIndex] = roundService.roundQuantity(hoursResourcesConverter.convertToResources(targetResource.ExpenditureCategoryId, targetResource.QuantityHoursValues[colIndex]));
|
|
targetResource.QuantityResourcesValues[monthIndex] = roundService.roundQuantity(hoursResourcesConverter.convertToResources(targetResource.ExpenditureCategoryId, targetResource.QuantityHoursValues[monthIndex]));
|
|
targetResource.TotalResourcesValue = roundService.roundQuantity(hoursResourcesConverter.convertToResources(targetResource.ExpenditureCategoryId, targetResource.TotalHoursValue));
|
|
|
|
var isAvg = isAvgMode();
|
|
var actualResourceData = getActualData4CurrentMode(targetResource, colIndex, monthIndex);
|
|
if (actualResourceData) {
|
|
targetResource.Cells[colIndex] = actualResourceData.WeekValue;
|
|
targetResource.Cells[monthIndex] = isAvg ? roundService.roundQuantity(actualResourceData.MonthValue / monthVisibleWeeks) : actualResourceData.MonthValue;
|
|
targetResource.TotalValue = isAvg ? roundService.roundQuantity(actualResourceData.TotalValue / calendarVisibleWeeks) : actualResourceData.TotalValue;
|
|
}
|
|
};
|
|
function updateCategoryValueInViewModel(expenditureCategoryId, deltaHours, deltaCost, colIndex) {
|
|
if (!expenditureCategoryId)
|
|
return;
|
|
|
|
if (isNaN(colIndex = parseInt(colIndex)) || colIndex < 0)
|
|
return;
|
|
|
|
var weekHeader = $scope.ViewModel.WeekHeaders[colIndex],
|
|
monthHeader = $scope.ViewModel.MonthHeaders[weekHeader.MonthHeader],
|
|
monthIndex = monthHeader.WeekHeaders[monthHeader.WeekHeaders.length - 1] + 1,
|
|
monthVisibleWeeks = monthHeader.ColspanValues[$scope.ViewModel.CalendarFilter.ViewModeName],
|
|
calendarVisibleWeeks = $scope.ViewModel.ColspanValues[$scope.ViewModel.CalendarFilter.ViewModeName];
|
|
|
|
var targetCategory = $scope.ViewModel.Total.Expenditures[expenditureCategoryId];
|
|
|
|
// update view model values for category row
|
|
targetCategory.QuantityHoursValues[colIndex] = roundService.roundQuantity(targetCategory.QuantityHoursValues[colIndex] + (deltaHours || 0));
|
|
targetCategory.QuantityHoursValues[monthIndex] = roundService.roundQuantity(targetCategory.QuantityHoursValues[monthIndex] + (deltaHours || 0));
|
|
targetCategory.TotalHoursValue = roundService.roundQuantity(targetCategory.TotalHoursValue + (deltaHours || 0));
|
|
targetCategory.QuantityResourcesValues[colIndex] = roundService.roundQuantity(hoursResourcesConverter.convertToResources(expenditureCategoryId, targetCategory.QuantityHoursValues[colIndex]));
|
|
targetCategory.QuantityResourcesValues[monthIndex] = roundService.roundQuantity(hoursResourcesConverter.convertToResources(expenditureCategoryId, targetCategory.QuantityHoursValues[monthIndex]));
|
|
targetCategory.TotalResourcesValue = roundService.roundQuantity(hoursResourcesConverter.convertToResources(expenditureCategoryId, targetCategory.TotalHoursValue));
|
|
targetCategory.CostValues[colIndex] = roundService.roundCost(targetCategory.CostValues[colIndex] + (deltaCost || 0));
|
|
targetCategory.CostValues[monthIndex] = roundService.roundCost(targetCategory.CostValues[monthIndex] + (deltaCost || 0));
|
|
targetCategory.TotalCostValue = roundService.roundCost(targetCategory.TotalCostValue + (deltaCost || 0));
|
|
|
|
var isAvg = isAvgMode();
|
|
var actualCategoryData = getActualData4CurrentMode(targetCategory, colIndex, monthIndex);
|
|
if (actualCategoryData) {
|
|
targetCategory.Cells[colIndex] = actualCategoryData.WeekValue;
|
|
targetCategory.Cells[monthIndex] = isAvg ? roundService.roundQuantity(actualCategoryData.MonthValue / monthVisibleWeeks) : actualCategoryData.MonthValue;
|
|
targetCategory.TotalValue = isAvg ? roundService.roundQuantity(actualCategoryData.TotalValue / calendarVisibleWeeks) : actualCategoryData.TotalValue;
|
|
}
|
|
};
|
|
function updateTeamValueInViewModel(teamId, teamData, colIndex) {
|
|
if (!teamId || !teamData)
|
|
return;
|
|
|
|
if (isNaN(colIndex = parseInt(colIndex)) || colIndex < 0)
|
|
return;
|
|
|
|
var weekHeader = $scope.ViewModel.WeekHeaders[colIndex],
|
|
monthHeader = $scope.ViewModel.MonthHeaders[weekHeader.MonthHeader],
|
|
monthIndex = monthHeader.WeekHeaders[monthHeader.WeekHeaders.length - 1] + 1,
|
|
monthVisibleWeeks = monthHeader.ColspanValues[$scope.ViewModel.CalendarFilter.ViewModeName],
|
|
calendarVisibleWeeks = $scope.ViewModel.ColspanValues[$scope.ViewModel.CalendarFilter.ViewModeName];
|
|
|
|
// update view model values for team row
|
|
var targetTeam = $scope.ViewModel.Teams[teamId];
|
|
|
|
targetTeam.QuantityHoursValues[colIndex] = roundService.roundQuantity(teamData.WeekHoursValue);
|
|
targetTeam.QuantityHoursValues[monthIndex] = roundService.roundQuantity(teamData.MonthHoursValue);
|
|
targetTeam.TotalHoursValue = roundService.roundQuantity(teamData.TotalHoursValue);
|
|
targetTeam.QuantityResourcesValues[colIndex] = roundService.roundQuantity(teamData.WeekResourcesValue);
|
|
targetTeam.QuantityResourcesValues[monthIndex] = roundService.roundQuantity(teamData.MonthResourcesValue);
|
|
targetTeam.TotalResourcesValue = roundService.roundQuantity(teamData.TotalResourcesValue);
|
|
|
|
var isAvg = isAvgMode();
|
|
var actualTeamData = getActualData4CurrentMode(targetTeam, colIndex, monthIndex);
|
|
if (actualTeamData) {
|
|
targetTeam.Cells[colIndex] = actualTeamData.WeekValue;
|
|
targetTeam.Cells[monthIndex] = isAvg ? roundService.roundQuantity(actualTeamData.MonthValue / monthVisibleWeeks) : actualTeamData.MonthValue;
|
|
targetTeam.TotalValue = isAvg ? roundService.roundQuantity(actualTeamData.TotalValue / calendarVisibleWeeks) : actualTeamData.TotalValue;
|
|
}
|
|
};
|
|
function updateTotalValueInViewModel(deltaHours, deltaCost, colIndex) {
|
|
if (isNaN(colIndex = parseInt(colIndex)) || colIndex < 0)
|
|
return;
|
|
|
|
var weekHeader = $scope.ViewModel.WeekHeaders[colIndex],
|
|
monthHeader = $scope.ViewModel.MonthHeaders[weekHeader.MonthHeader],
|
|
monthIndex = monthHeader.WeekHeaders[monthHeader.WeekHeaders.length - 1] + 1,
|
|
monthVisibleWeeks = monthHeader.ColspanValues[$scope.ViewModel.CalendarFilter.ViewModeName],
|
|
calendarVisibleWeeks = $scope.ViewModel.ColspanValues[$scope.ViewModel.CalendarFilter.ViewModeName];
|
|
|
|
// update view model values for total row
|
|
var targetTotal = $scope.ViewModel.Total;
|
|
targetTotal.QuantityHoursValues[colIndex] = roundService.roundQuantity(targetTotal.QuantityHoursValues[colIndex] + (deltaHours || 0));
|
|
targetTotal.QuantityHoursValues[monthIndex] = roundService.roundQuantity(targetTotal.QuantityHoursValues[monthIndex] + (deltaHours || 0));
|
|
targetTotal.TotalHoursValue = roundService.roundQuantity(targetTotal.TotalHoursValue + (deltaHours || 0));
|
|
targetTotal.QuantityResourcesValues[colIndex] = roundService.roundQuantity(recalculateTotalResourceValue(colIndex));
|
|
targetTotal.QuantityResourcesValues[monthIndex] = roundService.roundQuantity(recalculateTotalResourceValue(monthIndex));
|
|
targetTotal.TotalResourcesValue = roundService.roundQuantity(recalculateTotalResourceValue(-1));
|
|
targetTotal.CostValues[colIndex] = roundService.roundCost(targetTotal.CostValues[colIndex] + (deltaCost || 0));
|
|
targetTotal.CostValues[monthIndex] = roundService.roundCost(targetTotal.CostValues[monthIndex] + (deltaCost || 0));
|
|
targetTotal.TotalCostValue = roundService.roundCost(targetTotal.TotalCostValue + (deltaCost || 0));
|
|
|
|
var isAvg = isAvgMode();
|
|
var actualTotalData = getActualData4CurrentMode(targetTotal, colIndex, monthIndex);
|
|
if (actualTotalData) {
|
|
targetTotal.Cells[colIndex] = actualTotalData.WeekValue;
|
|
targetTotal.Cells[monthIndex] = isAvg ? roundService.roundQuantity(actualTotalData.MonthValue / monthVisibleWeeks) : actualTotalData.MonthValue;
|
|
targetTotal.TotalValue = isAvg ? roundService.roundQuantity(actualTotalData.TotalValue / calendarVisibleWeeks) : actualTotalData.TotalValue;
|
|
}
|
|
};
|
|
function getActualData4CurrentMode(row, weekIndex, monthIndex) {
|
|
if (!row)
|
|
return null;
|
|
|
|
if (!$scope.ViewModel.CalendarFilter.IsTableModeQuantity) {
|
|
var data = {
|
|
WeekValue: row.CostValues ? row.CostValues[weekIndex] || 0 : 0,
|
|
MonthValue: row.CostValues ? row.CostValues[monthIndex] || 0 : 0,
|
|
TotalValue: row.TotalCostValue || 0
|
|
};
|
|
return data;
|
|
}
|
|
else if ($scope.ViewModel.CalendarFilter.IsUOMHours) {
|
|
var data = {
|
|
WeekValue: row.QuantityHoursValues ? row.QuantityHoursValues[weekIndex] || 0 : 0,
|
|
MonthValue: row.QuantityHoursValues ? row.QuantityHoursValues[monthIndex] || 0 : 0,
|
|
TotalValue: row.TotalHoursValue || 0
|
|
};
|
|
return data;
|
|
}
|
|
else {
|
|
var data = {
|
|
WeekValue: row.QuantityResourcesValues ? row.QuantityResourcesValues[weekIndex] || 0 : 0,
|
|
MonthValue: row.QuantityResourcesValues ? row.QuantityResourcesValues[monthIndex] || 0 : 0,
|
|
TotalValue: row.TotalResourcesValue || 0
|
|
};
|
|
return data;
|
|
}
|
|
};
|
|
function checkGrandTotalIsEditable() {
|
|
var isEditable = $scope.ViewModel.CalendarFilter.IsTableModeQuantity &&
|
|
!$scope.ViewModel.CalendarFilter.ShowActuals &&
|
|
$scope.ViewModel.Total &&
|
|
$scope.ViewModel.Total.Expenditures &&
|
|
Object.keys($scope.ViewModel.Total.Expenditures).length > 0; // if there are no categories were assigned grand total editing operation does not make sense
|
|
|
|
$scope.ViewModel.Total.IsEditable = isEditable;
|
|
};
|
|
function getAssignedResourcesFromAllTeams() {
|
|
var assignedResources = [];
|
|
if (!$scope.ViewModel.Teams)
|
|
return assignedResources;
|
|
|
|
angular.forEach($scope.ViewModel.Teams, function (teamRow) {
|
|
if (teamRow.AssignedResources) {
|
|
angular.forEach(teamRow.AssignedResources, function (resource) {
|
|
assignedResources.push(resource);
|
|
});
|
|
}
|
|
});
|
|
|
|
return assignedResources;
|
|
};
|
|
function checkResourcesCanBeDeleted() {
|
|
var assignedResources = getAssignedResourcesFromAllTeams();
|
|
if (!assignedResources || assignedResources.length <= 0)
|
|
return;
|
|
|
|
angular.forEach(assignedResources, checkResourceCanBeDeleted);
|
|
};
|
|
function checkResourceCanBeDeleted(resource) {
|
|
if (!resource)
|
|
return;
|
|
|
|
if (!$scope.ViewModel.CalendarFilter.IsTableModeQuantity) {
|
|
resource.CanBeDeleted = false;
|
|
}
|
|
else {
|
|
resource.CanBeDeleted = true;
|
|
}
|
|
};
|
|
function checkExpenditureCategory(expenditureCategoryId) {
|
|
return dataSources.checkExpenditureCategory(expenditureCategoryId,
|
|
$scope.ViewModel.CalendarFilter.CategoryType,
|
|
$scope.ViewModel.CalendarFilter.GLAccount,
|
|
$scope.ViewModel.CalendarFilter.CreditDepartment,
|
|
$scope.ViewModel.CalendarFilter.SelectedExpCats);
|
|
};
|
|
|
|
/* Cells highlighting methods */
|
|
function refreshResourceWeekCss(resource, weekIndex) {
|
|
if (!resource || weekIndex < 0)
|
|
return null;
|
|
|
|
var weekHeader = $scope.ViewModel.WeekHeaders[weekIndex];
|
|
if (!weekHeader || weekHeader.DataType == C_HEADER_DATA_TYPE_TOTALS)
|
|
return null;
|
|
|
|
var actualData = getActualData4CurrentMode(resource, weekIndex, null);
|
|
if (!actualData)
|
|
return null;
|
|
|
|
var resourceInScenario = scenarioDetailsService.getResourceInScenario($scope.ViewModel.ScenarioId, resource.ExpenditureCategoryId, resource.TeamId, resource.Id);
|
|
if (!resourceInScenario || !resourceInScenario.QuantityValues || !resourceInScenario.CapacityQuantityValues)
|
|
return null;
|
|
|
|
// clear current highlight classes
|
|
resource.CSSClass[weekIndex] = cellHighlightingService.removeHighlightClasses(resource.CSSClass[weekIndex]);
|
|
|
|
var capacity = parseFloat(resourceInScenario.CapacityQuantityValues[weekHeader.Milliseconds]) || 0;
|
|
if (capacity >= 0 && actualData.WeekValue > 0) {
|
|
var compareResult = cellHighlightingService.compare(actualData.WeekValue, capacity)
|
|
// highlight only overallocation
|
|
if (compareResult == 1)
|
|
{
|
|
var compareResultClass = cellHighlightingService.getCssClass(compareResult),
|
|
resultClass = cellHighlightingService.addCssClass(resource.CSSClass[weekIndex], compareResultClass);
|
|
resource.CSSClass[weekIndex] = resultClass;
|
|
}
|
|
return compareResult;
|
|
}
|
|
|
|
return null;
|
|
};
|
|
function refreshResourceMonthCss(resource, monthIndex) {
|
|
if (!resource || monthIndex < 0)
|
|
return;
|
|
|
|
var weeks = getVisibleWeeksInMonth(monthIndex);
|
|
if (weeks.length <= 0)
|
|
return;
|
|
|
|
var monthCompareResult = null,
|
|
monthIndexInWeeks = weeks[weeks.length - 1] + 1;
|
|
|
|
for (var i = 0; i < weeks.length; i++) {
|
|
var weekCompareResult = refreshResourceWeekCss(resource, weeks[i]);
|
|
if (cellHighlightingService.checkOverResult(weekCompareResult))
|
|
monthCompareResult = weekCompareResult;
|
|
}
|
|
|
|
// clear current highlight classes
|
|
resource.CSSClass[monthIndexInWeeks] = cellHighlightingService.removeHighlightClasses(resource.CSSClass[monthIndexInWeeks]);
|
|
|
|
if (monthCompareResult != null) {
|
|
var compareResultClass = cellHighlightingService.getCssClass(monthCompareResult),
|
|
resultClass = cellHighlightingService.addCssClass(resource.CSSClass[monthIndexInWeeks], compareResultClass);
|
|
|
|
resource.CSSClass[monthIndexInWeeks] = resultClass;
|
|
}
|
|
};
|
|
function refreshResourceCss(resource) {
|
|
if (!resource)
|
|
return;
|
|
|
|
for (var i = 0; i < $scope.ViewModel.MonthHeaders.length; i++) {
|
|
refreshResourceMonthCss(resource, i);
|
|
}
|
|
};
|
|
function refreshGridCss() {
|
|
var assignedResources = getAssignedResourcesFromAllTeams();
|
|
if (assignedResources.length <= 0)
|
|
return;
|
|
|
|
for (var i = 0; i < assignedResources.length; i++) {
|
|
refreshResourceCss(assignedResources[i]);
|
|
}
|
|
};
|
|
}]); |