5945 lines
237 KiB
JavaScript
5945 lines
237 KiB
JavaScript
'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';
|
||
};
|
||
}]); |