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