'use strict'; var C_HEADER_PERIOD_MONTH = 1; var C_HEADER_PERIOD_WEEK = 2; var C_HEADER_DATA_TYPE_ORDINAL = 1; var C_HEADER_DATA_TYPE_TOTALS = 100; var C_CALENDAR_VIEW_MODE_FORECAST = "F"; var C_CALENDAR_VIEW_MODE_ACTUALS = "A"; app .factory('timeTracker', ['$timeout', function ($timeout) { var ctx = { trackerQueue: [], startTrack: function (key) { if (!(key in ctx.trackerQueue)) { ctx.trackerQueue[key] = new Date(); console.log('tracking of ' + key + ' started: ' + ctx.trackerQueue[key]); } }, endTrack: function (key) { if (key in ctx.trackerQueue) { console.log('tracking of ' + key + ' took: ' + (new Date() - ctx.trackerQueue[key]) + ' ms'); delete ctx.trackerQueue[key]; } }, logTrack: function (key) { if (key in ctx.trackerQueue) { console.log('tracking of ' + key + ' taking: ' + (new Date() - ctx.trackerQueue[key]) + ' ms'); } } }; return ctx; }]) .directive('ngMax', function () { return { restrict: 'A', require: 'ngModel', link: function (scope, elem, attr, ctrl) { scope.$watch(attr.ngMax, function () { ctrl.$setViewValue(ctrl.$viewValue); }); var maxValidator = function (value) { var max = scope.$eval(attr.ngMax) || Infinity; if (value && value > max) { ctrl.$setValidity('ngMax', false); return value; } else { ctrl.$setValidity('ngMax', true); return value; } }; ctrl.$parsers.push(maxValidator); ctrl.$formatters.push(maxValidator); } }; }) .directive('requireMultiple', function () { return { require: 'ngModel', link: function postLink(scope, element, attrs, ngModel) { ngModel.$validators.required = function (value) { return angular.isArray(value) && value.length > 0; }; } }; }) .directive('postRepeatDirective', ['$timeout', 'timeTracker', function ($timeout, timeTracker) { return function (scope, element, attrs) { if (scope.$last) { //var start = new Date(); //$log.debug('DOM rendering started ' + start); $timeout(function () { //var end = new Date(); timeTracker.endTrack('gridRender'); //$log.debug("## DOM rendering list took: " + (end - start) + " ms"); //$log.debug('DOM rendering end ' + end); }); } }; }]) .filter('exceptThis', function () { return function (inputArray, filterCriteria) { if (null === inputArray || undefined === inputArray) return null; return inputArray.filter(function (item) { // if the value of filterCriteria is "falsy", retain the inputArray as it is // then check if the currently checked item in the inputArray is different from the filterCriteria, // if so, keep it in the filtered results var exists = false; if (filterCriteria) { for (var i = 0; i < filterCriteria.length; i++) { if (filterCriteria[i].Id === item.Id) { exists = true; break; } } } return !filterCriteria || !exists; }); }; }) .controller('scenarioDetailsCalendarController', ['$scope', '$rootScope', '$http', '$location', '$timeout', '$filter', '$sce', '$document', 'timeTracker', 'dndKey', function ($scope, $rootScope, $http, $location, $timeout, $filter, $sce, $document, timeTracker, dndKey) { var AllocationMode = { AssignRest: 1, AssignAll: 2, Reset: 3 }; // SA. ENV-574 $scope.Monthes = ['Jun', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; $scope.CollapsedIcon = 'fa-plus-square'; $scope.NonCollapsedIcon = 'fa-minus-square'; $scope.GuidEmpty = '00000000-0000-0000-0000-000000000000'; $scope.CommonErrorMessage = 'An error occurred while processing your request. Please, try again later.'; $scope.ExpendituresIsOverAllocated = false; $scope.ExpendituresIsUnderAllocated = false; $scope.TeamsIsOverAllocated = false; $scope.IsPossibleMarginDesignated = true; $scope.refreshGraph = false; //notification from the Scenario Header controller $scope.$on('refreshScenarioDetails', function (event, data) { console.log(data); try { var sd = new Date(data.startDate); var ed = new Date(data.endDate); $scope.data.Scenario.Id = data.id; $scope.data.Scenario.Name = data.name; $scope.data.Scenario.ParentId = data.parentId; $scope.data.Scenario.OldTemplateId = data.oldTemplateId; $scope.data.Scenario.TemplateId = data.templateId; $scope.data.Scenario.StartDate = Date.UTC(sd.getFullYear(), sd.getMonth(), sd.getDate()); $scope.data.Scenario.EndDate = Date.UTC(ed.getFullYear(), ed.getMonth(), ed.getDate()); $scope.data.Scenario.GrowthScenario = data.growthScenario; $scope.data.Scenario.ProjectedRevenue = data.revenue; $scope.data.Scenario.TDDirectCosts = data.expense; $scope.data.Scenario.UseLMMargin = data.useLMMargin; $scope.data.Scenario.GrossMargin = data.grossMargin; $scope.data.Scenario.LMMargin = data.lmMargin; $scope.data.Scenario.IsActiveScenario = data.isActiveScenario; $scope.data.Scenario.CostSavings = data.costSaving; $scope.data.Scenario.NeedToAdjustMargin = data.needToAdjustMargin; if (data.dateForStartOfChanges) { var sc = new Date(data.dateForStartOfChanges); $scope.data.Scenario.DateForStartOfChanges = Date.UTC(sc.getFullYear(), sc.getMonth(), sc.getDate()); } // need to clear all information about teams for full recalculation on the server // when user came from the header scenario details grid has no changes so we can to recalculate data again $.each($scope.data.Calendar.Expenditures, function (expCatId, expCat) { delete expCat.Teams; }); $scope.data.TeamsInScenario = null; $scope.data.NeedToRefreshAllTeams = true; $scope.data.NeedToRecalculateScenarioDetails = true; if (refreshScenarioStatus && typeof refreshScenarioStatus == 'function') refreshScenarioStatus(data.isActiveScenario); } catch (e) { showErrorModal('Oops!', $scope.CommonErrorMessage); return; } $scope.gridChanged(); $scope.getCalendar(); }); $scope.$on('changeScenarioDetails', function (event, data) { if (!data) return; $scope.data.Calendar.Expenditures = data.Expenditures || {}; $scope.data.Rates = data.Rates || []; $scope.data.AvailableExpenditures = []; $scope.data.LocalRatesLoaded = true; for (var expCatId in data.Expenditures) { $scope.data.AvailableExpenditures.push({ Id: expCatId, Name: data.Expenditures[expCatId].ExpenditureCategoryName }); } $scope.getCalendar(null, normalizeTeamQuantityValues); }); // SA. ENV-1148. Bug fix: Teams for EC-row disappear, when moving forward, back and forward in create scenario wizard $scope.initData = function () { $scope.data = { Scenario: { Id: null, Name: null, ParentId: null, OldTemplateId: null, TemplateId: null, StartDate: null, EndDate: null, GrowthScenario: false, ProjectedRevenue: null, TDDirectCosts: null, Type: 0, UseLMMargin: false, GrossMargin: null, LMMargin: null, IsActiveScenario: null, DateForStartOfChanges: null, ActualStartDate: 0, ActualEndDate: 0, GreenIndicator: null, YellowIndicator: null, RedIndicator: null, SaveAs: null, SaveAsDraft: false, // by default need to save full information by scenario NeedToAdjustMargin: false, CostSavings: null, DistributionType: 0 // original distribution type }, ScenarioGeneralInfoEditModel: { StartDate: null, EndDate: null, ProjectDeadline: null, ActualStartDate: null, ActualEndDate: null, HasActuals: false, IsChanged: false, DistributionType: 0 // original distribution type }, CalendarFilter: { CategoryType: null, LaborMaterials: null, IncomeType: null, GLAccount: null, CreditDepartment: null, SelectedExpCats: [], VisibleExpCats: [], SecondaryExpCats: [], IsTableModeQuantity: true, IsViewModeMonth: true, ShowActuals: false, IsUOMHours: false, ScenarioId: null, ShowAvgTotals: false, ShowFilters: null, ViewModeName: "", // SA. ENV-667 DragNDropReplaceValues: true }, Calendar: { Headers: null, WeekHeaders: null, // SA. ENV-667 MonthHeaders: null, // SA. ENV-667 Rows: null, Expenditures: null }, ExpenditureAddGroups: [ // SA. ENV-840 { Id: 0, Name: "Other Categories" }, { Id: 1, Name: "Categories of current Teams" } ], AvailableExpenditures: null, Expenditures2Add: null, Teams: null, TeamsInScenario: null, CategoryTypes: null, CGEFX: null, IncomeTypes: null, GLAccounts: null, CreditDepartments: null, Rates: null, NeedToRebind: true, NeedToRecalculateScenarioDetails: false, // using with NeedToRecalculateScenarioDetails = true for refreshing all teams for all categories // e.g. it is usable when user come to scenario details from header form NeedToRefreshAllTeams: false, isDataChanged: false, VisibleCellCount: 1, Opener: 1 //'details' }; }; $scope.initData(); $scope.typeOptions = [ { name: 'Quantity', value: 'Quantity' }, { name: 'Cost', value: 'Cost' } ]; $scope.reallocator = { SourceExpCat: null, TargetExpCat: null, SourceWeekEnding: null, TargetWeekEnding: null, Percentage: 100, ReallocateBy: $scope.typeOptions[0].value, CurveType: 'source', OtherCurve: null, }; $scope.GraphFilter = { ExpCats: [], Actuals: false, ShowTotal: false, ShowTime: false, Quantity: false, }; $scope.pushpuller = { weeks: 1, push: true, resource: null, startdate: null, maxWeeks: null }; $scope.editTotal = { ExpCat: null, CurrentTotal: null, NewTotal: null, OtherCurve: null, }; $scope.formatCells = { ExpCats: [], SelectedExpCats: null, StartDate: null, EndDate: null, DecimalCalcCostPlaces: 4, DecimalCalcQtyPlaces: 6, RoundOptions: [ { Name: '10th', Value: 0.1 }, { Name: 'Fourth', Value: 0.25 }, { Name: 'Half', Value: 0.5 }, { Name: 'Full', Value: 1 }, ] }; $scope.formatCells.DecimalPlaces = $scope.formatCells.RoundOptions[1].Value; $scope.RoundingBasis = 0.01; $scope.RestTeams = []; $scope.$watch("reallocator.SourceExpCat", function (newValue, oldValue) { if (newValue != oldValue) { $scope.data.CalendarFilter.SecondaryExpCats = []; $.each($scope.data.CalendarFilter.VisibleExpCats, function (i, expCat) { if (expCat.Id != $scope.reallocator.SourceExpCat) { $scope.data.CalendarFilter.SecondaryExpCats.push(expCat); } }); if ($scope.data.CalendarFilter.SecondaryExpCats && ($scope.data.CalendarFilter.SecondaryExpCats.length > 0) && $scope.data.CalendarFilter.SecondaryExpCats[0]) { $scope.reallocator.TargetExpCat = $scope.data.CalendarFilter.SecondaryExpCats[0].Id; } else { $scope.reallocator.TargetExpCat = undefined; } } }); function updateECTotals(rowIndex, colIndex, newQuantity, oldQuantity, newCost, oldCost) { var monthHeaderIndex = $scope.data.Calendar.WeekHeaders[colIndex].MonthHeader; var monthHeader = $scope.data.Calendar.MonthHeaders[monthHeaderIndex]; var monthTotalCellIndex = monthHeader.TotalsHeader; var rowEC = $scope.data.Calendar.Rows[rowIndex]; var totalQuantity = 0; var totalCost = 0; var weeksCount = 0; for (var index = 0; index < monthHeader.WeekHeaders.length; index++) { var currentWeekHeaderIndex = monthHeader.WeekHeaders[index]; if (colIndex == currentWeekHeaderIndex) { totalQuantity += newQuantity; totalCost += newCost; weeksCount++; } else { if ($scope.data.Calendar.WeekHeaders[currentWeekHeaderIndex].Visible[$scope.data.CalendarFilter.ViewModeName]) { totalQuantity += rowEC.QuantityValues[currentWeekHeaderIndex]; totalCost += rowEC.CostValues[currentWeekHeaderIndex]; weeksCount++; } } } if (isAvgMode()) { newQuantity = Math.round(totalQuantity * 1000 / weeksCount) / 1000; oldQuantity = rowEC.QuantityValues[monthTotalCellIndex]; } else { oldQuantity = rowEC.QuantityValues[monthTotalCellIndex]; oldCost = rowEC.CostValues[monthTotalCellIndex]; newQuantity = totalQuantity; } newCost = totalCost; //update month cell rowEC.QuantityValues[monthTotalCellIndex] = calcWithSamePresicion(rowEC.QuantityValues[monthTotalCellIndex], (newQuantity - oldQuantity), false); rowEC.CostValues[monthTotalCellIndex] = calcWithSamePresicion(rowEC.CostValues[monthTotalCellIndex], (newCost - oldCost), true); // update row grand totals var grandTotalQuantity = 0; var grandTotalCost = 0; weeksCount = 0; for (var i = 0; i < $scope.data.Calendar.WeekHeaders.length; i++) { if (($scope.data.Calendar.WeekHeaders[i].DataType == C_HEADER_DATA_TYPE_ORDINAL) && ($scope.data.Calendar.WeekHeaders[i].Visible[$scope.data.CalendarFilter.ViewModeName])) { grandTotalQuantity = calcWithSamePresicion(grandTotalQuantity, rowEC.QuantityValues[i], false); grandTotalCost = calcWithSamePresicion(grandTotalCost, rowEC.CostValues[i], true); weeksCount++; } } if (isAvgMode()) { totalQuantity = Math.round(grandTotalQuantity * 1000 / weeksCount) / 1000; } else { totalQuantity = grandTotalQuantity; } totalCost = grandTotalCost; //Math.round(grandTotalCost / monthCount * 100) / 100; // update column totals var rowECTotal = $scope.data.Calendar.Rows[0]; rowECTotal.CostValues[monthTotalCellIndex] = calcWithSamePresicion(rowECTotal.CostValues[monthTotalCellIndex], (newCost - oldCost), true); rowECTotal.QuantityValues[monthTotalCellIndex] = calcWithSamePresicion(rowECTotal.QuantityValues[monthTotalCellIndex], (newQuantity - oldQuantity), false); rowECTotal.GrandTotalQuantity = calcWithSamePresicion(rowECTotal.GrandTotalQuantity, totalQuantity - rowEC.GrandTotalQuantity, false); rowECTotal.GrandTotalCost = calcWithSamePresicion(rowECTotal.GrandTotalCost, totalCost - rowEC.GrandTotalCost, true); setRowGrandTotal(rowECTotal); rowEC.GrandTotalQuantity = totalQuantity; rowEC.GrandTotalCost = totalCost; setRowGrandTotal(rowEC); } // you can send resource or team as entity parameter function updateTotals(entity, colIndex, newQuantity) { var monthHeaderIndex = $scope.data.Calendar.WeekHeaders[colIndex].MonthHeader; var monthHeader = $scope.data.Calendar.MonthHeaders[monthHeaderIndex]; var monthTotalCellIndex = monthHeader.TotalsHeader; var total = 0, weekCount = 0, oldQuantity = entity.QuantityValues[monthTotalCellIndex]; for (var index = 0; index < monthHeader.WeekHeaders.length; index++) { var currentWeekHeaderIndex = monthHeader.WeekHeaders[index]; if (colIndex == currentWeekHeaderIndex) { total += newQuantity; weekCount++; } else { if ($scope.data.Calendar.WeekHeaders[currentWeekHeaderIndex].Visible[$scope.data.CalendarFilter.ViewModeName]) { total += entity.QuantityValues[currentWeekHeaderIndex]; weekCount++; } } } if (isAvgMode()) { newQuantity = Math.round(total * 1000 / weekCount) / 1000; } else { //update month cell newQuantity = total; } //update month cell entity.QuantityValues[monthTotalCellIndex] += (newQuantity - oldQuantity); entity.RestQuantityValues[monthTotalCellIndex] -= (newQuantity - oldQuantity); } // update row grand totals function updateGrandTotals(entity) { var grandTotalQuantity = 0; var weekCount = 0; for (var i = 0; i < $scope.data.Calendar.WeekHeaders.length; i++) { if (($scope.data.Calendar.WeekHeaders[i].DataType == C_HEADER_DATA_TYPE_ORDINAL) && ($scope.data.Calendar.WeekHeaders[i].Visible[$scope.data.CalendarFilter.ViewModeName])) { grandTotalQuantity += entity.QuantityValues[i]; weekCount++; } } if (isAvgMode()) { grandTotalQuantity = Math.round(grandTotalQuantity * 1000 / weekCount) / 1000; } else { grandTotalQuantity = grandTotalQuantity; } entity.GrandTotalQuantity = grandTotalQuantity; } function allocateResource(resource, expCatId, team, mode) { if (resource != null) { resource.GrandTotalQuantity = 0; resource.GrandTotalCost = 0; for (var i = 0; i < $scope.data.Calendar.WeekHeaders.length; i++) { if ($scope.data.Calendar.WeekHeaders[i].DataType != C_HEADER_DATA_TYPE_ORDINAL) continue; if (resource.ReadOnly[i]) continue; var quantity = 0; if (mode == AllocationMode.Reset) { quantity = -resource.QuantityValues[i]; } else if (mode == AllocationMode.AssignRest) { var needToAssign = Math.max(team.QuantityValues[i] - team.AllocatedByResources[i], 0); var canBeAssigned = Math.max(resource.RestQuantityValues[i], 0); quantity = Math.min(needToAssign, canBeAssigned); } else if (mode == AllocationMode.AssignAll) { var needToAssign = Math.max(team.QuantityValues[i] - team.AllocatedByResources[i], 0); var canBeAssigned = Math.max(resource.CapacityQuantityValues[i] - resource.QuantityValues[i], 0); quantity = Math.min(needToAssign, canBeAssigned); } var newQuantity = resource.QuantityValues[i] + quantity; updateTotals(resource, i, newQuantity); resource.QuantityValues[i] = newQuantity; resource.RestQuantityValues[i] -= quantity; team.AllocatedByResources[i] += quantity; updateGrandTotals(resource); // SA. ENV-1135. Grand totals update moced to separate function var rawTeam = $scope.data.Calendar.Expenditures[expCatId].Teams[team.Id]; var weekEnding = $scope.data.Calendar.WeekHeaders[i].Milliseconds; rawTeam.Resources[resource.Id].QuantityValues[weekEnding] = resource.QuantityValues[i] / $scope.getUomMultiplier(expCatId); rawTeam.Resources[resource.Id].RestQuantityValues[weekEnding] = resource.RestQuantityValues[i] / $scope.getUomMultiplier(expCatId); rawTeam.Resources[resource.Id].Changed = true; rawTeam.Changed = true; } $scope.gridChanged(); $scope.RefreshCSSClasses(); } } function allocateTeam(expCat, team, mode) { if (!team || !expCat) return; team.GrandTotalQuantity = 0; team.GrandTotalCost = 0; for (var i = 0; i < $scope.data.Calendar.WeekHeaders.length; i++) { if ($scope.data.Calendar.WeekHeaders[i].DataType != C_HEADER_DATA_TYPE_ORDINAL) continue; var quantity = 0; if (mode == AllocationMode.Reset) { quantity = -team.QuantityValues[i]; } else if (mode == AllocationMode.AssignRest) { var needToAssign = Math.max(expCat.RestQuantity[i], 0); var canBeAssigned = Math.max(team.RestQuantityValues[i], 0); quantity = Math.min(needToAssign, canBeAssigned); } else if (mode == AllocationMode.AssignAll) { var needToAssign = Math.max(expCat.RestQuantity[i], 0); var canBeAssigned = Math.max(team.CapacityQuantityValues[i] - team.QuantityValues[i], 0); quantity = Math.min(needToAssign, canBeAssigned); } var newQuantity = team.QuantityValues[i] + quantity; updateTotals(team, i, newQuantity); team.QuantityValues[i] = newQuantity; team.RestQuantityValues[i] -= quantity; expCat.RestQuantity[i] -= quantity; updateGrandTotals(team); // SA. ENV-1135. Grand totals update moced to separate function $scope.data.Calendar.Expenditures[expCat.ExpCatId].Teams[team.Id].QuantityValues[$scope.data.Calendar.WeekHeaders[i].Milliseconds] = team.QuantityValues[i] / $scope.getUomMultiplier(expCat.ExpCatId); $scope.data.Calendar.Expenditures[expCat.ExpCatId].Teams[team.Id].RestQuantityValues[$scope.data.Calendar.WeekHeaders[i].Milliseconds] = team.RestQuantityValues[i] / $scope.getUomMultiplier(expCat.ExpCatId); $scope.data.Calendar.Expenditures[expCat.ExpCatId].Teams[team.Id].Changed = true; } $scope.gridChanged(); $scope.RefreshCSSClasses(); } function isAvgMode() { return $scope.data.CalendarFilter.ShowAvgTotals && !$scope.data.CalendarFilter.IsUOMHours && $scope.data.CalendarFilter.IsTableModeQuantity; } $scope.init = function (initData, fnCallback) { // SA. ENV-1148. Bug fix: Teams for EC-row disappear, when moving forward, back and forward in create scenario wizard $scope.initData(); $scope.data.Scenario.Id = initData.Id; $scope.data.Scenario.Name = initData.Name; $scope.data.Scenario.ParentId = initData.ParentId; $scope.data.Scenario.TemplateId = initData.TemplateId; $scope.data.Scenario.StartDate = initData.StartDate; $scope.data.Scenario.EndDate = initData.EndDate; $scope.data.Scenario.ProjectedRevenue = initData.ProjectedRevenue; $scope.data.Scenario.GrowthScenario = initData.GrowthScenario; $scope.data.Scenario.Type = initData.Type; $scope.data.Scenario.UseLMMargin = initData.UseLMMargin; $scope.data.Scenario.GrossMargin = initData.GrossMargin; $scope.data.Scenario.LMMargin = initData.LMMargin; $scope.data.Scenario.Duration = initData.Duration; $scope.data.Scenario.TDDirectCosts = initData.TDDirectCosts; $scope.data.Scenario.CGSplit = initData.CGSplit; $scope.data.Scenario.EFXSplit = initData.EFXSplit; $scope.data.Scenario.Shots = initData.Shots; $scope.data.Scenario.IsActiveScenario = initData.IsActiveScenario; $scope.data.Scenario.NeedToAdjustMargin = initData.NeedToAdjustMargin; $scope.data.CalendarFilter.ShowAvgTotals = initData.ShowAvgTotals; $scope.data.CalendarFilter.IsUOMHours = initData.IsUOMHours; $scope.data.CalendarFilter.ShowActuals = initData.ShowActuals; $scope.data.CalendarFilter.IsTableModeQuantity = initData.IsTableModeQuantity; $scope.data.CalendarFilter.IsViewModeMonth = initData.IsViewModeMonth; $scope.data.CalendarFilter.ShowFilters = initData.ShowFilters; $scope.data.AvailableExpenditures = initData.AvailableExpenditures; $scope.data.NeedToRecalculateScenarioDetails = initData.NeedToRecalculateScenarioDetails; $scope.Opener = initData.Opener; $scope.data.ScenarioGeneralInfoEditModel.StartDate = $filter('date')(initData.StartDate, 'MM/dd/yyyy'); $scope.data.ScenarioGeneralInfoEditModel.EndDate = $filter('date')(initData.EndDate, 'MM/dd/yyyy'); if ($scope.data.Scenario.Id == $scope.GuidEmpty) { if (!$scope.data.TeamsInScenario || $scope.data.TeamsInScenario.length <= 0) { $scope.data.TeamsInScenario = initData.TeamsInScenario; } else { if (initData.TeamsInScenario) { // user can edit teams allocation from the outside only if he did not edit grid yet // so we need to recalculate all teams information for current scenario if (!$scope.gridWasChanged()) { $.each($scope.data.TeamsInScenario, function (i, team) { removeTeamInternal(team.TeamId); }); } else { // removing unused teams and marking old teams $.each($scope.data.TeamsInScenario, function (i, team) { var exists = initData.TeamsInScenario.some(function (element, index, array) { if (element.TeamId == team.TeamId) { team.IsNew = false; return true; } return false; }); if (!exists) removeTeamInternal(team.TeamId); }); } // adding new teams $.each(initData.TeamsInScenario, function (i, team) { var exists = $scope.data.TeamsInScenario.some(function (element, index, array) { return (element.TeamId == team.TeamId); }); if (!exists) $scope.data.TeamsInScenario.push(team); }); } } } // SA. ENV-815 $scope.data.DataSection = initData.DataSection; $scope.data.Preferences = initData.PagePreferences; if (initData.PagePreferences && initData.PagePreferences.length > 0) { var pagePrefArray = JSON.parse(initData.PagePreferences); for (var i = 0; i < pagePrefArray.length; i++) { switch (pagePrefArray[i].Key) { case "uomMode": $scope.data.CalendarFilter.IsUOMHours = pagePrefArray[i].Value; break; case "chMode": $scope.data.CalendarFilter.IsTableModeQuantity = pagePrefArray[i].Value; break; case "monthWeekMode": $scope.data.CalendarFilter.IsViewModeMonth = pagePrefArray[i].Value; break; case "actualsMode": $scope.data.CalendarFilter.ShowActuals = pagePrefArray[i].Value; break; case "dragNDropReplaceValues": $scope.data.CalendarFilter.DragNDropReplaceValues = pagePrefArray[i].Value; break; case "showFilters": $scope.data.CalendarFilter.ShowFilters = pagePrefArray[i].Value; break; case "actualsModeGraph": $scope.GraphFilter.Actuals = pagePrefArray[i].Value; break; case "totalGraph": $scope.GraphFilter.ShowTotal = pagePrefArray[i].Value; break; //case "timeGraph": // $scope.GraphFilter.ShowTime = pagePrefArray[i].Value; // break; //case "quantityGraph": // $scope.GraphFilter.Quantity = pagePrefArray[i].Value; // break; default: console.log("Restore page preferences (Scenario Details): data key not found in parsing loaded preferences (datakey=" + pagePrefArray[i].Key + ", dataSection=" + $scope.dataSection + ")"); } } } if (initData.InitOnDemand !== true) $scope.getCalendar(fnCallback); }; $scope.getCalendar = function (fnCallback, fnRightAfterLoadCallback) { if ($scope.data.Scenario.StartDate <= 0 || $scope.data.Scenario.EndDate <= 0) return; blockUI(); // maybe it is temp solution var dataCopy = angular.copy($scope.data); var snapshot = { Scenario: $scope.data.Scenario, CalendarFilter: $scope.data.CalendarFilter, AvailableExpenditures: $scope.data.AvailableExpenditures, TeamsInScenario: $scope.data.TeamsInScenario, Rates: $scope.data.Rates, NeedToRebind: $scope.data.NeedToRebind, NeedToRecalculateScenarioDetails: $scope.data.NeedToRecalculateScenarioDetails, NeedToRefreshAllTeams: $scope.data.NeedToRefreshAllTeams, LocalRatesLoaded: $scope.data.LocalRatesLoaded, Calendar: { Expenditures: $scope.data.Calendar.Expenditures } }; var postData = JSON.parse(JSON.stringify(snapshot)); //$scope.data = null; $http.post('/Scenarios/RecalculateCalendar', postData). success(function (data, status, headers, config) { try { if (data.NeedToRebind) { $scope.data.AvailableExpenditures = data.AvailableExpenditures; $scope.data.CGEFX = data.CGEFX; $scope.data.Calendar = data.Calendar; $scope.data.CategoryTypes = data.CategoryTypes; $scope.data.CreditDepartments = data.CreditDepartments; $scope.data.GLAccounts = data.GLAccounts; $scope.data.IncomeTypes = data.IncomeTypes; $scope.data.Rates = data.Rates; $scope.data.Scenario = data.Scenario; $scope.data.Rates = data.Rates; $scope.data.Expenditures2Add = data.Expenditures2Add; $scope.data.Teams = data.Teams; $scope.data.NeedToRebind = false; $scope.data.ScenarioGeneralInfoEditModel.ProjectDeadline = data.Scenario.ProjectDeadline; $scope.data.ScenarioGeneralInfoEditModel.ActualStartDate = data.Scenario.ActualStartDate; $scope.data.ScenarioGeneralInfoEditModel.ActualEndDate = data.Scenario.ActualEndDate; $scope.data.ScenarioGeneralInfoEditModel.HasActuals = !!data.Scenario.ActualEndDate ? 1 : 0; // SA. ENV-840 for (var i = 0; i < $scope.data.Expenditures2Add.length; i++) { var exp = $scope.data.Expenditures2Add[i]; exp.GroupElement = $scope.data.ExpenditureAddGroups[exp.IsRelatedToScenario]; } } else { $scope.data = dataCopy; $scope.data.Scenario = data.Scenario; $scope.data.AvailableExpenditures = data.AvailableExpenditures; $scope.data.NeedToRecalculateScenarioDetails = data.NeedToRecalculateScenarioDetails; $scope.data.Calendar = data.Calendar; } $scope.data.TeamsInScenario = data.TeamsInScenario; $scope.data.NeedToRefreshAllTeams = false; if (!data.Calendar || !data.Calendar.Headers || !data.Calendar.Expenditures || Object.keys(data.Calendar.Expenditures).length <= 0) { $scope.data.Scenario.NeedToAdjustMargin = false; unblockUI(); return; } // SA. ENV-667 $scope.data.Calendar.WeekHeaders = $scope.data.Calendar.Headers[C_HEADER_PERIOD_WEEK]; $scope.data.Calendar.MonthHeaders = $scope.data.Calendar.Headers[C_HEADER_PERIOD_MONTH]; if ($scope.data.CalendarFilter.ShowActuals) $scope.data.CalendarFilter.ViewModeName = C_CALENDAR_VIEW_MODE_ACTUALS; else $scope.data.CalendarFilter.ViewModeName = C_CALENDAR_VIEW_MODE_FORECAST; if (angular.isFunction(fnRightAfterLoadCallback)) fnRightAfterLoadCallback(); //$scope.data = data; $scope.switchViewMode(); $scope.initGridRows(); $scope.setGridSource(); if ($scope.data.AvailableExpenditures != null && $scope.data.AvailableExpenditures.length > 0) { $scope.reallocator.SourceExpCat = $scope.data.AvailableExpenditures[0].Id; $scope.reallocator.TargetExpCat = $scope.data.AvailableExpenditures.length > 1 ? $scope.data.AvailableExpenditures[1].Id : null; $scope.reallocator.OtherCurve = $scope.data.AvailableExpenditures[0].Id; $scope.editTotal = { ExpCat: $scope.data.AvailableExpenditures[0].Id, OtherCurve: $scope.data.AvailableExpenditures[0].Id }; } $scope.GraphFilter.ExpCats = new Array(); $.each($scope.data.Calendar.Expenditures, function (expCatId, ecg) { $scope.GraphFilter.ExpCats[expCatId] = true; }); if ($scope.refreshGraph) { $scope.showGraph(); $scope.refreshGraph = false; } var visibleHeaders = $scope.data.Calendar.WeekHeaders.filter(function (obj) { return obj.Visible[$scope.data.CalendarFilter.ViewModeName]; }); if (visibleHeaders.length > 0) { // apply first column values to modal form's models $scope.reallocator.SourceWeekEnding = visibleHeaders[0].Title; var dt = new Date(visibleHeaders[0].Milliseconds); $scope.formatCells.InitStartDate = (dt.getUTCMonth() + 1) + "/" + dt.getUTCDate() + "/" + dt.getUTCFullYear(); $scope.formatCells.StartDate = $scope.formatCells.InitStartDate; // apply second column values to modal form's models if (visibleHeaders.length > 1) { $scope.reallocator.TargetWeekEnding = visibleHeaders[visibleHeaders.length - 2].Title; dt = new Date(visibleHeaders[visibleHeaders.length - 2].Milliseconds); $scope.formatCells.InitEndDate = (dt.getUTCMonth() + 1) + "/" + dt.getUTCDate() + "/" + dt.getUTCFullYear(); $scope.formatCells.EndDate = $scope.formatCells.InitEndDate; } } $scope.editTotal.CurrentTotal = roundToNearest($scope.data.Calendar.Rows[1].GrandTotalQuantity, $scope.RoundingBasis); var numberOfWeeks = 0; for (var i = 0; i < $scope.data.Calendar.WeekHeaders.length; i++) { if ($scope.data.Calendar.WeekHeaders[i].DataType == C_HEADER_DATA_TYPE_ORDINAL && $scope.data.Calendar.WeekHeaders[i].Visible[$scope.data.CalendarFilter.ViewModeName]) numberOfWeeks++; } $scope.pushpuller.maxWeeks = numberOfWeeks - 1; $scope.RestTeams = getRestTeams(); $scope.RefreshCSSClasses(); if ($scope.data.Scenario.NeedToAdjustMargin) { if ((Math.round($scope.data.Calendar.Rows[0].GrandTotalCost * 100) / 100) > (Math.round($scope.data.Scenario.TDDirectCosts * 100) / 100)) $scope.IsPossibleMarginDesignated = false; $scope.data.Scenario.NeedToAdjustMargin = false; } if (angular.isFunction(fnCallback)) fnCallback(); $document.trigger('sd.dataload.completed', $scope.getTotalCostsByMonthes()); unblockUI(); //console.log('load data ended ' + new Date()); //timeTracker.startTrack('gridRender'); } catch (e) { console.log(e); unblockUI(); showErrorModal('Oops!', $scope.CommonErrorMessage); } }). error(function (data, status, headers, config) { // called asynchronously if an error occurs // or server returns response with an error status. console.log("an error occurred while loading calendar"); unblockUI(); showErrorModal('Oops!', $scope.CommonErrorMessage); }); }; $scope.onPercentageChange = function (val) { $scope.reallocator.Percentage = val; }; $scope.checkValue = function (data, rowId, colIndex) { var newValue = parseFloat(data); if (isNaN(newValue)) newValue = 0; if (newValue < 0) { return "Value should not be less than zero"; } var isMonth = $scope.data.Calendar.WeekHeaders[colIndex].DataType == C_HEADER_DATA_TYPE_TOTALS; for (var i = 0; i < $scope.data.Calendar.Rows.length; i++) { if ($scope.data.Calendar.Rows[i].ExpCatId === rowId) { if (isMonth) { var ec = $scope.data.Calendar.Rows[i]; var ec_val = $scope.data.CalendarFilter.IsTableModeQuantity ? ec.QuantityValues[colIndex] : ec.CostValues[colIndex]; var weekHeader = $scope.data.Calendar.WeekHeaders[colIndex]; var monthHeaderIndex = weekHeader.MonthHeader; var weeks = $scope.data.Calendar.MonthHeaders[monthHeaderIndex].WeekHeaders.length; var oldValue = ec_val; var avg = ($scope.data.CalendarFilter.IsTableModeQuantity && $scope.data.CalendarFilter.ShowAvgTotals && !$scope.data.CalendarFilter.IsUOMHours); var coef = avg ? (newValue / oldValue) : (ec_val > 0 ? newValue / ec_val : 1); for (var j = colIndex - 1; j >= 0 && ($scope.data.Calendar.WeekHeaders[j].DataType == C_HEADER_DATA_TYPE_ORDINAL && $scope.data.Calendar.WeekHeaders[j].Visible[$scope.data.CalendarFilter.ViewModeName]) ; j--) { //When old month is zero spread equally, othewise use current curve var val = avg ? (ec_val > 0 ? ec.QuantityValues[j] * coef : newValue) : (ec_val > 0 ? ($scope.data.CalendarFilter.IsTableModeQuantity ? ec.QuantityValues[j] : ec.CostValues[j]) * coef : newValue / weeks); if ($scope.data.CalendarFilter.IsTableModeQuantity) { updateQuantityOrCostCell(rowId, i, j, val, null, true); } else { updateQuantityOrCostCell(rowId, i, j, null, val, false); } } } else { if ($scope.data.CalendarFilter.IsTableModeQuantity) { updateQuantityOrCostCell(rowId, i, colIndex, newValue, null, true); } else { updateQuantityOrCostCell(rowId, i, colIndex, null, newValue, false); } } break; } } $scope.RefreshCSSClasses(); //required to be false by xeditable grid return false; }; $scope.checkTotalValue = function (data, row, sourceRowIndex) { var newValue = parseFloat(data); if (isNaN(newValue)) newValue = 0; if (newValue < 0) { return "Value should not be less than zero"; } if (null === sourceRowIndex) { alert('Cannot find expenditure category to update.'); return false; } var oldQuantity = 0; var oldCost = 0; var oldValue = ($scope.data.CalendarFilter.IsTableModeQuantity ? row.GrandTotalQuantity : row.GrandTotalCost); var changeMult = oldValue != 0 ? parseFloat(newValue) / oldValue : 0; var sourceSumCost = 0, sourceSumQuantity = 0; var numberOfWeeks = 0; for (var i = 0; i < $scope.data.Calendar.WeekHeaders.length; i++) { if ($scope.data.Calendar.WeekHeaders[i].DataType == C_HEADER_DATA_TYPE_TOTALS || !$scope.data.Calendar.WeekHeaders[i].Visible[$scope.data.CalendarFilter.ViewModeName]) continue; sourceSumQuantity += $scope.data.Calendar.Rows[sourceRowIndex].QuantityValues[i]; sourceSumCost += $scope.data.Calendar.Rows[sourceRowIndex].CostValues[i]; numberOfWeeks++; } if (isAvgMode() && numberOfWeeks > 0) { sourceSumQuantity = sourceSumQuantity / numberOfWeeks; } //console.log("sourceSumQuantity: " + sourceSumQuantity + "; curveSumQuantity: " + curveSumQuantity + "; changeMult: " + changeMult); if (sourceSumQuantity > 0) for (var i = 0; i < $scope.data.Calendar.WeekHeaders.length; i++) { if ($scope.data.Calendar.WeekHeaders[i].DataType == C_HEADER_DATA_TYPE_TOTALS || !$scope.data.Calendar.WeekHeaders[i].Visible[$scope.data.CalendarFilter.ViewModeName]) { continue; } oldQuantity = $scope.data.Calendar.Rows[sourceRowIndex].QuantityValues[i]; oldCost = $scope.data.Calendar.Rows[sourceRowIndex].CostValues[i]; if ($scope.data.CalendarFilter.IsTableModeQuantity) updateQuantityOrCostCell(row.ExpCatId, sourceRowIndex, i, Math.abs(oldQuantity * changeMult), null, true); else updateQuantityOrCostCell(row.ExpCatId, sourceRowIndex, i, null, Math.abs(oldCost * changeMult), false); } else { for (var i = 0; i < $scope.data.Calendar.WeekHeaders.length; i++) { if ($scope.data.Calendar.WeekHeaders[i].DataType == C_HEADER_DATA_TYPE_TOTALS || !$scope.data.Calendar.WeekHeaders[i].Visible[$scope.data.CalendarFilter.ViewModeName]) { continue; } if (isAvgMode()) { if ($scope.data.CalendarFilter.IsTableModeQuantity) updateQuantityOrCostCell(row.ExpCatId, sourceRowIndex, i, newValue, null, true); else updateQuantityOrCostCell(row.ExpCatId, sourceRowIndex, i, null, newValue, false); } else { if ($scope.data.CalendarFilter.IsTableModeQuantity) updateQuantityOrCostCell(row.ExpCatId, sourceRowIndex, i, Math.abs(numberOfWeeks == 0 ? newValue : (newValue / numberOfWeeks)), null, true); else updateQuantityOrCostCell(row.ExpCatId, sourceRowIndex, i, null, Math.abs(numberOfWeeks == 0 ? newValue : (newValue / numberOfWeeks)), false); } } } $scope.RefreshCSSClasses(); //required to be false by xeditable grid return false; }; $scope.checkGrandTotalValue = function (data) { var newValue = parseFloat(data); if (isNaN(newValue)) newValue = 0; if (newValue < 0) { return "Value should not be less than zero"; } // it is incorrect situation when rows collection contains only grand total row if (!$scope.data.Calendar.Rows || $scope.data.Calendar.Rows.length <= 1) return false; var oldValue = $scope.data.CalendarFilter.IsTableModeQuantity ? $scope.data.Calendar.Rows[0].GrandTotalQuantity : $scope.data.Calendar.Rows[0].GrandTotalCost, changed = oldValue <= 0 ? (newValue / ($scope.data.Calendar.Rows.length - 1)) : (newValue / oldValue), distributed = 0; for (var i = 1; i < $scope.data.Calendar.Rows.length; i++) { var row = $scope.data.Calendar.Rows[i], newTotalRowValue = 0; if ((i + 1) === $scope.data.Calendar.Rows.length) { newTotalRowValue = newValue - distributed; } else { if (oldValue <= 0) newTotalRowValue = changed; else newTotalRowValue = (($scope.data.CalendarFilter.IsTableModeQuantity ? row.GrandTotalQuantity : row.GrandTotalCost) || 0) * changed; } $scope.checkTotalValue(newTotalRowValue, row, i); distributed += newTotalRowValue; } $scope.RefreshCSSClasses(); //required to be false by xeditable grid return false; }; $scope.checkResourceTotalValue = function (data, ec, team, resource) { if (!$scope.data.CalendarFilter.IsTableModeQuantity || !ec || !team || !resource) return false; var newValue = parseFloat(data); if (isNaN(newValue)) newValue = 0; if (newValue < 0) { return "Value should not be less than zero"; } var oldValue = resource.GrandTotalQuantity; var changeMult = oldValue != 0 ? parseFloat(newValue) / oldValue : 0; var sourceSumQuantity = 0, numberOfWeeks = 0; for (var i in $scope.data.Calendar.WeekHeaders) { if ($scope.data.Calendar.WeekHeaders[i].DataType == C_HEADER_DATA_TYPE_TOTALS || !$scope.data.Calendar.WeekHeaders[i].Visible[$scope.data.CalendarFilter.ViewModeName] || resource.ReadOnly[i]) continue; sourceSumQuantity += resource.QuantityValues[i]; numberOfWeeks++; } if (isAvgMode() && numberOfWeeks > 0) { sourceSumQuantity = sourceSumQuantity / numberOfWeeks; } for (var i in $scope.data.Calendar.WeekHeaders) { if ($scope.data.Calendar.WeekHeaders[i].DataType == C_HEADER_DATA_TYPE_TOTALS || !$scope.data.Calendar.WeekHeaders[i].Visible[$scope.data.CalendarFilter.ViewModeName] || resource.ReadOnly[i]) { continue; } if (sourceSumQuantity > 0) { $scope.checkResourceValue(ec.ExpCatId, team, resource, i, Math.abs(resource.QuantityValues[i] * changeMult), false); } else { if (isAvgMode()) $scope.checkResourceValue(ec.ExpCatId, team, resource, i, newValue, false); else $scope.checkResourceValue(ec.ExpCatId, team, resource, i, (numberOfWeeks == 0 ? newValue : (newValue / numberOfWeeks)), false); } } $scope.RefreshCSSClasses(); //required to be false by xeditable grid return false; }; $scope.checkTeamTotalValue = function (data, ec, team) { if (!$scope.data.CalendarFilter.IsTableModeQuantity || !ec || !team) return false; var newValue = parseFloat(data); if (isNaN(newValue)) newValue = 0; if (newValue < 0) { return "Value should not be less than zero"; } var oldValue = team.GrandTotalQuantity; var changeMult = oldValue != 0 ? parseFloat(newValue) / oldValue : 0; var sourceSumQuantity = 0, numberOfWeeks = 0; for (var i in $scope.data.Calendar.WeekHeaders) { if ($scope.data.Calendar.WeekHeaders[i].DataType == C_HEADER_DATA_TYPE_TOTALS || !$scope.data.Calendar.WeekHeaders[i].Visible[$scope.data.CalendarFilter.ViewModeName]) continue; sourceSumQuantity += team.QuantityValues[i]; numberOfWeeks++; } if (isAvgMode() && numberOfWeeks > 0) { sourceSumQuantity = sourceSumQuantity / numberOfWeeks; } for (var i in $scope.data.Calendar.WeekHeaders) { if ($scope.data.Calendar.WeekHeaders[i].DataType == C_HEADER_DATA_TYPE_TOTALS || !$scope.data.Calendar.WeekHeaders[i].Visible[$scope.data.CalendarFilter.ViewModeName]) { continue; } if (sourceSumQuantity > 0) { $scope.checkTeamValue(ec, team, i, Math.abs(team.QuantityValues[i] * changeMult), false); } else { if (isAvgMode()) $scope.checkTeamValue(ec, team, i, newValue, false); else $scope.checkTeamValue(ec, team, i, (numberOfWeeks == 0 ? newValue : (newValue / numberOfWeeks)), false); } } $scope.RefreshCSSClasses(); //required to be false by xeditable grid return false; }; $scope.watchKeyInput = function (t) { clearAllSelectedCells(); $timeout(function () { if (t.$editable.inputEl.select) t.$editable.inputEl.select(); else if (t.$editable.inputEl.setSelectionRange) t.$editable.inputEl.setSelectionRange(0, t.$editable.inputEl.val().length); }, 3); t.$editable.inputEl.on('keydown', function (e) { if (e.which == 9) { //when tab key is pressed e.preventDefault(); var tab2Cell; if (e.shiftKey) { // when shift + tab use with 'onblur' set to 'submit' for automatic submission find the parent of the editable before this one in the markup grab the editable and display it tab2Cell = $(this).parentsUntil('table#table').prevAll(":has(.editable:visible):first").find(".editable:visible:last"); t.$form.$submit(); tab2Cell.click(); } else { // when just tab use with 'onblur' set to 'submit' for automatic submission find the parent of the editable after this one in the markup grab the editable and display it tab2Cell = $(this).parentsUntil('table#table').nextAll(":has(.editable:visible):first").find(".editable:visible:first"); t.$form.$submit(); tab2Cell.click(); } } }); }; $scope.onTxtBlur = function (txt) { txt.$form.$submit(); }; $scope.gridChanged = function () { $scope.data.isDataChanged = true; if (typeof onDataChanged === 'function') { onDataChanged(); } $scope.$emit('gridChanged'); }; $scope.gridSaved = function () { $scope.data.isDataChanged = false; if (typeof onDataSaved === 'function') { onDataSaved(); } }; function updateQuantityOrCostCell(expCatId, rowIndex, colIndex, newQuantity, newCost, isUpdateQuantity) { /* isUpdateQuantity - indicates whether to update by quantity or by cost. * true - Quantity will be updated from newQuantity, Cost will be recalculated as newQuantity * rate * false - Cost will be updated from newCost, Quantity will be recalculated as newCost / rate * null - Quantity will be updated from newQuantity, Cost will be updated from newCost */ // DO NOT UPDATE ACTUALS CELLS if (!$scope.data.Calendar.WeekHeaders[colIndex].Visible[$scope.data.CalendarFilter.ViewModeName]) return; var dec = Math.pow(10, true === isUpdateQuantity ? parseInt($scope.formatCells.DecimalCalcQtyPlaces) : parseInt($scope.formatCells.DecimalCalcCostPlaces)); var oldQuantity = $scope.data.Calendar.Rows[rowIndex].QuantityValues[colIndex]; var oldCost = $scope.data.Calendar.Rows[rowIndex].CostValues[colIndex]; if (true === isUpdateQuantity) { // update quantity, recalculate cost based on exp cat rate if (oldQuantity === newQuantity || newQuantity < 0) return; newCost = Math.round(newQuantity * $scope.getRate(expCatId, $scope.data.Calendar.WeekHeaders[colIndex].Milliseconds) * dec) / dec; } else if (false === isUpdateQuantity) { // update cost, recalculate quantity based on exp cat rate if (oldCost === newCost || newCost < 0) return; newQuantity = Math.round(newCost / $scope.getRate(expCatId, $scope.data.Calendar.WeekHeaders[colIndex].Milliseconds) * dec) / dec; } // update modified cell $scope.data.Calendar.Rows[rowIndex].RestQuantity[colIndex] += newQuantity - $scope.data.Calendar.Rows[rowIndex].QuantityValues[colIndex]; $scope.data.Calendar.Rows[rowIndex].CostValues[colIndex] = newCost; $scope.data.Calendar.Rows[rowIndex].QuantityValues[colIndex] = newQuantity; $scope.data.Calendar.Expenditures[expCatId].Details[$scope.data.Calendar.WeekHeaders[colIndex].Milliseconds].ForecastQuantity = newQuantity / $scope.getUomMultiplier(expCatId); $scope.data.Calendar.Expenditures[expCatId].Details[$scope.data.Calendar.WeekHeaders[colIndex].Milliseconds].ForecastCost = newCost; $scope.data.Calendar.Expenditures[expCatId].Details[$scope.data.Calendar.WeekHeaders[colIndex].Milliseconds].Changed = true; // update column totals $scope.data.Calendar.Rows[0].CostValues[colIndex] += (newCost - oldCost); $scope.data.Calendar.Rows[0].QuantityValues[colIndex] += (newQuantity - oldQuantity); // update month cell and month total cell updateECTotals(rowIndex, colIndex, newQuantity, oldQuantity, newCost, oldCost); $scope.gridChanged(); } function calcWithSamePresicion(val1, val2, isCost) { var dec = Math.pow(10, parseInt(isCost ? $scope.formatCells.DecimalCalcCostPlaces : $scope.formatCells.DecimalCalcQtyPlaces)); return Math.round(val1 * dec) / dec + Math.round(val2 * dec) / dec; } function setRowGrandTotal(row) { if ($scope.data.CalendarFilter.IsTableModeQuantity) { row.GrandTotal = $filter('number')(row.GrandTotalQuantity || 0, 2); } else { row.GrandTotal = $filter('currency')(row.GrandTotalCost || 0); } } function prepareExpendituresForSaving() { var changedExpenditures = {}; if (!$scope.data.Calendar.Expenditures) return changedExpenditures; $.each($scope.data.Calendar.Expenditures, function (expCatId, expCat) { var details = {}, teams = {}; $.each(expCat.Details, function (milliseconds, detail) { if (detail.Changed) details[milliseconds] = detail; }); $.each(expCat.Teams, function (teamId, team) { if (team.Changed) { teams[teamId] = { Id: team.Id, Name: team.Name, QuantityValues: team.QuantityValues, Resources: {} }; $.each(team.Resources, function (resourceId, resource) { if (resource.Changed) teams[teamId].Resources[resourceId] = resource; }); } }); if (Object.keys(details).length > 0 || Object.keys(teams).length > 0) { changedExpenditures[expCatId] = angular.copy(expCat); changedExpenditures[expCatId].Details = details; changedExpenditures[expCatId].Teams = teams; } // SA. ENV-667. Begin var isAvailable = false; for (var index = 0; index < $scope.data.AvailableExpenditures.length; index++) { if ($scope.data.AvailableExpenditures[index].Id == expCatId) { isAvailable = true; break; } } if (!isAvailable) { var ecItem = new Object(); ecItem.CategoriesCount = 0; ecItem.Id = expCatId; ecItem.Name = expCat.ExpenditureCategoryName $scope.data.AvailableExpenditures[$scope.data.AvailableExpenditures.length] = ecItem; } // SA. ENV-667. End }); return changedExpenditures; } function getTotals() { if (!$scope.data.Calendar || !$scope.data.Calendar.Expenditures) return null; var result = []; $.each($scope.data.Calendar.Expenditures, function (expCatId, expCat) { var allocation = { ExpCatId: expCatId, ExpCatName: expCat.ExpenditureCategoryName, Teams: [], TeamsTotal: 0, ResourcesTotal: 0, Total: 0 }; $.each(expCat.Details, function (key, detail) { allocation.Total += detail.ForecastQuantity || 0; }); $.each(expCat.Teams, function (teamId, team) { var teamAllocation = { TeamId: teamId, Total: 0 }; $.each(team.QuantityValues, function (qvTeamKey, value) { teamAllocation.Total += value || 0; }); $.each(team.Resources, function (resourceId, resource) { $.each(resource.QuantityValues, function (qvResourceKey, value) { allocation.ResourcesTotal += value || 0; }); }); allocation.TeamsTotal += teamAllocation.Total; allocation.Teams.push(teamAllocation); }); result.push(allocation); }); return result; } $scope.getData4Saving = function () { return { Scenario: $scope.data.Scenario, AvailableExpenditures: $scope.data.AvailableExpenditures, TeamsInScenario: $scope.getTeamsAllocation(), Calendar: { Expenditures: prepareExpendituresForSaving() } }; }; $scope.getTeamsAllocation = function () { var totals = getTotals(); if (!totals || totals.length <= 0) return null; var totalByScenario = 0, teamsTotal = {}; for (var i in $scope.data.TeamsInScenario) { teamsTotal[$scope.data.TeamsInScenario[i].TeamId] = 0; } for (var i in totals) { totalByScenario += totals[i].Total; for (var j in totals[i].Teams) { var team = totals[i].Teams[j]; teamsTotal[team.TeamId] += team.Total; } } for (var i in $scope.data.TeamsInScenario) $scope.data.TeamsInScenario[i].Allocation = Math.round((totalByScenario <= 0 ? 0 : teamsTotal[$scope.data.TeamsInScenario[i].TeamId] / totalByScenario) * 100); return $scope.data.TeamsInScenario; } $scope.isValidAllocation = function () { return !$scope.ExpendituresIsOverAllocated && !$scope.TeamsIsOverAllocated; }; $scope.showWarningBlock = function () { return $scope.gridWasChanged() || $scope.ExpendituresIsUnderAllocated || !$scope.IsPossibleMarginDesignated; }; $scope.showMessagePanel = function () { return !$scope.isValidAllocation() || $scope.showWarningBlock(); }; $scope.saveChanges = function (saveAsDraft, saveAs) { if (saveAs != null && saveAs != undefined) $scope.data.Scenario.SaveAs = saveAs; // 0 - Update, 1 - New if ($scope.data.Scenario.SaveAs != 0 && $scope.data.Scenario.SaveAs != 1) return; if (!$scope.isValidAllocation()) return; if (saveAsDraft != null && saveAsDraft != undefined) $scope.data.Scenario.SaveAsDraft = saveAsDraft; blockUI(); $http.post('/Scenarios/SaveSnapshotChanges', JSON.stringify($scope.getData4Saving())). success(function (data, status, headers, config) { try { $scope.gridSaved(); // 'details' if (1 === $scope.Opener) { if (data && data.ScenarioId) window.location.href = window.location.href.replace(new RegExp(/\w{8}-(\w{4}-){3}\w{12}/), data.ScenarioId) // replace scenario id part with the new scenario id .replace(new RegExp(/#\/\w+/), ''); // cut the hash part else window.location.reload(true); } else { window.location.href = window.location.href.replace("newscenario", "scenarios"); } unblockUI(); } catch (e) { unblockUI(); if (!!hideModals && typeof (hideModals) == 'function') hideModals(); showErrorModal('Oops!', $scope.CommonErrorMessage); } }). error(function (data, status, headers, config) { console.log("an error occurred while saving calendar changes"); unblockUI(); if (!!hideModals && typeof (hideModals) == 'function') hideModals(); showErrorModal('Oops!', $scope.CommonErrorMessage); }); }; $scope.getRate = function (expCatId, date) { if ($scope.data.Rates && $scope.data.Rates.length > 0) { for (var i = 0; i < $scope.data.Rates.length; i++) { if ($scope.data.Rates[i].expCatId === expCatId) { if ($scope.data.Rates[i].rateValues && $scope.data.Rates[i].rateValues.length > 0) { for (var rIndex = 0; rIndex < $scope.data.Rates[i].rateValues.length; rIndex++) { if (date <= $scope.data.Rates[i].rateValues[rIndex].weekEndDate) return $scope.data.Rates[i].rateValues[rIndex].rateValue / $scope.getUomMultiplier(expCatId); } return $scope.data.Rates[i].rateValues[$scope.data.Rates[i].rateValues.length - 1].rateValue / $scope.getUomMultiplier(expCatId); } break; } } } return 0; }; $scope.toggleGraphExpCat = function (id) { $scope.GraphFilter.ExpCats[id] = !$scope.GraphFilter.ExpCats[id]; $scope.showGraph(); }; $scope.toggleGraphValue = function () { $scope.GraphFilter.ShowTime = !$scope.GraphFilter.ShowTime; savePagePreferences(); $scope.showGraph(); }; $scope.toggleGraphActuals = function () { $scope.GraphFilter.Actuals = !$scope.GraphFilter.Actuals; savePagePreferences(); $scope.showGraph(); }; $scope.toggleGraphTotal = function () { $scope.GraphFilter.ShowTotal = !$scope.GraphFilter.ShowTotal; savePagePreferences(); $scope.showGraph(); }; $scope.toggleGraphQuantity = function () { $scope.GraphFilter.Quantity = !$scope.GraphFilter.Quantity; savePagePreferences(); $scope.showGraph(); }; $scope.graphData = []; $scope.maxGraphValue = 0; $scope.showGraph = function () { // prepare data $scope.maxGraphValue = 0; $scope.graphData = []; var sd = $scope.data.Scenario.StartDate; if ($scope.data.Scenario.ActualStartDate > 0 && $scope.data.Scenario.ActualStartDate < sd) sd = $scope.data.Scenario.ActualStartDate; var ed = $scope.data.Scenario.EndDate; if ($scope.data.Scenario.ActualEndDate > 0 && $scope.data.Scenario.ActualEndDate < ed) ed = $scope.data.Scenario.ActualEndDate; var startDate = new Date(sd); var endDate = new Date(ed); var days = (endDate - startDate) / (1000 * 60 * 60 * 24); var i; var expCatData; var expCatActualsData; var actualsData = new Array(); var firstpass = true; for (i = 1; i < $scope.data.Calendar.Rows.length; i++) {//ExpCat Iteration var ExpCatId = $scope.data.Calendar.Rows[i].ExpCatId; var uomMultiplier = $scope.getUomMultiplier(ExpCatId) || 1; if (expCatData == undefined || !$scope.GraphFilter.ShowTotal) { expCatData = new Array(); } if (!expCatActualsData) expCatActualsData = new Array(); if (!$scope.GraphFilter.ExpCats[ExpCatId]) continue; var currentIndex = 0; for (var colIndex = 0; colIndex < $scope.data.Calendar.WeekHeaders.length; colIndex++) {//Dates Iteration if ($scope.data.Calendar.WeekHeaders[colIndex].DataType == C_HEADER_DATA_TYPE_TOTALS || !$scope.data.Calendar.WeekHeaders[colIndex].Visible[$scope.data.CalendarFilter.ViewModeName]) continue; if (!$scope.GraphFilter.ShowTotal || firstpass) expCatData[currentIndex] = new Array(); expCatData[currentIndex][0] = $scope.data.Calendar.WeekHeaders[colIndex].Milliseconds; if (!$scope.GraphFilter.ShowTotal || firstpass) {//if we are not calculating totals or it is the first pass if ($scope.data.CalendarFilter.IsUOMHours) expCatData[currentIndex][1] = $scope.data.Calendar.Expenditures[ExpCatId].Details[$scope.data.Calendar.WeekHeaders[colIndex].Milliseconds].ForecastQuantity || 0; else expCatData[currentIndex][1] = $scope.data.Calendar.Expenditures[ExpCatId].Details[$scope.data.Calendar.WeekHeaders[colIndex].Milliseconds].ForecastQuantity * uomMultiplier || 0; if (true !== $scope.data.CalendarFilter.IsTableModeQuantity) {//if (true !== $scope.data.CalendarFilter.IsTableModeQuantity) { expCatData[currentIndex][1] = $scope.data.Calendar.Expenditures[ExpCatId].Details[$scope.data.Calendar.WeekHeaders[colIndex].Milliseconds].ForecastCost || 0; } } else { if ($scope.data.CalendarFilter.IsUOMHours) expCatData[currentIndex][1] = +expCatData[currentIndex][1] + (+$scope.data.Calendar.Expenditures[ExpCatId].Details[$scope.data.Calendar.WeekHeaders[colIndex].Milliseconds].ForecastQuantity || 0); else expCatData[currentIndex][1] = +expCatData[currentIndex][1] + (+$scope.data.Calendar.Expenditures[ExpCatId].Details[$scope.data.Calendar.WeekHeaders[colIndex].Milliseconds].ForecastQuantity * uomMultiplier || 0); if (true !== $scope.data.CalendarFilter.IsTableModeQuantity) {//if (true !== $scope.data.CalendarFilter.IsTableModeQuantity) { expCatData[currentIndex][1] = +expCatData[currentIndex][1] + (+$scope.data.Calendar.Expenditures[ExpCatId].Details[$scope.data.Calendar.WeekHeaders[colIndex].Milliseconds].ForecastCost || 0); } } if ($scope.GraphFilter.Actuals) { if (firstpass || !expCatActualsData[currentIndex]) expCatActualsData[currentIndex] = new Array(); expCatActualsData[currentIndex][0] = $scope.data.Calendar.WeekHeaders[colIndex].Milliseconds; var value = (true !== $scope.data.CalendarFilter.IsTableModeQuantity) ? $scope.data.Calendar.Expenditures[ExpCatId].Details[$scope.data.Calendar.WeekHeaders[colIndex].Milliseconds].ActualsCost : $scope.data.Calendar.Expenditures[ExpCatId].Details[$scope.data.Calendar.WeekHeaders[colIndex].Milliseconds].ActualsQuantity; if (firstpass) { if (true !== $scope.data.CalendarFilter.IsTableModeQuantity) { expCatActualsData[currentIndex][1] = value; } else { if ($scope.data.CalendarFilter.IsUOMHours) expCatActualsData[currentIndex][1] = value; else expCatActualsData[currentIndex][1] = value ? value * uomMultiplier : null; } } else { if (true !== $scope.data.CalendarFilter.IsTableModeQuantity) { expCatActualsData[currentIndex][1] = (!expCatActualsData[currentIndex][1] && !value) ? null : +expCatActualsData[currentIndex][1] + value; } else { if ($scope.data.CalendarFilter.IsUOMHours) expCatActualsData[currentIndex][1] = (!expCatActualsData[currentIndex][1] && !value) ? null : +expCatActualsData[currentIndex][1] + value; else expCatActualsData[currentIndex][1] = (!expCatActualsData[currentIndex][1] && !value) ? null : +expCatActualsData[currentIndex][1] + (value ? value * uomMultiplier : null); } } } //todo: ask if we need to show avg data. If yes then we should manually get value but not from display data source (Cells) //if (true !== $scope.data.CalendarFilter.IsTableModeQuantity) { // expCatData[expCatData.length - 1][1] = $scope.data.Calendar.Rows[i].CostValues[colIndex]; //} if (expCatData[currentIndex][1] > $scope.maxGraphValue) $scope.maxGraphValue = expCatData[currentIndex][1]; if (expCatData[currentIndex] && expCatData[currentIndex][1]) expCatData[currentIndex][1] = expCatData[currentIndex][1].toFixed(2); if ($scope.GraphFilter.Actuals) { if (expCatActualsData[currentIndex] && expCatActualsData[currentIndex][1]) expCatActualsData[currentIndex][1] = expCatActualsData[currentIndex][1].toFixed(2); } currentIndex++; } var lastIndex = -1; var firstIndex = -1; for (var index in expCatActualsData) { if (expCatActualsData[index][1]) { if (firstIndex == -1) firstIndex = index; lastIndex = index; } } if (firstIndex == -1) firstIndex = 0; if (lastIndex == -1) lastIndex = firstIndex; for (var index = firstIndex; index <= lastIndex; index++) { if (expCatActualsData[index] && expCatActualsData[index].length > 0 && !expCatActualsData[index][1]) expCatActualsData[index][1] = 0; } if (!$scope.GraphFilter.ShowTotal || i == $scope.data.Calendar.Rows.length - 1) { $scope.graphData[$scope.graphData.length] = { label: ($scope.GraphFilter.ShowTotal) ? 'Total' : $scope.data.Calendar.Rows[i].ExpCatName, data: expCatData, lines: { show: true, fill: true, steps: false }, filledPoints: true, stack: true }; if ($scope.GraphFilter.Actuals) { $scope.graphData[$scope.graphData.length] = { label: ($scope.GraphFilter.ShowTotal) ? 'Total - Actuals' : $scope.data.Calendar.Rows[i].ExpCatName + ' - Actuals', data: jQuery.extend(true, [], expCatActualsData), lines: { show: true, fill: false, steps: false }, filledPoints: true, stack: false }; } } firstpass = false; } $timeout(function () { $scope.initGraph() }); }; $scope.initGraph = function () { // Clear graph placeholder $('#DetailsGraphContainer').html('
'); // SA. ENV-574 var C_MAX_GRAPH_TICK_LABELS = 8; var maxSeriePoints = 0; if ($scope.graphData && $scope.graphData.length > 0) { $.each($scope.graphData, function (index, data) { if (data.data && (data.data.length > 0)) { maxSeriePoints = Math.max(maxSeriePoints, data.data.length); } }); } var tickStep = Math.round((maxSeriePoints / 4) / C_MAX_GRAPH_TICK_LABELS); tickStep = (tickStep > 0) ? tickStep : 1; // Init Chart $('#divDetailsGraph').pixelPlot($scope.graphData, { //var plot = $.plot($('#jq-flot-graph2'), graphData, { series: { points: { show: false }, lines: { show: true, } }, xaxis: { mode: "time", tickSize: [tickStep, "month"], timeformat: "%b %Y" } }, { height: 255, width: 405, // SA. ENV-574. Tooltips for time points. formatDateMY func is defined in _scenarioCalendar.cshtml tooltipText: "y + ' units at ' + formatDateMY(new Date(x))" }); }; $scope.assignRestForResource = function (resource, team, expCatId) { allocateResource(resource, expCatId, team, AllocationMode.AssignRest); }; $scope.assignAllForResource = function (resource, team, expCatId) { allocateResource(resource, expCatId, team, AllocationMode.AssignAll); }; $scope.zeroResource = function (resource, team, expCatId) { if (confirm("Are you sure you want fill this resource quantities with 0s?")) allocateResource(resource, expCatId, team, AllocationMode.Reset); }; $scope.removeResource = function (resId, expCatId, team, $index) { if (confirm("Are you sure you want to remove this resource?")) { var rawTeam = $scope.data.Calendar.Expenditures[expCatId].Teams[team.Id]; rawTeam.Resources[resId].Changed = true; rawTeam.Resources[resId].Deleted = true; rawTeam.Changed = true; team.Resources.splice($index, 1); team.AvailableResources = getAvailableResources(rawTeam); } $scope.gridChanged(); $scope.setGridSource(); }; $scope.assignResource = function (expCat, team, $event) { if (expCat == null) return; if (!team || !team.ResourceToAssignId) return; var rawTeam = $scope.data.Calendar.Expenditures[expCat.ExpCatId].Teams[team.Id]; // first of all we should check for existence of resource in Resources and then in AllResources because if resource was removed it contains in Resources object var rawResource = rawTeam.Resources[team.ResourceToAssignId] || rawTeam.AllResources[team.ResourceToAssignId]; if (!rawResource) return; for (var j = 0; j < team.Resources.length; j++) { if (team.Resources[j].Id == rawResource.Id) { alert("The " + rawResource.Name + " has been assigned to the " + expCat.ExpCatName); break; } } var resource = { Id: rawResource.Id, Name: rawResource.Name, GrandTotalCost: 0, GrandTotalQuantity: 0, IsActiveEmployee: rawResource.IsActiveEmployee, CapacityQuantityValues: [], ReadOnly: [], RestQuantityValues: [], QuantityValues: [], Cells: [] }; rawResource.Changed = true; rawResource.Deleted = false; rawTeam.Resources[rawResource.Id] = angular.copy(rawResource); rawTeam.Changed = true; team.Resources.push(resource); team.ResourceToAssignId = null; team.AvailableResources = getAvailableResources(rawTeam); $scope.gridChanged(); $scope.setGridSource(); }; $scope.checkResourceValue = function (expCatId, team, resource, colIndex, data, refreshCss) { var uomMultiplier = $scope.getUomMultiplier(expCatId); var rawTeam = $scope.data.Calendar.Expenditures[expCatId].Teams[team.Id]; var rawResource = rawTeam.Resources[resource.Id]; var newValue = parseFloat(data); if (isNaN(newValue)) newValue = 0; if (newValue < 0) { return "Value should not be less than zero"; } // if any resource has been changed we need to mark team as changed also rawTeam.Changed = true; var isMonth = $scope.data.Calendar.WeekHeaders[colIndex].DataType == C_HEADER_DATA_TYPE_TOTALS; if ($scope.data.CalendarFilter.IsTableModeQuantity) { if (isMonth) { var ec_val = resource.QuantityValues[colIndex]; var weeks = 0; for (var j = colIndex - 1; j >= 0 && $scope.data.Calendar.WeekHeaders[j].DataType == C_HEADER_DATA_TYPE_ORDINAL && $scope.data.Calendar.WeekHeaders[j].Visible[$scope.data.CalendarFilter.ViewModeName]; j--) { weeks++; } var oldValue = ec_val; var avg = (isAvgMode()); var coef = avg ? (newValue / oldValue) : (ec_val > 0 ? newValue / ec_val : 1); for (var j = colIndex - 1; j >= 0 && $scope.data.Calendar.WeekHeaders[j].DataType == C_HEADER_DATA_TYPE_ORDINAL && $scope.data.Calendar.WeekHeaders[j].Visible[$scope.data.CalendarFilter.ViewModeName]; j--) { var val = avg ? (ec_val > 0 ? resource.QuantityValues[j] * coef : newValue) : (ec_val > 0 ? resource.QuantityValues[j] * coef : newValue / weeks); updateTotals(resource, j, val); resource.RestQuantityValues[j] -= val - resource.QuantityValues[j]; team.AllocatedByResources[j] += val - resource.QuantityValues[j]; resource.QuantityValues[j] = val; updateGrandTotals(resource); // SA. ENV-1135. Grand totals update moced to separate function rawResource.QuantityValues[$scope.data.Calendar.WeekHeaders[j].Milliseconds] = resource.QuantityValues[j] / uomMultiplier; rawResource.RestQuantityValues[$scope.data.Calendar.WeekHeaders[j].Milliseconds] = resource.RestQuantityValues[j] / uomMultiplier; rawResource.Changed = true; } } else { updateTotals(resource, colIndex, newValue); resource.RestQuantityValues[colIndex] -= newValue - resource.QuantityValues[colIndex]; team.AllocatedByResources[colIndex] += newValue - resource.QuantityValues[colIndex]; resource.QuantityValues[colIndex] = newValue; updateGrandTotals(resource); // SA. ENV-1135. Grand totals update moced to separate function rawResource.QuantityValues[$scope.data.Calendar.WeekHeaders[colIndex].Milliseconds] = resource.QuantityValues[colIndex] / uomMultiplier; rawResource.RestQuantityValues[$scope.data.Calendar.WeekHeaders[colIndex].Milliseconds] = resource.RestQuantityValues[colIndex] / uomMultiplier; rawResource.Changed = true; } } $scope.gridChanged(); if (refreshCss) $scope.RefreshCSSClasses(); //required to be false by xeditable grid return false; }; $scope.checkTeamValue = function (expCat, team, colIndex, data, refreshCss) { var uomMultiplier = $scope.getUomMultiplier(expCat.ExpCatId); var rawTeam = $scope.data.Calendar.Expenditures[expCat.ExpCatId].Teams[team.Id]; var newValue = parseFloat(data); if (isNaN(newValue)) newValue = 0; if (newValue < 0) { return "Value should not be less than zero"; } var isMonth = $scope.data.Calendar.WeekHeaders[colIndex].DataType == C_HEADER_DATA_TYPE_TOTALS; if ($scope.data.CalendarFilter.IsTableModeQuantity) { if (isMonth) { var ec_val = team.QuantityValues[colIndex]; var weeks = 0; for (var j = colIndex - 1; j >= 0 && $scope.data.Calendar.WeekHeaders[j].DataType == C_HEADER_DATA_TYPE_ORDINAL && $scope.data.Calendar.WeekHeaders[j].Visible[$scope.data.CalendarFilter.ViewModeName]; j--) { weeks++; } var oldValue = ec_val; var avg = (isAvgMode()); var coef = avg ? (newValue / oldValue) : (ec_val > 0 ? newValue / ec_val : 1); for (var j = colIndex - 1; j >= 0 && $scope.data.Calendar.WeekHeaders[j].DataType == C_HEADER_DATA_TYPE_ORDINAL && $scope.data.Calendar.WeekHeaders[j].Visible[$scope.data.CalendarFilter.ViewModeName]; j--) { var val = avg ? (ec_val > 0 ? team.QuantityValues[j] * coef : newValue) : (ec_val > 0 ? team.QuantityValues[j] * coef : newValue / weeks); updateTotals(team, j, val); team.RestQuantityValues[j] -= val - team.QuantityValues[j]; expCat.RestQuantity[j] -= val - team.QuantityValues[j]; team.QuantityValues[j] = val; updateGrandTotals(team); // SA. ENV-1135. Grand totals update moced to separate function rawTeam.QuantityValues[$scope.data.Calendar.WeekHeaders[j].Milliseconds] = team.QuantityValues[j] / uomMultiplier; rawTeam.RestQuantityValues[$scope.data.Calendar.WeekHeaders[j].Milliseconds] = team.RestQuantityValues[j] / uomMultiplier; rawTeam.Changed = true; } } else { updateTotals(team, colIndex, newValue); team.RestQuantityValues[colIndex] -= newValue - team.QuantityValues[colIndex]; expCat.RestQuantity[colIndex] -= newValue - team.QuantityValues[colIndex]; team.QuantityValues[colIndex] = newValue; updateGrandTotals(team); // SA. ENV-1135. Grand totals update moced to separate function rawTeam.QuantityValues[$scope.data.Calendar.WeekHeaders[colIndex].Milliseconds] = team.QuantityValues[colIndex] / uomMultiplier; rawTeam.RestQuantityValues[$scope.data.Calendar.WeekHeaders[colIndex].Milliseconds] = team.RestQuantityValues[colIndex] / uomMultiplier; rawTeam.Changed = true; } } $scope.gridChanged(); if (refreshCss) $scope.RefreshCSSClasses(); //required to be false by xeditable grid return false; }; function getAvailableResources(team) { var resources = []; if (!team || !team.AllResources) return resources; $.each(team.AllResources, function (resourceId, resource) { var isExists = !!team.Resources && !!team.Resources[resourceId] && !team.Resources[resourceId].Deleted; if (!isExists) { resources.push({ id: resource.Id, name: resource.Name }); } }); return resources; }; $scope.assignRestForTeam = function (expCat, team, $event) { $event.stopPropagation(); clearAllSelectedCells(); allocateTeam(expCat, team, AllocationMode.AssignRest); }; $scope.assignAllForTeam = function (expCat, team, $event) { $event.stopPropagation(); clearAllSelectedCells(); allocateTeam(expCat, team, AllocationMode.AssignAll); }; $scope.zeroTeam = function (expCat, team, $event) { $event.stopPropagation(); clearAllSelectedCells(); if (confirm("Are you sure you want fill this team quantities with 0s?")) allocateTeam(expCat, team, AllocationMode.Reset); }; function getVisibleCellCount() { var count = 0; for (var i = 0; i < $scope.data.Calendar.WeekHeaders.length; i++) { var header = $scope.data.Calendar.WeekHeaders[i]; if (header.Show) count++; } $scope.data.VisibleCellCount = count; } $scope.getUomMultiplier = function (expCatId) { if ($scope.data.CalendarFilter.IsUOMHours) return 1; return 1.0 / $scope.data.Calendar.Expenditures[expCatId].UOMValue; }; $scope.setGridHeaderState = function () { if (!$scope.data.Calendar.MonthHeaders || !$scope.data.Calendar.WeekHeaders) { return; } var isGridMonthViewMode = $scope.data.CalendarFilter.IsViewModeMonth; var header; var i; for (i = 0; i < $scope.data.Calendar.MonthHeaders.length; i++) { header = $scope.data.Calendar.MonthHeaders[i]; header.Show = $scope.getMonthHeaderShowStatus(header); header.IsCollapsed = isGridMonthViewMode; header.CollapsedClass = isGridMonthViewMode ? $scope.CollapsedIcon : $scope.NonCollapsedIcon; header.Initialized = header.Initialized || header.Show; } for (i = 0; i < $scope.data.Calendar.WeekHeaders.length; i++) { header = $scope.data.Calendar.WeekHeaders[i]; header.Show = $scope.getWeekHeaderShowStatus(header); header.Initialized = header.Initialized || header.Show; } getVisibleCellCount(); }; $scope.getWeekHeaderShowStatus = function (header) { var parentMonthHeader = $scope.data.Calendar.MonthHeaders[header.MonthHeader]; var isDisplayed = true; if (header.DataType == C_HEADER_DATA_TYPE_ORDINAL) // Ordinal data column isDisplayed = header.Visible[$scope.data.CalendarFilter.ViewModeName] && parentMonthHeader.Visible[$scope.data.CalendarFilter.ViewModeName] && !parentMonthHeader.IsCollapsed; else // Month totals data column isDisplayed = header.Visible[$scope.data.CalendarFilter.ViewModeName] && parentMonthHeader.Visible[$scope.data.CalendarFilter.ViewModeName] && parentMonthHeader.IsCollapsed; return isDisplayed; }; $scope.getMonthHeaderShowStatus = function (header) { return header.Visible[$scope.data.CalendarFilter.ViewModeName]; }; $scope.$watch('data.CalendarFilter.IsUOMHours', function (newValue, oldValue) { if (oldValue != newValue) $scope.switchUOMMode(); }); $scope.switchUOMMode = function () { $scope.setGridSource(); $scope.showGraph(); }; $scope.$watch('data.CalendarFilter.IsTableModeQuantity', function (newValue, oldValue) { if (oldValue != newValue) $scope.switchTableMode(); }); $scope.switchTableMode = function () { $scope.setGridSource(); $scope.RefreshCSSClasses(); $scope.showGraph(); //timeTracker.startTrack('gridRender'); }; $scope.$watch('data.CalendarFilter.IsViewModeMonth', function (newValue, oldValue) { if (oldValue != newValue) $scope.switchViewMode(); }); $scope.switchViewMode = function () { // SA. ENV-667. Begin $scope.setGridHeaderState(); // SA. ENV-667. End }; $scope.$watch('data.CalendarFilter.ShowActuals', function (newValue, oldValue) { if (oldValue != newValue) { // SA. ENV-667 if ($scope.data.CalendarFilter.ShowActuals) $scope.data.CalendarFilter.ViewModeName = C_CALENDAR_VIEW_MODE_ACTUALS; else $scope.data.CalendarFilter.ViewModeName = C_CALENDAR_VIEW_MODE_FORECAST; $scope.switchActualsMode(); } }); $scope.switchActualsMode = function () { //savePagePreferences(); // SA. ENV-667. Begin $scope.setGridHeaderState(); // SA. ENV-667. End $scope.setGridSource(); $scope.RefreshCSSClasses(); }; $scope.initGridRows = function () { $scope.data.Calendar.Rows = []; var totalRow = { ExpCatId: $scope.GuidEmpty, ExpCatName: "Totals", UseType: 3, // calculated Visible: true }; $scope.data.Calendar.Rows.push(totalRow); if ($scope.data.Calendar.Expenditures) { $.each($scope.data.Calendar.Expenditures, function (expCatId, ecg) { var row = { Collapsed: ecg.Collapsed, CollapsedClass: ecg.CollapsedClass, ExpCatId: expCatId, ExpCatName: ecg.ExpenditureCategoryName, Teams: [], UseType: 1, Visible: true }; $.each(ecg.Teams, function (teamId, team) { var rowTeam = { Id: teamId, Name: team.Name, Changed: team.Changed, CanBeDeleted: team.CanBeDeleted, IsAccessible: team.IsAccessible, ResourceToAssignId: null, Resources: [], AvailableResources: getAvailableResources(team), Collapsed: team.Collapsed, CollapsedClass: team.CollapsedClass, }; $.each(team.Resources, function (resourceId, resource) { var rowResource = { Id: resourceId, IsActiveEmployee: resource.IsActiveEmployee, Name: resource.Name, CapacityQuantityValues: [], ReadOnly: [], RestQuantityValues: [], QuantityValues: [], GrandTotalQuantity: 0 }; rowTeam.Resources.push(rowResource); }); row.Teams.push(rowTeam); }); $scope.data.Calendar.Rows.push(row); }); } }; $scope.isVisibleGridRow = function (expCat) { if (!expCat) return false; if ($scope.data.CalendarFilter.CategoryType && $scope.data.CalendarFilter.CategoryType != expCat.Type) return false; if ($scope.data.CalendarFilter.GLAccount && $scope.data.CalendarFilter.GLAccount != expCat.GLId) return false; if ($scope.data.CalendarFilter.CreditDepartment && $scope.data.CalendarFilter.CreditDepartment != expCat.CreditId) return false; if ($scope.data.CalendarFilter.SelectedExpCats && $scope.data.CalendarFilter.SelectedExpCats.length > 0 && $scope.data.CalendarFilter.SelectedExpCats.indexOf(expCat.ExpenditureCategoryId) < 0) return false; return true; }; $scope.setGridSource = function () { $scope.data.CalendarFilter.VisibleExpCats = []; $scope.data.CalendarFilter.SecondaryExpCats = []; if ($scope.data.Calendar.Expenditures) { var totalRow = $scope.data.Calendar.Rows[0]; totalRow.GrandTotalCost = 0; totalRow.GrandTotalQuantity = 0; totalRow.CostValues = []; totalRow.QuantityValues = []; totalRow.Visible = false; totalRow.IsEditable = true; $.each($scope.data.Calendar.Expenditures, function (expCatId, ecg) { var row = null, rowIndex = -1, uomMultiplier = $scope.getUomMultiplier(expCatId); for (var i = 1; i < $scope.data.Calendar.Rows.length; i++) { var rowItem = $scope.data.Calendar.Rows[i]; if (rowItem.ExpCatId == expCatId) { rowIndex = i; row = rowItem; row.CostValues = []; row.ForecastCompletedPercent = 0; row.ForecastCostTotalInActualsRange = 0; row.ActualsCostTotal = 0; row.GrandTotalCost = 0; row.GrandTotalQuantity = 0; row.QuantityValues = []; row.RestQuantity = []; row.ScenarioDetailIds = []; row.Visible = $scope.isVisibleGridRow(ecg); // needs to dnd module for storing selected cells row.SelectedCells = new Array($scope.data.Calendar.WeekHeaders.length); // array with target droppable elements row.ActiveDroppables = new Array($scope.data.Calendar.WeekHeaders.length); //env-843 start if (row.Visible) { $scope.data.CalendarFilter.VisibleExpCats.push({ Id: expCatId, Name: ecg.ExpenditureCategoryName }); if (i > 1) { $scope.data.CalendarFilter.SecondaryExpCats.push({ Id: expCatId, Name: ecg.ExpenditureCategoryName }); } } //env-843 end $.each(row.Teams, function (index, team) { team.QuantityValues = []; team.RestQuantityValues = []; team.CapacityQuantityValues = []; team.AllocatedByResources = []; team.GrandTotalQuantity = 0; $.each(team.Resources, function (resourceId, resource) { resource.CapacityQuantityValues = []; resource.RestQuantityValues = []; resource.QuantityValues = []; resource.ReadOnly = []; resource.GrandTotalQuantity = 0; }); }); // if any row is visible we need to show total row totalRow.Visible = true; break; } } if (row.Visible) { var monthCost = 0, monthQuantity = 0, weeks = 0, resourcesMonth = [], teamMonth = [], //TODO: do we need to also check for Quantity/Cost switcher? showAvg = $scope.data.CalendarFilter.ShowAvgTotals && !$scope.data.CalendarFilter.IsUOMHours; var rowHasActuals = false; $.each($scope.data.Calendar.WeekHeaders, function (index, header) { if (header.DataType == C_HEADER_DATA_TYPE_ORDINAL) { // if (header.Visible[$scope.data.CalendarFilter.ViewModeName]) { var isActualsCell = !header.Editable[$scope.data.CalendarFilter.ViewModeName]; // (header.Milliseconds >= $scope.data.Scenario.ActualStartDate && header.Milliseconds <= $scope.data.Scenario.ActualEndDate); var scenarioDetail = ecg.Details[header.Milliseconds]; if (isActualsCell) { row.CostValues.push(scenarioDetail.ActualsCost); row.QuantityValues.push(scenarioDetail.ActualsQuantity * uomMultiplier); row.ScenarioDetailIds.push(scenarioDetail.ActualsId); row.ForecastCostTotalInActualsRange += scenarioDetail.ForecastCost; row.ActualsCostTotal += scenarioDetail.ActualsCost; rowHasActuals = true; // SA. ENV-667 } else { row.CostValues.push(scenarioDetail.ForecastCost); row.QuantityValues.push(scenarioDetail.ForecastQuantity * uomMultiplier); row.ScenarioDetailIds.push(scenarioDetail.ForecastId); } row.RestQuantity.push(scenarioDetail.ForecastQuantity * uomMultiplier); if (header.Visible[$scope.data.CalendarFilter.ViewModeName]) { monthCost += row.CostValues[index]; monthQuantity += row.QuantityValues[index]; row.GrandTotalCost += row.CostValues[index]; row.GrandTotalQuantity += row.QuantityValues[index]; weeks++; } } else { // SA. ENV-667. Begin var parentMonthHeaderIndex = header.MonthHeader; var parentMonthHeader = $scope.data.Calendar.MonthHeaders[parentMonthHeaderIndex]; var weekCount = 0; if (parentMonthHeader.Visible[$scope.data.CalendarFilter.ViewModeName]) { for (var jj = 0; jj < parentMonthHeader.WeekHeaders.length; jj++) { var currentWeekHeaderIndex = parentMonthHeader.WeekHeaders[jj]; if ($scope.data.Calendar.WeekHeaders[currentWeekHeaderIndex].Visible[$scope.data.CalendarFilter.ViewModeName]) weekCount++; } } // SA. ENV-667. End row.CostValues.push(monthCost); row.QuantityValues.push(monthQuantity / (showAvg ? weekCount : 1)); row.ScenarioDetailIds.push($scope.GuidEmpty); row.RestQuantity.push(0); monthCost = monthQuantity = 0; } if (totalRow.CostValues.length == index) totalRow.CostValues.push(0); if (totalRow.QuantityValues.length == index) totalRow.QuantityValues.push(0); totalRow.CostValues[index] += row.CostValues[index]; totalRow.QuantityValues[index] += row.QuantityValues[index]; var allocatedQuantityByTeams = 0; $.each(row.Teams, function (index, team) { if (header.DataType == C_HEADER_DATA_TYPE_ORDINAL) { var qty = (ecg.Teams[team.Id].QuantityValues[header.Milliseconds] || 0) * uomMultiplier; var restQty = (ecg.Teams[team.Id].RestQuantityValues[header.Milliseconds] || 0) * uomMultiplier; var capacityQty = (ecg.Teams[team.Id].CapacityQuantityValues[header.Milliseconds] || 0) * uomMultiplier; allocatedQuantityByTeams += qty; team.QuantityValues.push(qty); team.RestQuantityValues.push(restQty); team.CapacityQuantityValues.push(capacityQty); if (!teamMonth[team.Id]) { teamMonth[team.Id] = { Quantity: 0, RestQuantity: 0, CapacityQuantity: 0 }; } team.GrandTotalQuantity += qty; teamMonth[team.Id].Quantity += qty; teamMonth[team.Id].RestQuantity += restQty; teamMonth[team.Id].CapacityQuantity += capacityQty; } else { var parentMonthHeader = $scope.data.Calendar.MonthHeaders[header.MonthHeader]; var weekCount = 0; for (var jj = 0; jj < parentMonthHeader.WeekHeaders.length; jj++) { var currentWeekHeader = $scope.data.Calendar.WeekHeaders[parentMonthHeader.WeekHeaders[jj]]; if (currentWeekHeader.Visible[$scope.data.CalendarFilter.ViewModeName]) weekCount++; } var monthQuantity = teamMonth[team.Id].Quantity / (showAvg ? weekCount : 1); var monthRestQuantity = teamMonth[team.Id].RestQuantity / (showAvg ? weekCount : 1); var monthCapacityQuantity = teamMonth[team.Id].CapacityQuantity / (showAvg ? weekCount : 1); team.QuantityValues.push(monthQuantity); team.RestQuantityValues.push(monthRestQuantity); team.CapacityQuantityValues.push(monthCapacityQuantity); delete teamMonth[team.Id]; } var allocatedByResources = 0; $.each(team.Resources, function (rIndex, resource) { if (header.DataType == C_HEADER_DATA_TYPE_ORDINAL) { var rawResource = ecg.Teams[team.Id].Resources[resource.Id]; var cqty = (rawResource.CapacityQuantityValues[header.Milliseconds] || 0) * uomMultiplier; var qty = (rawResource.QuantityValues[header.Milliseconds] || 0) * uomMultiplier; var restQty = (rawResource.RestQuantityValues[header.Milliseconds] || 0) * uomMultiplier; var readOnly = rawResource.ReadOnly[header.Milliseconds]; resource.CapacityQuantityValues.push(cqty); resource.QuantityValues.push(qty); resource.RestQuantityValues.push(restQty); resource.ReadOnly.push(readOnly); if (!resourcesMonth[resource.Id]) { resourcesMonth[resource.Id] = { CapacityQuantity: 0, RestQuantity: 0, Quantity: 0, ReadOnly: false }; } resource.GrandTotalQuantity += qty; resourcesMonth[resource.Id].CapacityQuantity += cqty; resourcesMonth[resource.Id].RestQuantity += restQty; resourcesMonth[resource.Id].Quantity += qty; resourcesMonth[resource.Id].ReadOnly |= readOnly; allocatedByResources += qty; } else { // SA. ENV-667. Begin var parentMonthHeaderIndex = header.MonthHeader; var parentMonthHeader = $scope.data.Calendar.MonthHeaders[parentMonthHeaderIndex]; var weekCount = 0; for (var jj = 0; jj < parentMonthHeader.WeekHeaders.length; jj++) { var currentWeekHeaderIndex = parentMonthHeader.WeekHeaders[jj]; var currentWeekHeader = $scope.data.Calendar.WeekHeaders[currentWeekHeaderIndex]; if (currentWeekHeader.Visible[$scope.data.CalendarFilter.ViewModeName]) weekCount++; } // SA. ENV-667. End var monthCapacityQuantity = resourcesMonth[resource.Id].CapacityQuantity / (showAvg ? weekCount : 1); var monthRestQuantity = resourcesMonth[resource.Id].RestQuantity / (showAvg ? weekCount : 1); var monthQuantity = resourcesMonth[resource.Id].Quantity / (showAvg ? weekCount : 1); var readOnly = resourcesMonth[resource.Id].ReadOnly; resource.CapacityQuantityValues.push(monthCapacityQuantity); resource.RestQuantityValues.push(monthRestQuantity); resource.QuantityValues.push(monthQuantity); resource.ReadOnly.push(readOnly); allocatedByResources += monthQuantity; delete resourcesMonth[resource.Id]; } }); team.AllocatedByResources.push(allocatedByResources); }); row.RestQuantity[index] -= allocatedQuantityByTeams; }); row.ForecastCompletedPercent = row.ForecastCostTotalInActualsRange == 0 ? 0 : Math.abs(row.ActualsCostTotal / row.ForecastCostTotalInActualsRange - 1.0); if (showAvg) { row.GrandTotalQuantity /= weeks; $.each(row.Teams, function (i, team) { team.GrandTotalQuantity /= weeks; $.each(team.Resources, function (j, resource) { resource.GrandTotalQuantity /= weeks; }); }); } // SA. ENV-667. Mark totals as editable or not row.IsEditable = !rowHasActuals; if (rowHasActuals) { totalRow.IsEditable = false; } } totalRow.GrandTotalCost += row.GrandTotalCost; totalRow.GrandTotalQuantity += row.GrandTotalQuantity; $scope.data.Calendar.Rows[rowIndex] = row; }); $scope.data.Calendar.Rows[0] = totalRow; } $.each($scope.data.Calendar.Rows, function (i, cat) { if ($scope.data.CalendarFilter.IsTableModeQuantity) cat.Cells = cat.QuantityValues; else cat.Cells = cat.CostValues; if ($scope.data.Calendar.Rows[i].Teams) { $.each($scope.data.Calendar.Rows[i].Teams, function (rIndex, team) { team.Cells = team.QuantityValues; if (team.Resources) { $.each(team.Resources, function (rIndex, res) { res.Cells = res.QuantityValues; }); } }); } setRowGrandTotal(cat); }); }; $scope.onOpenReallocate = function (row) { // SA. Fixed datepicker problems var srcDate = new Date($scope.reallocator.SourceWeekEnding); var trgDate = new Date($scope.reallocator.TargetWeekEnding); $('[name=SourceWeekEnding]').parent('div').data('datepicker').update(srcDate); $('[name=TargetWeekEnding]').parent('div').data('datepicker').update(trgDate); if (null == row) return; $scope.reallocator.SourceExpCat = row.ExpCatId; for (var i = 0; i < $scope.data.AvailableExpenditures.length; i++) { if ($scope.data.AvailableExpenditures[i].Id != row.ExpCatId) { $scope.reallocator.TargetExpCat = $scope.data.AvailableExpenditures[i].Id; return; } } }; $scope.reallocateResource = function () { var sourceQuantity = 0; var sourceCost = 0; if ($scope.reallocator.Percentage == 0) { alert('Percentage is 0, nothing to reallocate.'); return; } var sourceRowIndex, targetRowIndex, curveRowIndex, sourceColIndex, targetColIndex; var i; for (i = 0; i < $scope.data.Calendar.Rows.length; i++) { if ($scope.data.Calendar.Rows[i].ExpCatId === $scope.reallocator.SourceExpCat) sourceRowIndex = i; if ($scope.data.Calendar.Rows[i].ExpCatId === $scope.reallocator.TargetExpCat) targetRowIndex = i; if ($scope.reallocator.CurveType == 'other' && $scope.data.Calendar.Rows[i].ExpCatId === $scope.reallocator.OtherCurve) curveRowIndex = i; } if (sourceRowIndex == targetRowIndex) { alert('Cant reallocate within same expenditure category.'); return; } for (i = 0; i < $scope.data.Calendar.WeekHeaders.length; i++) { if ($scope.data.Calendar.WeekHeaders[i].DataType == C_HEADER_DATA_TYPE_TOTALS || !$scope.data.Calendar.WeekHeaders[i].Visible[$scope.data.CalendarFilter.ViewModeName]) continue; if ($scope.data.Calendar.WeekHeaders[i].Title === $scope.reallocator.SourceWeekEnding) sourceColIndex = i; if ($scope.data.Calendar.WeekHeaders[i].Title === $scope.reallocator.TargetWeekEnding) targetColIndex = i; } if (sourceColIndex == null || targetColIndex == null) { alert('Please check selected dates to match valid week endings.'); return; } else if (targetColIndex < sourceColIndex) { alert('Please select To week ending date later than From week ending date.'); return; } var sourceSumCost = 0, sourceSumQuantity = 0, targetSumCost = 0, targetSumQuantity = 0, otherSumCost = 0, otherSumQuantity = 0; var percentageMult = $scope.reallocator.Percentage / 100; for (i = sourceColIndex; i <= targetColIndex; i++) { if ($scope.data.Calendar.WeekHeaders[i].DataType == C_HEADER_DATA_TYPE_TOTALS || !$scope.data.Calendar.WeekHeaders[i].Visible[$scope.data.CalendarFilter.ViewModeName]) continue; sourceSumQuantity += $scope.data.Calendar.Rows[sourceRowIndex].QuantityValues[i]; sourceSumCost += $scope.data.Calendar.Rows[sourceRowIndex].CostValues[i]; targetSumQuantity += $scope.data.Calendar.Rows[targetRowIndex].QuantityValues[i]; targetSumCost += $scope.data.Calendar.Rows[targetRowIndex].CostValues[i]; if ($scope.reallocator.CurveType == 'other') { otherSumQuantity += $scope.data.Calendar.Rows[curveRowIndex].QuantityValues[i]; otherSumCost += $scope.data.Calendar.Rows[curveRowIndex].CostValues[i]; } } if ((sourceSumQuantity == 0 && $scope.reallocator.ReallocateBy == 'Quantity') || (sourceSumCost == 0 && $scope.reallocator.ReallocateBy == 'Cost')) { alert('Source is zero, nothing to reallocate.'); return; } if ($scope.reallocator.CurveType == 'target') if ((targetSumQuantity == 0 && $scope.reallocator.ReallocateBy == 'Quantity') || (targetSumCost == 0 && $scope.reallocator.ReallocateBy == 'Cost')) { alert('Target curve is zero. Please select a different curve.'); return; } if ($scope.reallocator.CurveType == 'other') if ((otherSumQuantity == 0 && $scope.reallocator.ReallocateBy == 'Quantity') || (otherSumCost == 0 && $scope.reallocator.ReallocateBy == 'Cost')) { alert('Different curve is zero. Please select a another curve.'); return; } var quantity2Move = 0; var cost2Move = 0; for (i = sourceColIndex; i <= targetColIndex; i++) { if ($scope.data.Calendar.WeekHeaders[i].DataType == C_HEADER_DATA_TYPE_TOTALS || !$scope.data.Calendar.WeekHeaders[i].Visible[$scope.data.CalendarFilter.ViewModeName]) { continue; } if ($scope.reallocator.CurveType == 'source') { sourceQuantity = $scope.data.Calendar.Rows[sourceRowIndex].QuantityValues[i]; sourceCost = $scope.data.Calendar.Rows[sourceRowIndex].CostValues[i]; // update column values in model if ($scope.reallocator.ReallocateBy == 'Quantity') { updateQuantityOrCostCell($scope.reallocator.SourceExpCat, sourceRowIndex, i, sourceQuantity * (1 - percentageMult), sourceCost * (1 - percentageMult), null); updateQuantityOrCostCell($scope.reallocator.TargetExpCat, targetRowIndex, i, $scope.data.Calendar.Rows[targetRowIndex].QuantityValues[i] + sourceQuantity * percentageMult, null, true); } else { updateQuantityOrCostCell($scope.reallocator.SourceExpCat, sourceRowIndex, i, sourceQuantity * (1 - percentageMult), sourceCost * (1 - percentageMult), null); updateQuantityOrCostCell($scope.reallocator.TargetExpCat, targetRowIndex, i, null, $scope.data.Calendar.Rows[targetRowIndex].CostValues[i] + sourceCost * percentageMult, false); } } else if ($scope.reallocator.CurveType == 'target') { sourceQuantity = $scope.data.Calendar.Rows[sourceRowIndex].QuantityValues[i]; sourceCost = $scope.data.Calendar.Rows[sourceRowIndex].CostValues[i]; quantity2Move = (percentageMult * sourceSumQuantity) * ($scope.data.Calendar.Rows[targetRowIndex].QuantityValues[i] / targetSumQuantity); cost2Move = (percentageMult * sourceSumCost) * ($scope.data.Calendar.Rows[targetRowIndex].CostValues[i] / targetSumCost); // update column values in model if ($scope.reallocator.ReallocateBy == 'Quantity') { // we cannot reallocate more than source quantity if (quantity2Move > $scope.data.Calendar.Rows[sourceRowIndex].QuantityValues[i]) quantity2Move = $scope.data.Calendar.Rows[sourceRowIndex].QuantityValues[i]; if (quantity2Move > 0) { updateQuantityOrCostCell($scope.reallocator.SourceExpCat, sourceRowIndex, i, sourceQuantity * (1 - percentageMult), null, true); updateQuantityOrCostCell($scope.reallocator.TargetExpCat, targetRowIndex, i, $scope.data.Calendar.Rows[targetRowIndex].QuantityValues[i] + quantity2Move, null, true); } } else { // we cannot reallocate more than source quantity if (cost2Move > $scope.data.Calendar.Rows[sourceRowIndex].CostValues[i]) cost2Move = $scope.data.Calendar.Rows[sourceRowIndex].CostValues[i]; if (cost2Move > 0) { updateQuantityOrCostCell($scope.reallocator.SourceExpCat, sourceRowIndex, i, null, sourceCost * (1 - percentageMult), false); updateQuantityOrCostCell($scope.reallocator.TargetExpCat, targetRowIndex, i, null, $scope.data.Calendar.Rows[targetRowIndex].CostValues[i] + cost2Move, false); } } } else if ($scope.reallocator.CurveType == 'other') { sourceQuantity = $scope.data.Calendar.Rows[sourceRowIndex].QuantityValues[i]; sourceCost = $scope.data.Calendar.Rows[sourceRowIndex].CostValues[i]; quantity2Move = (percentageMult * sourceSumQuantity) * ($scope.data.Calendar.Rows[curveRowIndex].QuantityValues[i] / otherSumQuantity); cost2Move = (percentageMult * sourceSumCost) * ($scope.data.Calendar.Rows[curveRowIndex].CostValues[i] / otherSumCost); // update column values in model if ($scope.reallocator.ReallocateBy == 'Quantity') { // we cannot reallocate more than source quantity if (quantity2Move > $scope.data.Calendar.Rows[sourceRowIndex].QuantityValues[i]) quantity2Move = $scope.data.Calendar.Rows[sourceRowIndex].QuantityValues[i]; if (quantity2Move > 0) { updateQuantityOrCostCell($scope.reallocator.SourceExpCat, sourceRowIndex, i, sourceQuantity * (1 - percentageMult), null, true); updateQuantityOrCostCell($scope.reallocator.TargetExpCat, targetRowIndex, i, $scope.data.Calendar.Rows[targetRowIndex].QuantityValues[i] + quantity2Move, null, true); } } else { // we cannot reallocate more than source quantity if (cost2Move > $scope.data.Calendar.Rows[sourceRowIndex].CostValues[i]) cost2Move = $scope.data.Calendar.Rows[sourceRowIndex].CostValues[i]; if (cost2Move > 0) { updateQuantityOrCostCell($scope.reallocator.SourceExpCat, sourceRowIndex, i, null, sourceCost * (1 - percentageMult), false); updateQuantityOrCostCell($scope.reallocator.TargetExpCat, targetRowIndex, i, null, $scope.data.Calendar.Rows[targetRowIndex].CostValues[i] + cost2Move, false); } } } } $scope.RefreshCSSClasses(); resetRTDataChanged(); $("#reallocator").modal("hide"); }; $scope.pushPull = function () { if (!$scope.pushpuller.weeks) { alert('Number of weeks is required'); return; } if ($scope.pushpuller.weeks > ($scope.pushpuller.maxWeeks)) { alert('You cannot set the number of weeks to push/pull that exceeds scenario duration.'); return; } if (null != $scope.pushpuller.resource && $scope.pushpuller.resource.length > 0) { for (var i = 1; i < $scope.data.Calendar.Rows.length; i++) { var catIndex = -1; for (var j = 0; j < $scope.pushpuller.resource.length; j++) { if ($scope.pushpuller.resource[j] === $scope.data.Calendar.Rows[i].ExpCatId) { catIndex = j; break; } } if (catIndex >= 0) $scope.pushPullExpCat($scope.data.Calendar.Rows[i], i, $scope.pushpuller.weeks, $scope.pushpuller.push); } } $scope.pushpuller.weeks = 1; $scope.pushpuller.resource = null; $('#pushPullExpCats').select2('val', ''); resetPPDataChanged(); $("#push_pull").modal("hide"); }; $scope.pushPullExpCat = function (data, sourceRowIndex, weeks, push) { var expCat = $scope.data.Calendar.Rows[sourceRowIndex]; var i; var j; var handledWeeks = 0; var leftMargin; // index of the cell next to the last modified cell from the left side of the table. E.g. if we push 2 weeks, then it will be [2], or [3] if there is only one week in the first month var rightMargin; // index of the cell previous to the first modified cell from the right side of the table. E.g. if we pull 2 weeks, then it will be [length-3], or [length-4] if there is only one week in the last month for (i = 0; i < $scope.data.Calendar.WeekHeaders.length; i++) { if ($scope.data.Calendar.WeekHeaders[i].DataType == C_HEADER_DATA_TYPE_TOTALS || !$scope.data.Calendar.WeekHeaders[i].Visible[$scope.data.CalendarFilter.ViewModeName]) continue; handledWeeks++; if (handledWeeks == weeks) { leftMargin = i + 1; break; } } handledWeeks = 0; for (i = $scope.data.Calendar.WeekHeaders.length - 1; i >= 0; i--) { if ($scope.data.Calendar.WeekHeaders[i].DataType == C_HEADER_DATA_TYPE_TOTALS || !$scope.data.Calendar.WeekHeaders[i].Visible[$scope.data.CalendarFilter.ViewModeName]) continue; handledWeeks++; if (handledWeeks == weeks) { rightMargin = i - 1; break; } } if (push) { for (i = $scope.data.Calendar.WeekHeaders.length - 1; i >= 0; i--) { if ($scope.data.Calendar.WeekHeaders[i].DataType == C_HEADER_DATA_TYPE_TOTALS || !$scope.data.Calendar.WeekHeaders[i].Visible[$scope.data.CalendarFilter.ViewModeName]) { continue; } if (i < leftMargin) { if ($scope.data.CalendarFilter.IsTableModeQuantity) { updateQuantityOrCostCell(data.ExpCatId, sourceRowIndex, i, 0, null, true); } else { updateQuantityOrCostCell(data.ExpCatId, sourceRowIndex, i, null, 0, false); } if (expCat.Teams && expCat.Teams.length > 0) { for (var tIndex in expCat.Teams) { $scope.checkTeamValue(expCat, expCat.Teams[tIndex], i, 0, false); if (expCat.Teams[tIndex].Resources && expCat.Teams[tIndex].Resources.length > 0) { for (var rIndex in expCat.Teams[tIndex].Resources) $scope.checkResourceValue(expCat.ExpCatId, expCat.Teams[tIndex], expCat.Teams[tIndex].Resources[rIndex], i, 0, false); } } } } else { //copy data from [i+weeks] cell handledWeeks = 0; for (j = i - 1; j >= 0; j--) { if ($scope.data.Calendar.WeekHeaders[j].DataType == C_HEADER_DATA_TYPE_TOTALS || !$scope.data.Calendar.WeekHeaders[j].Visible[$scope.data.CalendarFilter.ViewModeName]) continue; if (++handledWeeks == weeks) break; } if ($scope.data.CalendarFilter.IsTableModeQuantity) { updateQuantityOrCostCell(data.ExpCatId, sourceRowIndex, i, data.QuantityValues[j], null, true); } else { updateQuantityOrCostCell(data.ExpCatId, sourceRowIndex, i, null, data.CostValues[j], false); } if (expCat.Teams && expCat.Teams.length > 0) { for (var tIndex in expCat.Teams) { $scope.checkTeamValue(expCat, expCat.Teams[tIndex], i, data.Teams[tIndex].QuantityValues[j], false); if (expCat.Teams[tIndex].Resources && expCat.Teams[tIndex].Resources.length > 0) { for (var rIndex in expCat.Teams[tIndex].Resources) $scope.checkResourceValue(expCat.ExpCatId, expCat.Teams[tIndex], expCat.Teams[tIndex].Resources[rIndex], i, expCat.Teams[tIndex].Resources[rIndex].QuantityValues[j], false); } } } } } } else { for (i = 0; i < $scope.data.Calendar.WeekHeaders.length; i++) { if ($scope.data.Calendar.WeekHeaders[i].DataType == C_HEADER_DATA_TYPE_TOTALS || !$scope.data.Calendar.WeekHeaders[i].Visible[$scope.data.CalendarFilter.ViewModeName]) { continue; } if (i <= rightMargin) { //copy data from [i+weeks] cell handledWeeks = 0; for (j = i + 1; j < $scope.data.Calendar.WeekHeaders.length; j++) { if ($scope.data.Calendar.WeekHeaders[j].DataType == C_HEADER_DATA_TYPE_TOTALS || !$scope.data.Calendar.WeekHeaders[j].Visible[$scope.data.CalendarFilter.ViewModeName]) continue; if (++handledWeeks == weeks) break; } if ($scope.data.CalendarFilter.IsTableModeQuantity) { updateQuantityOrCostCell(data.ExpCatId, sourceRowIndex, i, data.QuantityValues[j], null, true); } else { updateQuantityOrCostCell(data.ExpCatId, sourceRowIndex, i, null, data.CostValues[j], false); } if (expCat.Teams && expCat.Teams.length > 0) { for (var tIndex in expCat.Teams) { $scope.checkTeamValue(expCat, expCat.Teams[tIndex], i, data.Teams[tIndex].QuantityValues[j], false); if (expCat.Teams[tIndex].Resources && expCat.Teams[tIndex].Resources.length > 0) { for (var rIndex in expCat.Teams[tIndex].Resources) $scope.checkResourceValue(expCat.ExpCatId, expCat.Teams[tIndex], expCat.Teams[tIndex].Resources[rIndex], i, expCat.Teams[tIndex].Resources[rIndex].QuantityValues[j], false); } } } } else if (i > rightMargin) { if ($scope.data.CalendarFilter.IsTableModeQuantity) { updateQuantityOrCostCell(data.ExpCatId, sourceRowIndex, i, 0, null, true); } else { updateQuantityOrCostCell(data.ExpCatId, sourceRowIndex, i, null, 0, false); } if (expCat.Teams && expCat.Teams.length > 0) { for (var tIndex in expCat.Teams) { $scope.checkTeamValue(expCat, expCat.Teams[tIndex], i, 0, false); if (expCat.Teams[tIndex].Resources && expCat.Teams[tIndex].Resources.length > 0) { for (var rIndex in expCat.Teams[tIndex].Resources) $scope.checkResourceValue(expCat.ExpCatId, expCat.Teams[tIndex], expCat.Teams[tIndex].Resources[rIndex], i, 0, false); } } } } } } $scope.RefreshCSSClasses(); }; $scope.hidePushPull = function () { $scope.pushpuller.weeks = 1; }; $scope.changeCurve = function () { var oldQuantity = 0; var oldCost = 0; var changeMult = parseFloat($scope.editTotal.NewTotal) / parseFloat($scope.editTotal.CurrentTotal); var sourceRowIndex = null; var curveRowIndex = null; var i; for (i = 0; i < $scope.data.Calendar.Rows.length; i++) { if ($scope.data.Calendar.Rows[i].ExpCatId === $scope.editTotal.ExpCat) sourceRowIndex = i; if ($scope.data.Calendar.Rows[i].ExpCatId === $scope.editTotal.OtherCurve) curveRowIndex = i; } if (null === sourceRowIndex) { alert('Cannot find expenditure category to update.'); return; } if (null === curveRowIndex) { alert('Cannot find expenditure category to use as a curve.'); return; } var sourceSumCost = 0, sourceSumQuantity = 0, curveSumCost = 0, curveSumQuantity = 0; var numberOfWeeks = 0; for (i = 0; i < $scope.data.Calendar.WeekHeaders.length; i++) { if ($scope.data.Calendar.WeekHeaders[i].DataType == C_HEADER_DATA_TYPE_TOTALS || !$scope.data.Calendar.WeekHeaders[i].Visible[$scope.data.CalendarFilter.ViewModeName]) continue; sourceSumQuantity += $scope.data.Calendar.Rows[sourceRowIndex].QuantityValues[i]; sourceSumCost += $scope.data.Calendar.Rows[sourceRowIndex].CostValues[i]; curveSumQuantity += $scope.data.Calendar.Rows[curveRowIndex].QuantityValues[i]; curveSumCost += $scope.data.Calendar.Rows[curveRowIndex].CostValues[i]; numberOfWeeks++; } if (curveSumQuantity == 0 || sourceSumQuantity == 0) { changeMult = parseFloat($scope.editTotal.NewTotal); } if (isAvgMode() && numberOfWeeks > 0) { sourceSumQuantity = sourceSumQuantity / numberOfWeeks; curveSumQuantity = curveSumQuantity / numberOfWeeks; } if (curveSumQuantity > 0) for (i = 0; i < $scope.data.Calendar.WeekHeaders.length; i++) { if ($scope.data.Calendar.WeekHeaders[i].DataType == C_HEADER_DATA_TYPE_TOTALS || !$scope.data.Calendar.WeekHeaders[i].Visible[$scope.data.CalendarFilter.ViewModeName]) { continue; } oldQuantity = $scope.data.Calendar.Rows[sourceRowIndex].QuantityValues[i]; oldCost = $scope.data.Calendar.Rows[sourceRowIndex].CostValues[i]; var targetQuantity = $scope.data.Calendar.Rows[curveRowIndex].QuantityValues[i]; var targetCost = $scope.data.Calendar.Rows[curveRowIndex].CostValues[i]; if (sourceSumQuantity != 0) if (isAvgMode()) { updateQuantityOrCostCell($scope.editTotal.ExpCat, sourceRowIndex, i, Math.abs(sourceSumQuantity * (targetQuantity / curveSumQuantity) * changeMult), Math.abs(sourceSumCost * (targetCost / curveSumCost) * changeMult), null); } else { updateQuantityOrCostCell($scope.editTotal.ExpCat, sourceRowIndex, i, Math.abs(sourceSumQuantity * (targetQuantity / curveSumQuantity) * changeMult), Math.abs(sourceSumCost * (targetCost / curveSumCost) * changeMult), null); } else { if (isAvgMode()) { updateQuantityOrCostCell($scope.editTotal.ExpCat, sourceRowIndex, i, Math.abs((targetQuantity / curveSumQuantity) * changeMult), Math.abs(sourceSumCost * (targetCost / curveSumCost) * changeMult), null); } else { updateQuantityOrCostCell($scope.editTotal.ExpCat, sourceRowIndex, i, Math.abs((targetQuantity / curveSumQuantity) * changeMult), Math.abs(sourceSumCost * (targetCost / curveSumCost) * changeMult), null); } } } else { for (i = 0; i < $scope.data.Calendar.WeekHeaders.length; i++) { if ($scope.data.Calendar.WeekHeaders[i].DataType == C_HEADER_DATA_TYPE_TOTALS || !$scope.data.Calendar.WeekHeaders[i].Visible[$scope.data.CalendarFilter.ViewModeName]) { continue; } if (isAvgMode()) { updateQuantityOrCostCell($scope.editTotal.ExpCat, sourceRowIndex, i, changeMult, changeMult * $scope.getRate($scope.editTotal.ExpCat, $scope.data.Calendar.WeekHeaders[i].Milliseconds), null); } else { updateQuantityOrCostCell($scope.editTotal.ExpCat, sourceRowIndex, i, Math.abs(changeMult / numberOfWeeks), Math.abs(changeMult * $scope.getRate($scope.editTotal.ExpCat, $scope.data.Calendar.WeekHeaders[i].Milliseconds) / numberOfWeeks), null); } } } $scope.RefreshCSSClasses(); resetETDataChanged(); $("#editTotal").modal("hide"); }; $scope.openChangeCurveModal = function (row) { if (null != row) { $scope.editTotal.ExpCat = row.ExpCatId; } for (var i = 0; i < $scope.data.Calendar.Rows.length; i++) { if ($scope.data.Calendar.Rows[i].ExpCatId === $scope.editTotal.ExpCat) { if ($scope.data.CalendarFilter.IsTableModeQuantity) { $scope.editTotal.CurrentTotal = roundToNearest($scope.data.Calendar.Rows[i].GrandTotalQuantity, $scope.RoundingBasis); } else { $scope.editTotal.CurrentTotal = roundToNearest($scope.data.Calendar.Rows[i].GrandTotalCost, $scope.RoundingBasis); } $scope.editTotal.NewTotal = $scope.editTotal.CurrentTotal; break; } } }; $scope.isOtherSelectedReallocator = function () { return $scope.reallocator.CurveType == 'other'; }; $scope.prefillFormatCells = function (row) { if (null != row) $scope.formatCells.SelectedExpCats = [row.ExpCatId]; else if ($scope.data.AvailableExpenditures.length > 0) $scope.formatCells.SelectedExpCats = [$scope.data.AvailableExpenditures[0].Id]; $scope.formatCells.StartDate = $scope.formatCells.InitStartDate; $scope.formatCells.EndDate = $scope.formatCells.InitEndDate; var dp = $('#modalFormatCells #fs-datepicker-range').data('datepicker').pickers; dp[0].setDate($scope.formatCells.StartDate); dp[1].setDate($scope.formatCells.EndDate); $('#roundExpCats').select2('val', $scope.formatCells.SelectedExpCats.join()); refreshSelect2($('#formatCellsDecimalPlaces')); }; $scope.submitFormatCells = function () { if ($scope.data.ScenarioType === 9) return false; if (isNaN($scope.formatCells.DecimalPlaces)) { alert("Decimal Places is not set."); return false; } if ($scope.formatCells.SelectedExpCats.length <= 0) { return false; } var colIndexes2Update = []; // gather column indexes which we're going to update var dtStart = $scope.formatCells.StartDate.split('/'); var msStart = Date.UTC(dtStart[2], dtStart[0] - 1, dtStart[1]); var dtEnd = $scope.formatCells.EndDate.split('/'); var msEnd = Date.UTC(dtEnd[2], dtEnd[0] - 1, dtEnd[1]); for (var headerIndex = 0; headerIndex < $scope.data.Calendar.WeekHeaders.length; headerIndex++) { if ($scope.data.Calendar.WeekHeaders[headerIndex].DataType == C_HEADER_DATA_TYPE_TOTALS || !$scope.data.Calendar.WeekHeaders[headerIndex].Visible[$scope.data.CalendarFilter.ViewModeName]) continue; if ($scope.data.Calendar.WeekHeaders[headerIndex].Milliseconds >= msStart && $scope.data.Calendar.WeekHeaders[headerIndex].Milliseconds <= msEnd) { colIndexes2Update[colIndexes2Update.length] = headerIndex; } } var oldQuantity = 0; var newQuantity = 0; for (var rowIndex = 0; rowIndex < $scope.data.Calendar.Rows.length; rowIndex++) { for (var catIndex = 0; catIndex < $scope.formatCells.SelectedExpCats.length; catIndex++) { if ($scope.formatCells.SelectedExpCats[catIndex] === $scope.data.Calendar.Rows[rowIndex].ExpCatId) { for (var colIndex = 0; colIndex < colIndexes2Update.length; colIndex++) { oldQuantity = $scope.data.Calendar.Rows[rowIndex].QuantityValues[colIndexes2Update[colIndex]]; newQuantity = roundToNearest(oldQuantity, $scope.formatCells.DecimalPlaces); if (oldQuantity === newQuantity) continue; updateQuantityOrCostCell($scope.data.Calendar.Rows[rowIndex].ExpCatId, rowIndex, colIndexes2Update[colIndex], newQuantity, null, true); } break; } } } $scope.RefreshCSSClasses(); $scope.formatCells.SelectedExpCats = null; $('#roundExpCats').select2('val', ''); refreshSelect2($('#formatCellsDecimalPlaces')); resetFCDataChanged(); $("#modalFormatCells").modal("hide"); return true; }; $scope.onMonthHeaderClick = function (header, headerIndex, $event) { $event.stopPropagation(); header.IsCollapsed = !header.IsCollapsed; header.CollapsedClass = header.IsCollapsed ? $scope.CollapsedIcon : $scope.NonCollapsedIcon; if (header.WeekHeaders && header.WeekHeaders.length > 0) { for (var i = 0; i < header.WeekHeaders.length; i++) { var weekendingHeader = $scope.data.Calendar.WeekHeaders[header.WeekHeaders[i]]; weekendingHeader.Show = $scope.getWeekHeaderShowStatus(weekendingHeader); weekendingHeader.Initialized = weekendingHeader.Initialized || weekendingHeader.Show; } // SA. Month header visibility var monthHeader = $scope.data.Calendar.WeekHeaders[header.TotalsHeader]; monthHeader.Show = $scope.getWeekHeaderShowStatus(monthHeader); monthHeader.Initialized = monthHeader.Initialized || monthHeader.Show; } getVisibleCellCount(); }; $scope.onExpCatCollapseClick = function (row, $event) { $event.stopPropagation(); $event.preventDefault(); var rawEC = $scope.data.Calendar.Expenditures[row.ExpCatId]; onCollapseClick(row, rawEC); }; $scope.onTeamCollapseClick = function (expCatId, team, $event) { $event.stopPropagation(); $event.preventDefault(); var rawTeam = $scope.data.Calendar.Expenditures[expCatId].Teams[team.Id]; onCollapseClick(team, rawTeam); }; // row - collapsable row, rawCollapsable - collapsable model that will be sent to the server function onCollapseClick(row, rawCollapsable) { // skip collapsing in actuals mode if ($scope.data.CalendarFilter.ShowActuals) return; row.Collapsed = rawCollapsable.Collapsed = !row.Collapsed; row.CollapsedClass = rawCollapsable.CollapsedClass = row.Collapsed ? $scope.CollapsedIcon : $scope.NonCollapsedIcon; }; $scope.go = function (Id, $event) { // SA. Rewrited to open in new window var url = "/Team/Board/?teamId=" + Id + "teamCalendar"; window.open(url); $event.stopPropagation(); } $scope.removeExpCat = function (row) { if (null == row) return; if (!row.IsEditable || $scope.data.CalendarFilter.VisibleExpCats.length <= 1) return; if (!confirm('Do you really want to delete ' + row.ExpCatName + '?')) return; $scope.data.AvailableExpenditures = $scope.data.AvailableExpenditures.filter(function (obj) { return obj.Id !== row.ExpCatId; }); $scope.data.NeedToRecalculateScenarioDetails = true; $scope.gridChanged(); $scope.getCalendar(); }; $scope.addExpCats = function () { if (null == $scope.data.CalendarFilter.ExpCats2Add || $scope.data.CalendarFilter.ExpCats2Add.length == 0) { $('#selExpCats2Add').select2('val', ''); return; } for (var i = 0; i < $scope.data.CalendarFilter.ExpCats2Add.length; i++) { var item2Add = $scope.data.CalendarFilter.ExpCats2Add[i]; for (var j = 0; j < $scope.data.Expenditures2Add.length; j++) { var item = $scope.data.Expenditures2Add[j]; if (item.Id === item2Add.Id) { $scope.data.AvailableExpenditures.push({ Id: item.Id, Name: item.Name }); break; } } } $scope.data.CalendarFilter.ExpCats2Add = null; $('#selExpCats2Add').select2('val', ''); $scope.data.NeedToRecalculateScenarioDetails = true; $scope.refreshGraph = true; $scope.gridChanged(); $scope.getCalendar(); }; $scope.addTeams = function () { if (null == $scope.data.CalendarFilter.Teams2Add || $scope.data.CalendarFilter.Teams2Add.length == 0) return; $.each($scope.data.CalendarFilter.Teams2Add, function (i, value) { var teamInScenario = $scope.data.TeamsInScenario.filter(function (team) { return team.TeamId == value.Value; }); if (!teamInScenario || teamInScenario.length <= 0) { $scope.data.TeamsInScenario.push({ TeamId: value.Value, Allocation: 0, IsNew: true }); } }); $scope.data.CalendarFilter.Teams2Add = null; $('#selTeams2Add').select2('val', ''); $scope.data.NeedToRecalculateScenarioDetails = true; $scope.gridChanged(); $scope.getCalendar(); }; $scope.removeTeam = function (team) { if (null == team) return; if (!confirm('Do you really want to delete ' + team.Name + '?')) return; removeTeamInternal(team.Id); $scope.RestTeams = getRestTeams(); // SA. ENV-1075. Deleted teams not appear in the available teams list to add $scope.gridChanged(); }; function removeTeamInternal(teamId) { if (!teamId) return; if ($scope.data) { if ($scope.data.Calendar) { if ($scope.data.Calendar.Rows) { for (var i in $scope.data.Calendar.Rows) { for (var j in $scope.data.Calendar.Rows[i].Teams) { if ($scope.data.Calendar.Rows[i].Teams[j].Id == teamId) { for (var z in $scope.data.Calendar.WeekHeaders) { if ($scope.data.Calendar.WeekHeaders[z].DataType != C_HEADER_DATA_TYPE_ORDINAL) continue; $scope.data.Calendar.Rows[i].RestQuantity[z] += $scope.data.Calendar.Rows[i].Teams[j].QuantityValues[z]; } $scope.data.Calendar.Rows[i].Teams.splice(j, 1); break; } } } } if ($scope.data.Calendar.Expenditures) { $.each($scope.data.Calendar.Expenditures, function (expCatId, expCat) { delete expCat.Teams[teamId]; }); } } if ($scope.data.TeamsInScenario) { $scope.data.TeamsInScenario = $scope.data.TeamsInScenario.filter(function (obj) { return obj.TeamId != teamId; }); } } } function getRestTeams() { if (!$scope.data || !$scope.data.Teams) return null; if (!$scope.data.TeamsInScenario || $scope.data.Teams.length <= 0) return $scope.data.Teams; return $scope.data.Teams.filter(function (team) { var teamInScenario = $scope.data.TeamsInScenario.filter(function (obj) { return team.Value == obj.TeamId; }); return (!teamInScenario || teamInScenario.length <= 0); }); }; // refresh CSS classes of the category or resource cell $scope.updateCSSClass = function (row, teamRow, resRow, colIndex) { if (colIndex == null || $scope.data.Calendar.WeekHeaders == null) return; if (row != null && teamRow != null && resRow != null) { // updating resource row resRow.CSSClass[colIndex] = ''; if (resRow.QuantityValues == null || resRow.QuantityValues.length <= colIndex) return; if (($scope.data.Calendar.WeekHeaders[colIndex].DataType == C_HEADER_DATA_TYPE_ORDINAL) && $scope.data.CalendarFilter.IsTableModeQuantity) { var allocatedByResources = 0, teamQuantity = 0; if (teamRow.AllocatedByResources != null && teamRow.AllocatedByResources.length >= colIndex) allocatedByResources = (Math.round(teamRow.AllocatedByResources[colIndex] * 100) / 100); if (teamRow.QuantityValues != null && teamRow.QuantityValues.length >= colIndex) teamQuantity = (Math.round(teamRow.QuantityValues[colIndex] * 100) / 100); var quantity = Math.round(resRow.QuantityValues[colIndex] * 100) / 100; var restCapacity = Math.round(resRow.RestQuantityValues[colIndex] * 100) / 100; // if quantity is 0 we do not need to check rest capacity for resource if ((quantity > 0 && restCapacity < 0) || allocatedByResources > teamQuantity) resRow.CSSClass[colIndex] = 'cellover'; else if (teamQuantity == allocatedByResources) { resRow.CSSClass[colIndex] = 'cellequal'; } } } else if (row != null && teamRow != null) { // updating team row teamRow.CSSClass[colIndex] = ''; if (teamRow.QuantityValues == null || teamRow.QuantityValues.length <= colIndex || teamRow.AllocatedByResources == null || teamRow.AllocatedByResources.length < colIndex || row.QuantityValues == null || row.QuantityValues.length < colIndex) return; if (($scope.data.Calendar.WeekHeaders[colIndex].DataType == C_HEADER_DATA_TYPE_ORDINAL) && $scope.data.CalendarFilter.IsTableModeQuantity) { var allocatedByResources = (Math.round(teamRow.AllocatedByResources[colIndex] * 100) / 100); var quantity = (Math.round(teamRow.QuantityValues[colIndex] * 100) / 100); var ecRestQuantity = (Math.round(row.RestQuantity[colIndex] * 100) / 100); if (allocatedByResources > quantity) { $scope.TeamsIsOverAllocated = true; teamRow.CSSClass[colIndex] = 'cellover'; } else if (ecRestQuantity < 0) { $scope.ExpendituresIsOverAllocated = true; teamRow.CSSClass[colIndex] = 'cellover'; } else if (ecRestQuantity > 0) { $scope.ExpendituresIsUnderAllocated = true; teamRow.CSSClass[colIndex] = 'cellless'; } else if (allocatedByResources == quantity || ecRestQuantity == 0) teamRow.CSSClass[colIndex] = 'cellequal'; } } else if (row != null) { // updating category row row.CSSClass[colIndex] = ''; if (row.RestQuantity == null || row.RestQuantity.length <= colIndex) return; if ($scope.data.CalendarFilter.ShowActuals) { if ($scope.data.Scenario.RedIndicator != null && row.ForecastCompletedPercent >= $scope.data.Scenario.RedIndicator) { row.CSSClass[colIndex] = 'red'; } else if ($scope.data.Scenario.YellowIndicator != null && row.ForecastCompletedPercent >= $scope.data.Scenario.YellowIndicator) { row.CSSClass[colIndex] = 'yellow'; } else if ($scope.data.Scenario.RedIndicator != null && $scope.data.Scenario.YellowIndicator != null) { //($scope.data.Scenario.GreenIndicator != null && row.ForecastCompletedPercent >= $scope.data.Scenario.GreenIndicator) { row.CSSClass[colIndex] = 'green'; } } else { if (($scope.data.Calendar.WeekHeaders[colIndex].DataType == C_HEADER_DATA_TYPE_ORDINAL) && $scope.data.CalendarFilter.IsTableModeQuantity) { var ecRestQuantity = Math.round(row.RestQuantity[colIndex] * 100) / 100; if (ecRestQuantity < 0) { $scope.ExpendituresIsOverAllocated = true; row.CSSClass[colIndex] = 'cellover'; } else if (ecRestQuantity > 0) row.CSSClass[colIndex] = 'cellless'; else row.CSSClass[colIndex] = 'cellequal'; } } } }; $scope.updateMonthCSSClass = function (CSSClass, monthWeeks, colIndex) { if (!CSSClass || !monthWeeks || !colIndex || CSSClass.length <= colIndex) return; var isExistsOverAllocated = false, isExistsUnderAllocated = false, isEquals = true; for (var i in monthWeeks) { if (CSSClass[monthWeeks[i]] == 'cellover') isExistsOverAllocated = true; if (CSSClass[monthWeeks[i]] == 'cellless') isExistsUnderAllocated = true; if (CSSClass[monthWeeks[i]] != 'cellequal') isEquals = false; } if (isExistsOverAllocated) CSSClass[colIndex] = 'cellover'; else if (isExistsUnderAllocated) CSSClass[colIndex] = 'cellless'; else if (isEquals) CSSClass[colIndex] = 'cellequal'; else CSSClass[colIndex] = ''; }; // refresh CSS classes for all cells $scope.RefreshCSSClasses = function () { $scope.ExpendituresIsOverAllocated = $scope.ExpendituresIsUnderAllocated = $scope.TeamsIsOverAllocated = false; // iterate through collection of all expenditure categories (except total) for (var rowIndex = 1; rowIndex < $scope.data.Calendar.Rows.length; rowIndex++) { var expCat = $scope.data.Calendar.Rows[rowIndex]; // init CSSClass array if necessary if (expCat.CSSClass == null) expCat.CSSClass = new Array($scope.data.Calendar.WeekHeaders.length); // iterate through each cell of selected expenditure category row for (var colIndex = 0; colIndex < $scope.data.Calendar.WeekHeaders.length; colIndex++) { var weekHeader = $scope.data.Calendar.WeekHeaders[colIndex]; var monthWeeks = $scope.data.Calendar.MonthHeaders[weekHeader.MonthHeader].WeekHeaders; if ($scope.data.Calendar.WeekHeaders[colIndex].DataType == C_HEADER_DATA_TYPE_TOTALS) // update CSS class of selected expenditure category month cell $scope.updateMonthCSSClass(expCat.CSSClass, monthWeeks, colIndex); else // update CSS of the selected expenditure category cell $scope.updateCSSClass(expCat, null, null, colIndex); // if expenditure category has assigned teams then we need to color teams as well if (expCat.Teams != null && expCat.Teams.length > 0) { // iterate through each team row cells for (var teamIndex = 0; teamIndex < expCat.Teams.length; teamIndex++) { var team = expCat.Teams[teamIndex]; // init CSSClass array if necessary if (team.CSSClass == null) team.CSSClass = new Array($scope.data.Calendar.WeekHeaders.length); if ($scope.data.Calendar.WeekHeaders[colIndex].DataType == C_HEADER_DATA_TYPE_TOTALS) // update CSS class of selected team month cell $scope.updateMonthCSSClass(team.CSSClass, monthWeeks, colIndex); else // update CSS classes of selected team cell $scope.updateCSSClass(expCat, team, null, colIndex); // if team has assigned resources then we need to color resources as well if (team.Resources != null && team.Resources.length > 0) { // iterate through each resource row cells for (var resIndex = 0; resIndex < team.Resources.length; resIndex++) { var resource = team.Resources[resIndex]; // init CSSClass array if necessary if (resource.CSSClass == null) resource.CSSClass = new Array($scope.data.Calendar.WeekHeaders.length); if ($scope.data.Calendar.WeekHeaders[colIndex].DataType == C_HEADER_DATA_TYPE_TOTALS) // update CSS class of selected resource month cell $scope.updateMonthCSSClass(resource.CSSClass, monthWeeks, colIndex); else // update CSS classes of selected resource cell $scope.updateCSSClass(expCat, team, resource, colIndex); } } } } } } }; $scope.gridWasChanged = function () { return $scope.data.isDataChanged; }; $scope.renderHtml = function (html) { return $sce.trustAsHtml(html); }; $scope.getScenarioDetailsData = function () { return { AvailableExpenditures: $scope.data.AvailableExpenditures, TeamsInScenario: $scope.getTeamsAllocation() }; }; $scope.setFinInfo = function (finInfoStr) { if (!finInfoStr || finInfoStr.trim().length <= 0) return; var finInfo = JSON.parse(finInfoStr); if (finInfo.IsRevenueGenerating) $scope.data.Scenario.ProjectedRevenue = finInfo.ProjectedRevenue; else $scope.data.Scenario.TDDirectCosts = finInfo.ProjectedExpense; if (finInfo.UseLMMargin) { $scope.data.Scenario.UseLMMargin = true; $scope.data.Scenario.LMMargin = finInfo.Margin; } else { $scope.data.Scenario.UseLMMargin = false; $scope.data.Scenario.GrossMargin = finInfo.Margin; } if (finInfo.CostSavings) { var startDate = new Date(finInfo.CostSavings.CostSavingStartDate), endDate = new Date(finInfo.CostSavings.CostSavingEndDate), costSavingItems = (finInfo.CostSavings.CostSavingItems || "").trim().length > 0 ? JSON.parse(finInfo.CostSavings.CostSavingItems) : null; $scope.data.Scenario.CostSavings = { CostSavingItems: costSavingItems, CostSavingStartDate: Date.UTC(startDate.getFullYear(), startDate.getMonth(), startDate.getDate()), CostSavingEndDate: Date.UTC(endDate.getFullYear(), endDate.getMonth(), endDate.getDate()), CostSavings: finInfo.CostSavings.CostSavings, CostSavingType: finInfo.CostSavings.CostSavingType, CostSavingDescription: finInfo.CostSavings.CostSavingDescription }; } }; $scope.clearSelection = function () { clearAllSelectedCells(); }; $scope.saveScenarioDetails = function () { var scenario = { Id: $scope.data.Scenario.Id, ProjectId: $scope.data.Scenario.ParentId, StartDate: $scope.data.Scenario.StartDate, EndDate: $scope.data.Scenario.EndDate, Expenditures: $scope.data.Calendar.Expenditures }; $rootScope.$broadcast('saveScenarioDetails', scenario); }; $scope.cancelScenarioDetails = function () { $rootScope.$broadcast('cancelScenarioDetails'); }; $scope.scenarioEditModelChanged = function () { $scope.data.ScenarioGeneralInfoEditModel.IsChanged = true; }; $scope.recalculateScenarioDetails = function () { if ($scope.data.ScenarioGeneralInfoEditModel.IsChanged === true) { if (angular.isFunction(scenarioDetailsEditFormIsValid) && scenarioDetailsEditFormIsValid()) { var startDate = new Date($scope.data.ScenarioGeneralInfoEditModel.StartDate); var endDate = new Date($scope.data.ScenarioGeneralInfoEditModel.EndDate); $scope.data.Scenario.DistributionType = $scope.data.ScenarioGeneralInfoEditModel.DistributionType; $scope.data.Scenario.StartDate = Date.UTC(startDate.getFullYear(), startDate.getMonth(), startDate.getDate()); $scope.data.Scenario.EndDate = Date.UTC(endDate.getFullYear(), endDate.getMonth(), endDate.getDate()); // need to adjust margin for original or keep total modes $scope.data.Scenario.NeedToAdjustMargin = $scope.data.Scenario.DistributionType == 0 || $scope.data.Scenario.DistributionType == 1; $scope.data.ScenarioGeneralInfoEditModel.IsChanged = false; $scope.data.NeedToRecalculateScenarioDetails = true; $scope.gridChanged(); $scope.getCalendar(null, normalizeTeamQuantityValues); } } }; // It is usefull for RMO when we have ALL capacity for each team and we should only extend or cut // quantity values for teams and their resources. Quantity values for expenditures are actual because thay came from the server function normalizeTeamQuantityValues() { if (!$scope.data.Calendar.Expenditures || !$scope.data.Calendar.WeekHeaders) return; var weekEndings = []; $scope.data.Calendar.WeekHeaders.forEach(function (item) { if (item.DataType === C_HEADER_DATA_TYPE_ORDINAL) weekEndings.push(item.Milliseconds); }); // extend with 0-values for (var weekIndex = 0; weekIndex < weekEndings.length; weekIndex++) { var weekEnding = weekEndings[weekIndex]; for (var expCatId in $scope.data.Calendar.Expenditures) { var category = $scope.data.Calendar.Expenditures[expCatId]; if (!category.Teams) continue; for (var teamId in category.Teams) { var team = category.Teams[teamId]; if (!team.QuantityValues) team.QuantityValues = {}; if (!team.QuantityValues[weekEnding]) team.QuantityValues[weekEnding] = 0; if (!team.Resources) continue; for (var resourceId in team.Resources) { var resource = team.Resources[resourceId]; if (!resource.QuantityValues) resource.QuantityValues = {}; if (!resource.QuantityValues[weekEnding]) resource.QuantityValues[weekEnding] = 0; } } } } // delete redundant for (var expCatId in $scope.data.Calendar.Expenditures) { var category = $scope.data.Calendar.Expenditures[expCatId]; if (!category.Teams) continue; for (var teamId in category.Teams) { var team = category.Teams[teamId]; if (!!team.QuantityValues) { var tkeys = Object.keys(team.QuantityValues); for (var i = 0; i < tkeys.length; i++) { var tkey = parseFloat(tkeys[i]); if (weekEndings.indexOf(tkey) < 0) delete team.QuantityValues[tkey]; } } if (!team.Resources) continue; for (var resourceId in team.Resources) { var resource = team.Resources[resourceId]; if (!!resource.QuantityValues) { var rkeys = Object.keys(resource.QuantityValues); for (var i = 0; i < rkeys.length; i++) { var rkey = parseFloat(rkeys[i]); if (weekEndings.indexOf(rkey) < 0) delete resource.QuantityValues[rkey]; } } } } } }; /* Drag and drop methods */ $scope.dragStart = function ($dragmodel) { if (!$dragmodel.ExpCat.SelectedCells[$dragmodel.Position]) { clearAllSelectedCells(); clearActiveDroppables($dragmodel.ExpCat); checkAndSelectMonthCell($dragmodel.ExpCat, $dragmodel.Position, true); } }; $scope.drag = function ($api) { if (!scrollGrid || typeof scrollGrid != 'function') return; var axis = $api.getAxis(); var coords = { x: axis.x, y: axis.y }; scrollGrid(coords); } $scope.dragEnd = function ($dragmodel) { clearActiveDroppables($dragmodel.ExpCat); clearSelectedCells($dragmodel.ExpCat); }; $scope.dragEnter = function ($dropmodel, $dragmodel) { recalculateActiveDroppables($dropmodel.ExpCat, calculateShift($dropmodel.Position, $dragmodel.Position)); }; $scope.drop = function ($dropmodel, $dragmodel) { copyRange($dropmodel.ExpCat, $dropmodel.ExpCatSourcePosition, calculateShift($dropmodel.Position, $dragmodel.Position)); clearActiveDroppables($dropmodel.ExpCat); clearSelectedCells($dropmodel.ExpCat); $scope.RefreshCSSClasses(); }; $scope.selectCell = function (expCat, index, $event) { $event.stopPropagation(); if ($scope.data.CalendarFilter.ShowActuals || // need to select cell only if user clicks by cell but not by any child inside cell $event.target != $event.currentTarget) { return; } var keys = dndKey.get(); if (keys && keys.length > 0) { var excludeExpCats = []; excludeExpCats.push(expCat.ExpCatId); clearAllSelectedCells(excludeExpCats); // with pressed Caps Lock user can select any cells from grid without range - we need to prohibit this ability if (dndKey.isset(20)) return; // Ctrl if (dndKey.isset(17)) { var needToSelectCell = !expCat.SelectedCells[index]; // check ability to select cell with pressed Ctrl key if (needToSelectCell && !checkCtrlSelection(expCat, index)) return; // check ability to unselect cell with pressed Ctrl key if (!needToSelectCell && !checkCtrlUnselection(expCat, index)) return; } // Shift else if (dndKey.isset(16)) { selectRange(expCat, index); return; } } else { clearAllSelectedCells(); } checkAndSelectMonthCell(expCat, index, !expCat.SelectedCells[index]); }; function checkAndSelectMonthCell(expCat, index, isSelected) { if (!isSelectableWeekEnding(index)) return; expCat.SelectedCells[index] = isSelected; // if user clicked month cell we need to select/unselect all weeks from this month var weekHeader = $scope.data.Calendar.WeekHeaders[index]; var monthHeader = $scope.data.Calendar.MonthHeaders[weekHeader.MonthHeader]; if (weekHeader.DataType == C_HEADER_DATA_TYPE_TOTALS) { for (var i in monthHeader.WeekHeaders) { if (isSelectableWeekEnding(monthHeader.WeekHeaders[i])) expCat.SelectedCells[monthHeader.WeekHeaders[i]] = isSelected; } } else { var monthIndexInWeekHeaders = Math.max.apply(null, monthHeader.WeekHeaders) + 1; if (isSelected) { if (areAllWeeksSelected(expCat, monthHeader)) expCat.SelectedCells[monthIndexInWeekHeaders] = true; } else { expCat.SelectedCells[monthIndexInWeekHeaders] = false; } } }; function clearSelectedCells(expCat) { if (!expCat || !expCat.SelectedCells || expCat.SelectedCells.indexOf(true) < 0) return; for (var i = 0; i < expCat.SelectedCells.length; i++) expCat.SelectedCells[i] = false; } function clearAllSelectedCells(excludeExpCats) { if (!excludeExpCats) excludeExpCats = []; for (var i = 0; i < $scope.data.Calendar.Rows.length; i++) { if (excludeExpCats.indexOf($scope.data.Calendar.Rows[i].ExpCatId) >= 0) continue; clearSelectedCells($scope.data.Calendar.Rows[i]); } } function checkCtrlSelection(expCat, selectingIndex) { if (!expCat || !expCat.SelectedCells) return false; var firstSelectedCellIndex = expCat.SelectedCells.indexOf(true); var lastSelectedCellIndex = expCat.SelectedCells.lastIndexOf(true); if (firstSelectedCellIndex < 0 && lastSelectedCellIndex < 0) return true; var prevIndexes = getPrevSelectableWeekEndings(firstSelectedCellIndex - 1); var nextIndexes = getNextSelectableWeekEndings(lastSelectedCellIndex + 1); return prevIndexes.indexOf(selectingIndex) >= 0 || nextIndexes.indexOf(selectingIndex) >= 0; } // check ability to unselect cell with pressed Ctrl key function checkCtrlUnselection(expCat, selectingIndex) { if (!expCat || !expCat.SelectedCells) return false; // if it is leftmost week's cell - can be unselected var firstSelectedCellIndex = expCat.SelectedCells.indexOf(true); if (firstSelectedCellIndex == selectingIndex) return true; // if it is leftmost month's cell - can be unselected var firstSelectedMonth = getMonthIndex(firstSelectedCellIndex); if (firstSelectedMonth == selectingIndex) return true; // if it is rightmost week's cell - can be unselected var lastSelectedWeekEndingIndex = getLastSelectedWeekEnding(expCat); if (lastSelectedWeekEndingIndex == selectingIndex) return true; // if it is rightmost month's cell - can be unselected var lastSelectedMonth = getMonthIndex(lastSelectedWeekEndingIndex); if (lastSelectedMonth == selectingIndex) return true; return false; } function areAllWeeksSelected(expCat, monthHeader) { if (!expCat || !expCat.SelectedCells) return false; if (!monthHeader || !monthHeader.WeekHeaders) return false; var areAllSelected = true; for (var i in monthHeader.WeekHeaders) { if (isSelectableWeekEnding(monthHeader.WeekHeaders[i]) && !expCat.SelectedCells[monthHeader.WeekHeaders[i]]) { areAllSelected = false; break; } } return areAllSelected; }; function getPrevSelectableWeekEndings(toIndex) { var weekEndings = []; for (var i = toIndex; i >= 0; i--) { if ($scope.data.Calendar.WeekHeaders[i].DataType == C_HEADER_DATA_TYPE_ORDINAL && $scope.data.Calendar.WeekHeaders[i].Visible[$scope.data.CalendarFilter.ViewModeName]) { weekEndings.push(i); weekEndings.push(getMonthIndex(i)); break; } } return weekEndings; } function getNextSelectableWeekEndings(startIndex) { var weekEndings = []; for (var i = startIndex; i < $scope.data.Calendar.WeekHeaders.length; i++) { if ($scope.data.Calendar.WeekHeaders[i].DataType == C_HEADER_DATA_TYPE_ORDINAL && $scope.data.Calendar.WeekHeaders[i].Visible[$scope.data.CalendarFilter.ViewModeName]) { weekEndings.push(i); weekEndings.push(getMonthIndex(i)); break; } } return weekEndings; } // gets the last selected week ending, not month function getLastSelectedWeekEnding(expCat) { var lastWeekEnding = -1; for (var i = expCat.SelectedCells.lastIndexOf(true) ; i >= 0; i--) { if ($scope.data.Calendar.WeekHeaders[i].DataType == C_HEADER_DATA_TYPE_ORDINAL && $scope.data.Calendar.WeekHeaders[i].Visible[$scope.data.CalendarFilter.ViewModeName] && expCat.SelectedCells[i]) { lastWeekEnding = i; break; } } return lastWeekEnding; } function isSelectableWeekEnding(index) { return $scope.data.Calendar.WeekHeaders[index].Visible[$scope.data.CalendarFilter.ViewModeName]; } function getMonthIndex(weekEndingIndex) { var weekHeader = $scope.data.Calendar.WeekHeaders[weekEndingIndex]; if (!weekHeader) return -1; var monthHeader = $scope.data.Calendar.MonthHeaders[weekHeader.MonthHeader]; if (!monthHeader) return -1; return Math.max.apply(null, monthHeader.WeekHeaders) + 1; } function selectRange(expCat, upToIndex) { if (!expCat || !expCat.SelectedCells) return false; var startIndex = expCat.SelectedCells.indexOf(true); // if there are no selected cells yet we need to select only single cell if (startIndex < 0) { checkAndSelectMonthCell(expCat, upToIndex, true); return; } else { clearSelectedCells(expCat); for (var i = startIndex; i < upToIndex; i++) { if (!isSelectableWeekEnding(i)) continue; expCat.SelectedCells[i] = true; } // need to check only last index checkAndSelectMonthCell(expCat, upToIndex, true); } } function clearActiveDroppables(expCat) { for (var i = 0; i < $scope.data.Calendar.WeekHeaders.length; i++) expCat.ActiveDroppables[i] = false; } function recalculateActiveDroppables(expCat, shift) { if (!expCat || !expCat.ActiveDroppables || !expCat.SelectedCells) return; clearActiveDroppables(expCat); if (shift > 0) { for (var i = 0; i < expCat.SelectedCells.length; i++) { if (!expCat.SelectedCells[i]) continue; if ($scope.data.Calendar.WeekHeaders[i].DataType != C_HEADER_DATA_TYPE_ORDINAL || !$scope.data.Calendar.WeekHeaders[i].Visible[$scope.data.CalendarFilter.ViewModeName]) { continue; } var activeIndex = -1; for (var j = i + 1, z = 0; j < expCat.SelectedCells.length; j++) { if ($scope.data.Calendar.WeekHeaders[j].DataType == C_HEADER_DATA_TYPE_ORDINAL && $scope.data.Calendar.WeekHeaders[j].Visible[$scope.data.CalendarFilter.ViewModeName]) { z++; } if (z == shift) { activeIndex = j; break; } } if (activeIndex < 0) continue; var monthIndex = $scope.data.Calendar.WeekHeaders[activeIndex].MonthHeader; var monthIndexInWeeksArray = Math.max.apply(null, $scope.data.Calendar.MonthHeaders[monthIndex].WeekHeaders) + 1; expCat.ActiveDroppables[activeIndex] = true; expCat.ActiveDroppables[monthIndexInWeeksArray] = true; } } else { for (var i = expCat.SelectedCells.length; i > 0; i--) { if (!expCat.SelectedCells[i]) continue; if ($scope.data.Calendar.WeekHeaders[i].DataType != C_HEADER_DATA_TYPE_ORDINAL || !$scope.data.Calendar.WeekHeaders[i].Visible[$scope.data.CalendarFilter.ViewModeName]) { continue; } var activeIndex = -1; for (var j = i - 1, z = 0; j >= 0; j--) { if ($scope.data.Calendar.WeekHeaders[j].DataType == C_HEADER_DATA_TYPE_ORDINAL && $scope.data.Calendar.WeekHeaders[j].Visible[$scope.data.CalendarFilter.ViewModeName]) { z++; } if (z == Math.abs(shift)) { activeIndex = j; break; } } if (activeIndex < 0) continue; var monthIndex = $scope.data.Calendar.WeekHeaders[activeIndex].MonthHeader; var monthIndexInWeeksArray = Math.max.apply(null, $scope.data.Calendar.MonthHeaders[monthIndex].WeekHeaders) + 1; expCat.ActiveDroppables[activeIndex] = true; expCat.ActiveDroppables[monthIndexInWeeksArray] = true; } } } function calculateShift(dropModelIndex, dragModelIndex) { var shift = 0, dragModelHeader = $scope.data.Calendar.WeekHeaders[dragModelIndex], dropModelHeader = $scope.data.Calendar.WeekHeaders[dropModelIndex]; if (dragModelHeader.DataType == C_HEADER_DATA_TYPE_TOTALS) { dragModelIndex = Math.min.apply(null, $scope.data.Calendar.MonthHeaders[dragModelHeader.MonthHeader].WeekHeaders); } if (dropModelHeader.DataType == C_HEADER_DATA_TYPE_TOTALS) { dropModelIndex = Math.min.apply(null, $scope.data.Calendar.MonthHeaders[dropModelHeader.MonthHeader].WeekHeaders); } // push if (dropModelIndex > dragModelIndex) { for (var i = dragModelIndex; i < dropModelIndex; i++) { if ($scope.data.Calendar.WeekHeaders[i].DataType == C_HEADER_DATA_TYPE_ORDINAL && $scope.data.Calendar.WeekHeaders[i].Visible[$scope.data.CalendarFilter.ViewModeName]) { shift++; } } } else { for (var i = dropModelIndex; i < dragModelIndex; i++) { if ($scope.data.Calendar.WeekHeaders[i].DataType == C_HEADER_DATA_TYPE_ORDINAL && $scope.data.Calendar.WeekHeaders[i].Visible[$scope.data.CalendarFilter.ViewModeName]) { shift--; } } } //console.log(shift); return shift; } function getActiveDroppablesCount(expCat) { var count = 0; for (var i = expCat.ActiveDroppables.lastIndexOf(true) ; i >= expCat.ActiveDroppables.indexOf(true) ; i--) { if ($scope.data.Calendar.WeekHeaders[i].DataType == C_HEADER_DATA_TYPE_TOTALS || !$scope.data.Calendar.WeekHeaders[i].Visible[$scope.data.CalendarFilter.ViewModeName]) { continue; } count++; } return count; } $scope.getSelectedCellsCount = function (expCat) { var count = 0; for (var i = expCat.SelectedCells.lastIndexOf(true) ; i >= expCat.SelectedCells.indexOf(true) ; i--) { if (!$scope.data.Calendar.WeekHeaders[i] || $scope.data.Calendar.WeekHeaders[i].DataType == C_HEADER_DATA_TYPE_TOTALS || !$scope.data.Calendar.WeekHeaders[i].Visible[$scope.data.CalendarFilter.ViewModeName]) { continue; } count++; } return count; } function copyRange(expCat, position, shift) { if (!expCat || !expCat.ActiveDroppables || !expCat.SelectedCells) { console.log('pushRange - incorrect data'); return; } // e.g. we have array: [1, 2, 3, 4, 5, 6] // we select [2, 3, 4, 5] and try to move 3 steps to the right // so we can place only 2 cells: 2->5, 3->6 and other cells (4, 5) will be removed var needToSkip = Math.max($scope.getSelectedCellsCount(expCat) - getActiveDroppablesCount(expCat), 0); var startActiveDroppableIndex = expCat.ActiveDroppables.indexOf(true); var endActiveDroppableIndex = expCat.ActiveDroppables.lastIndexOf(true); var startSelectedCellIndex = expCat.SelectedCells.indexOf(true); var endSelectedCellIndex = expCat.SelectedCells.lastIndexOf(true); if (shift > 0) { for (var i = endActiveDroppableIndex; i >= startActiveDroppableIndex; i--) { if ($scope.data.Calendar.WeekHeaders[i].DataType == C_HEADER_DATA_TYPE_TOTALS || !$scope.data.Calendar.WeekHeaders[i].Visible[$scope.data.CalendarFilter.ViewModeName] || !expCat.ActiveDroppables[i]) { continue; } for (var j = endSelectedCellIndex; j >= startSelectedCellIndex; j--) { if ($scope.data.Calendar.WeekHeaders[j].DataType == C_HEADER_DATA_TYPE_TOTALS || !$scope.data.Calendar.WeekHeaders[j].Visible[$scope.data.CalendarFilter.ViewModeName] || !expCat.SelectedCells[j]) { continue; } if (needToSkip > 0) { needToSkip--; continue; } endSelectedCellIndex = j; break; } if (endSelectedCellIndex >= 0 && endSelectedCellIndex < $scope.data.Calendar.WeekHeaders.length) { copyColumn(expCat, position, endSelectedCellIndex, i); } endSelectedCellIndex--; } // clear shifted cells for (var i = startSelectedCellIndex; i <= expCat.SelectedCells.lastIndexOf(true) ; i++) { if ($scope.data.Calendar.WeekHeaders[i].DataType == C_HEADER_DATA_TYPE_TOTALS || !$scope.data.Calendar.WeekHeaders[i].Visible[$scope.data.CalendarFilter.ViewModeName] || !expCat.SelectedCells[i]) { continue; } clearColumn(expCat, position, i); if (--shift == 0) break; } } else { for (var i = startActiveDroppableIndex; i <= endActiveDroppableIndex; i++) { if ($scope.data.Calendar.WeekHeaders[i].DataType == C_HEADER_DATA_TYPE_TOTALS || !$scope.data.Calendar.WeekHeaders[i].Visible[$scope.data.CalendarFilter.ViewModeName] || !expCat.ActiveDroppables[i]) { continue; } for (var j = startSelectedCellIndex; j <= endSelectedCellIndex; j++) { if ($scope.data.Calendar.WeekHeaders[j].DataType == C_HEADER_DATA_TYPE_TOTALS || !$scope.data.Calendar.WeekHeaders[j].Visible[$scope.data.CalendarFilter.ViewModeName] || !expCat.SelectedCells[j]) { continue; } if (needToSkip > 0) { needToSkip--; continue; } startSelectedCellIndex = j; break; } if (startSelectedCellIndex >= 0 && startSelectedCellIndex < $scope.data.Calendar.WeekHeaders.length) { copyColumn(expCat, position, startSelectedCellIndex, i); } startSelectedCellIndex++; } // clear shifted cells for (var i = endSelectedCellIndex; i >= expCat.SelectedCells.indexOf(true) ; i--) { if ($scope.data.Calendar.WeekHeaders[i].DataType == C_HEADER_DATA_TYPE_TOTALS || !$scope.data.Calendar.WeekHeaders[i].Visible[$scope.data.CalendarFilter.ViewModeName] || !expCat.SelectedCells[i]) { continue; } clearColumn(expCat, position, i); if (++shift == 0) break; } } } function copyColumn(expCat, sourceIndex, copyFromIndex, copyToIndex) { if (!expCat) return; if ($scope.data.CalendarFilter.IsTableModeQuantity) { var newValue = $scope.data.CalendarFilter.DragNDropReplaceValues ? expCat.QuantityValues[copyFromIndex] : (expCat.QuantityValues[copyFromIndex] + expCat.QuantityValues[copyToIndex]); updateQuantityOrCostCell(expCat.ExpCatId, sourceIndex, copyToIndex, newValue, null, true); } else { var newValue = $scope.data.CalendarFilter.DragNDropReplaceValues ? expCat.CostValues[copyFromIndex] : (expCat.CostValues[copyFromIndex] + expCat.CostValues[copyToIndex]); updateQuantityOrCostCell(expCat.ExpCatId, sourceIndex, copyToIndex, null, newValue, false); } if (expCat.Teams && expCat.Teams.length > 0) { for (var tIndex in expCat.Teams) { var newTeamValue = $scope.data.CalendarFilter.DragNDropReplaceValues ? expCat.Teams[tIndex].QuantityValues[copyFromIndex] : (expCat.Teams[tIndex].QuantityValues[copyFromIndex] + expCat.Teams[tIndex].QuantityValues[copyToIndex]); $scope.checkTeamValue(expCat, expCat.Teams[tIndex], copyToIndex, newTeamValue, false); if (expCat.Teams[tIndex].Resources && expCat.Teams[tIndex].Resources.length > 0) { for (var rIndex in expCat.Teams[tIndex].Resources) { var newResourceValue = $scope.data.CalendarFilter.DragNDropReplaceValues ? expCat.Teams[tIndex].Resources[rIndex].QuantityValues[copyFromIndex] : (expCat.Teams[tIndex].Resources[rIndex].QuantityValues[copyFromIndex] + expCat.Teams[tIndex].Resources[rIndex].QuantityValues[copyToIndex]); $scope.checkResourceValue(expCat.ExpCatId, expCat.Teams[tIndex], expCat.Teams[tIndex].Resources[rIndex], copyToIndex, newResourceValue, false); } } } } } function clearColumn(expCat, sourceIndex, index) { if (!expCat || index < 0) return; if ($scope.data.CalendarFilter.IsTableModeQuantity) { updateQuantityOrCostCell(expCat.ExpCatId, sourceIndex, index, 0, null, true); } else { updateQuantityOrCostCell(expCat.ExpCatId, sourceIndex, index, null, 0, false); } if (expCat.Teams && expCat.Teams.length > 0) { for (var tIndex in expCat.Teams) { $scope.checkTeamValue(expCat, expCat.Teams[tIndex], index, 0, false); if (expCat.Teams[tIndex].Resources && expCat.Teams[tIndex].Resources.length > 0) { for (var rIndex in expCat.Teams[tIndex].Resources) $scope.checkResourceValue(expCat.ExpCatId, expCat.Teams[tIndex], expCat.Teams[tIndex].Resources[rIndex], index, 0, false); } } } } // rounds "numToRound" to nearest "roundCoeff". // e.g.:roundToNearest(0.6, 0.25)=0.5 // roundToNearest(0.8, 0.25)=0.75 // roundToNearest(1.49999, 1)=1 // roundToNearest(1.5, 1)=2 function roundToNearest(numToRound, roundCoeff) { if (roundCoeff == 0) return numToRound; roundCoeff = 1 / (roundCoeff); return Math.round(numToRound * roundCoeff) / roundCoeff; } function refreshSelect2(obj) { $timeout(function () { // You might need this timeout to be sure its run after DOM render. obj.trigger("change"); }, 0, false); } $scope.getTotalCostsByMonthes = function () { if (!$scope.data.Calendar.MonthHeaders || $scope.data.Calendar.MonthHeaders.length <= 0) return {}; if (!$scope.data.Calendar.Rows || $scope.data.Calendar.Rows.length <= 0 || !$scope.data.Calendar.Rows[0].CostValues || $scope.data.Calendar.Rows[0].CostValues.length <= 0) { return {}; } var totals = {}; for (var i = 0; i < $scope.data.Calendar.WeekHeaders.length; i++) { var week = $scope.data.Calendar.WeekHeaders[i]; if (week.DataType == C_HEADER_DATA_TYPE_ORDINAL) continue; var weekEndingMonth = new Date(week.Milliseconds); var monthStart = new Date(weekEndingMonth.getFullYear(), weekEndingMonth.getMonth(), 1).getTime(); totals[monthStart] = $scope.data.Calendar.Rows[0].CostValues[i] || 0; } return totals; } }]);