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

5945 lines
237 KiB
JavaScript
Raw Permalink 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', 'teamInfoService', 'dataSources', 'mixProjectService', '$q', function ($scope, $rootScope, $http, $filter, $element, $timeout, $document, $compile, teamInfoService, dataSources, mixProjectService, $q) {
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.';
// Urls to get data from server
$scope.dataUrls = {
getTeamsByExpCatUrl: '/Team/GetTeamsByExpenditureCategory',
getScenarioDetailsUrl: '/Mix/GetScenarioDetails',
editScenarioDetailsUrl: '/Mix/EditScenarioDetails',
getCalendarUrl: '/Mix/LoadCalendar',
loadMixUrl: '/Mix/LoadMix',
saveMixUrl: '/Mix/SaveMix',
deleteMixUrl: '/Mix/DeleteMix',
importScenarioUrl: '/Mix/ActivateScenario',
getScenariosTimestampsUrl: '/Mix/GetScenariosTimestamps',
editScenarioFinInfoUrl: '/Mix/EditScenarioFinInfo',
getProjectsFromLiveUrl: '/Mix/GetProjectsDataFromLive',
CanDoRaceUrl: '/Mix/HasResourceAssignments'
};
$scope.copier = {
scenario: null
};
$scope.data = {
Projects2Add: [],
SelectedFilterTeamsAndViews: [], // Selected teams and views in the Mix header filter
SelectedFilterCostCenters: [],
SelectedFilterProjectRoles: []
};
$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 = [];
$scope.IsDataLoaded = false;
$scope.DataChanged = false;
$scope.clickShift = 0
$scope.cellWidth = 114;
$scope.UnassignedExpendituresProjectsExist = false;
$scope.UnscheduledProjectsExist = false;
$scope.ShowChangedObjectsWarning = false;
$scope.ShowDeletedObjectsWarning = false;
$scope.ChangedInLiveDbProjects = [];
$scope.OutOfMixProjects = []; // collection of projects that hit to out of range after mix updating from the live database
$scope.showOutOfMixProjectsExistWarning = false; // show yellow warning about out of range projects
/* Display Mode -------------------------------------------------------------------------------*/
$scope.DisplayMode = {
IsViewModeMonth: true, // do not display weeks by default
IsUOMHours: false, // 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,
ShowResources: true // display bottom part (starting Non-Project Time row until the bottom of the grid)
};
$scope.ImportScenarioForm = initImportScenarioForm();
$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('groupByChanged', getGroupByMode());
}
});
$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.$watch('DisplayMode.ShowResources', function (newValue, oldValue) {
if (oldValue != newValue) {
$scope.$broadcast('showResourcesChanged', 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: [],
GridLayout: {}, // Mix Calendar Layout
SuperExpenditures: {} // List of available super ExpCats
};
};
function initImportScenarioForm() {
return {
ProjectId: null,
NewTeams: [],
Name: "",
IsActive: true
};
};
$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) { // 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;
case "showResources":
$scope.DisplayMode.ShowResources = 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); // 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 {
if (!data) {
unblockUI();
return;
}
if (data.Calendar && data.Calendar.Teams) {
$scope.IsDataLoaded = true;
// set available teams from header controller
data.Filter.Variants.AvailableTeamsAndViews = availableTeams;
$scope.storeMixFilter(data);
var teamIds = [];
for (var index = 0; index < data.Calendar.Teams.length; index++) {
teamIds.push(data.Calendar.Teams[index].Id);
}
var expCatsLoadTask = dataSources.load();
var resourcesLoadTask = dataSources.loadResourcesByTeamIds(teamIds);
var allTasks = [expCatsLoadTask, resourcesLoadTask];
$q.all(allTasks).then(function (result) {
$scope.rebuildCalendar(data.Calendar);
$rootScope.$broadcast('resourceCountChanged', data.CanRunResourceRace, data.canDoRace);
$rootScope.$broadcast('SuperECCountChanged', data.CanRunTeamRace, data.canDoRace);
if (callbackFn && (typeof callbackFn === 'function'))
callbackFn(data);
unblockUI();
});
}
else {
throw "Teams collection is empty";
}
} catch (e) {
console.error(e);
unblockUI();
showErrorModal();
}
})
.error(function (data, status, headers, config) {
console.error('A server error occurred in ' + $scope.dataUrls.getCalendarUrl + ' action');
unblockUI();
showErrorModal();
});
} catch (e) {
console.error(e);
unblockUI();
showErrorModal();
}
});
$scope.$on('clientFilterChanged', function (event, filter) {
if (filter) {
// Storing client-side filters
$scope.data.SelectedFilterCostCenters = angular.copy(filter.CostCenters);
$scope.data.SelectedFilterProjectRoles = angular.copy(filter.ProjectRoles);
}
rebuildBottomPart();
});
$scope.$on('raceChanged', function (event, filter, availableTeams, teamLevel) {
var postData = {};
postData.Filter = {};
postData.Filter.Selection = filter;
postData.TeamLevelRace = teamLevel;
$scope.fixFilterForSave(postData.Filter); // 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("/Mix/DoRace", postData);
try {
$http(request)
.success(function (data, status, headers, config) {
try {
$scope.IsDataLoaded = true;
data.mixData.forEach(function (raceProj) {
var project = $scope.getProjectById(raceProj.Id);
var teamId = project.Teams[0];
var shiftX = getDatesShift(getNearestDate(project.Scenario.StartDate), getNearestDate(raceProj.NewStartWeek));
$scope.moveProjectInsideTeamForRace(raceProj.Id, teamId, shiftX, raceProj.RaceOrder, true, function () {
});
});
var RaceScore = data.raceScore;
$rootScope.$broadcast('raceFinished', RaceScore);
unblockUI();
} catch (e) {
console.error(e);
unblockUI();
showErrorModal();
}
})
.error(function (data, status, headers, config) {
console.error('A server error occurred in ' + $scope.dataUrls.getCalendarUrl + ' action');
unblockUI();
showErrorModal();
});
} catch (e) {
console.error(e);
unblockUI();
showErrorModal();
}
});
$scope.$on('race2Changed', function (event, filter, availableTeams, teamLevel) {
var postData = {};
postData.Filter = {};
postData.Filter.Selection = filter;
postData.TeamLevelRace = teamLevel;
postData.Id = $scope.MixId;
$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("/Mix/DoRace2", postData);
try {
$http(request)
.success(function (data, status, headers, config) {
try {
$scope.IsDataLoaded = true;
var RaceScore = 0;
data.mixData.forEach(function (raceProj) {
var project = $scope.getProjectById(raceProj.Id);
RaceScore = raceProj.TotalScore;
var teamId = project.Teams[0];
//var rowIndex = getProjectRowIndexTeamLayout(raceProj.Id, teamId);
var shiftX = getDatesShift(getNearestDate(project.Scenario.StartDate), getNearestDate(raceProj.NewStartWeek));
$scope.moveProjectInsideTeamForRace(raceProj.Id, teamId, shiftX, raceProj.RaceOrder, true);
});
$rootScope.$broadcast('raceFinished', RaceScore);
unblockUI();
} catch (e) {
console.error(e);
unblockUI();
showErrorModal();
}
})
.error(function (data, status, headers, config) {
console.error('A server error occurred in ' + $scope.dataUrls.getCalendarUrl + ' action');
unblockUI();
showErrorModal();
});
} catch (e) {
console.error(e);
unblockUI();
showErrorModal();
}
});
$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 (data.Calendar && data.Calendar.Teams) {
$scope.IsDataLoaded = true;
$scope.MixId = data.Id;
$scope.storeMixFilter(data);
var teamIds = [];
for (var index = 0; index < data.Calendar.Teams.length; index++) {
teamIds.push(data.Calendar.Teams[index].Id);
}
var expCatsLoadTask = dataSources.load();
var resourcesLoadTask = dataSources.loadResourcesByTeamIds(teamIds);
var allTasks = [expCatsLoadTask, resourcesLoadTask];
$q.all(allTasks).then(function (result) {
$scope.rebuildCalendar(data.Calendar);
$rootScope.$broadcast('resourceCountChanged', data.CanRunResourceRace, data.canDoRace);
$rootScope.$broadcast('SuperECCountChanged', data.CanRunTeamRace, data.canDoRace);
if (setFilterCallback && (typeof setFilterCallback === 'function')) {
// Treat mix as changed, if at least one project was deleted during mix load
var deletedProjectsExist = $scope.Calendar.ModifiedObjects && $scope.Calendar.ModifiedObjects.DeletedProjects
&& $scope.Calendar.ModifiedObjects.DeletedProjects.length > 0;
setFilterCallback(data, deletedProjectsExist);
}
unblockUI();
$scope.$parent.$broadcast('dataloaded');
});
}
else {
$scope.$parent.$broadcast('dataloaded');
throw "Teams collection is empty";
}
} catch (e) {
console.error(e);
unblockUI();
showErrorModal();
}
})
.error(function (data, status, headers, config) {
console.error('A server error occurred in ' + $scope.dataUrls.loadMixUrl + ' action');
unblockUI();
showErrorModal();
});
} catch (e) {
console.error(e);
unblockUI();
showErrorModal();
}
});
$scope.$on('saveChanges', function (event, saveData, successSaveCallBack, failedSaveCallBack, resetVersionInfo) {
if (!saveData) {
if (isCallbackValid(failedSaveCallBack)) {
failedSaveCallBack(commonErrorMessage);
}
return;
}
// Get filter values from header controller
$scope.createSaveDataPackage(saveData, resetVersionInfo);
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) {
console.error(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) {
console.error(err);
if (errorCallback && (typeof errorCallback === 'function'))
errorCallback();
unblockUI();
showErrorModal();
}
})
.error(function (data, status, headers, config) {
console.error('A server error occurred in ' + $scope.dataUrls.deleteMixUrl + ' action');
if (errorCallback && (typeof errorCallback === 'function'))
errorCallback();
unblockUI();
showErrorModal();
});
} catch (e) {
console.error(e);
if (errorCallback && (typeof errorCallback === 'function'))
errorCallback();
unblockUI();
showErrorModal();
}
});
$scope.$on('saveScenarioDetails', function (event, args) {
if (!!args && !!args.scenario && !!args.scenario.Id && !!args.scenario.ProjectId && args.scenario.Expenditures) {
var project = angular.copy($scope.getProjectById(args.scenario.ProjectId) || {});
if (!!project && !!project.Scenario && !!project.Scenario.Id && project.Scenario.Id.toUpperCase() === args.scenario.Id.toUpperCase()) {
blockUI();
var newScenarioStartDate = args.scenario.StartDate;
var newScenarioEndDate = args.scenario.EndDate;
var rangeChanged = project.Scenario.StartDate != newScenarioStartDate || project.Scenario.EndDate != newScenarioEndDate;
if (rangeChanged) {
project.Scenario.StartDate = newScenarioStartDate;
project.Scenario.EndDate = newScenarioEndDate;
}
project.Scenario.FinInfo = project.Scenario.FinInfo || {};
project.Scenario.FinInfo.ProjectedRevenue = args.scenario.ProjectedRevenue;
project.Scenario.FinInfo.TDDirectCosts = args.scenario.TDDirectCosts;
project.Scenario.FinInfo.UseLMMargin = args.scenario.UseLMMargin;
project.Scenario.FinInfo.GrossMargin = args.scenario.GrossMargin;
project.Scenario.FinInfo.LMMargin = args.scenario.LMMargin;
project.Scenario.FinInfo.CostSaving = args.scenario.CostSavings || project.Scenario.FinInfo.CostSaving;
refreshProjectData(project, args.scenario.Expenditures, true);
mixProjectService.recalculateScenarioFinInfo(project.Scenario).then(function (finInfo) {
project.Scenario.FinInfo = finInfo;
$scope.Calendar.Projects[args.scenario.ProjectId] = project;
setDataChanged(true, project.Id);
if (rangeChanged) {
for (var i in project.Teams) {
var teamId = project.Teams[i];
var rowIndex = getProjectRowIndexTeamLayout(args.scenario.ProjectId, teamId);
$scope.moveProjectInsideTeam(args.scenario.ProjectId, teamId, 0, rowIndex, rowIndex, false);
}
}
}).then(null, function () {
showErrorModal();
}).finally(function () {
if (project.Scenario && project.Scenario.Expenditures) {
angular.forEach(project.Scenario.Expenditures, function (expCat, expCatId) {
if (expCat && expCat.UnassignedAllocations && !unassignedValuesExist(expCat.UnassignedAllocations)) {
removeProjectFromUnassignedExpenditures(project.Id, expCatId);
}
});
}
if (isScenarioOutOfMix(project.Scenario))
moveProjectToQueue(project);
unblockUI();
angular.element(args.currentTarget).parents('[role=dialog]').data('action', 'save').modal('hide');
$scope.$broadcast('teaminfo.recreateRows');
});
return;
}
}
$document.trigger('rmo.close-scenario-details-window');
});
$scope.$on('cancelScenarioDetails', function (event, args) {
angular.element(args.currentTarget).parents('[role=dialog]').modal('hide');
});
// SA. User launched projects update from Live DB
$scope.updateMixFromLiveDatabase = function () {
if (!$scope.ChangedInLiveDbProjects && ($scope.ChangedInLiveDbProjects.length < 1))
return;
// Check mix teams are not deleted in Live DB
updateMixTeamsFromLiveDb(function () {
if ($scope.ChangedInLiveDbProjects && ($scope.ChangedInLiveDbProjects.length > 0)) {
var projectsToUpdate = [];
angular.forEach($scope.ChangedInLiveDbProjects, function (rec, index) {
if (rec && rec.ProjectId) {
projectsToUpdate.push(rec.ProjectId);
}
});
updateProjectsFromLiveDb(projectsToUpdate, confirmMultipleProjectsUpdateFromLiveDb);
}
});
};
$scope.updateProjectFromDB = function (projectId) {
if (!projectId)
return;
updateProjectsFromLiveDb([projectId], confirmSingleProjectUpdateFromLiveDb);
};
/* Drag and Drop -------------------------------------------------------------------------------*/
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;
};
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.dragEnter = function ($dropmodel, $dragmodel) {
$dropmodel.Team.Drop = {
Row: $dropmodel.Row
};
if (!$dragmodel) {
return;
}
var project = $scope.Calendar.Projects[$dragmodel.ProjectId];
if (!project || !project.Scenario || isProjectInUnscheduled($dragmodel.ProjectId)) {
$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);
};
$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 dtFormatted = DateTimeConverter.msFormatAsUtcString($dropmodel.Milliseconds);
var projectId = $dragmodel.ProjectId;
showCreateScenarioDialog({
ProjectId: projectId,
TargetTeam: $dropmodel.Team,
TargetRow: selectedRow,
StartDate: dtFormatted,
EndDate: dtFormatted,
HideName: true
});
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) {
ms = getNextVisibleMilliseconds($dragmodel.Milliseconds, Math.ceil($scope.clickShift / $scope.cellWidth) - 1);
}
var dragStartDate = getStartDate4Dragging($dragmodel.Team, $dragmodel.Row, cellIndex);//$dragmodel.CellIndex);
$scope.moveProjectInsideTeam($dragmodel.ProjectId, $dragmodel.Team.Id,
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, 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, $dragmodel.ProjectId);
};
// 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);
refreshScenarioTeamsCapacity(projectItem.Scenario);
copyAllocationsFromTeamToTeam(projectItem, sourceTeamId, targetTeamId);
removeTeamFromProjectInternal(projectItem.Id, sourceTeamId);
if ($scope.DisplayMode.UnassignedAllocations)
tryAllocateUnassignedExpCatsToTeam(projectItem.Id, targetTeamId);
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);
refreshScenarioTeamsCapacity(projectItem.Scenario);
copyAllocationsFromTeamToTeam(projectItem, sourceTeamId, targetTeamId);
removeTeamFromProjectInternal(projectItem.Id, sourceTeamId);
if ($scope.DisplayMode.UnassignedAllocations)
tryAllocateUnassignedExpCatsToTeam(projectItem.Id, targetTeamId);
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
addTeamToProject(projectItem.Id, targetTeamId, datesShift, selectedRow, placeBeforeSelectedRow);
refreshScenarioTeamsCapacity(projectItem.Scenario);
copyAllocationsFromTeamToTeam(projectItem, sourceTeamId, targetTeamId);
removeTeamFromProjectInternal(projectItem.Id, sourceTeamId);
if ($scope.DisplayMode.UnassignedAllocations)
tryAllocateUnassignedExpCatsToTeam(projectItem.Id, targetTeamId);
createProjectUnassignedExpendituresItems(projectItem, targetTeamId);
// Refresh calendar view
$scope.recreateView();
}
};
// 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(projectItem.Id, updatedData, allocatedExpCats, sourceTeamId, targetTeamId);
refreshProjectData(projectItem, updatedData);
}
};
// Copies allocations for given ECs from source team to target team
// ExpCatsdata is the property Expenditures of a Scenario object
function copyAllocationsFromTeamToTeamInternal(projectId, 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);
var newUnassignedAllocations = currentEC.UnassignedAllocations ?
angular.copy(currentEC.UnassignedAllocations) : {};
$.each(weekEndings, function (index, we) {
if (newUnassignedAllocations[we] === undefined)
newUnassignedAllocations[we] = 0;
});
if (dstTeam && dstTeam.QuantityValues) {
$.each(weekEndings, function (index, we) {
if (srcTeam.QuantityValues[we] !== undefined) {
if (dstTeam.QuantityValues[we] === undefined)
dstTeam.QuantityValues[we] = 0;
var expCatRestValue = getExpCatRestValue(currentEC, we, sourceTeamId);
var teamRestValue = getTeamRestValue(dstTeam, we, currentEC);
var availableToAssign = Math.min(expCatRestValue, teamRestValue);
var transferredValue = Math.min(availableToAssign, srcTeam.QuantityValues[we]);
var newAllocatedValue = Math.max(dstTeam.QuantityValues[we] + transferredValue, 0);
var unassignedValue = Math.max(srcTeam.QuantityValues[we] - transferredValue, 0);
dstTeam.QuantityValues[we] = newAllocatedValue;
newUnassignedAllocations[we] += unassignedValue;
}
});
}
else {
// The Category hasn't the desired destination team. Copy entire allocations to UnassignedAllocations
$.each(weekEndings, function (index, we) {
if (srcTeam.QuantityValues[we] !== undefined) {
newUnassignedAllocations[we] += srcTeam.QuantityValues[we];
}
});
}
if (unassignedValuesExist(newUnassignedAllocations))
currentEC.UnassignedAllocations = newUnassignedAllocations;
else {
currentEC.UnassignedAllocations = {};
removeProjectFromUnassignedExpenditures(projectId, expCatId);
}
}
});
};
function getExpCatRestValue(expCatItem, weekEnding, excludeTeamId) {
if (!expCatItem)
throw "getExpCatRestValue: EC item not specified";
if (!expCatItem.AllowResourceAssignment) {
// Super EC has infinite Remaining capacity
return Number.MAX_VALUE;
}
if (!expCatItem.Details || !expCatItem.Details[weekEnding])
return 0;
// Alternative variant. It may be correct. We'll find out it later. Don't remove:
//var detailsValue = expCatItem.Details[weekEnding].ActualsQuantity ? expCatItem.Details[weekEnding].ActualsQuantity :
// expCatItem.Details[weekEnding].ForecastQuantity;
var detailsValue = expCatItem.Details[weekEnding].ForecastQuantity;
if (!detailsValue)
return 0;
return Math.max(detailsValue, 0);
};
function getTeamRestValue(teamItem, we, expCatItem) {
if (!expCatItem)
return 0;
if (expCatItem.AllowResourceAssignment === false)
// Super EC has infinite capacity as well as its teams
return Number.MAX_VALUE;
if (!teamItem || !teamItem.RestQuantityValues || !we)
return 0;
return Math.max((teamItem.RestQuantityValues[we] || 0), 0);
};
// Creates or recalculates EC Details for Super EC
function recalculateProjectExpCatDetails(expCatItem) {
if (!expCatItem.Teams) {
expCatItem.Details = {};
return;
}
var resultExpCatDetails = {};
angular.forEach(expCatItem.Teams, function (team, teamId) {
if (team.QuantityValues) {
angular.forEach(team.QuantityValues, function (val, we) {
if (!resultExpCatDetails[we])
resultExpCatDetails[we] = 0;
if (angular.isNumber(val))
resultExpCatDetails[we] += val;
});
}
});
// Copy calculated details to ExpCat Item
if (expCatItem.Details) {
var calculatedWeekendings = Object.keys(resultExpCatDetails);
var sourceExpCatWeekendings = Object.keys(expCatItem.Details);
angular.forEach(sourceExpCatWeekendings, function (we, index) {
if (calculatedWeekendings.indexOf(we) < 0)
delete expCatItem.Details[we];
});
}
var ord = 1;
angular.forEach(calculatedWeekendings, function (we, index) {
if (!expCatItem.Details[we]) {
expCatItem.Details[we] = {
ForecastId: null,
ForecastQuantity: 0,
ForecastCost: 0,
ActualsId: null,
ActualsQuantity: null,
ActualsCost: null,
WeekOrdinal: ord,
Changed: false
};
};
expCatItem.Details[we].ForecastQuantity = resultExpCatDetails[we];
ord++;
});
};
function unassignedValuesExist(unassignedAllocationsCollection) {
if (!unassignedAllocationsCollection)
return false;
var result = false;
angular.forEach(unassignedAllocationsCollection, function (value, key) {
result = result || (value > 0);
});
return result;
};
// 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;
};
// Returns those categories from specified list, that not exist in the specified team
function categoriesNotInTeam(expCategories, teamId) {
var teamItem = teamInfoService.getById(teamId, true);
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 * 31, // row heigh=23 + paddings=(4+4) = 31
rowsWithBorder = $scope.GridLayout[team.Id].length || 0; // number of rows with bottom border 1px each
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;
};
$scope.storeMixFilter = function (data) {
if (!data || !data.Filter || !data.Filter.Selection)
return;
$scope.data.SelectedFilterTeamsAndViews = [];
angular.forEach(data.Filter.Selection.TeamsViews, function (item, index) {
if ($scope.data.SelectedFilterTeamsAndViews.indexOf(item.Id) < 0) {
$scope.data.SelectedFilterTeamsAndViews.push(item.Id);
}
});
$scope.data.AvailableTeams = $filter('filter')(data.Filter.Variants.AvailableTeamsAndViews, { Group: { Name: 'Teams' } });
// Storing client-side filters
$scope.data.SelectedFilterCostCenters = angular.copy(data.Filter.Selection.CostCenters);
$scope.data.SelectedFilterProjectRoles = angular.copy(data.Filter.Selection.ProjectRoles);
};
function convertToDictionary(arrayOfScalarItems) {
if (!arrayOfScalarItems || !angular.isArray(arrayOfScalarItems))
return null;
var result = {}
var itemsCount = arrayOfScalarItems.length;
for (var index = 0; index < itemsCount; index++) {
var value = arrayOfScalarItems[index];
if (!(value in result)) {
result[value] = true;
}
}
return result;
};
function createAvailableExpendituresCache(weekendings, selectedCostCenters, selectedProjectRoles) {
var result = {};
var costCentersIndexed = null;
var projectRolesIndexed = {};
if (selectedCostCenters && angular.isArray(selectedCostCenters) && selectedCostCenters.length) {
// Reorganise Cost Centers list to check fast an EC fits selected cost centers
costCentersIndexed = convertToDictionary(selectedCostCenters);
}
if (selectedProjectRoles && angular.isArray(selectedProjectRoles) && selectedProjectRoles.length) {
// Reorganise selected Project Roles to perform fast checks
projectRolesIndexed = convertToDictionary(selectedProjectRoles);
}
// Create cached list of expenditures, that fit selected cost centers, for fast filtering AC cached data
var expCats = dataSources.getExpenditures();
if (expCats) {
for (var expCatId in expCats) {
var expCatItem = expCats[expCatId];
var isProjectRole = !expCatItem.AllowResourceAssignment;
var selectedByUserInFilter = expCatId in projectRolesIndexed;
var hasNonZeroAllocations = false;
if (expCatItem != null) {
// Check the EC fits filter by Cost Centers (if no any Cost Center selected, EC fits filter)
var fitsFilter = !costCentersIndexed || (expCatItem.CreditId && (expCatItem.CreditId in costCentersIndexed));
if (fitsFilter && isProjectRole) {
// If EC is a Project Role, check it has non-zero team allocations
hasNonZeroAllocations = teamInfoService.expenditureCategoryHasNonZeroTeamAllocations(expCatId, weekendings);
fitsFilter = hasNonZeroAllocations;
if (!fitsFilter) {
// Project Role has no team allocations, check if it selected in the Project Roles filter
fitsFilter = selectedByUserInFilter;
}
}
if (fitsFilter) {
// EC fits filter.
result[expCatId] = isProjectRole;
}
}
}
}
return result;
};
$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, data.NeedAllocations);
$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 || [];
$scope.Calendar.ManagedProjects = data.ManagedProjects || [];
$scope.Calendar.SuperExpenditures = data.SuperExpenditures || {};
// after mix updating we do not need to show warning about out of range projects
$scope.OutOfMixProjects = [];
$scope.showOutOfMixProjectsExistWarning = false;
$scope.GridLayout = $scope.getLayoutForClient(data.Layout);
// Store deleted objects
if (data.ModifiedObjects) {
$scope.Calendar.ModifiedObjects = data.ModifiedObjects;
}
$scope.ShowDeletedObjectsWarning = false;
if ($scope.Calendar.ModifiedObjects) {
if ($scope.Calendar.ModifiedObjects.DeletedResources && $scope.Calendar.ModifiedObjects.DeletedResources.length > 0)
$scope.ShowDeletedObjectsWarning = true;
if ($scope.Calendar.ModifiedObjects.DeletedProjects && $scope.Calendar.ModifiedObjects.DeletedProjects.length > 0)
$scope.ShowDeletedObjectsWarning = true;
}
$scope.prepareManagedToDisplay();
$scope.prepareUnscheduledToDisplay();
$scope.prepareQueuedToDisplay();
$scope.prepareUnassignedEcsProjectsToDisplay();
$scope.setMonthesExpanded(!$scope.DisplayMode.IsViewModeMonth);
$scope.recreateView();
// Warning for changed on server projects
enumerateChangedProjects();
rebuildBottomPart();
// 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 = 38;
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;
}
} 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) - 120; //- 80;
firstCell.OverEnd = cell.OverEnd;
} else {
cell.width = '20px';
}
}
if (cell.colSpan == 0)
cell.colSpan = 1;
cell.CssStyle = getProjectCSS($scope.getProjectById(cell.Id));
//drawBorder(currentTeam, i, j);
}
}
};
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;
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 && weekProject.Scenario) {
// Create and add project cell
var isFirstCell = (weekProjects[weekProject.Id] === undefined);
var isLastCell = week.Milliseconds == $scope.Calendar.EndDate;
var overStart = false;
var overEnd = false;
var changedInLiveDb = false;
var scenarioStartDateUtc = weekProject.Scenario.StartDate;
var scenarioEndDateUtc = weekProject.Scenario.EndDate;
var endDelta = Math.floor((week.Milliseconds - scenarioEndDateUtc) / 86400000);
var isProjectLastCell = (endDelta >= 0 && endDelta < 7) || (isLastCell && scenarioEndDateUtc >= week.Milliseconds);
if (isFirstCell)
overStart = Math.floor(($scope.Calendar.StartDate - scenarioStartDateUtc) / 86400000) > 7;
if (isLastCell)
overEnd = Math.floor((weekProject.Scenario.EndDate - scenarioEndDateUtc) / 86400000) > 0;
if (weekProject.Scenario.VersionInfo)
changedInLiveDb = weekProject.Scenario.VersionInfo.ChangedInMain;
var cell = createCell(weekProject.Id, weekProject.Name, '', '', isFirstCell,
overStart, overEnd, isProjectLastCell, weekProject.Pinned, changedInLiveDb, weekProject.HasDependency, weekProject.HasLink, weekProject.DependencyPinned, weekProject.DependencyToolTip);
cells.push(cell);
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;
var scenarioStartDateUtc = monthProject.Scenario.StartDate;
var scenarioEndDateUtc = monthProject.Scenario.EndDate;
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 - scenarioEndDateUtc) / 86400000) : -1;
isProjectLastMonthCell = (endDelta >= 0 && endDelta < 7) || (isLastCell && scenarioEndDateUtc >= week.Milliseconds);
if (isProjectLastMonthCell)
break;
}
var overStart = (isFirstCell && monthProject.Scenario ? (Math.floor(($scope.Calendar.StartDate - scenarioStartDateUtc) / 86400000) > 7) : false);
var overEnd = (isLastCell && monthProject.Scenario ? (Math.floor((scenarioEndDateUtc - $scope.Calendar.EndDate) / 86400000) > 0) : false);
var changedInLiveDb = (monthProject.Scenario && monthProject.Scenario.VersionInfo) ? monthProject.Scenario.VersionInfo.ChangedInMain : false;
var cell = createCell(monthProject.Id, monthProject.Name, '', '', isFirstCell,
overStart, overEnd, isProjectLastMonthCell, monthProject.Pinned, changedInLiveDb, monthProject.HasDependency, monthProject.HasLink, monthProject.DependencyPinned, monthProject.DependencyToolTip);
cells.push(cell);
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),
currentProjectStart = new Date(currentProjectNearestStartDate),
currentProjectNearestEndDate = getNearestDate(currentProject.Scenario.EndDate),
currentProjectEnd = new Date(currentProjectNearestEndDate),
// check if the project can be placed in the begin of the row
firstProjectNearestStartDate = getNearestDate(firstProject.Scenario.StartDate),
firstProjectStart = new Date(firstProjectNearestStartDate),
lastProjectNearestEndDate = getNearestDate(lastProject.Scenario.EndDate),
lastProjectEnd = new Date(lastProjectNearestEndDate);
if (currentProjectNearestEndDate < firstProjectNearestStartDate) {
projectCovers = currentProject.Scenario.EndDate -
Math.max($scope.Calendar.StartDate, currentProject.Scenario.StartDate);
var remaining = layoutFillingRemaining[j] - projectCovers;
if (currentProjectEnd.getUTCMonth() != firstProjectStart.getUTCMonth()) {
if (remaining < minRemaining) {
minRemaining = remaining;
optimalX = j;
optimalY = 0;
}
} else {
//optimalX = j;
//optimalY = z + 1;
}
}
// 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 (currentProjectStart.getUTCMonth() != lastProjectEnd.getUTCMonth()) {
if (remaining < minRemaining) {
minRemaining = remaining;
optimalX = j;
optimalY = layout[j].length;
}
} else {
//optimalX = j;
//optimalY = z + 1;
}
}
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);
var d1 = new Date($scope.Calendar.FiscalCalendarWeekEndings[binarySearch($scope.Calendar.FiscalCalendarWeekEndings,
prevProjectNearestEndDate, 0, $scope.Calendar.FiscalCalendarWeekEndings.length, true)]);
var d2 = new Date($scope.Calendar.FiscalCalendarWeekEndings[binarySearch($scope.Calendar.FiscalCalendarWeekEndings,
nextProjectNearestStartDate, 0, $scope.Calendar.FiscalCalendarWeekEndings.length, true)]);
// 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 (currentProjectEnd.getUTCMonth() != d2.getUTCMonth() && currentProjectStart.getUTCMonth() != d1.getUTCMonth()) {
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))
foundProject;
for (var index = 0; index < mapRow.length; index++) {
if (mapRow[index].Scenario) {
var scenarioStartDate = mapRow[index].Scenario.StartDate;
var scenarioEndDate = mapRow[index].Scenario.EndDate;
if ((getNearestDate(scenarioStartDate) <= week.Milliseconds) &&
(getNearestDate(scenarioEndDate) >= 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);
}
}
};
function removeProjectFromLayout(projectId) {
if (!projectId || !$scope.GridLayout) {
return;
}
for (var teamId in $scope.GridLayout) {
removeProjectFromTeamLayout(projectId, teamId);
}
};
function getProjectRowIndexTeamLayout(projectId, teamId) {
if (!$scope.GridLayout || !$scope.GridLayout[teamId] || ($scope.GridLayout[teamId].length < 1)) {
return;
}
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) {
return rowIndex;
}
}
}
return -1;
};
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, fromProjectId) {
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);
var sourceProjectName = '';
//moved to their own function
if (!$scope.CanMoveProjectInsideTeam(project.Id, shiftX))
return;
project.Scenario.StartDate += (shiftedStartDate - nearestStartDate);
project.Scenario.EndDate += (shiftedEndDate - nearestEndDate);
if (!isScenarioLoaded(project.Scenario)) {
loadScenarioDetails(project.Scenario.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;
var isRedrawBottomRequired = false;
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: {} });
// add super expenditure to the team if it does not exist yet
teamInfoService.addResourceToSuperEC(targetTeam.Id, expCatId, resourceId);
isRedrawBottomRequired = true;
}
compareValuesAndTriggerChanges(targetTeam.Resources[resourceId].QuantityValues, sourceTeam.Resources[resourceId].QuantityValues, targetTeam.Id, expCatId, resourceId, isRedrawBottomRequired);
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];
teamInfoService.removeAssignedResourceFromSuperEC(targetTeam.Id, expCatId, resourceId);
isRedrawBottomRequired = true;
}
}
}
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;
}
}
}
if (isRedrawBottomRequired)
$scope.$broadcast('queue.recreateRows');
};
function compareValuesAndTriggerChanges(oldQuantityValues, newQuantityValues, teamId, expCatId, resourceId, isRedrawBottomRequired) {
if ((!oldQuantityValues && !newQuantityValues) || !teamId || !expCatId)
return;
if (!oldQuantityValues)
oldQuantityValues = {};
if (!newQuantityValues)
newQuantityValues = {};
var keys = union(Object.keys(oldQuantityValues), Object.keys(newQuantityValues));
for (var i = 0; i < keys.length; i++) {
var weekDateMs = parseFloat(keys[i]);
if (weekDateMs > 0) {
var oldValue = oldQuantityValues[weekDateMs] || 0;
var newValue = newQuantityValues[weekDateMs] || 0;
if (newValue === oldValue)
continue;
if (!resourceId)
teamValueChanged(teamId, expCatId, weekDateMs, (newValue - oldValue));
else
resourceValueChanged(teamId, expCatId, resourceId, weekDateMs, (newValue - oldValue), isRedrawBottomRequired);
}
}
};
function copyExpendituresNeedValues(scenarioId, targetScenarioData, sourceScenarioData) {
if (!scenarioId || !targetScenarioData || !sourceScenarioData)
return [];
if (!targetScenarioData.Expenditures) {
targetScenarioData.Expenditures = {};
}
var changes = [];
if (sourceScenarioData.Expenditures) {
for (var expCatId in sourceScenarioData.Expenditures) {
var sourceExpCat = sourceScenarioData.Expenditures[expCatId];
var targetExpCat = (expCatId in targetScenarioData.Expenditures) ? targetScenarioData.Expenditures[expCatId] : null;
if (!targetExpCat) {
// Target scenario has no expenditure in it's data set. Perform adding
targetExpCat = angular.copy(sourceExpCat);
targetExpCat.Teams = {};
targetExpCat.Details = {};
targetScenarioData.Expenditures[expCatId] = targetExpCat;
}
if (!targetExpCat.Details) {
targetExpCat.Details = {};
}
// Get weekendings for values to add, remove and modify
var sourceWeeks = sourceExpCat.Details ? Object.keys(sourceExpCat.Details) : [];
var targetWeeks = Object.keys(targetExpCat.Details);
var weeksToAdd = $filter('filter')(sourceWeeks, function (value, index, array) {
return targetWeeks.indexOf(value) < 0;
});
var weeksToRemove = $filter('filter')(targetWeeks, function (value, index, array) {
return sourceWeeks.indexOf(value) < 0;
});
var weeksToCheckChanged = $filter('filter') (sourceWeeks, function (value, index, array) {
return targetWeeks.indexOf(value) >= 0;
});
if (sourceExpCat.Details) {
// Perform adding need values to target expenditure
for (var weIndex = 0; weIndex < weeksToAdd.length; weIndex++) {
var we = weeksToAdd[weIndex];
var newValue = angular.isNumber(sourceExpCat.Details[we].ForecastQuantity) && !isNaN(sourceExpCat.Details[we].ForecastQuantity) ? Number(sourceExpCat.Details[we].ForecastQuantity): 0;
targetExpCat.Details[we] = angular.copy(sourceExpCat.Details[we]);
changes.push({
scenarioId: scenarioId,
expCatId: expCatId,
weekending: we,
value: newValue,
action: 'add'
});
}
// Perform changing need values in target expenditure
for (var weIndex = 0; weIndex < weeksToCheckChanged.length; weIndex++) {
var we = weeksToCheckChanged[weIndex];
var newValue = angular.isNumber(sourceExpCat.Details[we].ForecastQuantity) && !isNaN(sourceExpCat.Details[we].ForecastQuantity) ? Number(sourceExpCat.Details[we].ForecastQuantity) : 0;
var currentValue = angular.isNumber(targetExpCat.Details[we].ForecastQuantity) && !isNaN(targetExpCat.Details[we].ForecastQuantity) ? Number(targetExpCat.Details[we].ForecastQuantity) : 0;
var delta = newValue - currentValue;
if (delta != 0) {
targetExpCat.Details[we] = angular.copy(sourceExpCat.Details[we]);
targetExpCat.Details[we].ForecastQuantity = newValue;
changes.push({
scenarioId: scenarioId,
expCatId: expCatId,
weekending: we,
value: newValue,
action: 'change'
});
}
}
}
// Perfrom deleting of values in target data set
for (var weIndex = 0; weIndex < weeksToRemove.length; weIndex++) {
var we = weeksToRemove[weIndex];
delete targetExpCat.Details[we];
changes.push({
scenarioId: scenarioId,
expCatId: expCatId,
weekending: we,
action: 'remove'
});
}
}
}
if (changes.length) {
// Perform data changes in data service
teamInfoService.changeExpenditureNeedValues(changes);
}
return changes;
};
// 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;
}
var needToRecreateRowsInBottomPart = false;
var sourceScenarioData = {
Expenditures: data
};
var changesInNeedValues = copyExpendituresNeedValues(project.Scenario.Id, project.Scenario, sourceScenarioData);
needToRecreateRowsInBottomPart = changesInNeedValues && angular.isArray(changesInNeedValues) && changesInNeedValues.length > 0;
for (var expCatId in data) {
if (!project.Scenario.Expenditures[expCatId]) {
project.Scenario.Expenditures[expCatId] = angular.extend({}, data[expCatId],
{
Teams: {}
});
}
else {
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];
});
}
// Copy anassigned allocations for current EC from DATA to Project
project.Scenario.Expenditures[expCatId].UnassignedAllocations = angular.copy(data[expCatId].UnassignedAllocations);
}
if (doProjectCleaning) {
var expendituresInCurrentScenario = Object.keys(project.Scenario.Expenditures);
var expendituresInData = Object.keys(data);
var deletedExpenditures = $.grep(expendituresInCurrentScenario, function (ec, index) {
return ($.inArray(ec, expendituresInData) < 0);
});
for (var i = 0; i < deletedExpenditures.length; i++) {
var expCatId = deletedExpenditures[i];
var ec = project.Scenario.Expenditures[expCatId];
if (!ec)
continue;
if (ec.Teams) {
for (var teamId in ec.Teams) {
var projectTeam = project.Scenario.Expenditures[expCatId].Teams[teamId];
var teamKiller = createZeroAllocationsTeamFromTeam(projectTeam);
copyQuantityValues(expCatId, projectTeam, teamKiller);
if (!ec.AllowResourceAssignment) {
teamInfoService.removeAssignedExpenditure(teamId, expCatId);
}
}
}
delete project.Scenario.Expenditures[expCatId];
needToRecreateRowsInBottomPart = true;
}
}
if (needToRecreateRowsInBottomPart) {
$scope.$broadcast('queue.recreateRows');
}
// SetResourceRaceCondition();
};
function SetResourceRaceCondition() {
var postData = {};
$scope.createSaveDataPackage(postData);
var request = getAntiXSRFRequest($scope.dataUrls.CanDoRaceUrl, postData.Calendar);
try {
$http(request).success(function (data, status, headers, config) {
try {
var CanRunResourceRace = data.allowResourceRace;
var CanRunTeamRace = data.CanRunTeamRace;
var canDoRace = data.allowRace;
$rootScope.$broadcast('resourceCountChanged', CanRunResourceRace, canDoRace);
$rootScope.$broadcast('SuperECCountChanged', CanRunTeamRace, canDoRace);
} catch (e) {
console.error(e);
unblockUI();
showErrorModal();
}
}).error(function (data, status, headers, config) {
console.error('A server error occurred in ' + $scope.dataUrls.CanDoRaceUrl + ' action');
unblockUI();
showErrorModal();
});
} catch (e) {
console.error(e);
unblockUI();
showErrorModal();
}
};
// 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;
};
// Return the copy of Project.Scenario.Expenditures for specified project with all allocations turned to zero
function createProjectKiller(project) {
if (!project || !project.Scenario || !project.Scenario.Expenditures)
return null;
var projectKiller = angular.copy(project.Scenario.Expenditures);
var expKeys = Object.keys(projectKiller);
for (var expKeyIndex = 0; expKeyIndex < expKeys.length; expKeyIndex++) {
var currentExpCat = projectKiller[expKeys[expKeyIndex]];
if (currentExpCat.Teams) {
var teamKeys = Object.keys(currentExpCat.Teams);
for (var teamKeyIndex = 0; teamKeyIndex < teamKeys.length; teamKeyIndex++) {
var currentTeam = currentExpCat.Teams[teamKeys[teamKeyIndex]];
currentExpCat.Teams[teamKeys[teamKeyIndex]] = createZeroAllocationsTeamFromTeam(currentTeam);
}
}
}
return projectKiller;
};
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.moveProjectInsideTeamForRace = function (projectId, teamId, shiftX, targetRow, insertBefore, callbackFn) {
if (!projectId)
if (!!callbackFn && typeof callbackFn === 'function') {
callbackFn();
}
shiftX = shiftX || 0;
var project = $scope.getProjectById(projectId);
if (!project || !project.Scenario)
if (!!callbackFn && typeof callbackFn === 'function') {
callbackFn();
}
console.log('Pre async ' + project.Name + ' ' + shiftX + ' ' + targetRow);
changeProjectRange(project, shiftX, function () {
var sourceRow = getProjectRowIndexTeamLayout(projectId, teamId);
var rowToInsertIn = targetRow;
console.log('Post async ' + project.Name + ' ' + shiftX + ' ' + targetRow);
// Check for projects intersection within row
if (!insertBefore && checkProjectsIntersection(project, teamId, targetRow)) {
rowToInsertIn++;
insertBefore = true;
}
if (sourceRow != rowToInsertIn) {
$scope.updateProjectInLayout(projectId, teamId, sourceRow, rowToInsertIn, insertBefore);
}
project = $scope.getProjectById(projectId);
// Move copies of the project in other teams
synchronizeProjectInLayoutTeamBlocks(project, teamId);
$scope.recreateView();
if (!!callbackFn && typeof callbackFn === 'function') {
callbackFn();
}
});
};
function buildAndMoveProjectDependancies(project, teamId, shiftX, sourceRow, targetRow, insertBefore) {
var count = project.AllLinksAndDependencies.length;
var canMove=true;
var onItem = 0;
for (var idx in project.AllLinksAndDependencies){
//project.AllLinksAndDependencies.forEach(function (projectid) {
var projectid = project.AllLinksAndDependencies[idx];
if (canMove)
canMove = $scope.CanMoveProjectInsideTeam(projectid, shiftX);
}
if (canMove) {
for (var idx in project.AllLinksAndDependencies) {
//project.AllLinksAndDependencies.forEach(function (projectid) {
var projectid = project.AllLinksAndDependencies[idx];
var p = $scope.getProjectById(projectid);
var sr = getProjectRowIndexTeamLayout(projectid, teamId);
var rowToInsertIn = sr;
var isb = true;
$scope.moveProjectInsideTeam(projectid, teamId, shiftX, sr, rowToInsertIn, isb, true)
}
}
return canMove;
};
$scope.CanMoveProjectInsideTeam = function (projectId, shiftX) {
var project = $scope.getProjectById(projectId);
if (!project || !project.Scenario || shiftX == 0)
return false;
var nearestStartDate = getNearestDate(project.Scenario.StartDate);
var nearestEndDate = getNearestDate(project.Scenario.EndDate);
var shiftedStartDate = getNewDate(nearestStartDate, shiftX);
var shiftedEndDate = getNewDate(nearestEndDate, shiftX);
var sourceProjectName = '';
if (!shiftedStartDate) {
if (!$scope.Calendar.FiscalCalendarWeekEndings || $scope.Calendar.FiscalCalendarWeekEndings.length <= 0) {
bootbox.alert('Fiscal Calendar is incorrect');
} else {
var startDateAsText = DateTimeConverter.msFormatAsUtcString($scope.Calendar.FiscalCalendarWeekEndings[0]);
bootbox.alert('Financial Calendar starts on ' + startDateAsText + '. You cannot move scenario for ' + project.Name + ' so it exceeds the date range of Financial Calendar.');
}
return false;
}
if (!shiftedEndDate) {
if (!$scope.Calendar.FiscalCalendarWeekEndings || $scope.Calendar.FiscalCalendarWeekEndings.length <= 0) {
bootbox.alert('Fiscal Calendar is incorrect');
} else {
var endDateAsText = DateTimeConverter.msFormatAsUtcString($scope.Calendar.FiscalCalendarWeekEndings[$scope.Calendar.FiscalCalendarWeekEndings.length - 1]);
bootbox.alert('Financial Calendar ends on ' + endDateAsText + '. You cannot move scenario for '+project.Name+' so it exceeds the date range of Financial Calendar. ');
}
return false;
}
if (project.Deadline > 0 && shiftedEndDate > project.Deadline) {
var deadline = DateTimeConverter.msToUtcDate(project.Deadline);
var deadlineStr = (deadline.getMonth() + 1) + '/' + deadline.getDate() + '/' + deadline.getFullYear();
bootbox.alert('Scenario End Date for '+project.Name+' should not exceed Project Deadline date on ' + deadlineStr);
return false;
}
return true;
};
$scope.moveProjectInsideTeam = function (projectId, teamId, shiftX, sourceRow, targetRow, insertBefore, isChildCall) {
if (!projectId)
return;
shiftX = shiftX || 0;
var project = $scope.getProjectById(projectId);
if (!project || !project.Scenario)
return;
if ((project.HasDependency || project.HasLink) && !isChildCall) {
if (!$scope.CanMoveProjectInsideTeam(projectId, shiftX))
return;
if (!buildAndMoveProjectDependancies(project, teamId, shiftX, sourceRow, targetRow, insertBefore))
return;
}
changeProjectRange(project, shiftX, function () {
var rowToInsertIn = targetRow;
// Check for projects intersection within row
if (!insertBefore && checkProjectsIntersection(project, teamId, targetRow)) {
rowToInsertIn++;
insertBefore = true;
}
if (sourceRow != rowToInsertIn) {
$scope.updateProjectInLayout(projectId, teamId, sourceRow, rowToInsertIn, insertBefore);
}
project = $scope.getProjectById(projectId);
// Move copies of the project in other teams
synchronizeProjectInLayoutTeamBlocks(project, teamId);
$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, 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], true))
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);
}
}
};
function fillExpenditures4TeamById(project, teamId) {
if (!project || !project.Scenario || !teamId) {
return;
}
var team = teamInfoService.getById(teamId, true);
if (!team || !team.ExpCategories) {
return;
}
fillExpenditures4Team(project, team);
};
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) {
var ecInScenario = project.Scenario.Expenditures[expCatId];
var isNewTeam = !ecInScenario.Teams[team.Id];
var ecTeam = ecInScenario.Teams[team.Id] || {
Id: team.Id,
QuantityValues: {},
AllResources: {},
CanBeDeleted: false,
Changed: false,
Collapsed: true,
CollapsedClass: "fa-plus-square",
IsAccessable: true,
Name: team.Name,
CapacityQuantityValues: {},
Resources: {},
RestQuantityValues: {}
};
if (team.ExpCategories[expCatId]) {
// Take team resources for this EC from mix teams collection
if (!team.ExpCategories[expCatId].Resources) {
continue;
}
for (var resourceId in team.ExpCategories[expCatId].Resources) {
if (ecTeam.AllResources[resourceId])
continue;
var resource = team.ExpCategories[expCatId].Resources[resourceId];
resource = teamInfoService.extendResourceModelWithAttributes(resource, team.Id);
ecTeam.AllResources[resourceId] = {
Id: resourceId,
Name: resource.Name,
QuantityValues: {}
};
}
if (isNewTeam)
for (var i = startDateIndex; i <= endDateIndex; i++) {
var date = $scope.Calendar.FiscalCalendarWeekEndings[i];
ecTeam.QuantityValues[date] = 0;
for (var resourceId in team.ExpCategories[expCatId].Resources) {
ecTeam.AllResources[resourceId].QuantityValues[date] = 0;
}
}
}
else {
if ($scope.Calendar.SuperExpenditures[expCatId]) {
// Take team resources for this EC from all scenario teams
$.each(team.ExpCategories, function (teamExpCatId, teamExpCatItem) {
if (teamExpCatItem.Resources) {
for (var resourceId in teamExpCatItem.Resources) {
if (ecTeam.AllResources[resourceId])
continue;
var resource = teamExpCatItem.Resources[resourceId];
resource = teamInfoService.extendResourceModelWithAttributes(resource, team.Id);
ecTeam.AllResources[resourceId] = {
Id: resourceId,
Name: resource.Name,
QuantityValues: {}
};
}
}
});
if (isNewTeam)
for (var i = startDateIndex; i <= endDateIndex; i++) {
var date = $scope.Calendar.FiscalCalendarWeekEndings[i];
var currentEcTeam = ecTeam;
currentEcTeam.QuantityValues[date] = 0;
$.each(currentEcTeam.AllResources, function (resourceId, resourceItem) {
resourceItem.QuantityValues[date] = 0;
});
}
}
}
if (Object.keys(ecTeam.Resources).length || Object.keys(ecTeam.AllResources).length)
ecInScenario.Teams[team.Id] = ecTeam;
}
};
function removeTeamFromScenario(project, teamId) {
if (!project || !project.Scenario || !teamId) {
return;
}
var team = teamInfoService.getById(teamId, true);
if (!team || !team.ExpCategories) {
return;
}
if (!project.Scenario.Expenditures) {
return;
}
// 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)) {
// Completelly remove the project from layout (from all teams in the calendar)
removeProjectFromLayout(project.Id);
removeProjectFromManaged(project.Id);
// 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);
}
rebuildBottomPart();
setDataChanged(true, projectId);
};
// 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)
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 (!!targetTeamId && !$scope.projectHasTeam(project, targetTeamId))
project.Teams.push(targetTeamId);
pushProjectToManaged(project);
removeProjectFromUnscheduled(project.Id);
if (!!targetTeamId)
addProjectToLayout(project.Id, targetTeamId, (targetRow || 0) < 0 ? 0 : targetRow, true);
synchronizeProjectAndScenarioTeams(project);
};
function synchronizeProjectAndScenarioTeams(project) {
if (!project)
return;
var teamsInProject = project.Teams || [];
// 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 < teamsInProject.length; i++) {
fillExpenditures4TeamById(project, teamsInProject[i]);
}
synchronizeProjectAndScenarioTeamsInLayout(project);
};
function synchronizeProjectAndScenarioTeamsInLayout(project) {
if (!project || !project.Scenario)
return;
project.Teams = union((project.Teams || []), getTeamsInScenario(project.Scenario));
for (var i = 0; i < project.Teams.length; i++) {
var teamId = project.Teams[i];
var layout = $scope.GridLayout[teamId];
if (!layout)
continue;
var projectPositionInLayout = findProjectPositionInLayout(project.Id, layout);
if (projectPositionInLayout)
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;
}
var d1 = new Date($scope.Calendar.FiscalCalendarWeekEndings[binarySearch($scope.Calendar.FiscalCalendarWeekEndings, checkableProject.Scenario.EndDate, 0, $scope.Calendar.FiscalCalendarWeekEndings.length, true)]);
var d2 = new Date($scope.Calendar.FiscalCalendarWeekEndings[binarySearch($scope.Calendar.FiscalCalendarWeekEndings, projectNearestStartDate, 0, $scope.Calendar.FiscalCalendarWeekEndings.length, true)]);
var d3 = new Date($scope.Calendar.FiscalCalendarWeekEndings[binarySearch($scope.Calendar.FiscalCalendarWeekEndings, checkableProject.Scenario.StartDate, 0, $scope.Calendar.FiscalCalendarWeekEndings.length, true)]);
var d4 = new Date($scope.Calendar.FiscalCalendarWeekEndings[binarySearch($scope.Calendar.FiscalCalendarWeekEndings, projectNearestEndDate, 0, $scope.Calendar.FiscalCalendarWeekEndings.length, true)]);
// return true if dates month intersect
if (d1.getUTCMonth() == d2.getUTCMonth() || d3.getUTCMonth() == d4.getUTCMonth()) {
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 isProjectInUnscheduled(projectId) {
var result = false;
if (!projectId || (projectId.length < 1))
return result;
for (var i = 0; i < $scope.Calendar.UnscheduledProjects.length; i++) {
if ($scope.Calendar.UnscheduledProjects[i].Id == projectId) {
result = true;
break;
}
}
return result;
};
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) {
var project = $scope.Calendar.Projects[projectId];
return project;
};
// Returns project by its scenarioId. Searches in Active and Inactive scenarios
$scope.getProjectByScenarioId = function (scenarioId) {
if (!scenarioId || !$scope.Calendar.Projects)
return null;
var foundProject = null;
angular.forEach($scope.Calendar.Projects, function (project, index) {
if (project) {
if (project.Scenario && (project.Scenario.Id == scenarioId)) {
foundProject = project;
}
if (project.InactiveScenarios && project.InactiveScenarios[scenarioId])
foundProject = project;
}
});
return foundProject;
};
function createCell(id, name, cssClass, cssStyle, isFirstCell, overStart, overEnd, isProjectLastCell,
pinned, changedInLiveDb, hasDependency, hasLink, dependencyPinned, dependencyToolTip) {
var cell = {
Id: id,
Title: name,
CssClass: cssClass,
CssStyle: cssStyle,
IsFirstCell: isFirstCell,
OverStart: overStart,
OverEnd: overEnd,
IsProjectLastCell: isProjectLastCell,
Pinned: pinned,
ChangedInLiveDb: changedInLiveDb,
HasDependency: hasDependency,
HasLink: hasLink,
DependencyToolTip: dependencyToolTip,
DependencyPinned: dependencyPinned
};
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) {
if (project.Scenario)
project.Scenario = null;
var css = getProjectCSS(project);
var projData = {
Id: project.Id,
Name: project.Name,
CssStyle: css
};
$scope.Calendar.UnscheduledProjects.push(projData);
};
function isProjectInUnassignedExpenditures(projectId, expCatId) {
return $scope.Calendar.UnassignedExpendituresProjects.some(function (data) {
return (data.ProjectId === projectId) && (data.ExpCatId === expCatId);
});
};
$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;
};
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;
}
};
// 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 isProjectInQueue(projectId) {
return $scope.Calendar.Queue4UnscheduledProjects.some(function (project) {
return project.Id === projectId;
});
};
function moveProjectToQueue(project) {
if (!project)
return;
removeProjectFromLayout(project.Id);
removeProjectFromManaged(project.Id);
$scope.pushProjectToQueue(project);
$scope.OutOfMixProjects.push(project.Name);
$scope.showOutOfMixProjectsExistWarning = true;
};
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;
}
}
$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;
}
};
$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: 1px solid #a0a0a0 !important;';
}
return css;
};
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, resetVersionInfo) {
dataPackage.Calendar = {};
dataPackage.Calendar.Projects = {};
dataPackage.Calendar.ManagedProjects = [];
dataPackage.Calendar.UnscheduledProjects = [];
dataPackage.Calendar.QueuedProjects = [];
dataPackage.Calendar.UnassignedExpendituresProjects = [];
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);
}
// 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) {
var packageProject = angular.copy($scope.getProjectById(projectId));
dataPackage.Calendar.Projects[projectId] = packageProject;
if (resetVersionInfo && packageProject && packageProject.Scenario) {
packageProject.Scenario.VersionInfo = GetDefaultScenarioVersionInfo();
}
}
// Saving grid layout
dataPackage.Calendar.Layout = $scope.getLayoutForServer($scope.GridLayout);
// Saving teams
var teams = teamInfoService.getAll(true);
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);
});
}
};
// Creates default sctruct of VersionInfo for scenario
function GetDefaultScenarioVersionInfo() {
return {
SourceVersion: null,
RmoVersion: 1,
ChangedInMain: false,
ChangedInRmo: false
};
};
$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;
};
function loadScenarioDetails(scenarioId, successCallbackFn) {
var scenarios = [];
scenarios.push(scenarioId);
loadMultipleScenarioDetails(scenarios, function (loadedScenarios) {
if (loadedScenarios && (Object.keys(loadedScenarios).length > 0) && angular.isFunction(successCallbackFn)) {
var keys = Object.keys(loadedScenarios);
successCallbackFn(loadedScenarios[keys[0]]);
}
});
};
function loadMultipleScenarioDetails(scenarios, successCallbackFn) {
blockUI();
var requestData = {
MixId: $scope.MixId,
Scenarios: scenarios
};
var request = getAntiXSRFRequest($scope.dataUrls.getScenarioDetailsUrl, requestData);
try {
$http(request).success(function (scenarios, status, headers, config) {
try {
if (!scenarios) {
unblockUI();
return;
}
// remove deleted resources from scenario model
if ($scope.Calendar.ModifiedObjects.DeletedResources) {
angular.forEach(scenarios, function (scItem, scKey) {
angular.forEach(scItem, function (ecItem, ecKey) {
if (ecItem.Teams)
angular.forEach(ecItem.Teams, function (tItem, tKey) {
angular.forEach($scope.Calendar.ModifiedObjects.DeletedResources, function (resItem, resKey) {
delete tItem.AllResources[resKey];
delete tItem.Resources[resKey];
});
});
});
});
}
if (angular.isFunction(successCallbackFn)) {
successCallbackFn(scenarios);
}
unblockUI();
} catch (e) {
console.error(e);
unblockUI();
showErrorModal();
}
}).error(function (data, status, headers, config) {
console.error('A server error occurred in ' + $scope.dataUrls.getScenarioDetailsUrl + ' action');
unblockUI();
showErrorModal();
});
} catch (e) {
console.error(e);
unblockUI();
showErrorModal();
}
};
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 teamValueChanged(teamId, expCatId, weekEndingMs, deltaValue) {
teamInfoService.changeExpenditureCategoryValue(teamId, expCatId, weekEndingMs, deltaValue, true);
$scope.$broadcast('teamValueChanged', {
TeamId: teamId,
ExpenditureCategoryId: expCatId,
WeekEnding: weekEndingMs
});
};
// Fires event after resource cell value has been changed
function resourceValueChanged(teamId, expCatId, resourceId, weekEndingMs, deltaValue, isRedrawBottomRequired) {
teamInfoService.changeResourceValue(teamId, expCatId, resourceId, weekEndingMs, deltaValue, true);
// broadcast weekly cell value change event only if entire bottom part redraw is not required
if (!isRedrawBottomRequired) {
$scope.$broadcast('resourceValueChanged', {
TeamId: teamId,
ExpenditureCategoryId: expCatId,
ResourceId: resourceId,
WeekEnding: weekEndingMs
});
}
};
/* Events Triggers End */
$scope.createScenario = function (model, createScenarioModel) {
if (!model || !model.Scenario || !model.Calendar || !createScenarioModel || !createScenarioModel.ProjectId || !createScenarioModel.TargetTeam)
return;
var promptToChangeView = (model.Scenario.EndDate < $scope.Calendar.StartDate) ||
(model.Scenario.StartDate > $scope.Calendar.EndDate);
var backup = angular.copy($scope.Calendar);
var project = $scope.getProjectById(createScenarioModel.ProjectId);
project.Scenario = {
Duration: model.Scenario.Duration,
StartDate: model.Scenario.StartDate,
EndDate: model.Scenario.EndDate,
Expenditures: {},
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
ParentId: model.Scenario.ParentId,
TemplateId: model.Scenario.TemplateId,
Type: model.Scenario.Type,
IsBottomUp: model.Scenario.IsBottomUp,
IsNew: true,
VersionInfo: {
RmoVersion: 1,
ChangedInRmo: true
},
FinInfo: {
ProjectedRevenue: model.Scenario.ProjectedRevenue,
UseLMMargin: model.Scenario.UseLMMargin,
GrossMargin: model.Scenario.GrossMargin,
LMMargin: model.Scenario.LMMargin,
TDDirectCosts: model.Scenario.TDDirectCosts,
LaborSplitPercentage: ((!model.Scenario.CGSplit && model.Scenario.CGSplit != 0) ? 1 : model.Scenario.CGSplit) * 100,
EFXSplit: model.Scenario.EFXSplit,
CostSaving: model.Scenario.CostSavings
}
};
blockUI();
mixProjectService.recalculateScenarioFinInfo(angular.extend({}, project.Scenario, { Expenditures: model.Calendar.Expenditures })).then(function (finInfo) {
try {
project.Scenario.FinInfo = finInfo;
refreshProjectData(project, model.Calendar.Expenditures, true);
createScenarioCallback(promptToChangeView, createScenarioModel, project);
if (!promptToChangeView)
$scope.recreateView();
setDataChanged(true, project.Scenario.ParentId);
}
catch (e) {
console.error(e);
$scope.Calendar = backup;
showErrorModal();
};
}).then(null, function () {
$scope.Calendar = backup;
showErrorModal();
}).finally(function () {
unblockUI();
});
};
function createScenarioCallback(promptToChangeView, createScenarioModel, project) {
if (!createScenarioModel || !project)
return;
if (!promptToChangeView) {
// Project fit mix dates and should be displayed in the Calendar
// some interface logic for project displaying
if (!isProjectInManaged(createScenarioModel.ProjectId)) {
addProjectFromUnscheduled(createScenarioModel.ProjectId, createScenarioModel.TargetTeam.Id, createScenarioModel.TargetRow || 1);
}
}
else {
// Project dates are out of the mix dates
var extendedViewDates = getExtendedViewDates(project.Scenario.StartDate, project.Scenario.EndDate,
$scope.Calendar.StartDate, $scope.Calendar.EndDate);
var promptText = getChangeViewPrompt(extendedViewDates.StartDate, extendedViewDates.EndDate);
$timeout(function () {
bootbox.dialog({
message: promptText,
buttons: {
success: {
label: "OK",
className: "btn-success",
callback: function () {
if (!isProjectInManaged(createScenarioModel.ProjectId)) {
addProjectFromUnscheduled(createScenarioModel.ProjectId, createScenarioModel.TargetTeam.Id, createScenarioModel.TargetRow || 1);
}
// Go expanding current view
$rootScope.$broadcast("changeDisplayView", extendedViewDates.StartDate, extendedViewDates.EndDate);
}
},
cancel: {
label: "Keep Current Date Range",
className: "btn-primary",
callback: function () {
// Move the project to Queued section, because now it has a scenario
removeProjectFromUnscheduled(project.Id);
$scope.pushProjectToQueue(project, true);
}
}
}
});
});
}
};
$scope.editPinProject = function (id, teamid, state, isChild) {
for (var i in $scope.Calendar.Teams) {
var currentTeam = $scope.Calendar.Teams[i];
for (var k in currentTeam.Allocations) {
var allocation = currentTeam.Allocations[k];
for (var j = 0; j < allocation.Cells.length; j++) {
var cell = allocation.Cells[j];
if (cell.Id == id)
cell.Pinned = state;
}
}
}
//cell.Pinned = state;
var project = $scope.getProjectById(id);
project.Pinned = state;
$("#menu_dd_" + id + "_" + teamid).removeClass("open");
setDataChanged(true);
getProjectCSS(project);
if (project.AllLinksAndDependencies.length > 0 && !isChild)
pinnDependencies(project, teamid, state);
return false;
};
function pinnDependencies(project, teamid, state) {
project.AllLinksAndDependencies.forEach(function (projectid) {
var p = $scope.getProjectById(projectid);
p.Pinned = project.Pinned;
$scope.editPinProject(projectid, teamid, state, true);
});
};
$scope.editScenarioDetails = function (projectId, pinned) {
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, function (expenditures) {
setScenarioExpenditures(project.Scenario, expenditures);
loadScenarioDetailsEditForm(project.Scenario, pinned);
});
}
};
function loadScenarioDetailsEditForm(scenario, pinned) {
if (!scenario)
return;
blockUI();
var project = $scope.getProjectById(scenario.ParentId);
// we should send scenario w/o expenditures for reducing a traffic (this property do not use at this action method)
var requestData = {
Scenario: angular.extend({}, scenario, { Pinned: pinned, Expenditures: null }),
TeamsInScenario: union((project.Teams || []), getTeamsInScenario(scenario)),
MixScenarioDependencyData: getScenarioDates()
};
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
refreshScenarioTeamsCapacity(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', destroyAngularForm);
var $element = $compile($html)($scope);
var data = {
Expenditures: scenario.Expenditures,
Rates: scenario.Rates,
CostSaving: scenario.FinInfo.CostSaving
};
$document.trigger('rmo.open-scenario-details-window', { html: $element });
$scope.$broadcast('changeScenarioDetails', data);
unblockUI();
} catch (e) {
console.error(e);
unblockUI();
showErrorModal();
}
}).error(function (data, status, headers, config) {
console.error('A server error occurred in ' + $scope.dataUrls.editScenarioDetailsUrl + ' action');
unblockUI();
showErrorModal();
});
} catch (e) {
console.error(e);
unblockUI();
showErrorModal();
}
};
function getScenarioDates() {
var rt = [];
var idx = 0;
angular.forEach($scope.Calendar.Projects, function (value, key) {
if (value.HasDependency || value.HasLink) {
if (value.Scenario) {
rt[idx] = { ProjectId: value.Id, ScenaroId: value.Scenario.Id, StartDate: value.Scenario.StartDate, EndDate: value.Scenario.EndDate };
idx++;
}
}
});
return rt;
};
$scope.editScenarioTeams = function (projectId) {
if (!projectId)
return;
var project = $scope.getProjectById(projectId);
if (!project || !project.Scenario)
return;
if (isScenarioLoaded(project.Scenario)) {
fillEditTeamsForm(project);
}
else {
loadScenarioDetails(project.Scenario.Id, function (expenditures) {
setScenarioExpenditures(project.Scenario, expenditures);
fillEditTeamsForm(project);
});
}
};
$scope.resetAddExpCatsAndTeams = 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.resetAddExpCatsAndTeams();
$scope.AddTeamForm.ProjectId = project.Id;
// 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
var allInTeam = teamInfoService.getAll(true);
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
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) {
if (($scope.AddTeamForm.ProjectInTeams2Add && $scope.AddTeamForm.ProjectInTeams2Add.length > 0) ||
($scope.AddTeamForm.ProjectOutTeams2Add && $scope.AddTeamForm.ProjectOutTeams2Add.length > 0) ||
($scope.AddTeamForm.ProjectExpCats2Add && $scope.AddTeamForm.ProjectExpCats2Add.length > 0)) {
blockUI();
var project = $scope.getProjectById($scope.AddTeamForm.ProjectId);
// add expenditures/
$scope.addProjectExpCats(project).then(function () {// success callback
return $scope.addTeams2Project(project);
}).then(function (result) {// success callback
// commit project changes to Calendar
$scope.Calendar.Projects[project.Id] = project;
//clear form
$scope.resetAddExpCatsAndTeams();
if (typeof resetAddExpCatsDataChanged === 'function') {
resetAddExpCatsDataChanged();
}
// mark mix as changed
setDataChanged(true, project.Id);
// open scenario details if related button has been clicked
if (isOpenDetails)
loadScenarioDetailsEditForm(project.Scenario, false);
// close modal form
$document.trigger('rmo.close-scenario-teams-window');
}).then(false, function (ex) {// fail callback, raised if any error occurred in the chain
showErrorModal();
}).finally(function () {
unblockUI();
});
}
else {
bootbox.alert('At least one input field should be populated to save changes');
}
};
// returns a promise
// Adds expenditures selcted in dropdown box to the specified project.
// Each added expenditure filled with Team allocations and resource allocations
$scope.addProjectExpCats = function (project) {
var scenarioExpCatKeys = Object.keys(project.Scenario.Expenditures);
return teamInfoService.getTeamsById(project.Teams, true).then(function (projectTeams) {
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 not 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 = {};
angular.forEach(projectTeams, function (projectTeam, teamIndex) {
var projectExpCatKeys = Object.keys(projectTeam.ExpCategories);
for (var expCatIndex = 0; expCatIndex < projectExpCatKeys.length; expCatIndex++) {
var expCatToAddKey = projectExpCatKeys[expCatIndex];
var expCatToAddItem = projectTeam.ExpCategories[expCatToAddKey];
// For ordinary category we add to it all available teams and their resources.
// For Super EC we add to it all teams from the project and all resources
// from all project teams and all project ordinary ExpCats
if ((expCatToAddKey == expCat.ExpenditureCategoryId) ||
(!expCat.AllowResourceAssignment && expCatToAddItem.AllowResourceAssignment)) {
var team = null;
if (!teams[projectTeam.Id]) {
// Add and init team to EC as it was not added yet
var team = {
Id: projectTeam.Id,
Name: projectTeam.Name,
AllResources: {},
Resources: {},
CanBeDeleted: true,
Changed: false,
Collapsed: true,
CollapsedClass: "fa-plus-square",
IsAccessible: false,
QuantityValues: {},
CapacityQuantityValues: {},
RestQuantityValues: {},
};
for (var i = startDateIndex; i <= endDateIndex; i++) {
var millisec = $scope.Calendar.FiscalCalendarWeekEndings[i];
team.QuantityValues[millisec] = 0;
}
teams[team.Id] = team;
}
else
team = teams[projectTeam.Id];
var resKeys = Object.keys(expCatToAddItem.Resources);
for (var resIndex = 0; resIndex < resKeys.length; resIndex++) {
var resKey = resKeys[resIndex];
if (!team.AllResources[resKey]) {
// Resource was not added yet. Do it
var res = teamInfoService.extendResourceModelWithAttributes(expCatToAddItem.Resources[resKey], team.Id);
var resCopy = angular.copy(res);
delete resCopy.TotalCapacity;
resCopy.Changed = false;
resCopy.Deleted = false;
resCopy.AllocatedCapacity = {};
resCopy.QuantityValues = {};
resCopy.CapacityQuantityValues = angular.copy(res.TotalCapacity);
team.AllResources[resKey] = resCopy;
}
}
}
}
});
expCat.Teams = teams;
// add new expenditure record to scenario
project.Scenario.Expenditures[item.Id] = expCat;
}
}
});
});
};
$scope.addTeams2Project = function (project) {
var deferrer = $q.defer();
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);
refreshScenarioTeamsCapacity(project.Scenario);
if ($scope.DisplayMode.UnassignedAllocations)
tryAllocateUnassignedExpCatsToTeam(project.Id, item.Id);
}
});
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);
// load info about teams from server
teamInfoService.getTeamsById(outTeamIds, true).then(function (teams) {
angular.forEach(teams, function (team, teamIndex) { // add each new team with empty allocations for each expenditure in scenario
angular.forEach(scenarioExpCatKeys, function (expCatKey, expCatIndex) {
addTeamToProjectWithStructs(project.Id, expCatKey, team);
});
});
unblockUI();
// 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 () {
blockUI();
$scope.$apply(function () {
var newFilterSelection = $scope.data.SelectedFilterTeamsAndViews;
for (var outTeamIndex = 0; outTeamIndex < outTeamIds.length; outTeamIndex++) {
newFilterSelection.push(outTeamIds[outTeamIndex]);
}
// notify header panel that filters were changed
$rootScope.$broadcast("changeDisplayView", $scope.Calendar.StartDate, $scope.Calendar.EndDate, newFilterSelection, function () {
// auto absorb unassigned expenditures if related switcher is On and there are any
if ($scope.DisplayMode.UnassignedAllocations) {
refreshScenarioTeamsCapacity(project.Scenario);
for (var outTeamIndex = 0; outTeamIndex < outTeamIds.length; outTeamIndex++) {
tryAllocateUnassignedExpCatsToTeam(project.Id, outTeamIds[outTeamIndex]);
}
}
deferrer.resolve();
});
});
});
});
} else {
$scope.recreateView();
deferrer.resolve();
}
return deferrer.promise;
};
function rebuildBottomPart() {
// Update bottom part as project data could be changed
var weekendings = [];
for (var wIndex = 0; wIndex < $scope.Calendar.Header.Weeks.length; wIndex++) {
var weekItem = $scope.Calendar.Header.Weeks[wIndex];
if (weekItem && (weekItem.DataType == Header.DataType.Week)) {
weekendings.push(weekItem.Milliseconds);
}
}
var visibleExpCats = createAvailableExpendituresCache(weekendings, $scope.data.SelectedFilterCostCenters, $scope.data.SelectedFilterProjectRoles);
teamInfoService.applyFilter(visibleExpCats);
$scope.$broadcast('rebindTeamInfo', {
Header: angular.copy($scope.Calendar.Header),
DisplayMode: castDisplayModeIntoTeamInfoMode()
});
};
function refreshScenarioTeamsCapacity(scenario) {
if (!scenario || !scenario.Expenditures)
return;
var teams = teamInfoService.getAll(true);
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;
// We take current local date as millisecond to compare with weekendings, because
// user treats weekendings on the page as local dates.
var currentDateTimeAsMs = DateTimeConverter.getUtcNowMs();
var expCatInTeam = null;
targetTeam.CapacityQuantityValues = {};
targetTeam.RestQuantityValues = {};
if (expCatId in sourceTeam.ExpCategories) {
expCatInTeam = sourceTeam.ExpCategories[expCatId];
if (expCatInTeam.AllowResourceAssignment) {
// Copy capacity for normal ECs only. Skip this step for Super ECs as they have infinite capacity
var plannedCapacityWeeks = Object.keys(expCatInTeam.PlannedCapacityValues || {});
var actualCapacityWeeks = Object.keys(expCatInTeam.ActualCapacityValues || {});
var capacityWeeks = union(plannedCapacityWeeks, actualCapacityWeeks);
for (var i = 0; i < capacityWeeks.length; i++) {
var weekEndingUtc = parseFloat(capacityWeeks[i]),
capacity = 0;
if (weekEndingUtc < currentDateTimeAsMs)
capacity = expCatInTeam.ActualCapacityValues[capacityWeeks[i]] || 0;
else
capacity = expCatInTeam.PlannedCapacityValues[capacityWeeks[i]] || 0;
for (var resourceId in targetTeam.AllResources) {
if (!expCatInTeam.Resources[resourceId])
continue;
var sourceResource = expCatInTeam.Resources[resourceId];
capacity -= teamInfoService.getResourceSummaryNptAllocation(sourceResource, capacityWeeks[i]);
}
targetTeam.CapacityQuantityValues[capacityWeeks[i]] = Math.max(capacity, 0);
targetTeam.RestQuantityValues[capacityWeeks[i]] = capacity - (expCatInTeam.NeedCapacity ? (expCatInTeam.NeedCapacity[capacityWeeks[i]] || 0) : 0);
}
}
if (!expCatInTeam.Resources || Object.keys(expCatInTeam.Resources).length <= 0)
return;
}
if (!!targetTeam.AllResources) {
for (var resourceId in targetTeam.AllResources) {
var targetResource = targetTeam.AllResources[resourceId];
var sourceResource = null;
if (expCatInTeam && (resourceId in expCatInTeam.Resources)) {
// Get resource directly from source team
sourceResource = expCatInTeam.Resources[resourceId];
}
else {
if (targetResource.OwnExpenditureCategoryId && (targetResource.OwnExpenditureCategoryId in sourceTeam.ExpCategories)) {
// Try to get resource from its own EC and team
var resourceOwnExpCat = sourceTeam.ExpCategories[targetResource.OwnExpenditureCategoryId];
sourceResource = resourceOwnExpCat.Resources[resourceId];
}
}
if (sourceResource) {
copyResourceCapacityInfo(targetResource, sourceResource);
}
}
}
if (!!targetTeam.Resources) {
for (var resourceId in targetTeam.Resources) {
var targetResource = targetTeam.Resources[resourceId];
var sourceResource = null;
if (expCatInTeam && (resourceId in expCatInTeam.Resources)) {
// Get resource directly from source team
sourceResource = expCatInTeam.Resources[resourceId];
}
else {
if (targetResource.OwnExpenditureCategoryId && (targetResource.OwnExpenditureCategoryId in sourceTeam.ExpCategories)) {
// Try to get resource from its own EC and team
var resourceOwnExpCat = sourceTeam.ExpCategories[targetResource.OwnExpenditureCategoryId];
sourceResource = resourceOwnExpCat.Resources[resourceId];
}
}
if (sourceResource) {
copyResourceCapacityInfo(targetResource, sourceResource);
}
}
}
};
function copyResourceCapacityInfo(targetResource, sourceResource) {
if (!targetResource || !sourceResource)
return;
targetResource.CapacityQuantityValues = angular.copy(sourceResource.TotalCapacity || {});
targetResource.RestQuantityValues = {};
targetResource.ReadOnly = {};
for (var key in targetResource.CapacityQuantityValues) {
var weekEnding = parseFloat(key);
var npTime = teamInfoService.getResourceSummaryNptAllocation(sourceResource, weekEnding);
if (targetResource.CapacityQuantityValues[weekEnding] && targetResource.CapacityQuantityValues[weekEnding] > 0 && npTime > 0) {
targetResource.CapacityQuantityValues[weekEnding] = Math.max(targetResource.CapacityQuantityValues[weekEnding] - npTime, 0);
}
var capacity = (targetResource.CapacityQuantityValues[weekEnding] || 0);
var allocated = !sourceResource.AllocatedCapacity ? 0 : (sourceResource.AllocatedCapacity[weekEnding] || 0);
targetResource.RestQuantityValues[weekEnding] = capacity - allocated;
}
// we should fill ReadOnly object for all fiscal period because user can change scenario range from scenario details window
// and we need to give actual data about availability of the resource
for (var i = 0; i < $scope.Calendar.FiscalCalendarWeekEndings.length; i++) {
var weekEnding = $scope.Calendar.FiscalCalendarWeekEndings[i];
var valueIsEditable = false;
if (sourceResource.Teams && (typeof sourceResource.Teams.IsInRange === 'function')) {
for (var index = 0; index < sourceResource.Teams.length; index++) {
var teamMembershipInfo = sourceResource.Teams[index];
valueIsEditable = teamMembershipInfo.StartDate && (weekEnding >= teamMembershipInfo.StartDate) &&
(!teamMembershipInfo.EndDate || (weekEnding <= teamMembershipInfo.EndDate));
if (valueIsEditable)
break;
}
}
targetResource.ReadOnly[weekEnding] = !valueIsEditable;
}
};
function getExtendedViewDates(scenarioStartDate, scenarioEndDate, currentViewStartDate, currentViewEndDate) {
var startDateMs = scenarioStartDate < currentViewStartDate ? scenarioStartDate : currentViewStartDate;
var endDateMs = scenarioEndDate > currentViewEndDate ? scenarioEndDate : currentViewEndDate;
return {
StartDate: startDateMs,
EndDate: endDateMs
};
};
function getChangeViewPrompt(startDateMs, endDateMs) {
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 startDateText = DateTimeConverter.msFormatAsUtcString(startDateMs);
var endDateText = DateTimeConverter.msFormatAsUtcString(endDateMs);
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 delta = Math.abs(oldDate - newDate);
if (delta <= 8.64e+7)
oldDate = newDate;
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;
};
// Project and scenario identification parameters added
// To mark general changes in the Mix, call it with undefined projectId
// To mark a scenario changed as well, call it with projectId
function setDataChanged(value, projectId) {
if ($scope.DataChanged != value) {
$scope.DataChanged = value;
$rootScope.$broadcast("dataChanged", $scope.DataChanged);
}
if (projectId) {
var project = $scope.getProjectById(projectId);
if (project && project.Scenario) {
if (!project.Scenario.VersionInfo) {
project.Scenario.VersionInfo = {
RmoVersion: 1
};
}
var versionInfo = project.Scenario.VersionInfo;
versionInfo.RmoVersion += 1;
versionInfo.ChangedInRmo = versionInfo.ChangedInRmo || (versionInfo.RmoVersion > 1);
}
}
};
// Enumerate project to find changed in Live Database ones
function enumerateChangedProjects() {
$scope.ChangedInLiveDbProjects = [];
angular.forEach($scope.Calendar.Projects, function (project, key) {
if (project && project.Scenario && project.Scenario.VersionInfo &&
project.Scenario.VersionInfo.ChangedInMain)
if (!isProjectInQueue(project.Id)) {
$scope.ChangedInLiveDbProjects.push({
ProjectId: project.Id,
ProjectName: project.Name
});
}
});
$scope.ShowChangedObjectsWarning = $scope.ChangedInLiveDbProjects.length > 0;
};
function initPageControls() {
$element.find('#selProjects2Add').select2('val', '');
$(".projectEdit").on('click', function (e) {
$('#outdated').show();
});
};
// 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);
};
$scope.copyScenario = function (scenario) {
if (!scenario)
return;
$scope.copier.scenario = angular.copy(scenario);
$scope.copier.scenario.Name = !$scope.copier.scenario.Name ? 'Copy' : $scope.copier.scenario.Name + ' Copy';
$scope.copier.scenario.ParentId = Object.keys($scope.Calendar.Projects)[0];
$('#copierProjectId').select2().select2('val', $('#copierProjectId option:eq(1)').val());
$("#copyScenario").modal('show');
};
$scope.copyScenarioConfirmed = function () {
if (!$scope.copier || !$scope.copier.scenario || !$scope.copier.scenario.ParentId)
return;
var targetProject = $scope.getProjectById($scope.copier.scenario.ParentId);
if (!targetProject)
return;
if (isScenarioLoaded($scope.copier.scenario)) {
$scope.copier.scenario.Id = (targetProject.Scenario || {}).Id || Math.uuid();
$scope.copier.scenario.VersionInfo = (targetProject.Scenario || {}).VersionInfo || {
RmoVersion: 1,
ChangedInRmo: true
};
setActiveScenario(targetProject, $scope.copier.scenario, false);
}
else {
loadScenarioDetails($scope.copier.scenario.Id, function (expenditures) {
$scope.copier.scenario.Id = (targetProject.Scenario || {}).Id || Math.uuid();
$scope.copier.scenario.VersionInfo = (targetProject.Scenario || {}).VersionInfo || {
RmoVersion: 1,
ChangedInRmo: true
};
setScenarioExpenditures($scope.copier.scenario, expenditures);
setActiveScenario(targetProject, $scope.copier.scenario, false);
});
}
};
/* 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;
$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, true);
if (teamItem && teamItem.ExpCategories) {
var expCatsInTeam = Object.keys(teamItem.ExpCategories)
var alreadyAddedExpCats = [];
$.each(expCatsInTeam, function (index, expCatId) {
var currentExpCatItem = teamItem.ExpCategories[expCatId];
var ItemToPush = {
Id: currentExpCatItem.Id,
Name: currentExpCatItem.Name
}
$scope.frmReassign_ExpCatsInTeam.push(ItemToPush);
alreadyAddedExpCats.push(ItemToPush.Id);
});
// Append super expenditures to list
$.each($scope.Calendar.SuperExpenditures, function (expCatId, expCatItem) {
if (alreadyAddedExpCats.indexOf(expCatId) < 0) {
var ItemToPush = {
Id: expCatId,
Name: expCatItem.ExpenditureCategoryName
}
$scope.frmReassign_ExpCatsInTeam.push(ItemToPush);
alreadyAddedExpCats.push(ItemToPush.Id);
}
});
}
}
// 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, true) ? thisMixGroup : otherTeamsGroup
}
$scope.frmReassign_MixTeams.push(teamRecord);
});
if (callbackFuncName && (callbackFuncName.length > 0)) {
var callbackFunc = new Function(callbackFuncName + "('" + $scope.frmReassign_Action + "')");
callbackFunc();
}
}
})
};
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) {
console.error(e);
unblockUI();
showErrorModal();
}
}).error(function (data, status, headers, config) {
unblockUI();
showErrorModal();
console.error('A server error occurred in ' + $scope.dataUrls.getTeamsByExpCatUrl + ' action');
});
} catch (e) {
console.error(e);
unblockUI();
showErrorModal();
}
};
$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);
};
$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;
if (!isScenarioLoaded(projectItem.Scenario)) {
loadScenarioDetails(projectItem.Scenario.Id, function (expenditures) {
setScenarioExpenditures(projectItem.Scenario, expenditures);
reassignUnassignedAllocationsInternal(openScenarioDetails);
})
}
else {
reassignUnassignedAllocationsInternal(openScenarioDetails);
}
};
function reassignUnassignedAllocationsInternal(openScenarioDetails) {
var projectId = $scope.frmReassign_CurrentItem.ProjectId;
var sourceExpCatId = $scope.frmReassign_CurrentItem.ExpCatId;
if (!projectId || !sourceExpCatId)
return;
var project = $scope.getProjectById(projectId);
switch ($scope.frmReassign_Action) {
case "remove":
removeUnassignedAllocations(projectId, sourceExpCatId);
removeProjectFromUnassignedExpenditures(projectId, sourceExpCatId);
setDataChanged(true, projectId);
if (openScenarioDetails)
$scope.openScenarioDetailsWindow();
break;
case "otherEC":
var targetExpCatId = $scope.frmReassign_ToExpCatId;
var targetTeamId = $scope.frmReassign_CurrentItem.TargetTeamId;
refreshScenarioTeamsCapacity(project.Scenario);
setUnassignedAllocationsToExpCat(projectId, targetTeamId, sourceExpCatId, targetExpCatId);
setDataChanged(true, projectId);
if (openScenarioDetails)
$scope.openScenarioDetailsWindow();
break;
case "otherTeam":
var targetTeamId = $scope.frmReassign_ToTeamId;
setUnassignedAllocationsToOtherTeam(projectId, sourceExpCatId, targetTeamId, openScenarioDetails);
$scope.recreateView();
break;
}
};
function setUnassignedAllocationsToExpCat(projectId, teamId, sourceExpCatId, targetExpCatId) {
var project = $scope.getProjectById(projectId);
var teamItem = teamInfoService.getById(teamId, true);
if (!project || !project.Scenario || !project.Scenario.Expenditures || !teamItem)
return;
var isNewExpenditure = false;
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) {
// Target EC not exists in the project. Need to add it to project
var expCatFoundAndAdded = false;
var teamExpCats = Object.keys(teamItem.ExpCategories);
if ($.inArray(targetExpCatId, teamExpCats) >= 0) {
// EC was taken from the team in mix Teams collection
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.AllowResourceAssignment = teamExpCatTemplate.AllowResourceAssignment;
expCatItem.Teams = {};
expCatItem.UnassignedAllocations = {};
// TODO. SA. Get from EC-template for Team the following 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;
expCatFoundAndAdded = true;
}
else {
// Try to take EC from mix SuperCategories Collection
var superExpCatsInMix = Object.keys($scope.Calendar.SuperExpenditures);
if ($.inArray(targetExpCatId, superExpCatsInMix) >= 0) {
var expCatItem = angular.copy($scope.Calendar.SuperExpenditures[targetExpCatId]);
expCatItem.Teams = {};
expCatItem.UnassignedAllocations = {};
project.Scenario.Expenditures[targetExpCatId] = expCatItem;
expCatFoundAndAdded = true;
}
}
if (!expCatFoundAndAdded)
throw "Target Expenditure Category not found";
isNewExpenditure = true;
}
// Check team exists in target EC
var targetExpCat = project.Scenario.Expenditures[targetExpCatId];
if (!targetExpCat.Teams)
targetExpCat.Teams = {};
if (!targetExpCat.Teams[teamId]) {
fillExpenditures4TeamById(project, teamId);
refreshScenarioTeamsCapacity(project.Scenario);
}
// 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;
var expCatRestValue = getExpCatRestValue(targetExpCat, we, targetExpCatTeam.Id);
var teamRestValue = getTeamRestValue(targetExpCatTeam, we, targetExpCat);
var availableToAssign = Math.min(expCatRestValue, teamRestValue);
var transferredValue = Math.min(availableToAssign, allocationItem);
var newAllocatedValue = Math.max(targetExpCatTeam.QuantityValues[we] + transferredValue, 0);
var unassignedValue = Math.max(allocationItem - transferredValue, 0);
targetExpCatTeam.QuantityValues[we] = newAllocatedValue;
sourceExpCat.UnassignedAllocations[we] = unassignedValue;
});
if (!targetExpCat.AllowResourceAssignment)
// Recalculate scenario details for super EC
recalculateProjectExpCatDetails(targetExpCat);
refreshProjectData(project, updatedData);
if (!unassignedValuesExist(sourceExpCat.UnassignedAllocations)) {
sourceExpCat.UnassignedAllocations = {};
removeProjectFromUnassignedExpenditures(projectId, sourceExpCatId);
}
}
};
function setUnassignedAllocationsToOtherTeam(projectId, expCatId, targetTeamId, openScenarioDetails) {
var teamItem = teamInfoService.getById(targetTeamId, true);
if (!teamItem) {
// Team is not in the Mix
teamInfoService.getTeamsById([targetTeamId], true).then(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 newFilterSelection = $scope.data.SelectedFilterTeamsAndViews;
if (newFilterSelection.indexOf(targetTeamId) < 0) {
newFilterSelection.push(targetTeamId);
};
$rootScope.$broadcast("changeDisplayView", $scope.Calendar.StartDate, $scope.Calendar.EndDate, newFilterSelection,
function () {
// add allocations to the new team of the project
var project = $scope.getProjectById(projectId);
if (!isScenarioLoaded(project.Scenario)) {
loadScenarioDetails(project.Scenario.Id, function (expenditures) {
setScenarioExpenditures(project.Scenario, expenditures);
refreshScenarioTeamsCapacity(project.Scenario);
setUnassignedAllocationsToTeam(projectId, expCatId, targetTeamId);
setDataChanged(true, projectId);
// Try to allocate other unassigned ECs of the project to selected team
if ($scope.frmReassign_TryAssignOtherECs) {
tryAllocateUnassignedExpCatsToTeam(projectId, targetTeamId);
}
if (openScenarioDetails) {
$scope.openScenarioDetailsWindow();
}
});
}
else {
refreshScenarioTeamsCapacity(project.Scenario);
setUnassignedAllocationsToTeam(projectId, expCatId, targetTeamId);
setDataChanged(true, projectId);
// Try to allocate other unassigned ECs of the project to selected team
if ($scope.frmReassign_TryAssignOtherECs) {
tryAllocateUnassignedExpCatsToTeam(projectId, targetTeamId);
}
if (openScenarioDetails) {
$scope.openScenarioDetailsWindow();
}
}
});
});
});
});
}
else {
// Team exists in the Mix
var project = $scope.getProjectById(projectId);
if (!isScenarioLoaded(project.Scenario)) {
loadScenarioDetails(project.Scenario.Id, function (expenditures) {
setScenarioExpenditures(project.Scenario, expenditures);
refreshScenarioTeamsCapacity(project.Scenario);
setUnassignedAllocationsToTeam(projectId, expCatId, teamItem.Id);
setDataChanged(true, projectId);
// Try to allocate other unassigned ECs of the project to selected team
if ($scope.frmReassign_TryAssignOtherECs) {
tryAllocateUnassignedExpCatsToTeam(projectId, teamItem.Id);
}
if (openScenarioDetails) {
$scope.openScenarioDetailsWindow();
}
});
}
else {
addTeamToProjectWithStructs(projectId, expCatId, teamItem);
refreshScenarioTeamsCapacity(project.Scenario);
setUnassignedAllocationsToTeam(projectId, expCatId, teamItem.Id);
setDataChanged(true, projectId);
// Try to allocate other unassigned ECs of the project to selected team
if ($scope.frmReassign_TryAssignOtherECs) {
tryAllocateUnassignedExpCatsToTeam(projectId, teamItem.Id);
}
if (openScenarioDetails) {
$scope.openScenarioDetailsWindow();
}
}
}
};
function addTeamToProjectWithStructs(projectId, expCatId, team) {
if (!projectId || !expCatId || !team)
return;
var project = $scope.getProjectById(projectId);
if (!project || !project.Scenario)
return;
// Adding team to project
if (!$scope.projectHasTeam(project, team.Id)) {
addTeamToProject(project.Id, team.Id, 0, 0, true);
}
if (project.Scenario.Expenditures) {
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 = {};
var teamKeys = Object.keys(project.Scenario.Expenditures[expCatId].Teams);
if (teamKeys.indexOf(team.Id) < 0) {
if (!teamInfoService.isExists(team.Id, true)) {
fillExpenditures4Team(project, team);
}
else {
fillExpenditures4TeamById(project, team.Id);
}
}
}
};
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;
var expCatRestValue = getExpCatRestValue(expCat, we, expCatTeam.Id);
var teamRestValue = getTeamRestValue(expCatTeam, we, expCat);
var availableToAssign = Math.min(expCatRestValue, teamRestValue);
var transferredValue = Math.min(availableToAssign, allocationItem);
var newAllocatedValue = Math.max(expCatTeam.QuantityValues[we] + transferredValue, 0);
var unassignedValue = Math.max(allocationItem - transferredValue, 0);
expCatTeam.QuantityValues[we] = newAllocatedValue;
expCat.UnassignedAllocations[we] = unassignedValue;
});
refreshProjectData(project, updatedData);
if (!unassignedValuesExist(expCat.UnassignedAllocations)) {
expCat.UnassignedAllocations = {};
removeProjectFromUnassignedExpenditures(projectId, expCatId);
}
};
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) {
project.Scenario.Expenditures[expCatId].UnassignedAllocations = {};
}
}
};
$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;
};
// 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);
}
};
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);
};
/* 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, 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
refreshScenarioTeamsCapacity(project.Scenario);
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.");
});
}
};
// 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];
return expCatData && unassignedValuesExist(expCatData.UnassignedAllocations);
});
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;
}
// Creates map for distribution of allocations form source team (specified by teamId) to other existing teams
// in the project.
// Result: dictionary [ExpCatId, [teams to move this expCat allocations to]]
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;
}
// 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) {
var unassignedValue = redistributeQuantityValuesByCurve(currentEC, targetTeams, we, srcTeam.QuantityValues[we]);
// Reset value, because it was distributed to other teams
srcTeam.QuantityValues[we] = unassignedValue;
});
}
// 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;
});
}
}
});
}
// 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(expCat, teams, weekending, valueToDistribute) {
var multipliers = {};
var valueSumm = 0;
var remainingSumm = 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;
}
});
var valueForCurrentTeam = 0;
var teamCount = Object.keys(multipliers).length;
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;
if (valueSumm > 0)
// Curve exists
valueForCurrentTeam = valueToDistribute * multipliers[team.Id];
else {
// No curve, because all teams has Zero values currently allocated
if (teamCount > 0) {
valueForCurrentTeam = valueToDistribute / teamCount;
}
else {
valueForCurrentTeam = 0;
}
}
if (valueForCurrentTeam > 0) {
var expCatRestValue = 0;
var teamRestValue = 0;
if (expCat.AllowResourceAssignment) {
expCatRestValue = getExpCatRestValue(expCat, weekending, team.Id);
teamRestValue = getTeamRestValue(team, weekending, expCat);
}
else {
// For Super EC we can assign any values as Super EC has infinite capacity
expCatRestValue = Number.MAX_VALUE;
teamRestValue = Number.MAX_VALUE;
}
var availableToAssign = Math.min(expCatRestValue, teamRestValue);
var transferredValue = Math.min(availableToAssign, valueForCurrentTeam);
var newAllocatedValue = Math.max(team.QuantityValues[weekending] + transferredValue, 0);
var unassignedValue = Math.max(valueForCurrentTeam - transferredValue, 0);
team.QuantityValues[weekending] = newAllocatedValue;
remainingSumm += unassignedValue;
}
});
return remainingSumm;
}
$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 = angular.copy(project.InactiveScenarios[scenarioId] || {});
if (!newScenario || Object.keys(newScenario).length <= 0)
return;
delete project.InactiveScenarios[scenarioId];
changeActiveScenario(project, newScenario, true);
};
$scope.importScenarioToProjectOpenWindow = function (projectId) {
if (!projectId)
return;
var project = $scope.getProjectById(projectId);
if (!project || !project.Scenario)
return;
$scope.ImportScenarioForm.ProjectId = projectId;
$scope.ImportScenarioForm.Name = '';
$scope.ImportScenarioForm.IsActive = true;
$scope.ImportScenarioForm.NewTeams = [];
if (!!project.Teams && project.Teams.length > 0) {
for (var i = 0; i < project.Teams.length; i++) {
var team = teamInfoService.getById(project.Teams[i], true);
if (!team || !team.IsNew)
continue;
$scope.ImportScenarioForm.NewTeams.push(team);
}
}
$document.trigger('rmo.open-import-scenario-window');
};
$scope.importScenarioToProjectConfirm = function () {
if (angular.isFunction(isValidImportScenarioToProjectForm) && !isValidImportScenarioToProjectForm())
return;
if (!$scope.ImportScenarioForm.ProjectId)
return;
var project = $scope.getProjectById($scope.ImportScenarioForm.ProjectId);
if (!project || !project.Scenario)
return;
blockUI();
var project4Import = angular.copy(project);
project4Import.Scenario.Name = $scope.ImportScenarioForm.Name;
var saveData = {
MixId: $scope.MixId,
Project: project4Import,
IsActive: $scope.ImportScenarioForm.IsActive,
Teams: $scope.ImportScenarioForm.NewTeams
};
var request = getAntiXSRFRequest($scope.dataUrls.importScenarioUrl, saveData);
try {
$http(request)
.success(function (data, status, headers, config) {
project.InactiveScenarios = data;
if (!!$scope.ImportScenarioForm.NewTeams && $scope.ImportScenarioForm.NewTeams.length > 0) {
for (var i = 0; i < $scope.ImportScenarioForm.NewTeams.length; i++) {
var team = teamInfoService.getById($scope.ImportScenarioForm.NewTeams[i].Id, true);
if (!!team) {
team.IsNew = false;
setDataChanged(true);
}
}
}
if ($scope.ImportScenarioForm.IsActive) {
updateProjectScenarioVersion(project.Id, true);
}
unblockUI();
$document.trigger('rmo.close-import-scenario-window');
bootbox.alert('Scenario has been imported successfully.');
})
.error(function (data, status, headers, config) {
console.error('A server error occurred in ' + $scope.dataUrls.importScenarioUrl + ' action');
unblockUI();
showErrorModal();
});
} catch (e) {
console.error(e);
unblockUI();
showErrorModal();
}
};
function changeActiveScenario(project, newScenario, addCurrentScenarioToInactive) {
if (!project || !newScenario)
return;
if (isScenarioLoaded(newScenario)) {
setActiveScenario(project, newScenario, addCurrentScenarioToInactive);
}
else {
loadScenarioDetails(newScenario.Id, function (expenditures) {
setScenarioExpenditures(newScenario, expenditures);
setActiveScenario(project, newScenario, addCurrentScenarioToInactive);
});
}
};
function setActiveScenario(project, scenario, addCurrentScenarioToInactive) {
if (!project || !scenario)
return;
var isUnscheduledProject = !project.Scenario || !project.Scenario.Id;
if (isUnscheduledProject)
project.Scenario = {};
var oldScenario = angular.copy(project.Scenario);
if (isUnscheduledProject || isScenarioLoaded(oldScenario)) {
project.Scenario = angular.extend({}, scenario, { Expenditures: angular.copy(oldScenario.Expenditures || {}) });
refreshProjectData(project, scenario.Expenditures, true);
if (addCurrentScenarioToInactive) {
project.InactiveScenarios[oldScenario.Id] = oldScenario;
}
if (isUnscheduledProject)
addProjectFromUnscheduled(project.Id);
else
synchronizeProjectAndScenarioTeams(project);
setDataChanged(true, project.Id);
$scope.recreateView();
}
else {
loadScenarioDetails(oldScenario.Id, function (expenditures) {
setScenarioExpenditures(oldScenario, expenditures);
project.Scenario = angular.extend({}, scenario, { Expenditures: angular.copy(oldScenario.Expenditures) });
refreshProjectData(project, scenario.Expenditures, true);
if (addCurrentScenarioToInactive) {
project.InactiveScenarios[oldScenario.Id] = oldScenario;
}
synchronizeProjectAndScenarioTeams(project);
setDataChanged(true, project.Id);
$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;
};
// Get from server current timestamp (version) for each scenario or project in the specified list
// and sets recievied timestamps (versions) to mix scenarios.
// If an active scenario ID for some project is unknown, push project ID to the parameter array
function updateVersionForScenarios(scenarioOrProjectIdArray) {
var request = getAntiXSRFRequest($scope.dataUrls.getScenariosTimestampsUrl, scenarioOrProjectIdArray);
try {
$http(request)
.success(function (data, status, headers, config) {
if (data) {
setNewVersionToScenarios(data);
enumerateChangedProjects();
$scope.recreateView();
setDataChanged(true);
}
})
.error(function (data, status, headers, config) {
console.error('A server error occurred in ' + $scope.dataUrls.getScenariosTimestampsUrl + ' action');
unblockUI();
showErrorModal();
});
} catch (e) {
console.error(e);
unblockUI();
showErrorModal();
}
}
// Get from server current timestamp (version) of an active scenario for the specified project ID
// and sets it to the project scenario in current mix. If isActiveScenario is TRUE, scenario ID will be
// overwriten, when response with timestamp is received from server
function updateProjectScenarioVersion(projectId, isActiveScenario) {
var struct = {};
struct[projectId] = isActiveScenario ? true : false;
updateVersionForScenarios(struct)
}
function setNewVersionToScenarios(resultArray) {
if (!resultArray)
return;
angular.forEach(resultArray, function (rec, index) {
var project = $scope.getProjectById(rec.ProjectId);
if (project && project.Scenario) {
if ((project.Scenario.Id == rec.ScenarioId) || rec.ChangeActiveScenarioId) {
// Version must be set to currently active scenario of the project
if (!project.Scenario.VersionInfo) {
project.Scenario.VersionInfo = GetDefaultScenarioVersionInfo();
}
project.Scenario.VersionInfo.SourceVersion = rec.Timestamp;
project.Scenario.VersionInfo.ChangedInMain = false;
if (rec.ChangeActiveScenarioId) {
// Version must be set to active scenario of the project, but scenario ID must be changed
project.Scenario.Id = rec.ScenarioId;
}
}
else {
if (project.InactiveScenarios && (Object.keys(project.InactiveScenarios).length > 0)) {
var foundInactiveScens =
$.grep(project.InactiveScenarios, function (scenItem, index) {
return (scenItem.Id == rec.ScenarioId);
});
if (foundInactiveScens.length > 0) {
// Version must be set to one of the inactive scenarios of the project
if (!foundInactiveScens[0].VersionInfo) {
foundInactiveScens[0].VersionInfo = GetDefaultScenarioVersionInfo();
}
foundInactiveScens[0].VersionInfo.SourceVersion = rec.Timestamp;
foundInactiveScens[0].VersionInfo.ChangedInMain = false;
}
}
}
}
});
}
$scope.deletedProjectsWarningTailVisible = false;
$scope.liveDbChangedProjectsWarningTailVisible = false;
$scope.liveDbDeletedResourcesWarningTailVisible = false;
// Hides page warning message about projects, that were changed in Live DB
$scope.hideLiveDbChangesWarning = function () {
$scope.ShowChangedObjectsWarning = false;
}
$scope.showLiveDbChangedProjectsWarningTail = function () {
$scope.liveDbChangedProjectsWarningTailVisible = true;
}
$scope.showLiveDbDeletedResourcesWarningTail = function () {
$scope.liveDbDeletedResourcesWarningTailVisible = true;
}
// Hides page warning message about deleted from current mix projects
$scope.hideDeletedProjectsWarning = function () {
$scope.ShowDeletedObjectsWarning = false;
};
$scope.showDeletedProjectsWarningTail = function () {
$scope.deletedProjectsWarningTailVisible = true;
}
$scope.hideOutOfMixProjectsExistWarning = function () {
$scope.showOutOfMixProjectsExistWarning = false;
};
// Gets data for specified projects from server live database and
// replaces mix projects with received data
function updateProjectsFromLiveDb(projects, callbackFunc) {
if (!projects || (projects.length < 1))
return;
var project;
var scenariosToLoad = [];
angular.forEach(projects, function (projectId, index) {
project = $scope.getProjectById(projectId);
if (project.Scenario && !isScenarioLoaded(project.Scenario))
scenariosToLoad.push(project.Scenario.Id);
});
if (scenariosToLoad.length > 0) {
loadMultipleScenarioDetails(scenariosToLoad, function (loadedScenarios) {
angular.forEach(loadedScenarios, function (scenarioExpenditures, scenarioId) {
var projectItem = $scope.getProjectByScenarioId(scenarioId);
if (projectItem && projectItem.Scenario) {
setScenarioExpenditures(projectItem.Scenario, scenarioExpenditures);
}
});
getProjectsFromLiveDbInternal(projects, callbackFunc);
});
}
else {
getProjectsFromLiveDbInternal(projects, callbackFunc);
}
}
// Gets projects from Live DB and launches update of them in the Mix
function getProjectsFromLiveDbInternal(projects, callbackFn) {
if (!projects || (projects.length < 1))
return;
blockUI();
var request = getAntiXSRFRequest($scope.dataUrls.getProjectsFromLiveUrl, projects);
try {
$http(request)
.success(function (data, status, headers, config) {
try {
if (data) {
unblockUI();
if (angular.isFunction(callbackFn)) {
callbackFn(projects, data);
};
}
} catch (err) {
console.error(err);
unblockUI();
showErrorModal();
}
})
.error(function (data, status, headers, config) {
console.error('A server error occurred in ' + $scope.dataUrls.getProjectsFromLiveUrl + ' action');
unblockUI();
showErrorModal();
});
} catch (e) {
console.error(e);
unblockUI();
showErrorModal();
}
}
$scope.frmLDBUpd_ProjectsToUpdate = [];
$scope.frmLDBUpd_ProjectsToDelete = [];
$scope.frmLDBUpd_ProjectsToRemoveScenario = [];
$scope.frmLDBUpd_ProjectsToReplaceScenario = [];
$scope.frmLDBUpd_ReceivedProjectsData = undefined;
function confirmMultipleProjectsUpdateFromLiveDb(updatableProjects, receivedData) {
// Sort projects by needed actions
sortProjectsForUpdateFromLiveDb(updatableProjects, receivedData);
$scope.frmLDBUpd_ReceivedProjectsData = receivedData;
if (($scope.frmLDBUpd_ProjectsToDelete && ($scope.frmLDBUpd_ProjectsToDelete.length > 0)) ||
($scope.frmLDBUpd_ProjectsToRemoveScenario && ($scope.frmLDBUpd_ProjectsToRemoveScenario.length > 0)) ||
($scope.frmLDBUpd_ProjectsToReplaceScenario && ($scope.frmLDBUpd_ProjectsToReplaceScenario.length > 0)))
$('#projectsUpdateFromLiveDialog').modal('show');
else
// No user confirmation needed
$scope.updateProjectsFromLiveDbConfirmed(false);
};
function confirmSingleProjectUpdateFromLiveDb(updatableProjects, receivedData) {
// Sort projects by needed actions
sortProjectsForUpdateFromLiveDb(updatableProjects, receivedData);
$scope.frmLDBUpd_ReceivedProjectsData = receivedData;
var msg = "";
if ($scope.frmLDBUpd_ProjectsToDelete && ($scope.frmLDBUpd_ProjectsToDelete.length > 0)) {
msg = "The project was deleted in live database. Updating will remove the project from the mix.";
}
else if ($scope.frmLDBUpd_ProjectsToRemoveScenario && ($scope.frmLDBUpd_ProjectsToRemoveScenario.length > 0)) {
msg = "Scenario for the project was deactivated in live database. Updating will move the project to unscheduled.";
}
else if ($scope.frmLDBUpd_ProjectsToReplaceScenario && ($scope.frmLDBUpd_ProjectsToReplaceScenario.length > 0)) {
msg = "Active scenario was changed in live database. Updating will change active scenario.";
}
if (msg.length > 0)
bootbox.confirm(msg, function (result) {
$scope.$apply(function () {
if (result) {
$scope.updateProjectsFromLiveDbConfirmed(true);
}
else return;
});
});
else $scope.updateProjectsFromLiveDbConfirmed(true);
};
$scope.updateProjectsFromLiveDbConfirmed = function (single) {
var updatableProjectActions = $scope.frmLDBUpd_ProjectsToDelete.concat($scope.frmLDBUpd_ProjectsToRemoveScenario)
.concat($scope.frmLDBUpd_ProjectsToReplaceScenario).concat($scope.frmLDBUpd_ProjectsToUpdate);
for (var index = 0; index < updatableProjectActions.length; index++) {
updateProjectFromLiveDb(updatableProjectActions[index]);
}
enumerateChangedProjects();
$scope.recreateView();
// Update bottom part as project data could be changed
rebuildBottomPart();
setDataChanged(true);
if (single)
bootbox.alert('Update of the project completed');
else bootbox.alert('Update of the Mix projects completed');
};
// Performs selected action on specified project in Update form live db process
function updateProjectFromLiveDb(projectActionRec) {
if (!projectActionRec || !projectActionRec.Action)
return;
var projectId = projectActionRec.ProjectId;
var project = $scope.getProjectById(projectId);
var replaceScenario = false;
switch (projectActionRec.Action) {
case 'Delete':
// Completelly remove project from current mix
// Reel off removable project allocations from top and bottom part of the Calendar
var killerStruct = createProjectKiller(project);
refreshProjectData(project, killerStruct, true);
removeProjectFromUnassignedExpenditures(projectId);
removeProjectFromLayout(projectId);
removeProjectFromManaged(projectId);
delete $scope.Calendar.Projects[projectId];
break;
case 'RemoveScenario':
// Delete active scenario for project and throw it out to Unschaduled
// Reel off removable project allocations from top and bottom part of the Calendar
var killerStruct = createProjectKiller(project);
refreshProjectData(project, killerStruct, true);
removeProjectFromUnassignedExpenditures(projectId);
removeProjectFromLayout(projectId);
removeProjectFromManaged(projectId);
$scope.pushProjectToUnscheduled(project);
break;
case 'ReplaceScenario':
replaceScenario = true;
case 'Update':
var foundItems = $.grep($scope.frmLDBUpd_ReceivedProjectsData, function (item, index) {
return item.Id == projectId;
});
if (foundItems.length > 0) {
var receivedProject = foundItems[0];
project.Name = receivedProject.Name;
project.Color = receivedProject.Color;
project.ColorRGB = receivedProject.ColorRGB;
project.Deadline = receivedProject.Deadline;
project.InactiveScenarios = receivedProject.InactiveScenarios;
// Remove project from Teams at the Calendar layout
var removedFromProjectTeams = $.grep(project.Teams, function (teamId, index) {
return !receivedProject.Teams || (receivedProject.Teams && (receivedProject.Teams.indexOf(teamId) < 0));
});
for (var index = 0; index < removedFromProjectTeams.length; index++) {
var teamId = removedFromProjectTeams[index];
removeProjectFromTeamLayout(project.Id, teamId);
}
project.Teams = receivedProject.Teams;
if (project.Scenario) {
if (replaceScenario) {
project.Scenario.Id = receivedProject.Scenario.Id;
}
project.Scenario.ParentId = receivedProject.Scenario.ParentId;
project.Scenario.TemplateId = receivedProject.Scenario.TemplateId;
project.Scenario.GrowthScenario = receivedProject.Scenario.GrowthScenario;
project.Scenario.Type = receivedProject.Scenario.Type;
project.Scenario.VersionInfo = receivedProject.Scenario.VersionInfo;
project.Scenario.Rates = receivedProject.Scenario.Rates;
project.Scenario.FinInfo = receivedProject.Scenario.FinInfo;
project.Scenario.StartDate = receivedProject.Scenario.StartDate;
project.Scenario.EndDate = receivedProject.Scenario.EndDate;
if (receivedProject.Scenario.Expenditures) {
refreshProjectData(project, receivedProject.Scenario.Expenditures, true);
}
}
removeProjectFromUnassignedExpenditures(projectId);
if (isScenarioOutOfMix(project.Scenario)) {
moveProjectToQueue(project);
}
else if (!isProjectInManaged(projectId)) {
addProjectFromUnscheduled(projectId);
};
}
break;
case 'NoAction':
break;
}
};
// Sorts projects, queued from update, by applicable action
function sortProjectsForUpdateFromLiveDb(updatableProjects, receivedProjectsFromServer) {
$scope.frmLDBUpd_ProjectsToUpdate = [];
$scope.frmLDBUpd_ProjectsToDelete = [];
$scope.frmLDBUpd_ProjectsToRemoveScenario = [];
$scope.frmLDBUpd_ProjectsToReplaceScenario = [];
var projectsToRemove = $.grep(updatableProjects, function (projectId, index) {
var foundProjects = $.grep(receivedProjectsFromServer, function (prjItem, index) {
return prjItem.Id == projectId;
});
return foundProjects.length < 1;
});
if (projectsToRemove.length > 0) {
angular.forEach(projectsToRemove, function (projectId, index) {
var project = $scope.getProjectById(projectId);
var newRec = {
ProjectId: projectId,
ProjectName: project.Name,
Action: 'Delete'
};
$scope.frmLDBUpd_ProjectsToDelete.push(newRec);
});
}
var itemProcessed = false;
angular.forEach(receivedProjectsFromServer, function (receivedProject, index) {
itemProcessed = false;
if (receivedProject && receivedProject.Id) {
var project = $scope.getProjectById(receivedProject.Id);
var newRec = {
ProjectId: receivedProject.Id,
ProjectName: project.Name
};
if (!receivedProject.Scenario || (receivedProject.Scenario.Id == C_EMPTY_GUID)) {
newRec.Action = 'RemoveScenario';
$scope.frmLDBUpd_ProjectsToRemoveScenario.push(newRec);
itemProcessed = true;
}
// TODO: review if we really need to check for ChangedInRmo flag here. Example:
// I created project with scenario S1 in the alive DB
// Then I created and saved mix with this project and scenario
// Then I changed scenario in the alive database and called it S2
// Seems like we should replace old scenario S1 in the mix with S2, but ChangedInRmo is false in this case
// Refresh mix and then activate just refreshed scenario in the alive DB. Seems like scenario S1 will be replaced there instead of S2
if (!itemProcessed && receivedProject.Scenario && project && project.Scenario &&
(receivedProject.Scenario.Id != project.Scenario.Id) && project.Scenario.VersionInfo &&
project.Scenario.VersionInfo.ChangedInRmo) {
newRec.Action = 'ReplaceScenario';
$scope.frmLDBUpd_ProjectsToReplaceScenario.push(newRec);
itemProcessed = true;
}
if (!itemProcessed && (projectsToRemove.indexOf(receivedProject.Id) < 0)) {
newRec.Action = 'Update';
$scope.frmLDBUpd_ProjectsToUpdate.push(newRec);
}
}
});
}
$scope.editScenarioFinInfo = function (projectId) {
if (!projectId)
return;
var project = $scope.getProjectById(projectId);
if (!project || !project.Scenario)
return;
if (isScenarioLoaded(project.Scenario)) {
loadScenarioFinInfoEditForm(project.Scenario);
}
else {
loadScenarioDetails(project.Scenario.Id, function (expenditures) {
setScenarioExpenditures(project.Scenario, expenditures);
loadScenarioFinInfoEditForm(project.Scenario);
});
}
};
$scope.showActivateScenarioTooltip = function (event, hasDependency) {
if (!hasDependency)
return;
var text = "Cannot activate this scenario due to a project dependency";
var $target = $(event.currentTarget);
if ($target.data('bs.tooltip')) {
$target.attr('data-original-title', text)
return;
}
var opts = {
trigger: 'hover',
html: true,
container: 'body',
title: text
};
$target.tooltip(opts).on('show.bs.tooltip', hideRedundantTooltips);
$target.tooltip('show');
};
$scope.showProjectTooltip = function (event, text) {
var $target = $(event.currentTarget);
if ($target.data('bs.tooltip')) {
$target.attr('data-original-title', text)
return;
}
var opts = {
trigger: 'hover',
html: true,
container: 'body',
title: text
};
$target.tooltip(opts).on('show.bs.tooltip', hideRedundantTooltips);
$target.tooltip('show');
};
function hideRedundantTooltips(exceptObj) {
$('.tooltip').not(exceptObj).removeClass('in').hide();
}
function loadScenarioFinInfoEditForm(scenario) {
if (!scenario)
return;
blockUI();
var project = $scope.getProjectById(scenario.ParentId);
// we should send scenario w/o expenditures for reducing a traffic (this property do not use at this action method)
var requestData = {
Scenario: angular.extend({}, scenario, { Expenditures: null }),
TeamsInScenario: union((project.Teams || []), getTeamsInScenario(scenario))
};
var request = getAntiXSRFRequest($scope.dataUrls.editScenarioFinInfoUrl, 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
refreshScenarioTeamsCapacity(scenario);
var $html = angular.element(content);
// we should destroy dynamic scopes before DOM elements will be destroyed
$html.on('$destroy', destroyAngularForm);
var $element = $compile($html)($scope);
var data = {
Expenditures: scenario.Expenditures,
Rates: scenario.Rates
};
$document.trigger('rmo.open-scenario-fininfo-window', {
html: $element
});
$scope.$broadcast('changeScenarioDetails', data);
unblockUI();
} catch (e) {
console.error(e);
unblockUI();
showErrorModal();
}
}).error(function (data, status, headers, config) {
console.error('A server error occurred in ' + $scope.dataUrls.editScenarioFinInfoUrl + ' action');
unblockUI();
showErrorModal();
});
} catch (e) {
console.error(e);
unblockUI();
showErrorModal();
}
};
// Checks all mix teams exist in Live DB and reloads mix, if not
function updateMixTeamsFromLiveDb(callbackFn) {
var mixTeams = teamInfoService.getAll(true);
var mixTeamsIds = [];
var absentTeams = [];
angular.forEach(mixTeams, function (team, key) {
if (!team.IsNew) {
mixTeamsIds.push(team.Id);
}
});
blockUI();
try {
teamInfoService.getTeamsById(mixTeamsIds, true).then(function (receivedTeams) {
absentTeams = $.grep(mixTeamsIds, function (mixTeamId, index) {
return !receivedTeams[mixTeamId];
});
unblockUI();
if (absentTeams.length > 0) {
bootbox.alert('Some Mix teams no longer exist in Live database. Press OK to reload Mix before projects update.<br/>' +
'Unsaved Mix data will be preserved...', function () {
$scope.$apply(function () {
var newFilterSelection = $.grep($scope.data.SelectedFilterTeamsAndViews, function (item, index) {
return (absentTeams.indexOf(item) < 0);
});
$rootScope.$broadcast("changeDisplayView", $scope.Calendar.StartDate, $scope.Calendar.EndDate, newFilterSelection, callbackFn);
});
});
}
else {
if (angular.isFunction(callbackFn))
callbackFn();
}
});
} catch (e) {
console.error(e);
unblockUI();
showErrorModal();
}
};
function isScenarioOutOfMix(scenario) {
if (!scenario)
return false;
var scenStartDateMs = getNearestDate(scenario.StartDate),
scenEndDateMs = getNearestDate(scenario.EndDate),
mixStartDateMs = getNearestDate($scope.Calendar.StartDate),
mixEndDateMs = getNearestDate($scope.Calendar.EndDate);
return (scenStartDateMs > mixEndDateMs || scenEndDateMs < mixStartDateMs)
};
function castDisplayModeIntoTeamInfoMode() {
var displayMode = angular.copy($scope.DisplayMode);
displayMode.GroupBy = getGroupByMode();
return displayMode;
};
function getGroupByMode() {
return $scope.DisplayMode.GroupCapacityByTeam === true ? 'Team' : 'Category';
};
}]);