EnVisageOnline/Main-RMO/Source/EnVisage/Scripts/Angular/Controllers/MixControllers/mixProjectController.js

4182 lines
166 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use strict';
app.controller('mixProjectController', ['$scope', '$rootScope', '$http', '$filter', '$element', '$timeout', '$document', '$compile', 'hoursResourcesConverter', 'teamInfoService', 'dataSources', function ($scope, $rootScope, $http, $filter, $element, $timeout, $document, $compile, hoursResourcesConverter, teamInfoService, dataSources) {
var TeamAllocationRow = (function () {
var TeamAllocationRow = function (isInitialized, show, cells) {
this.IsInitialized = isInitialized;
this.Show = show;
this.Cells = cells;
};
TeamAllocationRow.prototype = {
initialize: function () {
this.IsInitialized = true;
},
toggleShow: function () {
this.Show = !this.Show;
}
};
return TeamAllocationRow;
})();
var AllocationMode = {
AssignRest: 1,
AssignAll: 2,
Reset: 3
};
var FormatCells = {
DecimalCalcQtyPlaces: 6
};
var commonErrorMessage = 'An error occurred while processing your request. Please, try again later.';
var C_EMPTY_GUID = '00000000-0000-0000-0000-000000000000'; // SA. ENV-1254
// SA. ENV-1001. Urls to get data from server
$scope.dataUrls = {
getTeamsByExpCatUrl: '/Team/GetTeamsByExpenditureCategory', // SA. ENV-1254
getTeamsByIdUrl: '/Mix/GetTeamsById', // SA. ENV-1254
getScenarioDetailsUrl: '/Mix/GetScenarioDetails',
editScenarioDetailsUrl: '/Mix/EditScenarioDetails',
getCalendarUrl: '/Mix/LoadCalendar',
loadMixUrl: '/Mix/LoadMix',
saveMixUrl: '/Mix/SaveMix',
deleteMixUrl: '/Mix/DeleteMix'
};
$scope.copier = {
parentProjectId: null,
targetProjectId: null,
scenarioId: null,
scenarioName: 'copy'
};
$scope.data = {
Projects2Add: [],
SelectedFilterItems: [] // SA. ENV-1254. Selected teams and views in the Mix header filter
};
$scope.AddTeamForm = {
AvailableProjectExpCats2Add: [], // categories to add
AvailableInTeams: [], // teams in layout to add
AvailableProjectOutTeams2Add: [], // teams out of view to add
ProjectId: null // selected project for which we open the modal form
};
$scope.Calendar = initCalendar();
$scope.projectBars = [];
// SA. Shows, the page has unsaved mix data, that must be transferred to the server, when filter changed
$scope.IsDataLoaded = false;
$scope.DataChanged = false; // SA. ENV-1153
$scope.clickShift = 0
$scope.cellWidth = 114;
$scope.UnassignedExpendituresProjectsExist = false; // SA. ENV-1210
$scope.UnscheduledProjectsExist = false; // SA. ENV-1210
$scope.PageWarningMessage = ""; // SA. ENV-1210
/* Display Mode -------------------------------------------------------------------------------*/
$scope.DisplayMode = {
IsViewModeMonth: true, // do not display weeks by default
IsUOMHours: true, // display data in Hours
GroupCapacityByTeam: false, // do not group capacity by team
TotalsAs: 1, //Allocated/Capacity
CapacityView: false, // Planned
IsAvgMode: false, // Totals average mode
UnassignedAllocations: false // SA. ENV-1298
};
$scope.$watch('DisplayMode.IsViewModeMonth', function (newValue, oldValue) {
if (oldValue != newValue) {
$scope.setMonthesExpanded(!newValue);
for (var teamIndex in $scope.Calendar.Teams) {
var team = $scope.Calendar.Teams[teamIndex];
refreshTeamProjectCssStyles(team);
}
}
});
$scope.$watch('DisplayMode.IsUOMHours', function (newValue, oldValue) {
if (oldValue != newValue) {
$scope.recreateView();
$scope.$broadcast('changeUOMMode', newValue);
}
});
$scope.$watch('DisplayMode.GroupCapacityByTeam', function (newValue, oldValue) {
if (oldValue != newValue) {
$scope.$broadcast('groupCapacityByTeamChanged', newValue);
}
});
$scope.$watch('DisplayMode.TotalsAs', function (newValue, oldValue) {
if (oldValue != newValue) {
$scope.$broadcast('totalsAsChanged', newValue);
}
});
$scope.$watch('DisplayMode.CapacityView', function (newValue, oldValue) {
if (oldValue != newValue) {
$scope.$broadcast('capacityViewChanged', newValue);
}
});
$scope.setMonthesExpanded = function (expand) {
if (expand)
$scope.Calendar.Header.expandMonthes();
else
$scope.Calendar.Header.collapseMonthes();
};
function initCalendar() {
return {
StartDate: null, // Mix Start Date
EndDate: null, // Mix End Date
FiscalCalendarWeekEndings: [], // Mix Calendar Weekendings
Header: null, // Calendar header. See details on ~/Scripts/Angular/Types/GridHeader.js
Teams: [], // Displayed in the Mix Calendar Teams
Projects: {}, // Mix Projects Raw Data
UnscheduledProjects: [], // List of Mix unscheduled projects (Ids only)
Queue4UnscheduledProjects: [], // List of Mix queued projects (Ids only)
ManagedProjects: [], // List of Mix Calendar displayed projects (Ids only)
UnassignedExpendituresProjects: [], // SA. ENV-1210
GridLayout: {} // Mix Calendar Layout
};
};
$scope.toggleMonth = function (monthIndex) {
$scope.Calendar.Header.toggleMonth(monthIndex);
for (var teamIndex in $scope.Calendar.Teams) {
var team = $scope.Calendar.Teams[teamIndex];
refreshTeamProjectCssStyles(team);
}
};
$scope.init = function (data) {
if (!data) {
return;
}
if (data.showAvgTotals) { // SA. ENV-1030. Average totals mode
$scope.DisplayMode.IsAvgMode = data.showAvgTotals;
}
if (data.prefs && (data.prefs.length > 0)) {
var prefs = angular.fromJson(data.prefs);
for (var i = 0; i < prefs.length; i++) {
switch (prefs[i].Key) {
case "monthWeekMode":
$scope.DisplayMode.IsViewModeMonth = prefs[i].Value;
break;
case "uomMode":
$scope.DisplayMode.IsUOMHours = prefs[i].Value;
break;
case "groupCapacityByTeam":
$scope.DisplayMode.GroupCapacityByTeam = prefs[i].Value;
break;
case "showOption":
$scope.DisplayMode.TotalsAs = prefs[i].Value;
break;
case "capacityView":
$scope.DisplayMode.CapacityView = prefs[i].Value;
break;
case "unassignedAllocations":
$scope.DisplayMode.UnassignedAllocations = prefs[i].Value;
break;
}
}
}
};
/* Event receivers -------------------------------------------------------------------------------*/
$scope.$on('filterChanged', function (event, filter, availableTeams, callbackFn) {
var postData = {};
postData.Filter = {};
postData.Filter.Selection = filter;
$scope.fixFilterForSave(postData.Filter); // SA. Fix for valid deserialization of the filter on server
if ($scope.IsDataLoaded) {
// The page has data, that must be sent to the server to preserve user changes
$scope.createSaveDataPackage(postData);
}
var request = getAntiXSRFRequest($scope.dataUrls.getCalendarUrl, postData);
try {
$http(request)
.success(function (data, status, headers, config) {
try {
$scope.IsDataLoaded = true;
// set available teams from header controller
data.Filter.Variants.AvailableTeamsAndViews = availableTeams;
$scope.storeMixFilter(data); // SA. ENV-1254
$scope.rebuildCalendar(data.Calendar);
if (callbackFn && (typeof callbackFn === 'function'))
callbackFn(data);
unblockUI();
} catch (e) {
unblockUI();
showErrorModal('Oops!', commonErrorMessage);
}
})
.error(function (data, status, headers, config) {
unblockUI();
showErrorModal('Oops!', commonErrorMessage);
});
} catch (e) {
unblockUI();
showErrorModal('Oops!', commonErrorMessage);
}
});
$scope.$on('loadMix', function (event, mixId, setFilterCallback) {
blockUI();
var request = getAntiXSRFRequest($scope.dataUrls.loadMixUrl, mixId);
try {
$http(request)
.success(function (data, status, headers, config) {
try {
if (!data) {
unblockUI();
return;
}
if (setFilterCallback && (typeof setFilterCallback === 'function'))
setFilterCallback(data);
$scope.IsDataLoaded = true;
$scope.MixId = data.Id;
$scope.storeMixFilter(data); // SA. ENV-1254
$scope.rebuildCalendar(data.Calendar);
unblockUI();
} catch (e) {
unblockUI();
showErrorModal('Oops!', commonErrorMessage);
} finally {
$scope.$parent.$broadcast('dataloaded');
}
})
.error(function (data, status, headers, config) {
unblockUI();
showErrorModal('Oops!', commonErrorMessage);
});
} catch (e) {
unblockUI();
showErrorModal('Oops!', commonErrorMessage);
}
});
$scope.$on('saveChanges', function (event, saveData, successSaveCallBack, failedSaveCallBack) {
if (!saveData) {
if (isCallbackValid(failedSaveCallBack)) {
failedSaveCallBack(commonErrorMessage);
}
return;
}
// Get filter values from header controller
$scope.createSaveDataPackage(saveData);
var request = getAntiXSRFRequest($scope.dataUrls.saveMixUrl, saveData);
try {
$http(request)
.success(function (data, status, headers, config) {
if (isCallbackValid(successSaveCallBack)) {
successSaveCallBack(data);
}
})
.error(function (data, status, headers, config) {
if (isCallbackValid(failedSaveCallBack)) {
failedSaveCallBack(commonErrorMessage);
}
});
// unblockUI();
} catch (e) {
if (isCallbackValid(failedSaveCallBack)) {
failedSaveCallBack(commonErrorMessage);
}
}
});
$scope.$on('deleteMix', function (event, mixId, okCallback, errorCallback) {
var request = getAntiXSRFRequest($scope.dataUrls.deleteMixUrl, mixId);
try {
$http(request)
.success(function (data, status, headers, config) {
try {
if (okCallback && (typeof okCallback === 'function'))
okCallback(data);
else {
unblockUI();
}
} catch (err) {
if (errorCallback && (typeof errorCallback === 'function'))
errorCallback();
unblockUI();
showErrorModal('Oops!', commonErrorMessage);
}
})
.error(function (data, status, headers, config) {
if (errorCallback && (typeof errorCallback === 'function'))
errorCallback();
unblockUI();
showErrorModal('Oops!', commonErrorMessage);
});
} catch (e) {
if (errorCallback && (typeof errorCallback === 'function'))
errorCallback();
unblockUI();
showErrorModal('Oops!', commonErrorMessage);
}
});
$scope.$on('saveScenarioDetails', function (event, scenario) {
if (!!scenario && !!scenario.Id && !!scenario.ProjectId && scenario.Expenditures) {
var project = $scope.Calendar.Projects[scenario.ProjectId];
if (!!project && !!project.Scenario && project.Scenario.Id === scenario.Id) {
var rangeChanged = project.Scenario.StartDate != scenario.StartDate || project.Scenario.EndDate != scenario.EndDate;
if (rangeChanged) {
project.Scenario.StartDate = scenario.StartDate;
project.Scenario.EndDate = scenario.EndDate;
$scope.recreateView();
}
refreshProjectData(project, scenario.Expenditures);
setDataChanged(true);
}
}
$document.trigger('rmo.close-scenario-details-window');
});
$scope.$on('cancelScenarioDetails', function (event) {
$document.trigger('rmo.close-scenario-details-window');
});
/* Drag and Drop -------------------------------------------------------------------------------*/
$scope.dragEnter = function ($dropmodel, $dragmodel, $event) {
$dropmodel.Team.Drop = {
Row: $dropmodel.Row
};
if (!$dragmodel) {
return;
}
var project = $scope.Calendar.Projects[$dragmodel.ProjectId];
if (!project || !project.Scenario) {
$dropmodel.Team.Drop.Cells = {};
$dropmodel.Team.Drop.Cells[$dropmodel.Milliseconds] = true;
return;
}
var cellIndex = $dragmodel.CellIndex;
if ($scope.clickShift != 0) {
cellIndex = getNextVisibleIndex(cellIndex, Math.ceil($scope.clickShift / $scope.cellWidth) - 1);
//console.log(cellIndex);
}
var shiftX = getDatesShift(getStartDate4Dragging($dragmodel.Team, $dragmodel.Row, cellIndex), $dropmodel.Milliseconds);
$dropmodel.Team.Drop.Cells = getActiveCells(project.Scenario, shiftX);
};
function getStartDate4Dragging(team, rowIndex, cellIndex) {
if ($scope.Calendar.Header.Weeks.length <= cellIndex) {
return null;
}
var week = $scope.Calendar.Header.Weeks[cellIndex];
if (week.DataType === Header.DataType.Week) {
return week.Milliseconds; // return week value if cell is week cell, we should calculate date only for month's cells
}
if (!team || !team.Allocations || team.Allocations.length <= rowIndex) {
return week.Milliseconds; // return default value if allocations collection is incorrect
}
var projectsRow = team.Allocations[rowIndex];
if (!projectsRow.Cells || projectsRow.Cells.length <= cellIndex) {
return week.Milliseconds; // return default value if cells collection is incorrect
}
var month = $scope.Calendar.Header.Months[week.ParentIndex];
if (!month || !month.Childs) {
return week.Milliseconds;
}
var projectId = projectsRow.Cells[cellIndex].Id,
milliseconds = week.Milliseconds;
for (var i = 0; i < month.Childs.length; i++) {
if (projectsRow.Cells.length <= month.Childs[i])
continue;
if (projectsRow.Cells[month.Childs[i]].Id == projectId) {
milliseconds = $scope.Calendar.Header.Weeks[month.Childs[i]].Milliseconds;
break;
}
}
return milliseconds;
}
function getActiveCells(scenario, shiftX) {
if (!scenario)
return {};
var cells = {};
var startDateMs = getNewDate(getNearestDate(scenario.StartDate), shiftX),
endDateMs = getNewDate(getNearestDate(scenario.EndDate), shiftX);
for (var i = 0; i < $scope.Calendar.Header.Months.length; i++) {
var month = $scope.Calendar.Header.Months[i];
for (var j = 0; j < month.Childs.length; j++) {
var week = $scope.Calendar.Header.Weeks[month.Childs[j]];
if (week.Milliseconds >= startDateMs && week.Milliseconds <= endDateMs) {
cells[week.Milliseconds] = true;
if (month.IsCollapsed === true) {
var monthWeek = $scope.Calendar.Header.Weeks[month.SelfIndexInWeeks];
cells[monthWeek.Milliseconds] = true;
}
}
}
}
return cells;
}
function getNextVisibleIndex(startIndex, count) {
if (count <= 0)
return startIndex;
var countReturn = 0;
var countLeft = count;
var visibleCells = 0;
for (var i = 0; i < $scope.Calendar.Header.Weeks.length; i++) {
if (startIndex < visibleCells) {
countReturn++;
if ($scope.Calendar.Header.Weeks[i].Show) {
countLeft--;
if (countLeft == 0)
break;
}
}
visibleCells++;
}
return startIndex + countReturn;
//} else {
// for (var i = startIndex; i >= 0; i--) {
// if ($scope.Calendar.Header.Weeks[i].Show) {
// countLeft--;
// countReturn++;
// }
// if (countLeft == 0)
// break;
// }
// return startIndex - countReturn;
//}
}
function getNextVisibleMilliseconds(msStart, count) {
if (count <= 0)
return msStart;
var msReturn = null;
var countLeft = count;
var visibleCells = 0;
for (var i = 0; i < $scope.Calendar.Header.Weeks.length; i++) {
if (msStart < $scope.Calendar.Header.Weeks[i].Milliseconds) {
if ($scope.Calendar.Header.Weeks[i].Show) {
msReturn = $scope.Calendar.Header.Weeks[i].Milliseconds;
countLeft--;
if (countLeft == 0)
break;
}
}
}
return msReturn;
}
$scope.dragLeave = function ($dropmodel, $dragmodel) {
if (!$dropmodel || !$dropmodel.Team)
return;
$dropmodel.Team.Drop = {};
};
$scope.drop = function ($dropmodel, $dragmodel) {
if (!$dragmodel || !$dropmodel)
return;
var placeBeforeSelectedRow = (($dropmodel.Team.Drop.Mod == "after") || ($dropmodel.Team.Drop.Mod == "before")) ? true : false;
var selectedRow = ($dropmodel.Team.Drop.Mod == "after") ? $dropmodel.Team.Drop.Row + 1 : $dropmodel.Team.Drop.Row;
$dropmodel.Team.Drop = {};
if ($dragmodel.IsUnscheduled === true) {
var dt = new Date($dropmodel.Milliseconds);
var dtFormatted = (dt.getMonth() + 1) + "/" + dt.getDate() + "/" + dt.getFullYear();
//var ed = new Date($dropmodel.Milliseconds + (1000 * 60 * 60 * 24 * 7));
var projectId = $dragmodel.ProjectId;
showCreateScenarioDialog({
ProjectId: projectId,
TargetTeam: $dropmodel.Team,
TargetRow: selectedRow,
StartDate: dtFormatted, //$dropmodel.Milliseconds,
EndDate: dtFormatted //StartDate + 7days
});
// SA. ENV-1298
var projectItem = $scope.getProjectById(projectId);
if (projectItem && projectItem.Scenario && projectItem.Scenario.Expenditures) {
createProjectUnassignedExpendituresItems(projectItem, $dropmodel.Team.Id);
}
}
else {
if ($dragmodel.Team.Id == $dropmodel.Team.Id) {
// Move project within team block. Need to update project dates in all teams
var cellIndex = $dragmodel.CellIndex;
var ms = $dragmodel.Milliseconds;
if ($scope.clickShift != 0) {
//cellIndex = getNextVisibleIndex(cellIndex, Math.ceil($scope.clickShift / $scope.cellWidth)-1);
//$dropmodel.Milliseconds > $dragmodel.Milliseconds);
//console.log("w:" + Math.ceil($scope.clickShift / $scope.cellWidth) + " 1orig cellIndex:" + $dragmodel.CellIndex + " 1corrected cellIndex:" + cellIndex);
ms = getNextVisibleMilliseconds($dragmodel.Milliseconds, Math.ceil($scope.clickShift / $scope.cellWidth) - 1);
}
var dragStartDate = getStartDate4Dragging($dragmodel.Team, $dragmodel.Row, cellIndex);//$dragmodel.CellIndex);
console.log("$dragmodel.Milliseconds:" + $dragmodel.Milliseconds + " ms:" + ms);
//var tdSelector = "td[label-pos-miliseconds = '" + $dropmodel.Milliseconds + "'][label-pos-row ='" + ($dropmodel.Row) + "']";
//var labelSelector = "div[label-pos-projectId ='" + $dragmodel.ProjectId + "']";
$scope.moveProjectInsideTeam($dragmodel.ProjectId, $dragmodel.Team,
getDatesShift(ms, $dropmodel.Milliseconds), $dragmodel.Row, selectedRow, placeBeforeSelectedRow);
}
else {
var cellIndex = $dragmodel.CellIndex;
if ($scope.clickShift != 0) {
//cellIndex = getNextVisibleIndex(cellIndex, Math.ceil($scope.clickShift / $scope.cellWidth)-1, true); //$dropmodel.Milliseconds > $dragmodel.Milliseconds);
//console.log("w:"+Math.ceil($scope.clickShift / $scope.cellWidth)+" 2orig cellIndex:" + $dragmodel.CellIndex + " 2corrected cellIndex:" + cellIndex);
}
var dragStartDate = getStartDate4Dragging($dragmodel.Team, $dragmodel.Row, cellIndex); //$dragmodel.CellIndex);
var datesShift = getDatesShift(dragStartDate, $dropmodel.Milliseconds);
var projectItem = $scope.getProjectById($dragmodel.ProjectId);
if (!isScenarioLoaded(projectItem.Scenario)) {
// Load scenario details to perform the check
loadScenarioDetails(projectItem.Scenario.Id, projectItem.Id, function (expenditures) {
setScenarioExpenditures(projectItem.Scenario, expenditures);
checkProjectMovementToTeam(projectItem, $dragmodel.Team.Id, $dropmodel.Team.Id,
datesShift, selectedRow, placeBeforeSelectedRow);
});
}
else {
checkProjectMovementToTeam(projectItem, $dragmodel.Team.Id, $dropmodel.Team.Id,
datesShift, selectedRow, placeBeforeSelectedRow)
}
}
}
setDataChanged(true); // SA. ENV-1153
};
// SA. ENV-1210. Checks the project can be moved from source to target team with reassignmtnt of ECs
function checkProjectMovementToTeam(projectItem, sourceTeamId, targetTeamId, datesShift, selectedRow, placeBeforeSelectedRow) {
// Get categories, that have allocations in source team to be moved to target team
var sourceTeamAllocatedCategories =
getAllocatedScenarioExpendituresByTeam(projectItem.Scenario, sourceTeamId);
var nonExistingExpCatsInTargetTeam = [];
if (sourceTeamAllocatedCategories && (sourceTeamAllocatedCategories.length > 0)) {
// Get categories, which allocations can't be moved to target team, because
// these ECs don't exist in target team
nonExistingExpCatsInTargetTeam = categoriesNotInTeam(sourceTeamAllocatedCategories, targetTeamId);
}
if (nonExistingExpCatsInTargetTeam.length > 0) {
// We are here, if no EC can be auto reassigned to new team
bootbox.dialog({
message: "Expenditures allocated are not available on new team. To continue, press OK",
buttons: {
success: {
label: "OK",
className: "btn-primary",
callback: function () {
$scope.$apply(function () {
addTeamToProject(projectItem.Id, targetTeamId, datesShift, selectedRow, placeBeforeSelectedRow);
copyAllocationsFromTeamToTeam(projectItem, sourceTeamId, targetTeamId);
removeTeamFromProjectInternal(projectItem.Id, sourceTeamId);
if ($scope.DisplayMode.UnassignedAllocations)
tryAllocateUnassignedExpCatsToTeam(projectItem.Id, targetTeamId);
showExpCatMovementPageWarning();
createProjectUnassignedExpendituresItems(projectItem, targetTeamId);
// Refresh calendar view
$scope.recreateView();
});
}
},
details: {
label: "Open Scenario Details",
className: "btn-primary",
callback: function () {
$scope.$apply(function () {
addTeamToProject(projectItem.Id, targetTeamId, datesShift, selectedRow, placeBeforeSelectedRow);
copyAllocationsFromTeamToTeam(projectItem, sourceTeamId, targetTeamId);
removeTeamFromProjectInternal(projectItem.Id, sourceTeamId);
if ($scope.DisplayMode.UnassignedAllocations)
tryAllocateUnassignedExpCatsToTeam(projectItem.Id, targetTeamId);
showExpCatMovementPageWarning();
createProjectUnassignedExpendituresItems(projectItem, targetTeamId);
// Refresh calendar view
$scope.recreateView();
loadScenarioDetailsEditForm(projectItem.Scenario, false);
});
}
},
cancel: {
label: "Cancel",
className: "btn-default",
callback: function () {
// Project movement to another team chancelled
}
}
}
});
}
else {
// We are here, if reassignment of ECs can be performed automatically
$scope.hidePageWarningMessage();
addTeamToProject(projectItem.Id, targetTeamId, datesShift, selectedRow, placeBeforeSelectedRow);
copyAllocationsFromTeamToTeam(projectItem, sourceTeamId, targetTeamId);
removeTeamFromProjectInternal(projectItem.Id, sourceTeamId);
if ($scope.DisplayMode.UnassignedAllocations)
tryAllocateUnassignedExpCatsToTeam(projectItem.Id, targetTeamId);
// Refresh calendar view
$scope.recreateView();
}
}
// SA. ENV-1210. Moves project from source to target team, with reassignment of ECs allocations
function copyAllocationsFromTeamToTeam(projectItem, sourceTeamId, targetTeamId) {
// Get categories, that have allocations in source team to be moved to target team
var allocatedExpCats =
getAllocatedScenarioExpendituresByTeam(projectItem.Scenario, sourceTeamId);
if (allocatedExpCats && (allocatedExpCats.length > 0)) {
var updatedData = angular.copy(projectItem.Scenario.Expenditures);
copyAllocationsFromTeamToTeamInternal(updatedData, allocatedExpCats, sourceTeamId, targetTeamId);
refreshProjectData(projectItem, updatedData);
}
}
// SA. ENV-1210. Shows warning at the top of the Calendar
function showExpCatMovementPageWarning() {
$scope.PageWarningMessage =
"Destination team doesn't contain some necessary expenditure categories for the project. Please check allocations";
}
// SA. ENV-1210. Copies allocations for given exp. categories from source team to target team
// ExpCatsdata is the property Expenditures of a Scenario object
function copyAllocationsFromTeamToTeamInternal(ExpCatsdata, expCategories, sourceTeamId, targetTeamId) {
if (!ExpCatsdata)
return null;
$.each(expCategories, function (index, expCatId) {
var currentEC = ExpCatsdata[expCatId];
var srcTeam = currentEC.Teams[sourceTeamId];
var dstTeam = currentEC.Teams[targetTeamId];
if (srcTeam && srcTeam.QuantityValues) {
// Destination team exists in the Category. Perform copping of allocations
var weekEndings = Object.keys(srcTeam.QuantityValues);
if (dstTeam && dstTeam.QuantityValues) {
$.each(weekEndings, function (index, we) {
if (srcTeam.QuantityValues[we] !== undefined) {
if (dstTeam.QuantityValues[we] === undefined)
dstTeam.QuantityValues[we] = 0;
dstTeam.QuantityValues[we] += srcTeam.QuantityValues[we];
}
});
}
else {
// SA. ENV-1254. The Category hasn't the desired destination team. Copy allocations to
// UnassignedAllocations
if (!currentEC.UnassignedAllocations)
currentEC.UnassignedAllocations = {};
$.each(weekEndings, function (index, we) {
if (srcTeam.QuantityValues[we] !== undefined) {
if (currentEC.UnassignedAllocations[we] === undefined)
currentEC.UnassignedAllocations[we] = 0;
currentEC.UnassignedAllocations[we] += srcTeam.QuantityValues[we];
}
});
}
}
});
}
// SA. ENV-1210. Returns expenditures, that are have non-zero allocations
// within specified team in specified scenario of a project
function getAllocatedScenarioExpendituresByTeam(scenarioItem, teamId) {
var scenarioExpenditureKeys = Object.keys(scenarioItem.Expenditures);
var allocatedECs = $.grep(scenarioExpenditureKeys, function (key, index) {
// Check the EC belongs to specified team
var include = (scenarioItem.Expenditures[key].Teams != null) &&
(scenarioItem.Expenditures[key].Teams[teamId] !== undefined);
if (include) {
var currentTeam = scenarioItem.Expenditures[key].Teams[teamId];
if (currentTeam.QuantityValues) {
// Check EC has non-zero allocations within specified team
var weekendings = Object.keys(currentTeam.QuantityValues);
var nonZeroAllocations = $.grep(weekendings, function (we, index) {
return currentTeam.QuantityValues[we] > 0;
});
include = nonZeroAllocations.length > 0;
}
else {
// EC has no allocations within the team
include = false;
}
}
return include;
});
return allocatedECs;
}
// SA. ENV-1210. Returns those categories from specified list, that not exist in the specified team
function categoriesNotInTeam(expCategories, teamId) {
var teamItem = teamInfoService.getById(teamId);
var teamCategories = teamItem.ExpCategories ? Object.keys(teamItem.ExpCategories) : [];
var categoriesOutOfTeam = $.grep(expCategories, function (ecId, index) {
return teamCategories.indexOf(ecId) < 0;
});
return categoriesOutOfTeam;
}
/* Display Model -------------------------------------------------------------------------------*/
function calculateTeamHeight(team) {
if (!team || !team.Allocations || team.Allocations.length <= 0)
return 1;
var height = team.Allocations.length * 32, //52,
rowsWithBorder = $scope.GridLayout[team.Id].length || 0;
return (height - 9 + rowsWithBorder) + 'px'; // 4 (top padding) + 4 (bottom padding) + 1 (bottom-border) = 9
};
function convertToTeamViewModel(teams) {
var viewModel = [];
if (!teams)
return viewModel;
for (var i = 0; i < teams.length; i++) {
viewModel.push({
Id: teams[i].Id,
Name: teams[i].Name,
Allocations: [],
AllocationCssStyles: []
});
}
return viewModel;
};
// SA. ENV-1254
$scope.storeMixFilter = function (data) {
$scope.data.SelectedFilterItems = [];
angular.forEach(data.Filter.Selection.TeamsViews, function (item, index) {
$scope.data.SelectedFilterItems.push(item.Id);
});
$scope.data.AvailableTeams = $filter('filter')(data.Filter.Variants.AvailableTeamsAndViews, {Group: {Name: 'Teams'}});
}
$scope.rebuildCalendar = function (data) {
if (!data) {
data = {};
}
$scope.Calendar = initCalendar();
if (!data.WeekEndings || Object.keys(data.WeekEndings).length <= 0)
return;
// init team service with teams collection
teamInfoService.init(data.Teams);
$scope.Calendar.FiscalCalendarWeekEndings = data.FiscalCalendarWeekEndings || [];
$scope.Calendar.Header = new GridHeader(data.WeekEndings || {}).create();
$scope.Calendar.StartDate = $scope.Calendar.Header.Weeks[0].Milliseconds;
$scope.Calendar.EndDate = $scope.Calendar.Header.Weeks[$scope.Calendar.Header.Weeks.length - 2].Milliseconds;
$scope.Calendar.Teams = convertToTeamViewModel(data.Teams);
$scope.Calendar.Projects = data.Projects || {};
$scope.Calendar.UnscheduledProjects = data.UnscheduledProjects || [];
$scope.Calendar.Queue4UnscheduledProjects = data.QueuedProjects || [];
$scope.Calendar.UnassignedExpendituresProjects = data.UnassignedExpendituresProjects || []; // SA. ENV-1210
$scope.Calendar.ManagedProjects = data.ManagedProjects || [];
$scope.GridLayout = $scope.getLayoutForClient(data.Layout);
$scope.prepareManagedToDisplay();
$scope.prepareUnscheduledToDisplay();
$scope.prepareQueuedToDisplay();
$scope.prepareUnassignedEcsProjectsToDisplay(); // SA. ENV-1210
$scope.setMonthesExpanded(!$scope.DisplayMode.IsViewModeMonth);
$scope.recreateView();
// need to pass new objects for preventing using single instances of objects inside current and other controllers
$scope.$broadcast('rebindTeamInfo', {
Teams: angular.copy(data.Teams || []),
Vacations: angular.copy(data.Vacations || {}),
Trainings: angular.copy(data.Trainings || {}),
Header: angular.copy($scope.Calendar.Header),
DisplayMode: $scope.DisplayMode
});
// SA. ENV-1159. Init page javascript controls with default values
initPageControls();
};
$scope.recreateView = function () {
for (var teamIndex = 0; teamIndex < $scope.Calendar.Teams.length; teamIndex++) {
var currentTeam = $scope.Calendar.Teams[teamIndex];
currentTeam.Allocations = [];
currentTeam.AllocationCssStyles = [];
createLayoutForTeam(currentTeam.Id);
if (!$scope.GridLayout[currentTeam.Id] || $scope.GridLayout[currentTeam.Id].length < 1) {
// Draw blank row for empty team
createDummyViewForTeam(currentTeam);
} else {
createProjectsViewForTeam(currentTeam);
}
// prepare CSS styles for each allocation cels
refreshTeamProjectCssStyles(currentTeam);
}
};
function refreshTeamProjectCssStyles(currentTeam) {
for (var i = 0; i < currentTeam.Allocations.length; i++) {
currentTeam.AllocationCssStyles[i] = '';
if (i > 0)
currentTeam.AllocationCssStyles[i] += 'visibility:hidden;';
else
currentTeam.AllocationCssStyles[i] += 'height:' + calculateTeamHeight(currentTeam);
var colSpan = 1;
var firstCell = null;
var lastId = null;
for (var j = 0; j < currentTeam.Allocations[i].Cells.length; j++) {
var cell = currentTeam.Allocations[i].Cells[j];
cell.IsSpanned = false;
cell.colSpan = 1;
cell.width = 50;
//cell.Pinned = false;
if (!cell.Id) {
continue;
}
if ($scope.Calendar.Header.Weeks[j].Show) {
//last cell
if (cell.IsProjectLastCell) {
//one cell project
if (!cell.IsFirstCell) {
colSpan++;
cell.IsSpanned = true;
}
//if (firstCell && colSpan > 1) {
// firstCell.colSpan = colSpan;
// firstCell.width = (colSpan * $scope.cellWidth) - 66;
// firstCell.OverEnd = cell.OverEnd;
//}
} else {
//first cell
if (cell.IsFirstCell) {
firstCell = cell;
colSpan = 1;
//cell in a middle
} else {
if (firstCell) {
cell.IsSpanned = true;
colSpan++;
}
}
}
}
if (cell.IsProjectLastCell)
{
if (firstCell && colSpan > 1) {
firstCell.colSpan = colSpan;
firstCell.width = (colSpan * $scope.cellWidth) - 66;
firstCell.OverEnd = cell.OverEnd;
}
}
if (cell.colSpan == 0)
cell.colSpan = 1;
cell.CssStyle = getProjectCSS($scope.getProjectById(cell.Id));
//drawBorder(currentTeam, i, j);
}
//if (firstCell && colSpan > 1)
// firstCell.colSpan = colSpan;
}
}
function createProjectsViewForTeam(team) {
if (!team || !$scope.GridLayout || !$scope.GridLayout[team.Id])
return;
var currentLayout = $scope.GridLayout[team.Id];
var projectsMapHelper = [];
// Create helper projects layout struct for current team
for (var rowIndex = 0; rowIndex < currentLayout.length; rowIndex++) {
var helperRow = [];
for (var itemIndex = 0; itemIndex < currentLayout[rowIndex].length; itemIndex++) {
var projectId = currentLayout[rowIndex][itemIndex].ProjectId;
var projItem = $scope.getProjectById(projectId);
helperRow.push(projItem);
}
projectsMapHelper.push(helperRow);
}
for (var rowIndex = 0; rowIndex < projectsMapHelper.length; rowIndex++) {
var cells = [];
var weekProjects = [];
var monthProjects = [];
for (var mIndex = 0; mIndex < $scope.Calendar.Header.Months.length; mIndex++) {
var weekendsCount = $scope.Calendar.Header.Months[mIndex].Childs.length; // SA. ENV-1046
for (var wIndex = 0; wIndex < weekendsCount; wIndex++) {
var weekIndex = $scope.Calendar.Header.Months[mIndex].Childs[wIndex];
var week = $scope.Calendar.Header.Weeks[weekIndex];
var weekProject = getProjectAtWeekCell(week, projectsMapHelper[rowIndex]);
if (!!weekProject) {
// Create and add project cell
var isFirstCell = (weekProjects[weekProject.Id] === undefined);
var isLastCell = week.Milliseconds == $scope.Calendar.EndDate;
var endDelta = weekProject.Scenario ? Math.floor((week.Milliseconds - weekProject.Scenario.EndDate) / 86400000) : -1;
var isProjectLastCell = (endDelta >= 0 && endDelta <= 7) || (isLastCell && weekProject.Scenario.EndDate > week.Milliseconds);
cells.push(createCell(weekProject.Id, weekProject.Name, '', '', isFirstCell,
(isFirstCell && weekProject.Scenario ? (Math.floor(($scope.Calendar.StartDate - weekProject.Scenario.StartDate) / 86400000) > 7) : false),
(isLastCell && weekProject.Scenario ? (Math.floor((weekProject.Scenario.EndDate - $scope.Calendar.EndDate) / 86400000) > 0) : false),
isProjectLastCell, weekProject.Pinned));
weekProjects[weekProject.Id] = true;
} else {
// Create and add blank cell
cells.push(createBlankCell());
}
}
var monthProject = getProjectAtMonthCell($scope.Calendar.Header.Months[mIndex], projectsMapHelper[rowIndex]);
if (!!monthProject) {
var isFirstCell = (monthProjects[monthProject.Id] === undefined);
var isLastCell = $scope.Calendar.Header.Months.length - 1 == mIndex;
var endDelta = -1;
var isProjectLastMonthCell = false;
for (var wIndex = 0; wIndex < weekendsCount; wIndex++) {
var weekIndex = $scope.Calendar.Header.Months[mIndex].Childs[wIndex];
var week = $scope.Calendar.Header.Weeks[weekIndex];
var endDelta = monthProject.Scenario ? Math.floor((week.Milliseconds - monthProject.Scenario.EndDate) / 86400000) : -1;
isProjectLastMonthCell = (endDelta >= 0 && endDelta <= 7) || (isLastCell && monthProject.Scenario.EndDate > week.Milliseconds);
if (isProjectLastMonthCell)
break;
}
cells.push(createCell(monthProject.Id, monthProject.Name, '', '', isFirstCell,
(isFirstCell && monthProject.Scenario ? (Math.floor(($scope.Calendar.StartDate - monthProject.Scenario.StartDate) / 86400000) > 7) : false),
(isLastCell && monthProject.Scenario ? (Math.floor((monthProject.Scenario.EndDate - $scope.Calendar.EndDate) / 86400000) > 0) : false),
isProjectLastMonthCell, monthProject.Pinned));
monthProjects[monthProject.Id] = true;
}
else {
cells.push(createBlankCell());
}
}
team.Allocations.push((new TeamAllocationRow(true, true, cells)));
}
};
function createDummyViewForTeam(team) {
var cells = [];
var allocationCell;
for (var i = 0; i < $scope.Calendar.Header.Weeks.length; i++) {
allocationCell = $scope.createDummyAllocationCell();
cells.push(allocationCell);
}
team.Allocations.push((new TeamAllocationRow(true, true, cells)));
};
function createLayoutForTeam(teamId) {
if (!$scope.GridLayout)
$scope.GridLayout = {};
if (!$scope.GridLayout[teamId])
$scope.GridLayout[teamId] = [];
if ($scope.GridLayout[teamId].length < 1) {
// Recreate layout via automatic engine
$scope.GridLayout[teamId] = arrangeProjectsWithFCNR(teamId);
}
};
// arrange projects using Floor Сeiling No Rotation (FCNR) algorithm
function arrangeProjectsWithFCNR(teamId) {
var layout = [],
layoutFillingRemaining = []; // contains how many empty space exists in the row
if (!teamId)
return layout;
if (!$scope.Calendar.ManagedProjects || $scope.Calendar.ManagedProjects.length <= 0)
return layout;
for (var i in $scope.Calendar.ManagedProjects) {
var currentProject = $scope.Calendar.Projects[$scope.Calendar.ManagedProjects[i].Id];
if (!currentProject || !currentProject.Scenario || !$scope.projectHasTeam(currentProject, teamId))
continue;
var optimalX = -1,
optimalY = -1,
projectCovers = 0, // what period covers current project in selected range
// min value of remaining empty space if project will be placed at the row;
// we need to place project in the row in which empty space will be min after project will be placed
minRemaining = $scope.Calendar.EndDate - $scope.Calendar.StartDate;
for (var j in layout) {
var firstProject = $scope.Calendar.Projects[layout[j][0].ProjectId];
var lastProject = $scope.Calendar.Projects[layout[j][layout[j].length - 1].ProjectId];
var currentProjectNearestStartDate = getNearestDate(currentProject.Scenario.StartDate),
currentProjectNearestEndDate = getNearestDate(currentProject.Scenario.EndDate),
firstProjectNearestStartDate = getNearestDate(firstProject.Scenario.StartDate),
lastProjectNearestEndDate = getNearestDate(lastProject.Scenario.EndDate);
// check if the project can be placed in the begin of the row
if (currentProjectNearestEndDate < firstProjectNearestStartDate) {
projectCovers = currentProject.Scenario.EndDate -
Math.max($scope.Calendar.StartDate, currentProject.Scenario.StartDate);
var remaining = layoutFillingRemaining[j] - projectCovers;
if (remaining < minRemaining) {
minRemaining = remaining;
optimalX = j;
optimalY = 0;
}
}
// check if the project can be placed in the end of the row
if (currentProjectNearestStartDate > lastProjectNearestEndDate) {
projectCovers = Math.min($scope.Calendar.EndDate, currentProject.Scenario.EndDate) -
currentProject.Scenario.StartDate;
var remaining = layoutFillingRemaining[j] - projectCovers;
if (remaining < minRemaining) {
minRemaining = remaining;
optimalX = j;
optimalY = layout[j].length;
}
}
for (var z = 0; z < layout[j].length - 1; z++) {
var prevProject = $scope.Calendar.Projects[layout[j][z].ProjectId];
var nextProject = $scope.Calendar.Projects[layout[j][z + 1].ProjectId];
var prevProjectNearestEndDate = getNearestDate(prevProject.Scenario.EndDate),
nextProjectNearestStartDate = getNearestDate(nextProject.Scenario.StartDate);
// check if the project can be placed between 2 neighboring projects in the row
if (currentProjectNearestStartDate > prevProjectNearestEndDate &&
currentProjectNearestEndDate < nextProjectNearestStartDate) {
projectCovers = currentProject.Scenario.EndDate - currentProject.Scenario.StartDate;
var remaining = layoutFillingRemaining[j] - projectCovers;
if (remaining < minRemaining) {
minRemaining = remaining;
optimalX = j;
optimalY = z + 1;
}
}
}
}
if (optimalX < 0) {
layout.push([]);
layout[layout.length - 1].push(createLayoutRow(currentProject.Id));
var projectCovers = Math.min(currentProject.Scenario.EndDate, $scope.Calendar.EndDate) -
Math.max(currentProject.Scenario.StartDate, $scope.Calendar.StartDate);
layoutFillingRemaining.push([]);
layoutFillingRemaining[layoutFillingRemaining.length - 1] = $scope.Calendar.EndDate - $scope.Calendar.StartDate - projectCovers;
}
else {
if (!layout[optimalX])
layout[optimalX] = [];
if (optimalY < 0) {
layout[optimalX].push(createLayoutRow(currentProject.Id));
}
else {
layout[optimalX].splice(optimalY, 0, createLayoutRow(currentProject.Id));
}
layoutFillingRemaining[optimalX] -= projectCovers;
}
}
return layout;
}
function getProjectAtWeekCell(week, mapRow) {
var foundProject = null;
if (!mapRow || (mapRow.length < 1))
return null;
for (var index = 0; index < mapRow.length; index++) {
if (!!mapRow[index].Scenario &&
(getNearestDate(mapRow[index].Scenario.StartDate) <= week.Milliseconds) &&
(getNearestDate(mapRow[index].Scenario.EndDate) >= week.Milliseconds)) {
foundProject = mapRow[index];
break;
}
}
return foundProject;
};
function getProjectAtMonthCell(month, mapRow) {
var foundProject = null;
if (!month || !month.Childs || month.Childs.length <= 0 || !mapRow || (mapRow.length < 1))
return null;
for (var wIndex = month.Childs.length - 1; wIndex >= 0; wIndex--) {
var week = $scope.Calendar.Header.Weeks[month.Childs[wIndex]];
foundProject = getProjectAtWeekCell(week, mapRow);
if (!!foundProject)
break;
}
return foundProject;
};
function addProjectToLayout(projectId, teamId, index, insertBefore) {
if (!projectId || !teamId || (index < 0))
return;
if (!$scope.GridLayout) {
$scope.GridLayout = {};
}
if (!$scope.GridLayout[teamId]) {
$scope.GridLayout[teamId] = [];
}
var currentLayout = $scope.GridLayout[teamId];
var projectRow = createLayoutRow(projectId);
if (index >= currentLayout.length) {
// New index is out of bounds. Adding project in the end of the map
currentLayout.push([projectRow]);
} else {
// in this case we just put single project to new row
if (insertBefore) {
currentLayout.splice(index, 0, [projectRow]);
}
else {
// otherwise put project in existence row
currentLayout[index].push(projectRow);
}
}
};
// SA. ENV-1114
function removeProjectFromLayout(projectId) {
if (!projectId || !$scope.GridLayout) {
return;
}
for (var teamId in $scope.GridLayout) {
removeProjectFromTeamLayout(projectId, teamId);
}
};
function removeProjectFromTeamLayout(projectId, teamId) {
if (!$scope.GridLayout || !$scope.GridLayout[teamId] || ($scope.GridLayout[teamId].length < 1)) {
return;
}
var found = false;
var currentLayout = $scope.GridLayout[teamId];
for (var rowIndex = 0; rowIndex < currentLayout.length; rowIndex++) {
for (var itemIndex = 0; itemIndex < currentLayout[rowIndex].length; itemIndex++) {
if (currentLayout[rowIndex][itemIndex].ProjectId == projectId) {
currentLayout[rowIndex].splice(itemIndex, 1);
found = true;
break;
}
}
if (found) {
if (currentLayout[rowIndex].length < 1) {
currentLayout.splice(rowIndex, 1);
}
break;
}
}
};
$scope.updateProjectInLayout = function (projectId, teamId, sourceRow, targetRow, insertBefore) {
if (!$scope.GridLayout || !$scope.GridLayout[teamId] || ($scope.GridLayout[teamId].length < 1))
return;
if ((sourceRow === undefined) || (sourceRow < 0) || (targetRow === undefined) || (targetRow < 0) ||
(sourceRow >= $scope.GridLayout[teamId].length))
return;
if ((sourceRow == targetRow) && !insertBefore)
// No need to update map
return;
// Remove project from source row
var currentLayout = $scope.GridLayout[teamId];
var sourceRowMap = currentLayout[sourceRow];
var targetRowCorrected = targetRow;
for (var index = 0; index < sourceRowMap.length; index++) {
if (sourceRowMap[index].ProjectId == projectId) {
sourceRowMap.splice(index, 1);
break;
}
}
if (sourceRowMap.length < 1) {
currentLayout.splice(sourceRow, 1);
if (targetRow > sourceRow) {
targetRowCorrected--;
}
}
// Add project to team map as new position
addProjectToLayout(projectId, teamId, targetRowCorrected, insertBefore);
};
function changeProjectRange(project, shiftX, callbackFn) {
if (!project || !project.Scenario || shiftX == 0) {
if (!!callbackFn && typeof callbackFn === 'function') {
callbackFn();
}
return;
}
var nearestStartDate = getNearestDate(project.Scenario.StartDate);
var nearestEndDate = getNearestDate(project.Scenario.EndDate);
var shiftedStartDate = getNewDate(nearestStartDate, shiftX);
var shiftedEndDate = getNewDate(nearestEndDate, shiftX);
if (!shiftedStartDate) {
if (!$scope.Calendar.FiscalCalendarWeekEndings || $scope.Calendar.FiscalCalendarWeekEndings.length <= 0) {
bootbox.alert('Fiscal Calendar is incorrect.');
} else {
var startDate = new Date($scope.Calendar.FiscalCalendarWeekEndings[0]);
var startDateStr = (startDate.getMonth() + 1) + '/' + startDate.getDate() + '/' + startDate.getFullYear();
bootbox.alert('Financial Calendar starts on ' + startDateStr + '. You cannot move scenario so it exceeds the date range of Financial Calendar.');
}
return;
}
if (!shiftedEndDate) {
if (!$scope.Calendar.FiscalCalendarWeekEndings || $scope.Calendar.FiscalCalendarWeekEndings.length <= 0) {
bootbox.alert('Fiscal Calendar is incorrect.');
} else {
var endDate = new Date($scope.Calendar.FiscalCalendarWeekEndings[$scope.Calendar.FiscalCalendarWeekEndings.length - 1]);
var endDateStr = (endDate.getMonth() + 1) + '/' + endDate.getDate() + '/' + endDate.getFullYear();
bootbox.alert('Financial Calendar ends on ' + endDateStr + '. You cannot move scenario so it exceeds the date range of Financial Calendar.');
}
return;
}
if (project.Deadline > 0 && shiftedEndDate > project.Deadline) {
var deadline = new Date(project.Deadline);
var deadlineStr = (deadline.getMonth() + 1) + '/' + deadline.getDate() + '/' + deadline.getFullYear();
bootbox.alert('Scenario End Date should not exceed Project Deadline date on ' + deadlineStr);
return;
}
project.Scenario.StartDate += (shiftedStartDate - nearestStartDate);
project.Scenario.EndDate += (shiftedEndDate - nearestEndDate);
if (!isScenarioLoaded(project.Scenario)) {
loadScenarioDetails(project.Scenario.Id, project.Id, function (expenditures) {
setScenarioExpenditures(project.Scenario, expenditures);
shiftProjectData(project, shiftX, callbackFn);
});
}
else {
shiftProjectData(project, shiftX, callbackFn);
}
}
function shiftProjectData(project, shiftX, callbackFn) {
var shiftedData = shiftScenarioDetails(project.Scenario, shiftX);
if (!shiftedData) {
return;
}
refreshProjectData(project, shiftedData);
if (!!callbackFn && typeof callbackFn === 'function') {
callbackFn(project);
}
}
function shiftScenarioDetails(scenario, shiftX) {
if (!scenario || !scenario.Expenditures || !shiftX) {
return null;
}
var shiftedData = {};
for (var expCatId in scenario.Expenditures) {
var category = scenario.Expenditures[expCatId];
shiftedData[expCatId] = {
Details: {},
Teams: {}
};
if (!!category.Details) {
shiftedData[expCatId].Details = shiftDetailsArray(category.Details, shiftX);
}
if (!category.Teams) {
continue;
}
for (var teamId in category.Teams) {
var team = category.Teams[teamId];
shiftedData[expCatId].Teams[teamId] = {
QuantityValues: {},
Resources: {},
AllResources: {}
};
if (!team.QuantityValues) {
continue;
}
shiftedData[expCatId].Teams[teamId].QuantityValues = shiftDetailsArray(team.QuantityValues, shiftX);
if (!!team.Resources) {
for (var resourceId in team.Resources) {
var resource = team.Resources[resourceId];
shiftedData[expCatId].Teams[teamId].Resources[resourceId] = {
QuantityValues: {},
};
if (!resource.QuantityValues) {
continue;
}
shiftedData[expCatId].Teams[teamId].Resources[resourceId].QuantityValues = shiftDetailsArray(resource.QuantityValues, shiftX);
}
}
if (!!team.AllResources) {
for (var resourceId in team.AllResources) {
var resource = team.AllResources[resourceId];
shiftedData[expCatId].Teams[teamId].AllResources[resourceId] = {
QuantityValues: {},
};
if (!resource.QuantityValues) {
continue;
}
shiftedData[expCatId].Teams[teamId].AllResources[resourceId].QuantityValues = shiftDetailsArray(resource.QuantityValues, shiftX);
}
}
}
}
return shiftedData;
};
function copyQuantityValues(expCatId, targetTeam, sourceTeam) {
if (!targetTeam || !sourceTeam)
return;
compareValuesAndTriggerChanges(targetTeam.QuantityValues, sourceTeam.QuantityValues, targetTeam.Id, expCatId, null);
targetTeam.QuantityValues = sourceTeam.QuantityValues;
if (!!sourceTeam.Resources && !!targetTeam.Resources) {
for (var resourceId in sourceTeam.Resources) {
// create new resource if it has not added yet
if (!targetTeam.Resources[resourceId]) {
targetTeam.Resources[resourceId] = angular.extend({}, sourceTeam.Resources[resourceId], { QuantityValues: {} });
}
compareValuesAndTriggerChanges(targetTeam.Resources[resourceId].QuantityValues, sourceTeam.Resources[resourceId].QuantityValues, targetTeam.Id, expCatId, resourceId);
targetTeam.Resources[resourceId].QuantityValues = sourceTeam.Resources[resourceId].QuantityValues;
}
// delete resources from target team if they have deleted from source team
for (var resourceId in targetTeam.Resources) {
if (!sourceTeam.Resources[resourceId] || sourceTeam.Resources[resourceId].Deleted === true) {
delete targetTeam.Resources[resourceId];
}
}
}
if (!!sourceTeam.AllResources && !!targetTeam.AllResources) {
for (var resourceId in sourceTeam.AllResources) {
if (!targetTeam.AllResources[resourceId]) {
targetTeam.AllResources[resourceId] = angular.copy(sourceTeam.AllResources[resourceId]);
} else {
targetTeam.AllResources[resourceId].QuantityValues = sourceTeam.AllResources[resourceId].QuantityValues;
}
}
}
};
function compareValuesAndTriggerChanges(oldQuantityValues, newQuantityValues, teamId, expCatId, resourceId) {
if ((!oldQuantityValues && !newQuantityValues) || !teamId || !expCatId)
return;
if (!oldQuantityValues)
oldQuantityValues = {};
if (!newQuantityValues)
newQuantityValues = {};
for (var mIndex = 0; mIndex < $scope.Calendar.Header.Months.length; mIndex++) {
var month = $scope.Calendar.Header.Months[mIndex];
for (var wIndex = 0; wIndex < month.Childs.length; wIndex++) {
var week = $scope.Calendar.Header.Weeks[month.Childs[wIndex]];
var oldValue = oldQuantityValues[week.Milliseconds] || 0;
var newValue = newQuantityValues[week.Milliseconds] || 0;
if (newValue === oldValue)
continue;
if (!resourceId)
expenditureCategoryValueChanged(teamId, expCatId, month.Childs[wIndex], week.Milliseconds, (newValue - oldValue));
else
resourceValueChanged(teamId, expCatId, resourceId, month.Childs[wIndex], week.Milliseconds, (newValue - oldValue));
}
}
};
// SA. Set doProjectCleaning = true to remove from project expenditures the teams,
// which not exist in data.
function refreshProjectData(project, data, doProjectCleaning) {
if (!project || !project.Scenario || !data) {
return;
}
for (var expCatId in data) {
if (!project.Scenario.Expenditures[expCatId])
project.Scenario.Expenditures[expCatId] = angular.extend({}, data[expCatId], { Teams: {} });
project.Scenario.Expenditures[expCatId].Details = data[expCatId].Details || {};
if (!data[expCatId].Teams)
data[expCatId].Teams = {};
// Currently existing teams in DATA and Project
var currentExpCatDataTeams = Object.keys(data[expCatId].Teams);
var currentExpCatProjectTeams = Object.keys(project.Scenario.Expenditures[expCatId].Teams);
// Teams, which exist in DATA, but not exist in Project
var newExpCatProjectTeams = $.grep(currentExpCatDataTeams, function (teamId, index) {
return ($.inArray(teamId, currentExpCatProjectTeams) < 0)
});
$.each(newExpCatProjectTeams, function (index, teamId) {
var emptyTeam = {
AllResources: {},
Resources: {},
QuantityValues: {},
CapacityQuantityValues: {},
RestQuantityValues: {}
};
project.Scenario.Expenditures[expCatId].Teams[teamId] = angular.extend({}, data[expCatId].Teams[teamId], emptyTeam);
var sourceTeam = data[expCatId].Teams[teamId];
var targetTeam = project.Scenario.Expenditures[expCatId].Teams[teamId];
copyQuantityValues(expCatId, targetTeam, sourceTeam);
});
$.each(currentExpCatDataTeams, function (index, teamId) {
var sourceTeam = data[expCatId].Teams[teamId];
var targetTeam = project.Scenario.Expenditures[expCatId].Teams[teamId];
copyQuantityValues(expCatId, targetTeam, sourceTeam);
});
if (doProjectCleaning) {
var absentInDataProjectTeams = $.grep(currentExpCatProjectTeams, function (teamId, index) {
return ($.inArray(teamId, currentExpCatDataTeams) < 0);
});
$.each(absentInDataProjectTeams, function (index, teamId) {
// SA. Turn to zero Project team allocations to update bottom part of the Calendar
var projectTeam = project.Scenario.Expenditures[expCatId].Teams[teamId];
var teamKiller = createZeroAllocationsTeamFromTeam(projectTeam);
copyQuantityValues(expCatId, projectTeam, teamKiller);
delete project.Scenario.Expenditures[expCatId].Teams[teamId];
});
}
// SA. ENV-1254. Copy anassigned allocations for current EC from DATA to Project
project.Scenario.Expenditures[expCatId].UnassignedAllocations = angular.copy(data[expCatId].UnassignedAllocations);
}
};
// SA. ENV-1210. Return the copy for specified team with all allocations turned to zero
function createZeroAllocationsTeamFromTeam(team) {
if (!team)
return;
var teamKiller = angular.copy(team);
if (teamKiller.QuantityValues) {
var weekendings = Object.keys(teamKiller.QuantityValues);
$.each(weekendings, function (index, we) {
teamKiller.QuantityValues[we] = 0;
});
}
if (teamKiller.Resources) {
var resourceKeys = Object.keys(teamKiller.Resources);
$.each(resourceKeys, function (index, resId) {
var currentRes = teamKiller.Resources[resId];
if (currentRes.QuantityValues) {
var weekendings = Object.keys(currentRes.QuantityValues);
$.each(weekendings, function (index, we) {
currentRes.QuantityValues[we] = 0;
});
}
});
}
teamKiller.AllResources = {};
return teamKiller;
}
function triggerValuesChanged4NewScenario(scenario) {
if (!scenario || !scenario.Expenditures) {
return;
}
for (var expCatId in scenario.Expenditures) {
if (!scenario.Expenditures[expCatId].Teams || Object.keys(scenario.Expenditures[expCatId].Teams).length <= 0) {
continue;
}
for (var teamId in scenario.Expenditures[expCatId].Teams) {
var sourceTeam = scenario.Expenditures[expCatId].Teams[teamId];
if (!sourceTeam.QuantityValues)
continue;
compareValuesAndTriggerChanges({}, sourceTeam.QuantityValues, teamId, expCatId);
if (!sourceTeam.Resources)
continue;
for (var resourceId in sourceTeam.Resources) {
if (!sourceTeam.Resources[resourceId].QuantityValues)
continue;
compareValuesAndTriggerChanges({}, sourceTeam.Resources[resourceId].QuantityValues, teamId, expCatId, resourceId);
}
}
}
};
function shiftDetailsArray(detailsObject, shiftX) {
if (!detailsObject)
return;
var shiftedTeamQuantities = {};
for (var weekEnding in detailsObject) {
var shifted = getNewDate(weekEnding, shiftX);
if (!shifted) {
throw 'It is unavailable to shift date from [' + weekEnding + '] on [' + shiftX + '] positions';
}
shiftedTeamQuantities[shifted] = detailsObject[weekEnding];
}
return shiftedTeamQuantities;
}
$scope.moveProjectInsideTeam = function (projectId, team, shiftX, sourceRow, targetRow, insertBefore) {
if (!projectId)
return;
shiftX = shiftX || 0;
var project = $scope.getProjectById(projectId);
if (!project || !project.Scenario)
return;
changeProjectRange(project, shiftX, function () {
var rowToInsertIn = targetRow;
// Check for projects intersection within row
if (!insertBefore && checkProjectsIntersection(project, team.Id, targetRow)) {
rowToInsertIn++;
insertBefore = true;
}
if (sourceRow != rowToInsertIn) {
$scope.updateProjectInLayout(projectId, team.Id, sourceRow, rowToInsertIn, insertBefore);
}
project = $scope.getProjectById(projectId);
// Move copies of the project in other teams
synchronizeProjectInLayoutTeamBlocks(project, team.Id);
$scope.recreateView();
});
};
function addTeamToProject(projectId, teamId, shiftX, targetRow, insertBefore, callbackFn) {
if (!projectId)
return;
shiftX = shiftX || 0;
var project = $scope.getProjectById(projectId);
if (!project || !project.Scenario)
return;
if ($scope.projectHasTeam(project, teamId))
// Project already has the team
return;
// shift project if necessary
if (shiftX != 0) {
changeProjectRange(project, shiftX, function () {
addTeamToProjectInternal(project, teamId, targetRow, insertBefore, shiftX);
if (!!callbackFn && typeof callbackFn === 'function') {
callbackFn();
}
});
}
else {
if (!isScenarioLoaded(project.Scenario)) {
loadScenarioDetails(project.Scenario.Id, project.Id, function (expenditures) {
setScenarioExpenditures(project.Scenario, expenditures);
addTeamToProjectInternal(project, teamId, targetRow, insertBefore, shiftX);
if (!!callbackFn && typeof callbackFn === 'function') {
callbackFn();
}
});
}
else {
addTeamToProjectInternal(project, teamId, targetRow, insertBefore, shiftX);
if (!!callbackFn && typeof callbackFn === 'function') {
callbackFn();
}
}
}
};
function addTeamToProjectInternal(project, teamId, targetRow, insertBefore, shiftX) {
if (!project || !project.Teams || !teamId) {
return;
}
// Add team to project
project.Teams.push(teamId);
fillExpenditures4TeamById(project, teamId);
var rowToInsertIn = targetRow;
// Check for projects intersection within row
if (!insertBefore && checkProjectsIntersection(project, teamId, targetRow)) {
rowToInsertIn++;
insertBefore = true;
}
addProjectToLayout(project.Id, teamId, rowToInsertIn, insertBefore);
if (shiftX != 0) {
synchronizeProjectInLayoutTeamBlocks(project, teamId);
}
}
function synchronizeProjectInLayoutTeamBlocks(project, excludeTeam) {
if (!project || !project.Teams || !project.Teams.length <= 0)
return;
for (var teamIndex = 0; teamIndex < project.Teams.length; teamIndex++) {
if (project.Teams[teamIndex] == excludeTeam)
continue;
if (!teamInfoService.isExists(project.Teams[teamIndex]))
continue;
var position = findProjectPositionInLayout(project.Id, $scope.GridLayout[project.Teams[teamIndex]]);
if (!position)
continue;
var insertBefore = false;
var newRowIndex = position.RowIndex;
// Check for projects intersection within row
if (checkProjectsIntersection(project, project.Teams[teamIndex], position.RowIndex)) {
newRowIndex++;
insertBefore = true;
}
if (position.RowIndex != newRowIndex) {
$scope.updateProjectInLayout(project.Id, project.Teams[teamIndex], position.RowIndex, newRowIndex, insertBefore);
}
}
}
// SA. ENV-1254. Refactored for reusage
function fillExpenditures4TeamById(project, teamId) {
if (!project || !project.Scenario || !teamId) {
return;
}
var team = teamInfoService.getById(teamId);
if (!team || !team.ExpCategories) {
return;
}
fillExpenditures4Team(project, team);
}
// SA. ENV-1254. Refactored for reusage
function fillExpenditures4Team(project, team) {
if (!project || !project.Scenario) {
return;
}
if (!team || !team.ExpCategories) {
return;
}
if (!project.Scenario.Expenditures)
project.Scenario.Expenditures = {};
var startDateIndex = binarySearch($scope.Calendar.FiscalCalendarWeekEndings, project.Scenario.StartDate, 0, $scope.Calendar.FiscalCalendarWeekEndings.length, true);
var endDateIndex = binarySearch($scope.Calendar.FiscalCalendarWeekEndings, project.Scenario.EndDate, 0, $scope.Calendar.FiscalCalendarWeekEndings.length, true);
if (startDateIndex < 0 || endDateIndex < 0 ||
startDateIndex >= $scope.Calendar.FiscalCalendarWeekEndings.length ||
endDateIndex >= $scope.Calendar.FiscalCalendarWeekEndings.length) {
throw 'Invalid start [' + startDateIndex + '] or end [' + endDateIndex + '] indexes';
}
for (var expCatId in project.Scenario.Expenditures) {
if (!team.ExpCategories[expCatId]) {
continue;
}
// if scenario already has information for this category and this team we need to skip it
var ecInScenario = project.Scenario.Expenditures[expCatId];
if (!!ecInScenario.Teams[team.Id])
continue;
ecInScenario.Teams[team.Id] = {
Id: team.Id,
QuantityValues: {},
AllResources: {},
CanBeDeleted: false, // SA. 1210
Changed: false, // SA. 1210
Collapsed: true, // SA. 1210
CollapsedClass: "fa-plus-square", // SA. 1210
IsAccessable: true, // SA. 1210
Name: team.Name, // SA. 1210
CapacityQuantityValues: {}, // SA. 1210
Resources: {}, // SA. 1210
RestQuantityValues: {} // SA. 1210
};
if (!team.ExpCategories[expCatId].Resources) {
continue;
}
for (var resourceId in team.ExpCategories[expCatId].Resources) {
var resource = team.ExpCategories[expCatId].Resources[resourceId];
ecInScenario.Teams[team.Id].AllResources[resourceId] = {
Id: resourceId,
Name: resource.Name,
QuantityValues: {}
};
}
for (var i = startDateIndex; i <= endDateIndex; i++) {
var date = $scope.Calendar.FiscalCalendarWeekEndings[i];
ecInScenario.Teams[team.Id].QuantityValues[date] = 0;
// ecInScenario.Teams[teamId].RestQuantityValues[date] = 0; // SA. ENV-1210
for (var resourceId in team.ExpCategories[expCatId].Resources) {
ecInScenario.Teams[team.Id].AllResources[resourceId].QuantityValues[date] = 0;
}
}
}
}
function removeTeamFromScenario(project, teamId) {
if (!project || !project.Scenario || !teamId) {
return;
}
var team = teamInfoService.getById(teamId);
if (!team || !team.ExpCategories) {
return;
}
if (!project.Scenario.Expenditures) {
return;
}
// SA. ENV-1210. Remove team allocations from deleted categories
var performProjectRefresh = false;
var updatedData = angular.copy(project.Scenario.Expenditures);
var expCatKeys = Object.keys(project.Scenario.Expenditures);
$.each(expCatKeys, function (index, expCatId) {
var currentExpCat = project.Scenario.Expenditures[expCatId];
if (currentExpCat.Teams && currentExpCat.Teams[teamId]) {
delete updatedData[expCatId].Teams[teamId];
performProjectRefresh = true;
}
});
if (performProjectRefresh) {
refreshProjectData(project, updatedData, true);
}
}
function removeTeamFromProjectInternal(projectId, teamId) {
var project = $scope.Calendar.Projects[projectId];
if (!project || !project.Teams)
return;
for (var teamIndex = 0; teamIndex < project.Teams.length; teamIndex++) {
if (project.Teams[teamIndex] == teamId) {
project.Teams.splice(teamIndex, 1);
break;
}
}
removeTeamFromScenario(project, teamId);
if (projectHasNoTeams(projectId)) {
// SA. ENV-1114. Completelly remove the project from layout (from all teams in the calendar)
removeProjectFromLayout(project.Id);
removeProjectFromManaged(project.Id);
// SA. ENV-1218. Remove items from Projects with Unassigned Expenditures block
removeProjectFromUnassignedExpenditures(project.Id);
$scope.pushProjectToUnscheduled(project);
}
else {
// Project has other teams after the given team was removed
removeProjectFromTeamLayout(projectId, teamId);
}
// SA. ENV-1153
setDataChanged(true);
};
// SA. ENV-1114. Checks the project has any team, displayed in the calendar.
// It may be possible, the project has any team, but they are not in the calendar.
// Treat this as project has no teams
function projectHasNoTeams(projectId) {
if (!projectId || (projectId.length < 1))
return true;
var project = $scope.getProjectById(projectId);
if (!project || !project.Teams || (project.Teams.length < 1)) {
// Project has no teams
return true;
}
var projectHasCalendarTeam = false;
for (var index = 0; index < $scope.Calendar.Teams.length; index++) {
var team = $scope.Calendar.Teams[index];
if ($scope.projectHasTeam(project, team.Id)) {
projectHasCalendarTeam = true;
break;
}
}
return !projectHasCalendarTeam;
};
function addProjectFromUnscheduled(projectId, targetTeamId, targetRow) {
if (!projectId || !targetTeamId || targetRow < 0)
return;
var project = $scope.Calendar.Projects[projectId];
if (!project)
return;
if (!project.Teams)
throw 'Teams is undefined for project ' + projectId;
// we need to link current team with the project if it didn't yet
if (!$scope.projectHasTeam(project, targetTeamId))
project.Teams.push(targetTeamId);
pushProjectToManaged(project);
removeProjectFromUnscheduled(project.Id);
addProjectToLayout(project.Id, targetTeamId, targetRow, true);
project.Teams = union(project.Teams, getTeamsInScenario(project.Scenario));
for (var i = 0; i < project.Teams.length; i++) {
var teamId = project.Teams[i];
if (teamId === targetTeamId)
continue;
var layout = $scope.GridLayout[project.Teams[i]];
if (!layout)
continue;
addProjectToLayout(project.Id, teamId, layout.length, true);
}
};
function getTeamsInScenario(scenario) {
if (!scenario || !scenario.Expenditures)
return [];
var teams = [];
for (var expCatId in scenario.Expenditures) {
var category = scenario.Expenditures[expCatId];
if (!category.Teams)
continue;
for (var teamId in category.Teams) {
if (teams.indexOf(teamId) < 0)
teams.push(teamId);
}
}
return teams;
};
function checkProjectsIntersection(project, teamId, rowIndex) {
if (!project || !teamId || (rowIndex < 0) || !$scope.GridLayout || !$scope.GridLayout[teamId] ||
(rowIndex >= $scope.GridLayout[teamId].length)) {
return false;
}
var currentLayout = $scope.GridLayout[teamId];
var rowMap = currentLayout[rowIndex];
var projectNearestStartDate = getNearestDate(project.Scenario.StartDate),
projectNearestEndDate = getNearestDate(project.Scenario.EndDate);
for (var itemIndex = 0; itemIndex < rowMap.length; itemIndex++) {
if (rowMap[itemIndex].ProjectId != project.Id) {
var checkableProject = $scope.getProjectById(rowMap[itemIndex].ProjectId);
// return true if dates intersect
if (projectNearestStartDate <= getNearestDate(checkableProject.Scenario.EndDate) &&
projectNearestEndDate >= getNearestDate(checkableProject.Scenario.StartDate)) {
return true;
}
}
}
return false;
};
$scope.addProjectsFromQueue = function () {
if ($scope.data.Projects2Add == null || $scope.data.Projects2Add.length < 0) {
$scope.data.Projects2Add = [];
return;
}
$.each($scope.data.Projects2Add, function (index, item) {
var project = $scope.Calendar.Projects[item.Id];
if (!project)
return;
$scope.moveProjectFromQueue2Unscheduled(project);
});
$scope.data.Projects2Add = [];
$('#selProjects2Add').select2('val', '');
setDataChanged(true);
};
$scope.moveProjectFromQueue2Unscheduled = function (project) {
//validate params
if (project == null || project.Id == null)
return;
// do not add existing project
for (var i = 0; i < $scope.Calendar.UnscheduledProjects.length; i++) {
if ($scope.Calendar.UnscheduledProjects[i].Id == project.Id) {
return;
}
}
$scope.pushProjectToUnscheduled(project);
// Remove it from queue
var index2Remove = -1;
for (var i = 0; i < $scope.Calendar.Queue4UnscheduledProjects.length; i++) {
if ($scope.Calendar.Queue4UnscheduledProjects[i].Id == project.Id) {
index2Remove = i;
break;
}
}
if (index2Remove >= 0)
$scope.Calendar.Queue4UnscheduledProjects.splice(index2Remove, 1);
};
function removeProjectFromUnscheduled(projectId) {
if (!projectId || (projectId.length < 1))
return;
var index2Remove = -1;
for (var i = 0; i < $scope.Calendar.UnscheduledProjects.length; i++) {
if ($scope.Calendar.UnscheduledProjects[i].Id == projectId) {
index2Remove = i;
break;
}
}
if (index2Remove >= 0)
$scope.Calendar.UnscheduledProjects.splice(index2Remove, 1);
};
function removeProjectFromManaged(projectId) {
if (!projectId || (projectId.length < 1))
return;
var index2Remove = -1;
for (var i = 0; i < $scope.Calendar.ManagedProjects.length; i++) {
if ($scope.Calendar.ManagedProjects[i].Id == projectId) {
index2Remove = i;
break;
}
}
if (index2Remove >= 0)
$scope.Calendar.ManagedProjects.splice(index2Remove, 1);
};
$scope.projectHasTeam = function (project, teamId) {
return project.Teams.indexOf(teamId) >= 0;
};
$scope.getProjectById = function (projectId) {
return $scope.Calendar.Projects[projectId];
};
function createCell(id, name, cssClass, cssStyle, isFirstCell, overStart, overEnd, isProjectLastCell, pinned) {
var cell = {
Id: id,
Title: name,
CssClass: cssClass,
CssStyle: cssStyle,
IsFirstCell: isFirstCell,
OverStart: overStart,
OverEnd: overEnd,
IsProjectLastCell: isProjectLastCell,
Pinned: pinned
};
if (isFirstCell) {
cell.Name = name;
cell.CssClass += " first-cell";
}
return cell;
}
function createBlankCell() {
return {
Id: "",
CssStyle: ''
};
};
$scope.createDummyAllocationCell = function () {
var allocationCell = {
Id: "",
CssStyle: '',
IsDummyCell: true
};
return allocationCell;
};
$scope.pushProjectToUnscheduled = function (project) {
var css = getProjectCSS(project);
var projData = {
Id: project.Id,
Name: project.Name,
CssStyle: css
};
$scope.Calendar.UnscheduledProjects.push(projData);
};
// SA. ENV-1210.
function isProjectInUnassignedExpenditures(projectId, expCatId) {
return $scope.Calendar.UnassignedExpendituresProjects.some(function (data) {
return (data.ProjectId === projectId) && (data.ExpCatId === expCatId);
});
};
// SA. ENV-1210
$scope.pushProjectToUnassignedExpenditures = function (project, expCatIdArray, targetTeamId) {
if (!project || !project.Scenario || !project.Scenario.Expenditures || !expCatIdArray)
return;
$.each(expCatIdArray, function (index, expCatId) {
if (!isProjectInUnassignedExpenditures(project.Id, expCatId)) {
// Add project to Unassigned Expenditures Projects block
var expCatName = "";
if (project.Scenario.Expenditures[expCatId]) {
// Get expenditure categgory name from inside of the project
expCatName = project.Scenario.Expenditures[expCatId].ExpenditureCategoryName;
var css = getProjectCSS(project);
var projData = {
ProjectId: project.Id,
ExpCatId: expCatId,
TargetTeamId: targetTeamId,
Name: expCatName + "; " + project.Name,
CssStyle: css
};
$scope.Calendar.UnassignedExpendituresProjects.push(projData);
}
}
else {
// Update current (target) team in existing record of Unassigned Exp Projects
updateProjectFromUnassignedExpenditures(project.Id, expCatId, targetTeamId);
}
});
$scope.UnassignedExpendituresProjectsExist = $scope.Calendar.UnassignedExpendituresProjects.length > 0;
};
// SA. ENV-1254
function updateProjectFromUnassignedExpenditures(projectId, expCatId, newTargetTeamId) {
if (!projectId || !expCatId || !$scope.UnassignedExpendituresProjectsExist)
return;
var itemIndex = -1;
$.each($scope.Calendar.UnassignedExpendituresProjects, function (index, item) {
if ((item.ProjectId == projectId) && (item.ExpCatId == expCatId)) {
itemIndex = index;
}
});
if (itemIndex >= 0) {
$scope.Calendar.UnassignedExpendituresProjects[itemIndex].TargetTeamId = newTargetTeamId;
}
};
// SA. ENV-1254. If expCatId is undefined - removes all items for specified project
function removeProjectFromUnassignedExpenditures(projectId, expCatId) {
if (!projectId || !$scope.UnassignedExpendituresProjectsExist)
return;
for (var index = $scope.Calendar.UnassignedExpendituresProjects.length - 1; index >= 0; index--) {
var item = $scope.Calendar.UnassignedExpendituresProjects[index];
if ((item.ProjectId == projectId) && (!expCatId || (item.ExpCatId == expCatId)))
$scope.Calendar.UnassignedExpendituresProjects.splice(index, 1);
}
$scope.UnassignedExpendituresProjectsExist = $scope.Calendar.UnassignedExpendituresProjects.length > 0;
};
$scope.pushProjectToQueue = function (project, insertAtTheTop) {
var projData = {
Id: project.Id,
Name: project.Name,
Color: project.Color
}
if (insertAtTheTop) {
$scope.Calendar.Queue4UnscheduledProjects.unshift(projData);
}
else {
$scope.Calendar.Queue4UnscheduledProjects.push(projData);
}
};
function isProjectInManaged(projectId) {
return $scope.Calendar.ManagedProjects.some(function (project) {
return project.Id === projectId;
});
};
function pushProjectToManaged(project) {
var projData = {
Id: project.Id,
Name: project.Name,
Teams: project.Teams
};
$scope.Calendar.ManagedProjects.push(projData);
};
$scope.prepareUnscheduledToDisplay = function () {
if ($scope.Calendar && $scope.Calendar.UnscheduledProjects && $scope.Calendar.UnscheduledProjects.length) {
for (var index = 0; index < $scope.Calendar.UnscheduledProjects.length; index++) {
var project = $scope.getProjectById($scope.Calendar.UnscheduledProjects[index]);
var css = getProjectCSS(project);
var projData = {
Id: project.Id,
Name: project.Name,
CssStyle: css
};
$scope.Calendar.UnscheduledProjects[index] = projData;
}
}
// SA. ENV-1210
$scope.UnscheduledProjectsExist =
$scope.Calendar && $scope.Calendar.UnscheduledProjects &&
($scope.Calendar.UnscheduledProjects.length > 0);
};
$scope.prepareQueuedToDisplay = function () {
if (!$scope.Calendar || !$scope.Calendar.Queue4UnscheduledProjects)
return;
for (var index = 0; index < $scope.Calendar.Queue4UnscheduledProjects.length; index++) {
var project = $scope.getProjectById($scope.Calendar.Queue4UnscheduledProjects[index]);
var projData = {
Id: project.Id,
Name: project.Name,
Color: project.Color
};
$scope.Calendar.Queue4UnscheduledProjects[index] = projData;
}
};
// SA. ENV-1210
$scope.prepareUnassignedEcsProjectsToDisplay = function () {
if ($scope.Calendar && $scope.Calendar.UnassignedExpendituresProjects &&
$scope.Calendar.UnassignedExpendituresProjects.length) {
for (var index = 0; index < $scope.Calendar.UnassignedExpendituresProjects.length; index++) {
var currentItem = $scope.Calendar.UnassignedExpendituresProjects[index];
var project = $scope.getProjectById(currentItem.ProjectId);
var css = getProjectCSS(project);
var projData = {
ProjectId: currentItem.ProjectId,
ExpCatId: currentItem.ExpCatId,
TargetTeamId: currentItem.TargetTeamId,
Name: currentItem.Name,
CssStyle: css
};
$scope.Calendar.UnassignedExpendituresProjects[index] = projData;
}
}
$scope.UnassignedExpendituresProjectsExist =
$scope.Calendar && $scope.Calendar.UnassignedExpendituresProjects &&
($scope.Calendar.UnassignedExpendituresProjects.length > 0);
};
$scope.prepareManagedToDisplay = function () {
if (!$scope.Calendar || !$scope.Calendar.ManagedProjects)
return;
for (var index = 0; index < $scope.Calendar.ManagedProjects.length; index++) {
var project = $scope.getProjectById($scope.Calendar.ManagedProjects[index]);
var projData = {
Id: project.Id,
Name: project.Name,
Teams: project.Teams
};
$scope.Calendar.ManagedProjects[index] = projData;
}
};
// helper functions
function getProjectCSS(project) {
var colorRgb = project.ColorRGB || '';
var css = '';
if (colorRgb.length > 0) {
css += 'background-color: rgba(' + colorRgb + ', 0.8);';
//css += 'border-right-color: rgba(' + colorRgb + ', 0) !important;';
css += 'border: 1px solid #a0a0a0 !important;';
}
//if (!project.Pinned)
// css += 'cursor: pointer;';
return css;
};
//function drawBorder(team, rowIndex, colIndex) {
// if (!team || rowIndex < 0 || colIndex < 0)
// return;
// var projectRow = team.Allocations[rowIndex];
// if (!projectRow || !projectRow.Cells)
// return;
// var projectCell = projectRow.Cells[colIndex];
// if (!projectCell || !projectCell.Id)
// return;
// var projectNextCell = projectRow.Cells[colIndex + 1];
// var projectBelowCell = null;
// if (team.Allocations.length > (rowIndex + 1))
// projectBelowCell = team.Allocations[rowIndex + 1].Cells[colIndex];
// var borderStyle = '1px solid black !important;';
// // top border always draws
// var topBorder = 'border-top: ' + borderStyle,
// bottomBorder = '', rightBorder = '', leftBorder = '';
// // left border draws only if it is first project cell
// if (projectCell.IsFirstCell === true) {
// leftBorder = 'border-left: ' + borderStyle;
// }
// // right border draws only if it is last cell of project and next cell doesn't exist or is empty
// if (projectCell.IsFirstCell === true && (!projectNextCell || !projectNextCell.Id)) { //IsProjectLastCell
// rightBorder = 'border-right: ' + borderStyle;
// }
// // bottom border draws if below cell doesn't exist or is empty
// if ((!projectBelowCell || !projectBelowCell.Id)) {
// bottomBorder = 'border-bottom: ' + borderStyle;
// }
// projectCell.CssStyle += (topBorder + bottomBorder + leftBorder + rightBorder);
//};
function getProjectCssClass(project) {
var cssClass = 'headcol';
if ((team.Drop.Row == aIndex) && (!team.Drop.Mod) && team.Drop.Cells[Calendar.Header.Weeks[$index].Milliseconds]) {
cssClass += ' droppable-active';
}
}
$scope.onMouseMove = function (e, team) {
var elementAtMouse = $scope.getElementFromPoint(e.clientX, e.clientY);
var cellElement = elementAtMouse;
if (elementAtMouse.nodeName.toUpperCase() != "TD") {
var parentTds = $(elementAtMouse).closest("td");
if (parentTds.length > 0) {
cellElement = parentTds[0];
}
}
if (cellElement) {
var viewportRect = document.body.getBoundingClientRect();
var elementRect = cellElement.getBoundingClientRect();
var elementHeight = elementRect.bottom - elementRect.top;
var elementAbsTop = elementRect.top - viewportRect.top;
var elementAbsBottom = elementAbsTop + elementHeight;
var elementMouseRelativeTop = e.pageY - elementAbsTop;
if ((elementMouseRelativeTop / elementHeight) < 0.4) {
team.Drop.Mod = "before";
} else {
if ((elementMouseRelativeTop / elementHeight) > 0.8) {
team.Drop.Mod = "after";
} else {
team.Drop.Mod = undefined;
}
}
}
};
$scope.getElementFromPoint = function (x, y) {
var check = false, isRelative = true;
if (!document.elementFromPoint) return null;
if (!check) {
var sl;
if ((sl = $(document).scrollTop()) > 0) {
isRelative = (document.elementFromPoint(0, sl + $(window).height() - 1) == null);
} else if ((sl = $(document).scrollLeft()) > 0) {
isRelative = (document.elementFromPoint(sl + $(window).width() - 1, 0) == null);
}
check = (sl > 0);
}
if (!isRelative) {
x += $(document).scrollLeft();
y += $(document).scrollTop();
}
return document.elementFromPoint(x, y);
};
/* Mix loading and saving */
$scope.createSaveDataPackage = function (dataPackage) {
dataPackage.Calendar = {};
dataPackage.Calendar.Projects = {};
dataPackage.Calendar.ManagedProjects = [];
dataPackage.Calendar.UnscheduledProjects = [];
dataPackage.Calendar.QueuedProjects = [];
dataPackage.Calendar.UnassignedExpendituresProjects = []; // SA. ENV-1210
dataPackage.Calendar.Layout = [];
dataPackage.Calendar.Teams = [];
// Saving managed projects
for (var pIndex = 0; pIndex < $scope.Calendar.ManagedProjects.length; pIndex++) {
var currentProject = $scope.Calendar.ManagedProjects[pIndex];
dataPackage.Calendar.ManagedProjects.push(currentProject.Id);
}
// Saving unscheduled projects
for (var pIndex = 0; pIndex < $scope.Calendar.UnscheduledProjects.length; pIndex++) {
var currentProject = $scope.Calendar.UnscheduledProjects[pIndex];
dataPackage.Calendar.UnscheduledProjects.push(currentProject.Id);
}
// Saving queued projects
for (var pIndex = 0; pIndex < $scope.Calendar.Queue4UnscheduledProjects.length; pIndex++) {
var currentProject = $scope.Calendar.Queue4UnscheduledProjects[pIndex];
dataPackage.Calendar.QueuedProjects.push(currentProject.Id);
}
// SA. ENV-1210. Saving unassigned expenditures projects
for (var pIndex = 0; pIndex < $scope.Calendar.UnassignedExpendituresProjects.length; pIndex++) {
var dataItem = $scope.Calendar.UnassignedExpendituresProjects[pIndex];
var item2push = {
ProjectId: dataItem.ProjectId,
ExpCatId: dataItem.ExpCatId,
SourceTeamId: dataItem.SourceTeamId,
TargetTeamId: dataItem.TargetTeamId,
Name: dataItem.Name
};
dataPackage.Calendar.UnassignedExpendituresProjects.push(item2push);
}
// Saving scenario dates and attached teams for Managed projects
for (var projectId in $scope.Calendar.Projects) {
dataPackage.Calendar.Projects[projectId] = $scope.getProjectById(projectId);
}
// Saving grid layout
dataPackage.Calendar.Layout = $scope.getLayoutForServer($scope.GridLayout);
// Saving teams
var teams = teamInfoService.getAll();
if (!!teams) {
angular.forEach(teams, function (currentTeam, teamId) {
var pc = [];
for (var expCatId in currentTeam.ExpCategories) {
var expCatItem = currentTeam.ExpCategories[expCatId];
var nr = {
ExpCatId: expCatItem.Id,
Values: expCatItem.PlannedCapacityValues
}
pc.push(nr);
}
var team = {
Id: currentTeam.Id,
Name: currentTeam.Name,
IsNew: currentTeam.IsNew,
CompanyId: currentTeam.CompanyId,
UserId: currentTeam.UserId,
CostCenterId: currentTeam.CostCenterId,
ExpCategories: currentTeam.ExpCategories,
PlannedCapacity: pc
};
dataPackage.Calendar.Teams.push(team);
});
}
};
$scope.fixFilterForSave = function (filter) {
if (filter && filter.Selection && filter.Selection.TeamsViews) {
for (var index = 0; index < filter.Selection.TeamsViews.length; index++) {
var currentItem = filter.Selection.TeamsViews[index];
if (!currentItem.Data || (currentItem.Data == null)) {
currentItem.Data = [];
}
}
}
};
$scope.getLayoutForClient = function (serverLayout) {
var clientLayout = {};
if (!serverLayout || (serverLayout.length < 1)) {
return clientLayout;
}
for (var tIndex = 0; tIndex < $scope.Calendar.Teams.length; tIndex++) {
var teamId = $scope.Calendar.Teams[tIndex].Id;
clientLayout[teamId] = [];
var teamLayoutRecords = $scope.getTeamLayoutRecords(serverLayout, teamId);
var rowIndex = -1;
for (var rIndex = 0; rIndex < teamLayoutRecords.length; rIndex++) {
var rec = teamLayoutRecords[rIndex];
var layoutItem = {
ProjectId: rec.ProjectId
}
if (rec.Row != rowIndex) {
clientLayout[teamId].push([]);
rowIndex = rec.Row;
}
clientLayout[teamId][rowIndex].push(layoutItem);
}
}
return clientLayout;
};
$scope.getTeamLayoutRecords = function (serverLayout, teamId) {
var teamRecords = [];
var sortedRecords = [];
for (var index = 0; index < serverLayout.length; index++) {
if (serverLayout[index].TeamId == teamId) {
teamRecords.push(serverLayout[index]);
}
}
if (teamRecords.length < 1) {
return teamRecords;
}
sortedRecords = teamRecords.sort(function (a, b) {
if ((a.Row < b.Row) || ((a.Row == b.Row) && (a.Index < b.Index))) {
return -1;
}
if ((a.Row > b.Row) || ((a.Row == b.Row) && (a.Index > b.Index))) {
return 1;
}
return 0;
});
return sortedRecords;
};
$scope.getLayoutForServer = function (layout) {
var result = [];
for (var teamId in layout) {
var currentTeam = layout[teamId];
for (var rowIndex = 0; rowIndex < currentTeam.length; rowIndex++) {
for (var projIndex = 0; projIndex < currentTeam[rowIndex].length; projIndex++) {
var layoutItem = angular.copy(currentTeam[rowIndex][projIndex]);
layoutItem.Row = rowIndex;
layoutItem.Index = projIndex;
layoutItem.TeamId = teamId;
result.push(layoutItem);
}
}
}
return result;
};
$scope.setStartDnD = function ($event) {
$scope.clickShift = $event.offsetX;
}
$scope.removeProject = function ($event, teamId, projectId) {
bootbox.confirm("Are you sure you want to remove this project from the team?", function (result) {
$scope.$apply(function () {
if (result) {
removeTeamFromProjectInternal(projectId, teamId);
$scope.recreateView();
}
});
});
};
function loadScenarioDetails(scenarioId, projectId, successCallbackFn) {
blockUI();
var requestData = {
MixId: $scope.MixId,
ScenarioId: scenarioId
};
var request = getAntiXSRFRequest($scope.dataUrls.getScenarioDetailsUrl, requestData);
try {
$http(request).success(function (expenditures, status, headers, config) {
try {
if (!expenditures) {
unblockUI();
return;
}
if (angular.isFunction(successCallbackFn)) {
successCallbackFn(expenditures);
}
unblockUI();
} catch (e) {
unblockUI();
showErrorModal('Oops!', commonErrorMessage);
}
}).error(function (data, status, headers, config) {
unblockUI();
showErrorModal('Oops!', commonErrorMessage);
});
} catch (e) {
unblockUI();
showErrorModal('Oops!', commonErrorMessage);
}
};
function setScenarioExpenditures(scenario, expenditures) {
scenario.Expenditures = expenditures || {};
};
function createLayoutRow(id) {
return {
ProjectId: id
};
};
/* Events Triggers Start */
// Fires event after EC cell value has been changed
function expenditureCategoryValueChanged(teamId, expCatId, weekIndex, weekEndingMs, deltaValue) {
teamInfoService.changeExpenditureCategoryValue(teamId, expCatId, weekEndingMs, deltaValue);
$scope.$broadcast('expenditureCategoryValueChanged', {
TeamId: teamId,
ExpenditureCategoryId: expCatId,
WeekIndex: weekIndex
});
setDataChanged(true);
}
// Fires event after resource cell value has been changed
function resourceValueChanged(teamId, expCatId, resourceId, weekIndex, weekEndingMs, deltaValue) {
teamInfoService.changeResourceValue(teamId, expCatId, resourceId, weekEndingMs, deltaValue);
$scope.$broadcast('resourceValueChanged', {
TeamId: teamId,
ExpenditureCategoryId: expCatId,
ResourceId: resourceId,
WeekIndex: weekIndex
});
setDataChanged(true);
}
/* Events Triggers End */
$scope.createScenario = function (model, createScenarioModel) {
if (!model || !model.Scenario || !model.Calendar || !createScenarioModel || !createScenarioModel.ProjectId || !createScenarioModel.TargetTeam)
return;
// SA. ENV-1159
var promptToChangeView = (model.Scenario.EndDate < $scope.Calendar.StartDate) ||
(model.Scenario.StartDate > $scope.Calendar.EndDate);
var project = $scope.getProjectById(createScenarioModel.ProjectId);
project.Scenario = {
CGSplit: model.Scenario.CGSplit,
CostSavings: model.Scenario.CostSavings,
Duration: model.Scenario.Duration,
EFXSplit: model.Scenario.EFXSplit,
StartDate: model.Scenario.StartDate,
EndDate: model.Scenario.EndDate,
Expenditures: angular.copy(model.Calendar.Expenditures),
GrossMargin: model.Scenario.GrossMargin,
GrowthScenario: model.Scenario.GrowthScenario,
Id: Math.uuid(), // we need to create new GUID for new scenario because mongo will have many allocations and scenarios with ScenarioId == Guid.Empty
LMMargin: model.Scenario.LMMargin,
ParentId: model.Scenario.ParentId,
ProjectedRevenue: model.Scenario.ProjectedRevenue,
TDDirectCosts: model.Scenario.TDDirectCosts,
TemplateId: model.Scenario.TemplateId,
Type: model.Scenario.Type,
UseLMMargin: model.Scenario.UseLMMargin,
IsNew: true
};
// SA. ENV-1153.
setDataChanged(true);
if (!promptToChangeView) {
$scope.$apply(function () {
// Project fit mix dates and should be displayed in the Calendar
blockUI();
try {
// some interface logic for project displaying
if (!isProjectInManaged(createScenarioModel.ProjectId)) {
addProjectFromUnscheduled(createScenarioModel.ProjectId, createScenarioModel.TargetTeam.Id, createScenarioModel.TargetRow || 1);
}
$scope.recreateView();
triggerValuesChanged4NewScenario(project.Scenario);
// we need to add available teams which exists in the project but not exists in the scenario with zero-allocations
for (var i = 0; i < project.Teams.length; i++) {
fillExpenditures4TeamById(project, project.Teams[i]);
}
}
catch (e) {
unblockUI();
ShowErrorMessage(commonErrorMessage);
}
finally {
unblockUI();
}
});
}
else {
// Move the project to Queued section, because now it has a scenario
removeProjectFromUnscheduled(project.Id);
$scope.pushProjectToQueue(project, true);
// Project dates are out of the mix dates
var extendedViewDates = getExtendedViewDates(model.Scenario.StartDate, model.Scenario.EndDate,
$scope.Calendar.StartDate, $scope.Calendar.EndDate);
var promptText = getChangeViewPrompt(extendedViewDates.StartDate, extendedViewDates.EndDate);
$timeout(function () {
bootbox.dialog({
message: promptText,
// title: "Custom title",
buttons: {
success: {
label: "OK",
className: "btn-success",
callback: function () {
// Go expanding current view
$rootScope.$broadcast("changeDisplayView", extendedViewDates.StartDate, extendedViewDates.EndDate);
}
},
cancel: {
label: "Keep Current Date Range",
className: "btn-primary",
callback: function () {
// current view not changed
}
}
}
});
});
}
};
$scope.editPinProject = function (cell, id, teamid, $event, state) {
cell.Pinned = state;
var project = $scope.getProjectById(id);
project.Pinned = state;
$("#menu_dd_" + id + "_" + teamid).removeClass("open"); //removeClass("in").addClass("collapse"); ////addClass("collapse"); //css({'display': 'none'});
setDataChanged(true);
getProjectCSS(project);
}
$scope.editScenarioDetails = function (projectId, $event, pinned) {
if (!!$event && angular.isFunction($event.preventDefault))
$event.preventDefault();
if (!projectId)
return;
var project = $scope.getProjectById(projectId);
if (!project || !project.Scenario)
return;
if (isScenarioLoaded(project.Scenario)) {
loadScenarioDetailsEditForm(project.Scenario, pinned);
}
else {
loadScenarioDetails(project.Scenario.Id, projectId, function (expenditures) {
setScenarioExpenditures(project.Scenario, expenditures);
loadScenarioDetailsEditForm(project.Scenario, pinned);
});
}
};
function loadScenarioDetailsEditForm(scenario, pinned) {
if (!scenario)
return;
blockUI();
// we should send scenario w/o expenditures and cost savings for reducing a traffic (these properties do not use at this action method)
var requestData = angular.extend({}, scenario, { Pinned: pinned, Expenditures: null, CostSavings: null });
var request = getAntiXSRFRequest($scope.dataUrls.editScenarioDetailsUrl, requestData);
try {
$http(request).success(function (content, status, headers, config) {
try {
if (!content) {
unblockUI();
return;
}
// we should copy in the scenario information that does not store in the mix: capacity and rest capacity
// we need this information for edit scenario details form
copyToScenarioTeamInformation(scenario);
var $html = angular.element('<div class="row" data-section="scenarioCalendar">' + content + '</div>');
// we should destroy dynamic scopes before DOM elements will be destroyed
$html.on('$destroy', function (event) {
var controllers = angular.element(event.currentTarget).find('[ng-controller]');
for (var i = 0; i < controllers.length; i++) {
if (angular.isElement(controllers[i])) {
var scope = angular.element(controllers[i]).scope();
if (!!scope)
scope.$destroy();
}
}
});
var $element = $compile($html)($scope);
var data = {
Expenditures: scenario.Expenditures,
Rates: scenario.Rates
};
$scope.$broadcast('changeScenarioDetails', data);
$document.trigger('rmo.open-scenario-details-window', { html: $element });
unblockUI();
} catch (e) {
unblockUI();
showErrorModal('Oops!', commonErrorMessage);
}
}).error(function (data, status, headers, config) {
unblockUI();
showErrorModal('Oops!', commonErrorMessage);
});
} catch (e) {
unblockUI();
showErrorModal('Oops!', commonErrorMessage);
}
};
$scope.editScenarioTeams = function (projectId, $event) {
if (!!$event && angular.isFunction($event.preventDefault))
$event.preventDefault();
if (!projectId)
return;
var project = $scope.getProjectById(projectId);
if (!project || !project.Scenario)
return;
if (isScenarioLoaded(project.Scenario)) {
fillEditTeamsForm(project);
}
else {
loadScenarioDetails(project.Scenario.Id, projectId, function (expenditures) {
setScenarioExpenditures(project.Scenario, expenditures);
fillEditTeamsForm(project);
});
}
};
$scope.cancelAddExpCatsAndTeams = function() {
$scope.AddTeamForm.ProjectId = null;
$scope.AddTeamForm.ProjectExpCats2Add = null;
$scope.AddTeamForm.ProjectInTeams2Add = null;
$scope.AddTeamForm.ProjectOutTeams2Add = null;
$scope.AddTeamForm.AvailableProjectExpCats2Add = [];
$scope.AddTeamForm.AvailableInTeams = [];
$scope.AddTeamForm.AvailableProjectOutTeams2Add = [];
}
function fillEditTeamsForm(project) {
$scope.AddTeamForm.ProjectId = project.Id;
$scope.AddTeamForm.ProjectExpCats2Add = null;
$scope.AddTeamForm.AvailableProjectExpCats2Add = [];
// get data source for expenditures dropdown
var expCats = dataSources.getExpenditures();
var scenarioExpCatKeys = Object.keys(project.Scenario.Expenditures);
angular.forEach(expCats, function (item, index) {
if ($.inArray(item.ExpenditureCategoryId, scenarioExpCatKeys) < 0)
$scope.AddTeamForm.AvailableProjectExpCats2Add.push({ Id: item.ExpenditureCategoryId, Name: item.ExpenditureCategoryName });
});
// get date source for In Teams dropdown
$scope.AddTeamForm.AvailableInTeams = [];
var allInTeam = teamInfoService.getAll();
var inTeamKeys = Object.keys(allInTeam);
angular.forEach(allInTeam, function (item, index) { // add each selected team to project
if ($.inArray(item.Id, project.Teams) < 0) // add only if it already does not exist in project
{
$scope.AddTeamForm.AvailableInTeams.push(item);
}
});
// get datasource for Out Teams dropdown
$scope.AddTeamForm.AvailableProjectOutTeams2Add = [];
angular.forEach($scope.data.AvailableTeams, function (item, index) { // add each selected team to project
if ($.inArray(item.Id, project.Teams) < 0 && $.inArray(item.Id, inTeamKeys) < 0) // add only if it already does not exist in project or in mix
{
$scope.AddTeamForm.AvailableProjectOutTeams2Add.push({ Id: item.Id, Name: item.TVName });
}
});
// notify system that it should open modal form
$document.trigger('rmo.open-scenario-teams-window');
}
$scope.addTeamsAndExpenditures2Project = function(isOpenDetails){
var project = $scope.getProjectById($scope.AddTeamForm.ProjectId);
// add expenditures
$scope.addProjectExpCats(project);
// add in Teams
$scope.addTeams2Project(project, function ()
{
//clear form
$scope.cancelAddExpCatsAndTeams();
if (typeof resetAddExpCatsDataChanged === 'function') {
resetAddExpCatsDataChanged();
}
if (isOpenDetails)
loadScenarioDetailsEditForm(project.Scenario, false);
$document.trigger('rmo.close-scenario-teams-window');
});
};
$scope.addProjectExpCats = function (project) {
var scenarioExpCatKeys = Object.keys(project.Scenario.Expenditures);
angular.forEach($scope.AddTeamForm.ProjectExpCats2Add, function (item, index) { // add each selected expenditure to project
if ($.inArray(item.Id, scenarioExpCatKeys) < 0) // add only if it already does nto exist in project
{
var expCat = $.extend({}, dataSources.getExpenditureById(item.Id)); // get general expenditure info from common datastore
if (expCat != null)
{
// expand general object with controller specific properties
expCat.Collapsed = true;
expCat.CollapsedClass = "fa-plus-square";
// setup Details array
var detailsRow = {};
var counter = 1;
var startDateIndex = binarySearch($scope.Calendar.FiscalCalendarWeekEndings, project.Scenario.StartDate, 0, $scope.Calendar.FiscalCalendarWeekEndings.length, true);
var endDateIndex = binarySearch($scope.Calendar.FiscalCalendarWeekEndings, project.Scenario.EndDate, 0, $scope.Calendar.FiscalCalendarWeekEndings.length, true);
if (startDateIndex < 0 || endDateIndex < 0 ||
startDateIndex >= $scope.Calendar.FiscalCalendarWeekEndings.length ||
endDateIndex >= $scope.Calendar.FiscalCalendarWeekEndings.length) {
throw 'Invalid start [' + startDateIndex + '] or end [' + endDateIndex + '] indexes';
}
for (var i = startDateIndex; i <= endDateIndex; i++) {
var millisec = $scope.Calendar.FiscalCalendarWeekEndings[i];
detailsRow[millisec] = {};
detailsRow[millisec].ActualsCost = null;
detailsRow[millisec].ActualsId = null;
detailsRow[millisec].ActualsQuantity = null;
detailsRow[millisec].Changed = false;
detailsRow[millisec].ForecastCost = 0;
detailsRow[millisec].ForecastId = null;
detailsRow[millisec].ForecastQuantity = 0;
detailsRow[millisec].WeekOrdinal = counter++;
}
expCat.Details = detailsRow;
//setup Teams array
var teams = {};
for (var teamIndex = 0; teamIndex < project.Teams.length; teamIndex++)
{
var commonTeam = teamInfoService.getById(project.Teams[teamIndex]); // get team from bottom part
var expCatKeys = Object.keys(commonTeam.ExpCategories);
for (var expCatIndex=0;expCatIndex < expCatKeys.length;expCatIndex++)
{
var expCatKey = expCatKeys[expCatIndex];
var expCatItem = commonTeam.ExpCategories[expCatKey];
if (expCatKey == expCat.ExpenditureCategoryId)
{
var team = {
Id: commonTeam.Id,
Name: commonTeam.Name,
AllResources: {},
Resources: {},
CanBeDeleted: false,
Changed: false,
Collapsed: true,
CollapsedClass: "fa-plus-square",
IsAccessible: false,
QuantityValues: {}
};
var resKeys = Object.keys(expCatItem.Resources);
for (var resIndex = 0; resIndex < resKeys.length; resIndex++)
{
var resKey = resKeys[resIndex];
var res = expCatItem.Resources[resKey];
team.AllResources[resKey] = {
Id: res.Id,
Name: res.Name,
Changed: false,
Deleted: false,
IsActiveEmployee: false
};
}
for (var i = startDateIndex; i <= endDateIndex; i++) {
var millisec = $scope.Calendar.FiscalCalendarWeekEndings[i];
team.QuantityValues[millisec] = 0;
}
teams[team.Id] = team;
break;
}
}
}
expCat.Teams = teams;
// add new expenditure record to scenario
project.Scenario.Expenditures[item.Id] = expCat;
}
}
});
};
$scope.addTeams2Project = function (project, fnCallback) {
angular.forEach($scope.AddTeamForm.ProjectInTeams2Add, function (item, index) { // add each selected team to project
if ($.inArray(item.Id, project.Teams) < 0) // add only if it already does nto exist in project
{
addTeamToProject(project.Id, item.Id, 0, $scope.GridLayout[item.Id].length, true);
}
});
var outTeamIds = [];
angular.forEach($scope.AddTeamForm.ProjectOutTeams2Add, function (item, index) { // add each selected team to project
if ($.inArray(item.Id, project.Teams) < 0) // add only if it already does nto exist in project
{
outTeamIds.push(item.Id);
}
});
// if no out teams then redraw layout
if (outTeamIds.length > 0) {
var scenarioExpCatKeys = Object.keys(project.Scenario.Expenditures);
loadTeamsData(outTeamIds, function (teams) {
angular.forEach(teams, function (team, teamIndex) {
// add new team with empty allocations for each expenditures
angular.forEach(scenarioExpCatKeys, function (expCatKey, expCatIndex) {
addTeamToProjectWithStructs(project.Id, expCatKey, team);
});
});
// add new team to filter, apply changes and fill allocations on callback
bootbox.alert('Selected Teams out of Current View must be added to current Mix. Press OK to reload Mix<br/>' +
'Unsaved Mix data will be preserved...', function () {
$scope.$apply(function () {
var startDateAsDate = new Date($scope.Calendar.StartDate);
var endDateAsDate = new Date($scope.Calendar.EndDate);
var startDateAsText = (startDateAsDate.getMonth() + 1) + '/' + startDateAsDate.getDate() + '/' + startDateAsDate.getFullYear();
var endDateAsText = (endDateAsDate.getMonth() + 1) + '/' + endDateAsDate.getDate() + '/' + endDateAsDate.getFullYear();
var newFilterSelection = $scope.data.SelectedFilterItems;
for (var outTeamIndex = 0; outTeamIndex < outTeamIds.length; outTeamIndex++) {
newFilterSelection.push(outTeamIds[outTeamIndex]);
}
$rootScope.$broadcast("changeDisplayView", startDateAsText, endDateAsText, newFilterSelection, function ()
{
if (typeof (fnCallback) === 'function')
fnCallback();
});
});
});
});
} else {
$scope.recreateView();
if (typeof (fnCallback) === 'function')
fnCallback();
}
}
function copyToScenarioTeamInformation(scenario) {
if (!scenario || !scenario.Expenditures)
return;
var teams = teamInfoService.getAll();
if (!!teams) {
for (var expCatId in scenario.Expenditures) {
var expCat = scenario.Expenditures[expCatId];
if (!expCat || !expCat.Teams)
continue;
for (var teamId in expCat.Teams) {
var targetTeam = expCat.Teams[teamId];
var sourceTeam = teams[teamId];
copyTeamCapacityInfo(targetTeam, sourceTeam, expCatId);
}
}
}
};
function copyTeamCapacityInfo(targetTeam, sourceTeam, expCatId) {
if (!targetTeam || !sourceTeam || !expCatId || !sourceTeam.ExpCategories)
return;
var currentDate = new Date();
var currentDateUtc = new Date(currentDate.getUTCFullYear(), currentDate.getUTCMonth(), currentDate.getUTCDate());
var expCatInTeam = sourceTeam.ExpCategories[expCatId];
if (!expCatInTeam)
return;
var plannedCapacityWeeks = Object.keys(expCatInTeam.PlannedCapacityValues || {});
var actualCapacityWeeks = Object.keys(expCatInTeam.ActualCapacityValues || {});
var capacityWeeks = union(plannedCapacityWeeks, actualCapacityWeeks);
targetTeam.CapacityQuantityValues = {};
targetTeam.RestQuantityValues = {};
for (var i = 0; i < capacityWeeks.length; i++) {
var weekEnding = new Date(capacityWeeks[i]),
weekEndingUtc = new Date(weekEnding.getUTCFullYear(), weekEnding.getUTCMonth(), weekEnding.getUTCDate()),
capacity = 0;
if (weekEndingUtc < currentDateUtc)
capacity = expCatInTeam.ActualCapacityValues[capacityWeeks[i]] || 0;
else
capacity = expCatInTeam.PlannedCapacityValues[capacityWeeks[i]] || 0;
targetTeam.CapacityQuantityValues[capacityWeeks[i]] = capacity;
targetTeam.RestQuantityValues[capacityWeeks[i]] = capacity - (expCatInTeam.AllocatedCapacity ? (expCatInTeam.AllocatedCapacity[capacityWeeks[i]] || 0) : 0);
}
if (!expCatInTeam.Resources || Object.keys(expCatInTeam.Resources).length <= 0)
return;
if (!!targetTeam.AllResources) {
for (var resourceId in targetTeam.AllResources) {
if (!expCatInTeam.Resources[resourceId])
continue;
var targetResource = targetTeam.AllResources[resourceId];
var sourceResource = expCatInTeam.Resources[resourceId];
copyResourceCapacityInfo(targetResource, sourceResource);
}
}
if (!!targetTeam.Resources) {
for (var resourceId in targetTeam.Resources) {
if (!expCatInTeam.Resources[resourceId])
continue;
var targetResource = targetTeam.Resources[resourceId];
var sourceResource = expCatInTeam.Resources[resourceId];
copyResourceCapacityInfo(targetResource, sourceResource);
}
}
};
function copyResourceCapacityInfo(targetResource, sourceResource) {
if (!targetResource || !sourceResource)
return;
targetResource.CapacityQuantityValues = angular.copy(sourceResource.TotalCapacity || {});
targetResource.RestQuantityValues = {};
for (var weekEnding in targetResource.CapacityQuantityValues) {
var capacity = (targetResource.CapacityQuantityValues[weekEnding] || 0);
var allocated = !sourceResource.AllocatedCapacity ? 0 : (sourceResource.AllocatedCapacity[weekEnding] || 0);
targetResource.RestQuantityValues[weekEnding] = capacity - allocated;
}
};
// SA. ENV-1159
function getExtendedViewDates(scenarioStartDate, scenarioEndDate, currentViewStartDate, currentViewEndDate) {
var startDate = scenarioStartDate < currentViewStartDate ? scenarioStartDate : currentViewStartDate;
var endDate = scenarioEndDate > currentViewEndDate ? scenarioEndDate : currentViewEndDate;
var startDateObj = new Date(startDate);
var endDateObj = new Date(endDate);
var startDateText = (startDateObj.getMonth() + 1) + "/" + startDateObj.getDate() + "/" + startDateObj.getFullYear();
var endDateText = (endDateObj.getMonth() + 1) + "/" + endDateObj.getDate() + "/" + endDateObj.getFullYear();
return {
StartDate: startDateText,
EndDate: endDateText
};
};
// SA. ENV-1159
function getChangeViewPrompt(startDateText, endDateText) {
var basicText = "Project has been saved, but it is scheduled outside current view.<br/>" +
"Would you like to change current view to {0} to get the project displayed?";
var finalText = basicText.replace("{0}", "[" + startDateText + " - " + endDateText + "]");
return finalText;
};
function calcWithPrecision(value) {
var dec = Math.pow(10, FormatCells.DecimalCalcQtyPlaces);
return Math.round(value * dec) / dec;
}
function getDatesShift(oldDate, newDate) {
if (!oldDate || !newDate) {
return 0;
}
var oldDateIndex = binarySearch($scope.Calendar.FiscalCalendarWeekEndings, oldDate, 0, $scope.Calendar.FiscalCalendarWeekEndings.length, false);
var newDateIndex = binarySearch($scope.Calendar.FiscalCalendarWeekEndings, newDate, 0, $scope.Calendar.FiscalCalendarWeekEndings.length, false);
if (oldDateIndex < 0 || newDateIndex < 0) {
throw 'Incorrect oldDate [' + oldDate + '] and newDate [' + newDate + '] parameters';
}
return newDateIndex - oldDateIndex;
}
function getNewDate(date, shift) {
if (!date || !shift) {
return date;
}
var dateIndex = binarySearch($scope.Calendar.FiscalCalendarWeekEndings, date, 0, $scope.Calendar.FiscalCalendarWeekEndings.length, false);
if (dateIndex < 0) {
throw 'Incorrect date [' + date + '] parameter';
}
return $scope.Calendar.FiscalCalendarWeekEndings[dateIndex + shift];
}
function getNearestDate(date) {
if (!date)
return;
var index = binarySearch($scope.Calendar.FiscalCalendarWeekEndings, date, 0, $scope.Calendar.FiscalCalendarWeekEndings.length, true);
if (index < 0 ||
index >= $scope.Calendar.FiscalCalendarWeekEndings.length ||
index >= $scope.Calendar.FiscalCalendarWeekEndings.length) {
return null;
}
return $scope.Calendar.FiscalCalendarWeekEndings[index];
}
function findProjectPositionInLayout(projectId, layout) {
if (!projectId || !layout || layout.length <= 0) {
return null;
}
for (var i = 0; i < layout.length; i++) {
if (!layout[i])
continue;
for (var j = 0; j < layout[i].length; j++) {
if (layout[i][j].ProjectId == projectId) {
return {
RowIndex: i,
ColIndex: j
};
}
}
}
return null;
}
function findProjectInLayout(teamId, projectId) {
var currentLayout = $scope.GridLayout[teamId];
if (!currentLayout) {
return;
}
var projectPosition = findProjectPositionInLayout(projectId, currentLayout);
if (!projectPosition) {
return;
}
return currentLayout[projectPosition.RowIndex][projectPosition.ColIndex];
}
function isCallbackValid(callbackFunc) {
return (callbackFunc && (callbackFunc != null) && (typeof callbackFunc === 'function'));
}
function isValidNumber(number) {
if (!number && number != 0)
return false;
if (typeof number === 'number')
return true;
if (typeof number === 'string')
return number.match(new RegExp(/^\d*\.?\d*$/)) !== null;
throw 'number has type [' + (typeof number) + ']';
}
function getWeekends4Item(row, month, itemId) {
if (!row || !row.Cells || row.Cells.length <= 0 || !itemId || !month || !month.Childs)
return 0;
var count = 0;
for (var i = 0; i < month.Childs.length; i++) {
if (row.Cells[month.Childs[i]].Id == itemId)
count++;
}
return count;
}
// SA. ENV-1153
function setDataChanged(value) {
if ($scope.DataChanged != value) {
$scope.DataChanged = value;
$rootScope.$broadcast("dataChanged", $scope.DataChanged);
}
}
// SA. ENV-1159
function initPageControls() {
$element.find('#selProjects2Add').select2('val', '');
$(".projectEdit").on('click', function (e) {
$('#outdated').show();
});
}
// SA. ENV-1210. Compares simple arrays by data (ordering doesn't matter)
function ArraysAreEqual(array1, array2) {
var len1 = $(array1).not(array2).length;
var len2 = $(array2).not(array1).length;
return (len1 == len2) && (len1 == 0);
}
// SA. ENV-1210. Hides warning messageat the top of the Calendar
$scope.hidePageWarningMessage = function () {
$scope.PageWarningMessage = "";
}
$scope.copyScenario = function (id, scenId) {
$scope.copier.parentProjectId = id;
$scope.copier.scenarioId = scenId;
$scope.copier.targetProjectId = $scope.Calendar.Projects[Object.keys($scope.Calendar.Projects)[0]];
$('#copierProjectId').select2().select2('val', $('#copierProjectId option:eq(1)').val());
var scen = $scope.Calendar.Projects[Object.keys($scope.Calendar.Projects)[0]].InactiveScenarios[scenId];
if (typeof scen === 'undefined')
scen = $scope.Calendar.Projects[Object.keys($scope.Calendar.Projects)[0]].Scenario;
$scope.copier.scenarioName = scen.Name + ' Copy';
$("#copyScenario").modal('show');
}
$scope.copyScenarioConfirmed = function () {
var scenario = jQuery.extend(true, {}, $scope.Calendar.Projects[$scope.copier.parentProjectId].InactiveScenarios[$scope.copier.scenarioId]);
if (typeof scenario === 'undefined')
scenario = $scope.Calendar.Projects[$scope.copier.parentProjectId].Scenario;
scenario.Name = $scope.copier.scenarioName;
scenario.Id = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
var r = crypto.getRandomValues(new Uint8Array(1))[0] % 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
$scope.Calendar.Projects[$scope.copier.targetProjectId.Id].Scenario = scenario;
$.each($scope.Calendar.Projects[$scope.copier.parentProjectId].Teams, function (index, id) {
var rows = $scope.GridLayout[id];
if (typeof rows === 'undefined') rows = [0];
var k = -1, i = 0, len = $scope.Calendar.UnscheduledProjects.length;
for (; i < len; i++) {
if ($scope.Calendar.UnscheduledProjects[i].Id == $scope.copier.targetProjectId.Id) {
k = i;
}
}
if (k >= 0) addProjectFromUnscheduled($scope.copier.targetProjectId.Id, id, rows.length);
});
$scope.recreateView();
//$("#copyScenario").modal('hide');
console.log('Copied Successfully');
}
/* SA. ENV-1254. Reassign of Unassigned Expenditure Categories ------------------------------ */
$scope.frmReassign_ToTeamId = null;
$scope.frmReassign_ToExpCatId = null;
$scope.frmReassign_Action = null;
$scope.frmReassign_ExpCatsInTeam = [];
$scope.frmReassign_MixTeams = [];
$scope.frmReassign_ExpCatName = null;
$scope.frmReassign_OtherEcActionEnabled = true;
$scope.frmReassign_TryAssignOtherECs = false;
$scope.frmReassign_CurrentItem = null;
// SA. ENV-1254
$scope.reassignExpCat = function (expCatInfo, callbackFuncName) {
if (!expCatInfo)
return;
$scope.frmReassign_ExpCatName = '< Unknown >';
$scope.reassignToTeamId = null;
$scope.reassignToExpCatId = null;
$scope.frmReassign_TryAssignOtherECs = false;
$scope.frmReassign_ExpCatsInTeam = [];
$scope.frmReassign_MixTeams = [];
$scope.frmReassign_CurrentItem = expCatInfo;
$scope.frmReassign_OtherEcActionEnabled = expCatInfo.TargetTeamId && (expCatInfo.TargetTeamId != C_EMPTY_GUID);
$scope.frmReassign_Action = $scope.frmReassign_OtherEcActionEnabled ? 'otherEC' : 'otherTeam';
if (expCatInfo.Name && (expCatInfo.Name.length > 0)) {
var separatorPos = expCatInfo.Name.indexOf(";");
if (separatorPos > 0) {
$scope.frmReassign_ExpCatName = expCatInfo.Name.substr(0, separatorPos);
}
}
// Prepare EC list of current team
if ($scope.frmReassign_OtherEcActionEnabled) {
var teamItem = teamInfoService.getById(expCatInfo.TargetTeamId);
if (teamItem && teamItem.ExpCategories) {
var expCatsInTeam = Object.keys(teamItem.ExpCategories)
$.each(expCatsInTeam, function (index, expCatId) {
var currentExpCatItem = teamItem.ExpCategories[expCatId];
var ItemToPush = {
Id: currentExpCatItem.Id,
Name: currentExpCatItem.Name
}
$scope.frmReassign_ExpCatsInTeam.push(ItemToPush);
});
}
}
// Prepare list of teams, which contain specified category
var sourceExpCatId = $scope.frmReassign_CurrentItem.ExpCatId;
var projectId = expCatInfo.ProjectId;
var projectItem = $scope.getProjectById(projectId);
loadTeamsByExpCategory(sourceExpCatId, function (teams) {
if (teams) {
var thisMixGroup = {
Ord: 1,
Name: "Teams in Current Mix"
};
var otherTeamsGroup = {
Ord: 2,
Name: "Other Teams"
};
angular.forEach(teams, function (teamName, teamId) {
var teamNameDisplayed = (projectItem.Teams.indexOf(teamId) < 0) ? teamName : (teamName + ' *');
var teamRecord = {
Id: teamId,
Name: teamNameDisplayed,
Group: teamInfoService.isExists(teamId) ? thisMixGroup : otherTeamsGroup
}
$scope.frmReassign_MixTeams.push(teamRecord);
});
if (callbackFuncName && (callbackFuncName.length > 0)) {
var callbackFunc = new Function(callbackFuncName + "('" + $scope.frmReassign_Action + "')");
callbackFunc();
}
}
})
}
// SA. ENV-1254
function loadTeamsByExpCategory(expCatId, successCallbackFn) {
blockUI();
var requestData = {
id: expCatId
};
var request = getAntiXSRFRequest($scope.dataUrls.getTeamsByExpCatUrl);
request.data = requestData;
try {
$http(request).success(function (teams, status, headers, config) {
try {
if (!teams) {
unblockUI();
return null;
}
if (!!successCallbackFn && typeof successCallbackFn === 'function') {
successCallbackFn(teams);
}
unblockUI();
} catch (e) {
unblockUI();
showErrorModal('Oops!', commonErrorMessage);
}
}).error(function (data, status, headers, config) {
unblockUI();
showErrorModal('Oops!', commonErrorMessage);
});
} catch (e) {
unblockUI();
showErrorModal('Oops!', commonErrorMessage);
}
}
// SA. ENV-1254
$scope.openScenarioDetailsWindow = function () {
if (!$scope.frmReassign_CurrentItem)
return;
var projectId = $scope.frmReassign_CurrentItem.ProjectId;
var project = $scope.getProjectById(projectId);
if (!project || !project.Scenario)
return;
loadScenarioDetailsEditForm(project.Scenario, false);
}
// SA. ENV-1254
$scope.reassignUnassignedAllocations = function (openScenarioDetails) {
if (!$scope.frmReassign_CurrentItem)
return;
var projectId = $scope.frmReassign_CurrentItem.ProjectId;
var projectItem = $scope.getProjectById(projectId);
if (!projectItem || !projectItem.Scenario || !projectItem.Scenario.Id)
return;
$scope.hidePageWarningMessage();
if (!isScenarioLoaded(projectItem.Scenario)) {
loadScenarioDetails(projectItem.Scenario.Id, projectId, function (expenditures) {
setScenarioExpenditures(projectItem.Scenario, expenditures);
reassignUnassignedAllocationsInternal(openScenarioDetails);
})
}
else {
reassignUnassignedAllocationsInternal(openScenarioDetails);
}
}
// SA. ENV-1254
function reassignUnassignedAllocationsInternal(openScenarioDetails) {
var projectId = $scope.frmReassign_CurrentItem.ProjectId;
var sourceExpCatId = $scope.frmReassign_CurrentItem.ExpCatId;
switch ($scope.frmReassign_Action) {
case "remove":
removeUnassignedAllocations(projectId, sourceExpCatId);
removeProjectFromUnassignedExpenditures(projectId, sourceExpCatId);
if (openScenarioDetails)
$scope.openScenarioDetailsWindow();
break;
case "otherEC":
var targetExpCatId = $scope.frmReassign_ToExpCatId;
var targetTeamId = $scope.frmReassign_CurrentItem.TargetTeamId;
setUnassignedAllocationsToExpCat(projectId, targetTeamId, sourceExpCatId, targetExpCatId);
removeProjectFromUnassignedExpenditures(projectId, sourceExpCatId);
if (openScenarioDetails)
$scope.openScenarioDetailsWindow();
break;
case "otherTeam":
var targetTeamId = $scope.frmReassign_ToTeamId;
setUnassignedAllocationsToOtherTeam(projectId, sourceExpCatId, targetTeamId, openScenarioDetails);
$scope.recreateView();
break;
}
}
// SA. ENV-1254
function setUnassignedAllocationsToExpCat(projectId, teamId, sourceExpCatId, targetExpCatId) {
var project = $scope.getProjectById(projectId);
var teamItem = teamInfoService.getById(teamId);
if (!project || !project.Scenario || !project.Scenario.Expenditures || !teamItem)
return;
var projectExpCats = Object.keys(project.Scenario.Expenditures);
if ($.inArray(sourceExpCatId, projectExpCats) < 0)
// Scenario structure is invalid: source EC not found
return;
if ($.inArray(targetExpCatId, projectExpCats) < 0) {
var teamExpCats = Object.keys(teamItem.ExpCategories);
if ($.inArray(targetExpCatId, teamExpCats) >= 0) {
var teamExpCatTemplate = teamItem.ExpCategories[targetExpCatId];
var expCatItem = angular.copy(project.Scenario.Expenditures[sourceExpCatId]);
expCatItem.ExpenditureCategoryId = teamExpCatTemplate.Id;
expCatItem.ExpenditureCategoryName = teamExpCatTemplate.Name;
expCatItem.UOMValue = teamExpCatTemplate.UomValue;
expCatItem.Teams = {};
expCatItem.UnassignedAllocations = {};
// TODO. SA. Get from EC-template from Team the fallowing values
// (attributes must be added to Team in Mix.Teams collection):
// CGEFX, CreditId, GLId, SortOrder, SystemAttributeOne, SystemAttributeTwo, Type, UOMId, UseType
project.Scenario.Expenditures[targetExpCatId] = expCatItem;
}
}
// Check team exists in target EC
var targetExpCat = project.Scenario.Expenditures[targetExpCatId];
if (!targetExpCat.Teams)
targetExpCat.Teams = {};
if (!targetExpCat.Teams[teamId])
fillExpenditures4TeamById(project, teamId);
// Move unassigned allocations in source EC to target Team in target EC
if (targetExpCat.Teams[teamId]) {
var updatedData = angular.copy(project.Scenario.Expenditures);
var sourceExpCat = updatedData[sourceExpCatId];
targetExpCat = updatedData[targetExpCatId];
var targetExpCatTeam = targetExpCat.Teams[teamId];
angular.forEach(sourceExpCat.UnassignedAllocations, function (allocationItem, we) {
if (targetExpCatTeam.QuantityValues[we] === undefined)
targetExpCatTeam.QuantityValues[we] = 0;
targetExpCatTeam.QuantityValues[we] += allocationItem;
});
sourceExpCat.UnassignedAllocations = {};
refreshProjectData(project, updatedData);
}
}
// SA. ENV-1254
function loadTeamsData(teamIds, callbackFn) {
blockUI();
var requestData = {
ids: teamIds,
startDateMs: $scope.Calendar.StartDate,
endDateMs: $scope.Calendar.EndDate
};
var request = getAntiXSRFRequest($scope.dataUrls.getTeamsByIdUrl);
request.data = requestData;
try {
$http(request).success(function (teams, status, headers, config) {
try {
if (!teams) {
unblockUI();
return null;
}
if (!!callbackFn && typeof callbackFn === 'function') {
callbackFn(teams);
}
unblockUI();
} catch (e) {
unblockUI();
showErrorModal('Oops!', commonErrorMessage);
}
}).error(function (data, status, headers, config) {
unblockUI();
showErrorModal('Oops!', commonErrorMessage);
});
} catch (e) {
unblockUI();
showErrorModal('Oops!', commonErrorMessage);
}
}
// SA. ENV-1254
function setUnassignedAllocationsToOtherTeam(projectId, expCatId, targetTeamId, openScenarioDetails) {
var teamItem = teamInfoService.getById(targetTeamId);
if (!teamItem) {
// Team is not in the Mix
loadTeamsData([targetTeamId], function (teams) {
angular.forEach(teams, function (team, teamIndex) {
// add new team with empty allocations
addTeamToProjectWithStructs(projectId, expCatId, team);
});
// add new team to filter, apply changes and fill allocations on callback
bootbox.alert('The selected Team must be added to current Mix. Press OK to reload Mix<br/>' +
'Unsaved Mix data will be preserved...', function () {
$scope.$apply(function () {
var startDateAsDate = new Date($scope.Calendar.StartDate);
var endDateAsDate = new Date($scope.Calendar.EndDate);
var startDateAsText =
(startDateAsDate.getMonth() + 1) + '/' + startDateAsDate.getDate() + '/' + startDateAsDate.getFullYear();
var endDateAsText =
(endDateAsDate.getMonth() + 1) + '/' + endDateAsDate.getDate() + '/' + endDateAsDate.getFullYear();
var newFilterSelection = $scope.data.SelectedFilterItems;
newFilterSelection.push(targetTeamId);
$rootScope.$broadcast("changeDisplayView", startDateAsText, endDateAsText, newFilterSelection,
function () {
// add allocations to the new team of the project
setUnassignedAllocationsToTeam(projectId, expCatId, targetTeamId);
// remove record from "Project with Unassigned expenditures" block, with specific expenditure category
removeProjectFromUnassignedExpenditures(projectId, expCatId);
// SA. ENV-1298. Try to allocate other unassigned ECs of the project to selected team
if ($scope.frmReassign_TryAssignOtherECs) {
tryAllocateUnassignedExpCatsToTeam(projectId, teamId);
}
if (openScenarioDetails) {
$scope.openScenarioDetailsWindow();
}
});
});
});
});
}
else {
// Team exists in the Mix
var project = $scope.getProjectById(projectId);
if (!$scope.projectHasTeam(project, teamItem.Id)) {
addTeamToProjectWithStructs(projectId, expCatId, teamItem);
}
setUnassignedAllocationsToTeam(projectId, expCatId, teamItem.Id);
removeProjectFromUnassignedExpenditures(projectId, expCatId);
// SA. ENV-1298. Try to allocate other unassigned ECs of the project to selected team
if ($scope.frmReassign_TryAssignOtherECs) {
tryAllocateUnassignedExpCatsToTeam(projectId, teamItem.Id);
}
if (openScenarioDetails) {
$scope.openScenarioDetailsWindow();
}
}
}
// SA. ENV-1254
function addTeamToProjectWithStructs(projectId, expCatId, team) {
if (!projectId || !expCatId || !team)
return;
var project = $scope.getProjectById(projectId);
if (!project || !project.Scenario || !project.Scenario.Expenditures)
return;
if (Object.keys(project.Scenario.Expenditures).indexOf(expCatId) < 0)
// Expenditure not in the Project
return;
if (!project.Scenario.Expenditures[expCatId].Teams)
project.Scenario.Expenditures[expCatId].Teams = {};
// Adding team to project
addTeamToProject(project.Id, team.Id, 0, 0, true);
if (!teamInfoService.isExists(team.Id)) {
fillExpenditures4Team(project, team);
}
}
// SA. ENV-1254
function setUnassignedAllocationsToTeam(projectId, expCatId, teamId) {
if (!projectId || !expCatId || !teamId)
return;
var project = $scope.getProjectById(projectId);
if (!project || !project.Scenario || !project.Scenario.Expenditures)
return;
// Move unassigned allocations to target Team in target EC
var updatedData = angular.copy(project.Scenario.Expenditures);
var expCat = updatedData[expCatId];
var expCatTeam = expCat.Teams[teamId];
angular.forEach(expCat.UnassignedAllocations, function (allocationItem, we) {
if (expCatTeam.QuantityValues[we] === undefined)
expCatTeam.QuantityValues[we] = 0;
expCatTeam.QuantityValues[we] += allocationItem;
});
// Clear unassigned data only, because it was just moved
expCat.UnassignedAllocations = {};
refreshProjectData(project, updatedData);
}
// SA. ENV-1254
function removeUnassignedAllocations(projectId, expCatId) {
var project = $scope.getProjectById(projectId);
if (project && project.Scenario && project.Scenario.Expenditures) {
var expCatKeys = Object.keys(project.Scenario.Expenditures);
if ($.inArray(expCatId, expCatKeys) >= 0) {
delete project.Scenario.Expenditures[expCatId];
}
}
}
// SA. ENV-1254
$scope.IsReassignExpCatActionAvailable = function () {
var result =
((($scope.frmReassign_Action == "otherEC") && $scope.frmReassign_ToExpCatId) ||
(($scope.frmReassign_Action == "otherTeam") && $scope.frmReassign_ToTeamId) ||
($scope.frmReassign_Action == "remove"));
return result;
}
// SA. ENV-1298. If expCatsArray not specified, does the action for all found ECs in Unassigned ECs block
function tryAllocateUnassignedExpCatsToTeam(projectId, teamId, expCatsArray) {
if (!$scope.UnassignedExpendituresProjectsExist)
return;
var expCats = expCatsArray;
if (!expCats) {
// Get EC list from Unassigned Expenditures block currently existing records
expCats = [];
angular.forEach($scope.Calendar.UnassignedExpendituresProjects, function (rec, index) {
if (rec.ProjectId == projectId) {
expCats.push(rec.ExpCatId);
}
});
}
for (var index = 0; index < expCats.length; index++) {
var expCatId = expCats[index];
tryAllocateSingleUnassignedExpCatToTeam(projectId, teamId, expCatId);
}
}
// SA. ENV-1298.
function tryAllocateSingleUnassignedExpCatToTeam(projectId, teamId, expCatId) {
// Check project has the specified EC and Team
var project = $scope.getProjectById(projectId);
if (!project || !project.Scenario || !$scope.projectHasTeam(project, teamId))
return;
if (!project.Scenario.Expenditures || !project.Scenario.Expenditures[expCatId] ||
!project.Scenario.Expenditures[expCatId].Teams || !project.Scenario.Expenditures[expCatId].Teams[teamId])
return;
setUnassignedAllocationsToExpCat(projectId, teamId, expCatId, expCatId);
removeProjectFromUnassignedExpenditures(projectId, expCatId);
}
/* SA. ENV-1218. Functions to remove team from Project and redistribute allocations ----------- */
$scope.removeTeamFromProject = function (projectId, teamId) {
if (!projectId || !teamId || (projectId == C_EMPTY_GUID) || (teamId == C_EMPTY_GUID))
return;
var project = $scope.getProjectById(projectId);
if (!project.Teams || (project.Teams.indexOf(teamId) < 0))
return;
if (!isScenarioLoaded(project.Scenario)) {
loadScenarioDetails(project.Scenario.Id, projectId, function (expenditures) {
setScenarioExpenditures(project.Scenario, expenditures);
distributeTeamAllocationsToOtherTeams(project, teamId);
$scope.recreateView();
})
}
else {
distributeTeamAllocationsToOtherTeams(project, teamId);
$scope.recreateView();
}
}
function distributeTeamAllocationsToOtherTeams(project, sourceTeamId) {
if (!project || !sourceTeamId || (sourceTeamId == C_EMPTY_GUID))
return;
if (!project.Teams || (project.Teams.indexOf(sourceTeamId) < 0))
return;
// Create allocations distribution map
var allocationsDistributionMap = createAllocationsDistributionMap(project, sourceTeamId);
var movableExpCats = Object.keys(allocationsDistributionMap);
if (movableExpCats && (movableExpCats.length > 0)) {
// Perform redistribution in a copy of Project's expenditure allocations by map
var projectDataCopy = angular.copy(project.Scenario.Expenditures);
moveAllocationsByDistributionMap(projectDataCopy, sourceTeamId, allocationsDistributionMap)
refreshProjectData(project, projectDataCopy, false);
projectDataCopy = null;
}
removeTeamFromProjectInternal(project.Id, sourceTeamId);
var recordsCreated = createProjectUnassignedExpendituresItems(project);
if (recordsCreated > 0) {
$timeout(function () {
bootbox.alert("Allocations for some expenditure categories were not able to be distributed " +
"to other teams, and became unassigned.<br/>Please, Check 'Projects with Unassigned Expenditures' " +
"block for details.");
});
}
}
// SA. ENV-1218. Creates records in Projects with Unassigned Expenditures block for
// all project ExpCats, that has unZero values in ExpCat.UnassignedAllocations
function createProjectUnassignedExpendituresItems(project, setCurrentTeamId) {
var recordsCreatedCount = 0;
if (!project || !project.Scenario || !project.Scenario.Expenditures)
return recordsCreatedCount;
if (isProjectInManaged(project.Id)) {
var projectExpCats = Object.keys(project.Scenario.Expenditures);
if (projectExpCats.length > 0) {
var unassignedExpCats = $.grep(projectExpCats, function (expCatId, index) {
var expCatData = project.Scenario.Expenditures[expCatId];
var hasUnassignedQuantities = false;
if (expCatData && expCatData.UnassignedAllocations) {
angular.forEach(expCatData.UnassignedAllocations, function (value, we) {
hasUnassignedQuantities = hasUnassignedQuantities || (value > 0);
});
}
return hasUnassignedQuantities;
});
recordsCreatedCount = unassignedExpCats.length;
if (recordsCreatedCount > 0) {
if (!setCurrentTeamId) {
if (project.Teams.length == 1)
// If project in the last has only one team, it will be target team for unassigned allocations
setCurrentTeamId = project.Teams[0];
}
$scope.pushProjectToUnassignedExpenditures(project, unassignedExpCats, setCurrentTeamId);
}
}
}
return recordsCreatedCount;
}
// SA. ENV-1218
function createAllocationsDistributionMap(project, teamId) {
var dMap = {};
if (!project.Scenario || !project.Scenario.Expenditures)
return dMap;
angular.forEach(project.Scenario.Expenditures, function (expCatData, expCatId) {
if (expCatData && expCatData.Teams) {
var expCatTeams = Object.keys(expCatData.Teams);
var otherTeams = [];
if (expCatTeams.indexOf(teamId) >= 0) {
// The Team exists in current EC. Looking for proper redistribution plan
otherTeams = $.grep(expCatTeams, function (ecTeamId, index) {
return (ecTeamId != teamId) && (project.Teams.indexOf(ecTeamId) >= 0);
});
dMap[expCatId] = otherTeams;
}
}
});
return dMap;
}
// SA. ENV-1218. Moves allocations from source team to specified teams for every EC in the Distribution map
// ExpCatsdata is the property Expenditures of a Scenario object
// distributionMap - associative array of ECs. Every element is an array of the target team IDs
function moveAllocationsByDistributionMap(ExpCatsdata, sourceTeamId, distributionMap) {
if (!ExpCatsdata || !sourceTeamId || !distributionMap)
return;
angular.forEach(distributionMap, function (targetTeamKeys, expCatId) {
var currentEC = ExpCatsdata[expCatId];
var srcTeam = currentEC.Teams ? currentEC.Teams[sourceTeamId] : undefined;
if (currentEC && srcTeam && srcTeam.QuantityValues) {
var weekEndings = Object.keys(srcTeam.QuantityValues);
var targetTeamsCount = targetTeamKeys && Array.isArray(targetTeamKeys) ? targetTeamKeys.length : 0;
if (targetTeamsCount > 0) {
// Move source team allocations to other teams
var targetTeams = [];
angular.forEach(targetTeamKeys, function (targetTeamId, index) {
var targetTeam = currentEC.Teams[targetTeamId];
targetTeams.push(targetTeam);
if (!targetTeam.QuantityValues)
targetTeam.QuantityValues = {};
});
angular.forEach(weekEndings, function (we, index) {
redistributeQuantityValuesByCurve(targetTeams, we, srcTeam.QuantityValues[we]);
// Reset value, because it was distributed to other teams
srcTeam.QuantityValues[we] = 0;
});
}
// Move remaining allocations to UnassignedQuantityValues for EC
if (!currentEC.UnassignedAllocations)
currentEC.UnassignedAllocations = {};
var remainingNonZeroValues = $.grep(weekEndings, function (we, index) {
return srcTeam.QuantityValues[we] > 0;
});
if (remainingNonZeroValues.length > 0) {
angular.forEach(weekEndings, function (we, index) {
var valueToMove = srcTeam.QuantityValues[we];
if (currentEC.UnassignedAllocations[we] === undefined)
currentEC.UnassignedAllocations[we] = 0;
currentEC.UnassignedAllocations[we] += valueToMove;
srcTeam.QuantityValues[we] = 0;
});
}
}
});
}
// SA. ENV-1218. Redistributes value for single Weekending to specified teams by curve.
// If no allocations exist for all specified teams, distribution is performed by equal parts
function redistributeQuantityValuesByCurve(teams, weekending, valueToDistribute) {
var multipliers = {};
var valueSumm = 0;
angular.forEach(teams, function (team, index) {
if (team.QuantityValues && (team.QuantityValues[weekending] !== undefined)) {
multipliers[team.Id] = team.QuantityValues[weekending];
valueSumm += multipliers[team.Id];
}
else {
multipliers[team.Id] = 0;
}
});
if (valueSumm > 0) {
// Curve exists
angular.forEach(multipliers, function (value, key) {
multipliers[key] = value / valueSumm;
});
angular.forEach(teams, function (team, index) {
if (team.QuantityValues[weekending] === undefined)
team.QuantityValues[weekending] = 0;
team.QuantityValues[weekending] += valueToDistribute * multipliers[team.Id];
});
}
else {
// No curve, because all teams has Zero values currently allocated
var teamCount = Object.keys(multipliers).length;
angular.forEach(teams, function (team, index) {
if (team.QuantityValues[weekending] === undefined)
team.QuantityValues[weekending] = 0;
if (teamCount > 0) {
team.QuantityValues[weekending] += valueToDistribute / teamCount;
}
});
}
}
$scope.activateScenario = function (scenId, projId) {
bootbox.confirm({
message: "Are you sure you want to use this scenario in the current Mix? Your current scenario will be overwritten.",
callback: function (result) {
$scope.$apply(function () {
if (result) {
$scope.activateScenarioConfirmed(scenId, projId);
}
});
}
});
}
$scope.activateScenarioConfirmed = function (scenarioId, projectId) {
var project = $scope.Calendar.Projects[projectId];
if (!project)
return;
var newScenario = project.InactiveScenarios[scenarioId];
if (!newScenario)
return;
if (isScenarioLoaded(newScenario)) {
setActiveScenario(project, newScenario);
}
else {
loadScenarioDetails(newScenario.Id, project.Id, function (expenditures) {
setScenarioExpenditures(newScenario, expenditures);
setActiveScenario(project, newScenario);
});
}
};
function setActiveScenario(project, scenario) {
if (!project || !project.Scenario || !scenario)
return;
delete project.InactiveScenarios[scenario.Id];
var oldScenario = angular.copy(project.Scenario);
if (isScenarioLoaded(oldScenario)) {
project.Scenario = angular.extend({}, scenario, { Expenditures: angular.copy(oldScenario.Expenditures) });
refreshProjectData(project, scenario.Expenditures, true);
project.InactiveScenarios[oldScenario.Id] = oldScenario;
$scope.recreateView();
}
else {
loadScenarioDetails(oldScenario.Id, project.Id, function (expenditures) {
setScenarioExpenditures(oldScenario, expenditures);
project.Scenario = angular.extend({}, scenario, { Expenditures: angular.copy(oldScenario.Expenditures) });
refreshProjectData(project, scenario.Expenditures, true);
project.InactiveScenarios[oldScenario.Id] = oldScenario;
$scope.recreateView();
});
}
};
function isScenarioLoaded(scenario) {
// we should return true because otherwise it may be detected as command to load scenario for incorrect project
if (!scenario)
return true;
// if expenditures do not exist method return false
return !!scenario.Expenditures && Object.keys(scenario.Expenditures).length > 0;
};
}]);