'use strict'; 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', '$q', 'timeTracker', 'dndKey', 'costSavingService', 'scenarioDetailsService', 'dataSources', function ($scope, $rootScope, $http, $location, $timeout, $filter, $sce, $document, $q, timeTracker, dndKey, costSavingService, scenarioDetailsService, dataSources) { 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"; var AllocationMode = { AssignRest: 1, AssignAll: 2, Reset: 3 }; $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.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; $scope.WorkFlowSchema = null; $scope.NotesToAdd = []; $scope.NotesToDelete = []; $scope.HoldWorkFlowActions = []; $scope.HoldWorkFlowSchema = null; //notification from the Scenario Header controller $scope.$on('refreshScenarioDetails', function (event, 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; $scope.data.Scenario.UserDefinedFields = data.UserDefinedFields; $scope.data.Scenario.AdjustMarginRased = data.adjustMarginRased; $scope.data.Scenario.DateForStartOfChangesOld = data.dateForStartOfChangesOld; $scope.refreshGraph = true; if (data.dateForStartOfChanges) { var sc = new Date(data.dateForStartOfChanges); $scope.data.Scenario.DateForStartOfChanges = Date.UTC(sc.getFullYear(), sc.getMonth(), sc.getDate()); } if (data.DateForStartOfChangesOld) { var sc2 = new Date(data.DateForStartOfChangesOld); $scope.data.Scenario.DateForStartOfChangesOld = Date.UTC(sc2.getFullYear(), sc2.getMonth(), sc2.getDate()); } $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('refreshFinancialInformation', function (event, data) { try { $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.CostSavings = data.costSaving; $scope.data.Scenario.NeedToAdjustMargin = data.needToAdjustMargin; $scope.data.NeedToRecalculateScenarioDetails = true; $scope.data.Scenario.AdjustMarginRased = data.adjustMarginRased; $scope.data.Scenario.DateForStartOfChangesOld = data.dateForStartOfChangesOld; if (data.dateForStartOfChanges) { $scope.data.Scenario.DateForStartOfChanges = DateTimeConverter.stringToMs(data.dateForStartOfChanges); $scope.data.ScenarioGeneralInfoEditModel.DateForStartOfChanges = data.dateForStartOfChanges; $document.trigger('date-for-start-of-change-changed', $scope.data.ScenarioGeneralInfoEditModel.DateForStartOfChanges); } if (data.dateForStartOfChangesOld) { $scope.data.Scenario.DateForStartOfChangesOld = DateTimeConverter.stringToMs(data.dateForStartOfChangesOld); } } catch (e) { console.error(e); showErrorModal('Oops!', $scope.CommonErrorMessage); return; } $scope.gridChanged(); $scope.getCalendar(); }); $scope.$on('changeScenarioDetails', function (event, data) { if (!data) return; scenarioDetailsService.setExpenditures(getScenarioId(), data.Expenditures || {}); scenarioDetailsService.setRates(getScenarioId(), data.Rates || []); $scope.data.AvailableExpenditures = []; $scope.data.LocalRatesLoaded = true; if (!!data.CostSaving) { costSavingService.init({ ScenarioId: getScenarioId(), ScenarioStartDate: $scope.data.Scenario.StartDate, ScenarioEndDate: $scope.data.Scenario.EndDate, StartDate: data.CostSaving.CostSavingStartDate ? DateTimeConverter.msFormatAsUtcString(data.CostSaving.CostSavingStartDate) : "", EndDate: data.CostSaving.CostSavingEndDate ? DateTimeConverter.msFormatAsUtcString(data.CostSaving.CostSavingEndDate) : "", CostSavings: data.CostSaving.CostSavings, CostSavingType: data.CostSaving.CostSavingType, Description: data.CostSaving.CostSavingDescription, Items: !!data.CostSaving.CostSavingItems ? JSON.stringify(data.CostSaving.CostSavingItems) : null }); } for (var expCatId in data.Expenditures) { $scope.data.AvailableExpenditures.push({ Id: expCatId, Name: data.Expenditures[expCatId].ExpenditureCategoryName }); } $scope.getCalendar(null, normalizeTeamQuantityValues); }); $scope.$on('addNewExpenditureCategory', function (event, data) { if (!data) return; if (!data.expenditureCategoryId) { if (data.callback) data.callback(); return; } $scope.data.CalendarFilter.ExpCats2Add = [{ Id: data.expenditureCategoryId }]; $scope.addExpCats(data.callback); }); $scope.$on('deleteExpenditureCategories', function (event, data) { if (!data || !angular.isArray(data) || data.length <= 0) return; removeExpCatsInternal(data); }); $scope.$on('refreshTeamsList', function (event, data) { $scope.RestTeams = getRestTeams(); }); $scope.$on('scenarioIsChanged', function (event, data) { $scope.gridChanged(); }); $scope.$on('NoteAdded', function (event, n) { var Nidx = NoteIndex(n); if (Nidx < 0) $scope.NotesToAdd.push(n); else $scope.NotesToAdd.splice(Nidx, 1, n); }); $scope.$on('NoteRemoved', function (event, n) { var Nidx = NoteIndex(n); if (Nidx > 0) $scope.NotesToAdd.splice(Nidx, 1); else $scope.NotesToDelete.push(n); }); $scope.$on('scenarioWorkFlowSchemaChanged', function (event, data) { if ($scope.WorkFlowSchema != data.WorkFlowSchema) { if ($scope.HoldWorkFlowSchema == null) { $scope.HoldWorkFlowSchema = $scope.WorkFlowSchema; $scope.data.HoldWorkFlowActions = $scope.data.WorkFlowActions; } $scope.WorkFlowSchema = data.WorkFlowSchema; $scope.data.WorkFlowActions = []; if ( $scope.HoldWorkFlowSchema = $scope.WorkFlowSchema) $scope.data.WorkFlowActions = $scope.data.HoldWorkFlowActions; } }); $scope.$on('dependenciesvalidated', function (event, data) { $scope.Dependencies = data; }); $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, DateForStartOfChanges: 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: "", DragNDropReplaceValues: true }, Calendar: { Headers: null, WeekHeaders: null, MonthHeaders: null, Rows: null, Expenditures: null }, ExpenditureAddGroups: [ { Id: 0, Name: "Other Categories" }, { Id: 1, Name: "Categories of current Teams" } ], AvailableExpenditures: null, Expenditures2Add: null, Teams: null, CategoryTypes: null, CGEFX: null, IncomeTypes: null, GLAccounts: null, CreditDepartments: null, Rates: null, NeedToRebind: true, NeedToRecalculateScenarioDetails: false, isDataChanged: false, VisibleCellCount: 1, Opener: 1, //'details' WorkFlowActions: [{ command: null, Selected: false, Id: null, HasChanges:false}], WorkFlowStates: [{ state: null, Selected: false, Id: null, HasChanges:false}], WorkFlowSchema: null, Audit: null }; }; $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; } } }); $scope.$watch("data.ScenarioGeneralInfoEditModel.StartDate", function (newValue, oldValue) { if (newValue != oldValue) { $scope.scenarioEditModelChanged(); } }); $scope.$watch("data.ScenarioGeneralInfoEditModel.EndDate", function (newValue, oldValue) { if (newValue != oldValue) { $scope.scenarioEditModelChanged(); } }); 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(row, team, resource, 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; // skip all readonly cells except if you click reset button, because we need to reset ALL cells (clear corrupted data) if (resource.ReadOnly[i] && !(mode == AllocationMode.Reset && resource.QuantityValues[i] != 0)) 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; $scope.checkResourceValue(row, team, resource, i, newQuantity, false); } $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; $scope.checkTeamValue(expCat, team, i, newQuantity, false); } $scope.gridChanged(); $scope.RefreshCSSClasses(); } function isAvgMode() { return $scope.data.CalendarFilter.ShowAvgTotals && !$scope.data.CalendarFilter.IsUOMHours && $scope.data.CalendarFilter.IsTableModeQuantity; } $scope.init = function (initData, fnCallback) { $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.Scenario.IsBottomUp = initData.IsBottomUp; $scope.data.Scenario.UserDefinedFields = initData.UserDefinedFields; $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; if (initData.StartDate > 0) { $scope.data.ScenarioGeneralInfoEditModel.StartDate = DateTimeConverter.msFormatAsUtcString(initData.StartDate); } if (initData.EndDate > 0) { $scope.data.ScenarioGeneralInfoEditModel.EndDate = DateTimeConverter.msFormatAsUtcString(initData.EndDate); } var currentDateAsUts = DateTimeConverter.getUtcNowMs(); var dateTimeForStartChanges = (initData.StartDate > currentDateAsUts) ? initData.StartDate : (initData.EndDate > currentDateAsUts) ? currentDateAsUts : initData.EndDate; if (dateTimeForStartChanges > 0) { $scope.data.ScenarioGeneralInfoEditModel.DateForStartOfChanges = DateTimeConverter.msFormatAsLocalString(dateTimeForStartChanges); } $scope.data.DataSection = initData.DataSection; 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 "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 + ")"); } } } // init scenario details service scenarioDetailsService.setTeamsInScenario(getScenarioId(), initData.TeamsInScenario); var expendituresLoadTask = dataSources.load(); var dataSourcesLoadTask = scenarioDetailsService.loadDataSources(); $q.all([expendituresLoadTask, dataSourcesLoadTask]) .then(function (asyncResults) { $scope.data.Teams = asyncResults[1].teams; $scope.data.CGEFX = asyncResults[1].cgefx; $scope.data.CategoryTypes = asyncResults[1].categoryTypes; $scope.data.CreditDepartments = asyncResults[1].creditDepartments; $scope.data.GLAccounts = asyncResults[1].glAccounts; $scope.data.IncomeTypes = asyncResults[1].incomeTypes; if (initData.InitOnDemand !== true) $scope.getCalendar(fnCallback); }) .then(null, function () { showErrorModal('Oops!', $scope.CommonErrorMessage); }); }; $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 = { recalculationModel: { Scenario: $scope.data.Scenario, AvailableExpenditures: $scope.data.AvailableExpenditures, TeamsInScenario: scenarioDetailsService.getTeamsInScenarioArray(getScenarioId()), Rates: scenarioDetailsService.getScenarioRates(getScenarioId()), NeedToRebind: $scope.data.NeedToRebind, NeedToRecalculateScenarioDetails: $scope.data.NeedToRecalculateScenarioDetails, LocalRatesLoaded: $scope.data.LocalRatesLoaded, Calendar: { Expenditures: scenarioDetailsService.getExpenditures(getScenarioId()) }, filterStartDate: $scope.data.Scenario.StartDate, filterEndDate: $scope.data.Scenario.EndDate }}; var allExpenditures = dataSources.getExpenditures(); //$scope.data = null; $http.post('/Scenarios/RecalculateCalendar', snapshot). success(function (data, status, headers, config) { try { if (data.NeedToRebind) { var adjustMarginRased = data.Scenario.AdjustMarginRased; var dateForStartOfChangesOld = $scope.data.Scenario.DateForStartOfChangesOld; $scope.data.AvailableExpenditures = data.AvailableExpenditures; $scope.data.Calendar = data.Calendar; $scope.data.Scenario = data.Scenario; $scope.data.Scenario.AdjustMarginRased = adjustMarginRased; $scope.data.Scenario.DateForStartOfChangesOld = dateForStartOfChangesOld; $scope.data.Expenditures2Add = data.Expenditures2Add; $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; $scope.data.WorkFlowActions = data.WorkFlowActions; $scope.data.WorkFlowStates = data.WorkFlowStates; if ($scope.WorkFlowSchema == null || data.WorkFlowSchema != null) $scope.WorkFlowSchema = data.WorkFlowSchema; for (var i = 0; i < $scope.data.Expenditures2Add.length; i++) { var exp = $scope.data.Expenditures2Add[i]; exp.GroupElement = $scope.data.ExpenditureAddGroups[exp.IsRelatedToScenario]; } } else { var adjustMarginRased = data.Scenario.AdjustMarginRased; var dateForStartOfChangesOld = $scope.data.Scenario.DateForStartOfChangesOld; $scope.data = dataCopy; $scope.data.Scenario = data.Scenario; $scope.data.Scenario.AdjustMarginRased = adjustMarginRased; $scope.data.Scenario.DateForStartOfChangesOld = dateForStartOfChangesOld; $scope.data.AvailableExpenditures = data.AvailableExpenditures; $scope.data.NeedToRecalculateScenarioDetails = data.NeedToRecalculateScenarioDetails; $scope.data.Calendar = data.Calendar; } if (!data.Calendar || !data.Calendar.Headers || !data.Calendar.Expenditures) { $scope.data.Scenario.NeedToAdjustMargin = false; unblockUI(); $document.trigger('sd.dataload.completed', $scope.getTotalCostsByMonthes()); return; } // Loading scenario resources info var teamIds = []; for (var index = 0; index < data.TeamsInScenario.length; index++) { teamIds.push(data.TeamsInScenario[index].TeamId); } dataSources.loadResourcesByTeamIds(teamIds).then(function (result) { $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(); var scenarioWeekendings = $scope.getScenarioWeekEndings(); scenarioDetailsService.recalculateResourceAvailability4Categories(data.Calendar.Expenditures, scenarioWeekendings, allExpenditures); scenarioDetailsService.setTeamsInScenario(getScenarioId(), data.TeamsInScenario); scenarioDetailsService.setExpenditures(getScenarioId(), data.Calendar.Expenditures); scenarioDetailsService.setRates(getScenarioId(), data.Rates); $scope.switchViewMode(); initGraphData(); $scope.RestTeams = getRestTeams(); // if it is top-down scenario if (!$scope.data.Scenario.IsBottomUp) { $scope.initGridRows(); $scope.setGridSource(); initReallocatorModel(); initFormatCells(); initEditTotalModel(); initPushPullerMaxWeeks(); $scope.RefreshCSSClasses(); if ($scope.data.Scenario.NeedToAdjustMargin) { if ($scope.data.Scenario.ProjectedRevenue > 0) { 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(); } else { var eventData = { ScenarioId: getScenarioId(), MonthHeaders: angular.copy($scope.data.Calendar.MonthHeaders), WeekHeaders: angular.copy($scope.data.Calendar.WeekHeaders), CalendarFilter: angular.copy($scope.data.CalendarFilter) }; if (angular.isFunction(fnCallback)) { var result = fnCallback(); if (result && angular.isFunction(result.then)) result.then(function () { $scope.$broadcast('refreshScenarioDetailsGrid', eventData); }); else $scope.$broadcast('refreshScenarioDetailsGrid', eventData); } else { $scope.$broadcast('refreshScenarioDetailsGrid', eventData); } } $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); }); }; function initReallocatorModel() { if ($scope.data.AvailableExpenditures && $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; } }; function initGraphData() { $scope.GraphFilter.ExpCats = {}; var expenditures = scenarioDetailsService.getExpenditures(getScenarioId()); $.each(expenditures, function (expCatId, ecg) { $scope.GraphFilter.ExpCats[expCatId] = true; }); if ($scope.refreshGraph) { $scope.showGraph(); $scope.refreshGraph = false; } }; function initFormatCells() { 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; } } }; function initEditTotalModel() { if ($scope.data.AvailableExpenditures && $scope.data.AvailableExpenditures.length > 0) { $scope.editTotal = { ExpCat: $scope.data.AvailableExpenditures[0].Id, OtherCurve: $scope.data.AvailableExpenditures[0].Id }; } else { $scope.editTotal = {}; } }; function initPushPullerMaxWeeks() { 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.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) { var ec = $scope.data.Calendar.Rows[i]; if (isMonth) { 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); } } $.each(ec.Teams, function (teamId, team) { if (ec.Changed) { team.Changed = true; } }); 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, team, resource, i, Math.abs(resource.QuantityValues[i] * changeMult), false); } else { if (isAvgMode()) $scope.checkResourceValue(ec, team, resource, i, newValue, false); else $scope.checkResourceValue(ec, 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(); $timeout(function () { tab2Cell.click(); }, 0); } 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(); $timeout(function () { tab2Cell.click(); }, 0); } } }); }; $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]; var weekEnding = $scope.data.Calendar.WeekHeaders[colIndex].Milliseconds; var rawCategory = scenarioDetailsService.getCategoryInScenario(getScenarioId(), expCatId); 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, weekEnding) * 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, weekEnding) * 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; rawCategory.Details[weekEnding].ForecastQuantity = newQuantity / $scope.getUomMultiplier(expCatId); rawCategory.Details[weekEnding].ForecastCost = newCost; rawCategory.Details[weekEnding].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); } } $scope.prepareWorkFlowOptionsBeforeSaving = function (event, saveAs) { var changedExpenditures = {}, expendituresInScenario = scenarioDetailsService.getExpenditures(getScenarioId()); if (!expendituresInScenario) return; if (!saveAs) { $scope.data.Scenario.SaveAs = 0; if ($scope.data.WorkFlowActions.length <= 1) { event.preventDefault(); event.stopPropagation(); $scope.saveChanges(); } } else { $scope.data.Scenario.SaveAs = 1; } }; function prepareExpendituresForSaving() { var changedExpenditures = {}, expendituresInScenario = scenarioDetailsService.getExpenditures(getScenarioId()); if (!expendituresInScenario) return changedExpenditures; $.each(expendituresInScenario, 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, Changed: team.Changed, 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; } 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; } }); return changedExpenditures; }; $scope.getData4Saving = function () { return { Scenario: $scope.data.Scenario, AvailableExpenditures: $scope.data.AvailableExpenditures, TeamsInScenario: scenarioDetailsService.recalculateTeamsAllocation(getScenarioId()), Calendar: { Expenditures: prepareExpendituresForSaving() }, WorkFlowSchema:$scope.WorkFlowSchema, WorkFlowActions: $scope.data.WorkFlowActions, WorkFlowStates: $scope.data.WorkFlowStates, Notes: $scope.NotesToAdd, NotesToDelete: $scope.NotesToDelete, Dependencies: $scope.Dependencies, Audit: $scope.data.Audit }; }; $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; // we should allow to save scenario as draft even if it is over allocated, because drafts save without allocations if (!saveAsDraft && !$scope.isValidAllocation()) return; if (saveAsDraft != null && saveAsDraft != undefined) $scope.data.Scenario.SaveAsDraft = saveAsDraft; blockUI(); var dataToSave = $scope.getData4Saving(); $http.post('/Scenarios/SaveSnapshotChanges', JSON.stringify(dataToSave)). success(function (data, status, headers, config) { try { $scope.gridSaved(); // 'details' var openurl = window.location.href; if (1 === $scope.Opener ) { if (data && data.ScenarioId) window.location.href = window.location.origin + '/Scenarios/Details/' + data.ScenarioId + '?ptab=Details'; // 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 if (0 === $scope.Opener && openurl.indexOf('Project') > 0) { window.location.href = window.location.origin + '/Scenarios/Details/' + data.ScenarioId + '?ptab=Details'; } else { window.location.href = window.location.href.replace("newscenario", "scenarios").replace(new RegExp(/#\/?\w*/), ''); } unblockUI(); } catch (e) { unblockUI(); if (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 (typeof (hideModals) == 'function') hideModals(); showErrorModal('Oops!', $scope.CommonErrorMessage); }); }; $scope.getRate = function (expCatId, date) { return scenarioDetailsService.getRate(getScenarioId(), expCatId, date) / $scope.getUomMultiplier(expCatId); }; $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 () { savePagePreferences(); $scope.showGraph(); }; $scope.toggleGraphTotal = function () { 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 expendituresInScenario = scenarioDetailsService.getExpenditures(getScenarioId()); if (!$scope.data.Calendar || !expendituresInScenario || Object.keys(expendituresInScenario).length <= 0) return; 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 expCatData; var expCatActualsData; var actualsData = new Array(); var firstpass = true; var expenditures = Object.keys(expendituresInScenario); for (var i = 0; i < expenditures.length; i++) {//ExpCat Iteration var ExpCatId = expenditures[i]; 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(); var weekEnding = $scope.data.Calendar.WeekHeaders[colIndex].Milliseconds; expCatData[currentIndex][0] = weekEnding; if (!$scope.GraphFilter.ShowTotal || firstpass) {//if we are not calculating totals or it is the first pass if (!$scope.data.CalendarFilter.IsTableModeQuantity) expCatData[currentIndex][1] = expendituresInScenario[ExpCatId].Details[weekEnding].ForecastCost || 0; else if ($scope.data.CalendarFilter.IsUOMHours) expCatData[currentIndex][1] = expendituresInScenario[ExpCatId].Details[weekEnding].ForecastQuantity || 0; else expCatData[currentIndex][1] = expendituresInScenario[ExpCatId].Details[weekEnding].ForecastQuantity * uomMultiplier || 0; } else { if (!$scope.data.CalendarFilter.IsTableModeQuantity) expCatData[currentIndex][1] = +expCatData[currentIndex][1] + (+expendituresInScenario[ExpCatId].Details[weekEnding].ForecastCost || 0); else if ($scope.data.CalendarFilter.IsUOMHours) expCatData[currentIndex][1] = +expCatData[currentIndex][1] + (+expendituresInScenario[ExpCatId].Details[weekEnding].ForecastQuantity || 0); else expCatData[currentIndex][1] = +expCatData[currentIndex][1] + (+expendituresInScenario[ExpCatId].Details[weekEnding].ForecastQuantity * uomMultiplier || 0); } if ($scope.GraphFilter.Actuals) { if (firstpass || !expCatActualsData[currentIndex]) expCatActualsData[currentIndex] = new Array(); expCatActualsData[currentIndex][0] = weekEnding; var value = (true !== $scope.data.CalendarFilter.IsTableModeQuantity) ? expendituresInScenario[ExpCatId].Details[weekEnding].ActualsCost : expendituresInScenario[ExpCatId].Details[weekEnding].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 (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 == expenditures.length - 1) { $scope.graphData[$scope.graphData.length] = { label: ($scope.GraphFilter.ShowTotal) ? 'Total' : expendituresInScenario[ExpCatId].ExpenditureCategoryName, 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' : expendituresInScenario[ExpCatId].ExpenditureCategoryName + ' - 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('
'); var tickStep = 1; if ($scope.graphData && $scope.graphData.length > 0) CalculateMonthSpread('DetailsGraphContainer', $scope.graphData[0].data);//$scope.calculateMonthSpread(); // 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 (row, team, resource) { allocateResource(row, team, resource, AllocationMode.AssignRest); }; $scope.assignAllForResource = function (row, team, resource) { allocateResource(row, team, resource, AllocationMode.AssignAll); }; $scope.zeroResource = function (row, team, resource) { bootbox.confirm({ message: "Are you sure you want fill this resource quantities with 0s?", callback: function (result) { if (result) { $scope.$apply(function () { allocateResource(row, team, resource, AllocationMode.Reset); }); } } }); }; $scope.removeResource = function (row, team, resource, $index) { bootbox.confirm({ message: "Are you sure you want to remove this resource?", callback: function (result) { $scope.$apply(function () { if (result) { var resId = resource.Id; var rawTeam = scenarioDetailsService.getTeamInScenario(getScenarioId(), row.ExpCatId, team.Id); //Zero resource out allocateResource(row, team, resource, AllocationMode.Reset); //Remove resource rawTeam.Resources[resId].Changed = true; rawTeam.Resources[resId].Deleted = true; rawTeam.Changed = true; team.Resources.splice($index, 1); team.AvailableResources = getAvailableResources(rawTeam); refreshAssignResourceSelect(row.ExpCatId, team.Id); }; $scope.gridChanged(); $scope.setGridSource(); }); } }); }; $scope.assignResource = function (expCat, team, $event) { if (expCat == null) return; if (!team || !team.ResourceToAssignId) return; var rawTeam = scenarioDetailsService.getTeamInScenario(getScenarioId(), expCat.ExpCatId, 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, ExpenditureCategoryId: rawResource.ExpenditureCategoryId, 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); refreshAssignResourceSelect(expCat.ExpCatId, team.Id); $scope.gridChanged(); $scope.setGridSource(); }; $scope.assignTeam = function (expCat, $event) { if (expCat == null || expCat.TeamToAssignId == null) return; var teamId = expCat.TeamToAssignId; scenarioDetailsService.addTeamInScenario(getScenarioId(), teamId); $scope.data.NeedToRecalculateScenarioDetails = true; $scope.data.isDataChanged = true; $scope.gridChanged(); $scope.getCalendar(); }; $scope.checkResourceValue = function (row, team, resource, colIndex, data, refreshCss) { var uomMultiplier = $scope.getUomMultiplier(resource.ExpenditureCategoryId); var newValue = parseFloat(data); if (isNaN(newValue)) newValue = 0; if (newValue < 0) { return "Value should not be less than zero"; } var affectedCells = []; 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); affectedCells.push({ WeekIndex: j, OldValue: resource.QuantityValues[j], NewValue: val }); } } else { affectedCells.push({ WeekIndex: colIndex, OldValue: resource.QuantityValues[colIndex], NewValue: newValue }); } } if (affectedCells.length > 0) { var rawTeam = scenarioDetailsService.getTeamInScenario(getScenarioId(), row.ExpCatId, team.Id); // if any resource has been changed we need to mark team as changed also rawTeam.Changed = true; var rawResource = rawTeam.Resources[resource.Id]; for (var i = 0; i < affectedCells.length; i++) { var cell = affectedCells[i]; resource.QuantityValues[cell.WeekIndex] = cell.NewValue; team.AllocatedByResources[cell.WeekIndex] += (cell.NewValue - cell.OldValue); updateTotals(resource, cell.WeekIndex, cell.NewValue); var weekEnding = $scope.data.Calendar.WeekHeaders[cell.WeekIndex].Milliseconds; rawResource.QuantityValues[weekEnding] = resource.QuantityValues[cell.WeekIndex] / uomMultiplier; rawResource.Changed = true; updateResourceRestQuantityValue(resource.Id, cell.WeekIndex, resource.RestQuantityValues[cell.WeekIndex] - (cell.NewValue - cell.OldValue)); //env-2208 start // if it is super EC we should recalculate team and EC quantity according to resource allocations as in the case of BU scenario //if (!row.AllowResourceAssignment) { // recalculateBUTeamValue(row, team, cell.WeekIndex, resource.ExpenditureCategoryId); // recalculateBUECValue(row, cell.WeekIndex); //} //env-2208 End } updateGrandTotals(resource); } $scope.gridChanged(); if (refreshCss) $scope.RefreshCSSClasses(); //required to be false by xeditable grid return false; }; $scope.checkTeamValue = function (expCat, team, colIndex, data, refreshCss, originalExpCatId) { var uomMultiplier = $scope.getUomMultiplier(expCat.ExpCatId); var rawTeam = scenarioDetailsService.getTeamInScenario(getScenarioId(), expCat.ExpCatId, 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); var deltaValue = val - team.QuantityValues[j]; expCat.RestQuantity[j] -= deltaValue; team.QuantityValues[j] = val; updateGrandTotals(team); var weekEnding = $scope.data.Calendar.WeekHeaders[j].Milliseconds; rawTeam.QuantityValues[weekEnding] = team.QuantityValues[j] / uomMultiplier; rawTeam.Changed = true; updateTeamRestQuantityValue(team.Id, originalExpCatId || expCat.ExpCatId, j, deltaValue); } } else { updateTotals(team, colIndex, newValue); var deltaValue = newValue - team.QuantityValues[colIndex]; expCat.RestQuantity[colIndex] -= deltaValue; team.QuantityValues[colIndex] = newValue; updateGrandTotals(team); var weekEnding = $scope.data.Calendar.WeekHeaders[colIndex].Milliseconds; rawTeam.QuantityValues[weekEnding] = team.QuantityValues[colIndex] / uomMultiplier; rawTeam.Changed = true; updateTeamRestQuantityValue(team.Id, originalExpCatId || expCat.ExpCatId, colIndex, deltaValue); } } $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) { var resourceExt = scenarioDetailsService.extendResourceModelWithAttributes(resource, team.Id); if (resourceExt.IsVisible) { resources.push({ id: resourceExt.Id, name: resourceExt.Name, minAvailability: resourceExt.MinAvailability, maxAvailability: resourceExt.MaxAvailability, avgAvailability: resourceExt.AvgAvailability }); } } }); 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(); bootbox.confirm({ message: "Are you sure you want fill this team quantities with 0s?", callback: function (result) { if (result) { $scope.$apply(function () { 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; var category = dataSources.getExpenditureById(expCatId); if (!category) return 1; return 1.0 / category.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.$broadcast('refreshUOMMode', newValue); } }); $scope.switchUOMMode = function () { $scope.setGridSource(); $scope.showGraph(); }; $scope.$watch('data.CalendarFilter.IsTableModeQuantity', function (newValue, oldValue) { if (oldValue != newValue) { $scope.switchTableMode(); $scope.$broadcast('refreshTableMode', newValue); } }); $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(); var eventData = { ShowActuals: $scope.data.CalendarFilter.ShowActuals, ViewModeName: $scope.data.CalendarFilter.ViewModeName }; $scope.$broadcast('refreshActualsMode', eventData); } }); $scope.switchActualsMode = function () { $scope.setGridHeaderState(); $scope.setGridSource(); $scope.RefreshCSSClasses(); }; $scope.initGridRows = function () { $scope.data.Calendar.Rows = []; var totalRow = { ExpCatId: C_EMPTY_GUID, ExpCatName: "Totals", UseType: 3, // calculated Visible: true }; $scope.data.Calendar.Rows.push(totalRow); var expendituresInScenario = scenarioDetailsService.getExpenditures(getScenarioId()); if (expendituresInScenario) { $.each(expendituresInScenario, function (expCatId, ecg) { var row = { Collapsed: ecg.Collapsed, CollapsedClass: ecg.CollapsedClass, ExpCatId: expCatId, ExpCatName: ecg.ExpenditureCategoryName, Teams: [], AvailableTeams: [], UseType: 1, Visible: true, AllowResourceAssignment: ecg.AllowResourceAssignment }; $.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) { if (!resource.Deleted) { var rowResource = { Id: resourceId, ExpenditureCategoryId: resource.ExpenditureCategoryId, 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); }); refreshAvailableTeamsList(); } }; $scope.isVisibleGridRow = function (expCat) { if (!expCat) return false; return dataSources.checkExpenditureCategory(expCat.ExpenditureCategoryId, $scope.data.CalendarFilter.CategoryType, $scope.data.CalendarFilter.GLAccount, $scope.data.CalendarFilter.CreditDepartment, $scope.data.CalendarFilter.SelectedExpCats); }; $scope.applyGridFilter = function () { if ($scope.data.Scenario.IsBottomUp) { var eventData = { CategoryType: $scope.data.CalendarFilter.CategoryType, GLAccount: $scope.data.CalendarFilter.GLAccount, CreditDepartment: $scope.data.CalendarFilter.CreditDepartment, SelectedExpCats: angular.copy($scope.data.CalendarFilter.SelectedExpCats) }; $scope.$broadcast('applyGridFilter', eventData); } else { $scope.setGridSource(); } }; $scope.setGridSource = function () { $scope.data.CalendarFilter.VisibleExpCats = []; $scope.data.CalendarFilter.SecondaryExpCats = []; var expendituresInScenario = scenarioDetailsService.getExpenditures(getScenarioId()); if (expendituresInScenario && $scope.data.Calendar.Rows) { var totalRow = $scope.data.Calendar.Rows[0]; totalRow.GrandTotalCost = 0; totalRow.GrandTotalQuantity = 0; totalRow.CostValues = []; totalRow.QuantityValues = []; totalRow.Visible = false; totalRow.IsEditable = true; $.each(expendituresInScenario, function (expCatId, ecg) { var row = null, rowIndex = -1; var ecUomMultiplier = $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.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); row.Editable = []; row.HasActuals = false; //env-843 start if (row.Visible) { $scope.data.CalendarFilter.VisibleExpCats.push({ Id: expCatId, Name: ecg.ExpenditureCategoryName, AllowResourceAssignment: ecg.AllowResourceAssignment }); if (i > 1) { $scope.data.CalendarFilter.SecondaryExpCats.push({ Id: expCatId, Name: ecg.ExpenditureCategoryName, AllowResourceAssignment: ecg.AllowResourceAssignment }); } } //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; $.each($scope.data.Calendar.WeekHeaders, function (index, header) { if (header.DataType == C_HEADER_DATA_TYPE_ORDINAL) { var isActualsCell = !header.Editable[$scope.data.CalendarFilter.ViewModeName]; var scenarioDetail = ecg.Details[header.Milliseconds]; if (isActualsCell) { row.CostValues.push(scenarioDetail.ActualsCost); row.QuantityValues.push(scenarioDetail.ActualsQuantity * ecUomMultiplier); row.ForecastCostTotalInActualsRange += scenarioDetail.ForecastCost; row.ActualsCostTotal += scenarioDetail.ActualsCost; row.HasActuals = true; } else { row.CostValues.push(scenarioDetail.ForecastCost); row.QuantityValues.push(scenarioDetail.ForecastQuantity * ecUomMultiplier); } row.Editable.push({ //env-2208 start //"F": header.Editable[C_CALENDAR_VIEW_MODE_FORECAST] && row.AllowResourceAssignment, //"A": header.Editable[C_CALENDAR_VIEW_MODE_ACTUALS] && row.AllowResourceAssignment "F": header.Editable[C_CALENDAR_VIEW_MODE_FORECAST], "A": header.Editable[C_CALENDAR_VIEW_MODE_ACTUALS] //env-2208 end }); row.RestQuantity.push(scenarioDetail.ForecastQuantity * ecUomMultiplier); 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 { var parentMonthHeaderIndex = header.MonthHeader; var parentMonthHeader = $scope.data.Calendar.MonthHeaders[parentMonthHeaderIndex]; var weekCount = parentMonthHeader.ColspanValues[$scope.data.CalendarFilter.ViewModeName]; row.CostValues.push(monthCost); row.QuantityValues.push(monthQuantity / (showAvg ? weekCount : 1)); row.RestQuantity.push(0); row.Editable.push({ //env-2208 start //"F": header.Editable[C_CALENDAR_VIEW_MODE_FORECAST] && row.AllowResourceAssignment, //"A": header.Editable[C_CALENDAR_VIEW_MODE_ACTUALS] && row.AllowResourceAssignment "F": header.Editable[C_CALENDAR_VIEW_MODE_FORECAST], "A": header.Editable[C_CALENDAR_VIEW_MODE_ACTUALS] //env-2208 end }); 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) * ecUomMultiplier; var restQty = (ecg.Teams[team.Id].RestQuantityValues[header.Milliseconds] || 0) * ecUomMultiplier; var capacityQty = (ecg.Teams[team.Id].CapacityQuantityValues[header.Milliseconds] || 0) * ecUomMultiplier; 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, Resources: [] }; } 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 = parentMonthHeader.ColspanValues[$scope.data.CalendarFilter.ViewModeName]; 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); } 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 uomMultiplier = $scope.getUomMultiplier(rawResource.ExpenditureCategoryId); 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 (!teamMonth[team.Id].Resources[resource.Id]) { teamMonth[team.Id].Resources[resource.Id] = { CapacityQuantity: 0, RestQuantity: 0, Quantity: 0, ReadOnly: false }; } resource.GrandTotalQuantity += qty; teamMonth[team.Id].Resources[resource.Id].CapacityQuantity += cqty; teamMonth[team.Id].Resources[resource.Id].RestQuantity += restQty; teamMonth[team.Id].Resources[resource.Id].Quantity += qty; teamMonth[team.Id].Resources[resource.Id].ReadOnly |= readOnly; allocatedByResources += qty; } else { var parentMonthHeaderIndex = header.MonthHeader; var parentMonthHeader = $scope.data.Calendar.MonthHeaders[parentMonthHeaderIndex]; var weekCount = parentMonthHeader.ColspanValues[$scope.data.CalendarFilter.ViewModeName]; var monthCapacityQuantity = teamMonth[team.Id].Resources[resource.Id].CapacityQuantity / (showAvg ? weekCount : 1); var monthRestQuantity = teamMonth[team.Id].Resources[resource.Id].RestQuantity / (showAvg ? weekCount : 1); var monthQuantity = teamMonth[team.Id].Resources[resource.Id].Quantity / (showAvg ? weekCount : 1); var readOnly = teamMonth[team.Id].Resources[resource.Id].ReadOnly; resource.CapacityQuantityValues.push(monthCapacityQuantity); resource.RestQuantityValues.push(monthRestQuantity); resource.QuantityValues.push(monthQuantity); resource.ReadOnly.push(readOnly); allocatedByResources += monthQuantity; delete teamMonth[team.Id].Resources[resource.Id]; } }); // clear teamMonth summary variable after it has been used to fill teams and resources if (header.DataType != C_HEADER_DATA_TYPE_ORDINAL) { delete teamMonth[team.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; }); }); } //env-2208 start row.IsEditable = row.UseType != 3 && !row.HasActuals; // && row.AllowResourceAssignment; //env-2208 end if (!row.IsEditable) { totalRow.IsEditable = false; } } totalRow.GrandTotalCost += row.GrandTotalCost; totalRow.GrandTotalQuantity += row.GrandTotalQuantity; $scope.data.Calendar.Rows[rowIndex] = row; }); $scope.data.Calendar.Rows[0] = totalRow; } if ($scope.data.Calendar.Rows) { $.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); }); } $timeout(function () { angular.element('[ng-model="team.ResourceToAssignId"]').select2({ allowClear: true, placeholder: 'Select a person', dropdownAutoWidth: true, dropdownCss: { 'font-size': '9pt' }, minimumResultsForSearch: 5, formatResult: formatPeopleResourceOption }); angular.element('[ng-model="row.TeamToAssignId"]').select2({ allowClear: true, placeholder: 'Select a Team', dropdownAutoWidth: true, dropdownCss: { 'font-size': '9pt' }, minimumResultsForSearch: 5 }); }); }; $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 sourceDateMs, targetDateMs; 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; sourceDateMs = $scope.data.Calendar.WeekHeaders[i].Milliseconds; } if ($scope.data.Calendar.WeekHeaders[i].Title === $scope.reallocator.TargetWeekEnding) { targetColIndex = i; targetDateMs = $scope.data.Calendar.WeekHeaders[i].Milliseconds; } } 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); } } } } // Audit for client-side actions if (!$scope.data.Audit) $scope.data.Audit = {}; if (!$scope.data.Audit.Reallocations) $scope.data.Audit.Reallocations = {}; var auditRecord = { SourceExpCat: $scope.reallocator.SourceExpCat, TargetExpCat: $scope.reallocator.TargetExpCat, SourceWeekEnding: sourceDateMs, TargetWeekEnding: targetDateMs, Percentage: $scope.reallocator.Percentage, ReallocateBy: $scope.reallocator.ReallocateBy } if ($scope.reallocator.CurveType == 'source') auditRecord.UseCurveFromExpenditureId = $scope.reallocator.SourceExpCat; if ($scope.reallocator.CurveType == 'target') auditRecord.UseCurveFromExpenditureId = $scope.reallocator.TargetExpCat; if ($scope.reallocator.CurveType == 'other') auditRecord.UseCurveFromExpenditureId = $scope.reallocator.OtherCurve; var currentTime = DateTimeConverter.getUtcNowMs(); $scope.data.Audit.Reallocations[currentTime] = auditRecord; $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, 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, 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, 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, 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); } } } // Audit for client-side actions if (!$scope.data.Audit) $scope.data.Audit = {}; if (!$scope.data.Audit.CurveChanges) $scope.data.Audit.CurveChanges = {}; var auditRecord = { FromExpenditureId: $scope.editTotal.OtherCurve, ToExpenditureId: $scope.editTotal.ExpCat } var currentTime = DateTimeConverter.getUtcNowMs(); $scope.data.Audit.CurveChanges[currentTime] = auditRecord; $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; } } } // Audit for client-side actions if (!$scope.data.Audit) $scope.data.Audit = {}; if (!$scope.data.Audit.Roundings) $scope.data.Audit.Roundings = {}; var auditRecord = { ExpCats: angular.copy($scope.formatCells.SelectedExpCats), StartDate: msStart, EndDate: msEnd, } for (var index = 0; index < $scope.formatCells.RoundOptions.length; index++) { if ($scope.formatCells.RoundOptions[index].Value == $scope.formatCells.DecimalPlaces) { auditRecord.DecimalPlaces = $scope.formatCells.RoundOptions[index].Name; break; } } var currentTime = DateTimeConverter.getUtcNowMs(); $scope.data.Audit.Roundings[currentTime] = auditRecord; $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 = scenarioDetailsService.getCategoryInScenario(getScenarioId(), row.ExpCatId); onCollapseClick(row, rawEC); }; $scope.onTeamCollapseClick = function (expCatId, team, $event) { $event.stopPropagation(); $event.preventDefault(); var rawTeam = scenarioDetailsService.getTeamInScenario(getScenarioId(), expCatId, 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.goToTeamBoard = 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.HasActuals || $scope.data.CalendarFilter.VisibleExpCats.length <= 1) return; bootbox.confirm({ message: 'Do you really want to delete ' + row.ExpCatName + '?', callback: function (result) { if (result) { $scope.$apply(function () { removeExpCatsInternal([row.ExpCatId]); }); } } }); }; function removeExpCatsInternal(expCats) { if (!expCats || expCats.length <= 0) return; $scope.data.AvailableExpenditures = $scope.data.AvailableExpenditures.filter(function (obj) { return expCats.indexOf(obj.Id) < 0; }); $scope.data.NeedToRecalculateScenarioDetails = true; $scope.gridChanged(); $scope.getCalendar(); for (var i = 0; i < expCats.lenght; i++) { var data = { DomainId: null, ParentId: expCats[i] }; $rootScope.$broadcast('removeNoteCB', data); } }; $scope.addExpCats = function (callback) { 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(callback); }; $scope.addTeams = function () { scenarioDetailsService.addTeamsInScenario(getScenarioId(), $scope.data.CalendarFilter.Teams2Add); $scope.data.CalendarFilter.Teams2Add = null; $('#selTeams2Add').select2('val', ''); $scope.data.NeedToRecalculateScenarioDetails = true; $scope.gridChanged(); $scope.getCalendar(); }; $scope.removeTeam = function (team) { if (null == team) return; bootbox.confirm({ message: 'Do you really want to delete ' + team.Name + '?', callback: function (result) { if (result) { $scope.$apply(function () { removeTeamInternal(team.Id); $scope.RestTeams = getRestTeams(); refreshAvailableTeamsList(); $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; } } } } scenarioDetailsService.deleteTeamsFromScenario(getScenarioId(), teamId); } } } function refreshAvailableTeamsList() { if ($scope.data && $scope.data.Calendar && $scope.data.Calendar.Rows) { for (var i = 0; i < $scope.data.Calendar.Rows.length; i++) { var category = $scope.data.Calendar.Rows[i]; if (category.AllowResourceAssignment === false) { category.AvailableTeams = getRestTeams(); } else { category.AvailableTeams = getRestTeams(category.ExpCatId); } } } }; function getRestTeams(expenditureCategoryId) { if (!$scope.data || !$scope.data.Teams) return null; var rest = $scope.data.Teams.filter(function (team) { var isAssignedToScenario = scenarioDetailsService.checkTeamAssignedToScenario(getScenarioId(), team.Id); var containsCategory = !expenditureCategoryId || (team.Expenditures && team.Expenditures.indexOf(expenditureCategoryId) >= 0); return !isAssignedToScenario && containsCategory; }); return rest; }; // 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 quantity = Math.round(resRow.QuantityValues[colIndex] * 100) / 100; var restCapacity = Math.round(resRow.RestQuantityValues[colIndex] * 100) / 100; if (quantity == 0) return; // highlight with red if resource is overallocated against self capacity if (restCapacity < 0) resRow.CSSClass[colIndex] = 'cellover'; } } 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) 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); if (allocatedByResources > quantity) { $scope.TeamsIsOverAllocated = true; teamRow.CSSClass[colIndex] = 'cellover'; } else if (allocatedByResources < quantity) { //$scope.TeamsIsUnderAllocated = true; teamRow.CSSClass[colIndex] = 'cellless'; } else if (allocatedByResources == quantity) 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) { $scope.ExpendituresIsUnderAllocated = true; 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) if ($scope.data.Calendar.Rows) 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.canSaveScenario = function () { if ($scope.data.WorkFlowActions.length > 1 && $scope.isScenarioValid()) return true; if( $scope.gridWasChanged() && $scope.isScenarioValid()) return true; return false; }; $scope.isScenarioValid = function () { var expendituresInScenario = scenarioDetailsService.getExpenditures(getScenarioId()); var isValid = $scope.isValidAllocation() && expendituresInScenario && Object.keys(expendituresInScenario).length > 0; return isValid; }; $scope.renderHtml = function (html) { return $sce.trustAsHtml(html); }; $scope.getScenarioDetailsData = function () { return { AvailableExpenditures: $scope.data.AvailableExpenditures, TeamsInScenario: scenarioDetailsService.recalculateTeamsAllocation(getScenarioId()) }; }; $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 ($event) { var scenario = { Id: getScenarioId(), ProjectId: $scope.data.Scenario.ParentId, StartDate: $scope.data.Scenario.StartDate, EndDate: $scope.data.Scenario.EndDate, ProjectedRevenue: $scope.data.Scenario.ProjectedRevenue, TDDirectCosts: $scope.data.Scenario.TDDirectCosts, UseLMMargin: $scope.data.Scenario.UseLMMargin, GrossMargin: $scope.data.Scenario.GrossMargin, LMMargin: $scope.data.Scenario.LMMargin, CostSavings: $scope.data.Scenario.CostSavings, Expenditures: scenarioDetailsService.getExpenditures(getScenarioId()) }; var args = { scenario: scenario, currentTarget: $event.currentTarget }; $rootScope.$broadcast('saveScenarioDetails', args); }; $scope.updateWorkFlowAction = function ($scope2) { var i = 0; var len = $scope.data.WorkFlowActions.length; for (; i < len; i++) { if ($scope.data.WorkFlowActions[i].command == $scope2.command) { $scope.data.WorkFlowActions[i].Selected = true; return; } } } $scope.updateWorkFlowState = function ($scope2) { var i = 0; var len = $scope.data.WorkFlowStates.length; for (; i < len; i++) { if ($scope.data.WorkFlowStates[i].state == $scope2.state) { $scope.data.WorkFlowStates[i].Selected = true; return; } } } $scope.cancelScenarioDetails = function ($event) { var args = { currentTarget: $event.currentTarget }; $rootScope.$broadcast('cancelScenarioDetails', args); }; $scope.scenarioEditModelChanged = function () { $scope.data.ScenarioGeneralInfoEditModel.IsChanged = true; }; $scope.recalculateScenarioDetails = function () { if ($scope.data.ScenarioGeneralInfoEditModel.IsChanged === true) { if (angular.isFunction(scenarioDetailsEditFormIsValid) && scenarioDetailsEditFormIsValid()) { var startDateMs = DateTimeConverter.stringToMs($scope.data.ScenarioGeneralInfoEditModel.StartDate); var endDateMs = DateTimeConverter.stringToMs($scope.data.ScenarioGeneralInfoEditModel.EndDate); var date4StartOfChangesMs = DateTimeConverter.stringToMs($scope.data.ScenarioGeneralInfoEditModel.DateForStartOfChanges); var costSavingBackup = costSavingService.createBackup(getScenarioId()); try { var range = costSavingService.changeScenarioRange( getScenarioId(), $scope.data.ScenarioGeneralInfoEditModel.StartDate, $scope.data.ScenarioGeneralInfoEditModel.EndDate); if (!!range) { var isChanged = costSavingService.setRange(getScenarioId(), range.startDate, range.endDate); if (isChanged) $scope.data.Scenario.CostSavings = costSavingService.getCostSavings4Save(getScenarioId()); } } catch (e) { if (!!costSavingBackup) { costSavingService.restoreBackup(getScenarioId(), costSavingBackup); } showErrorModal('Oops!', $scope.CommonErrorMessage); return; } $scope.data.Scenario.DistributionType = $scope.data.ScenarioGeneralInfoEditModel.DistributionType; $scope.data.Scenario.StartDate = startDateMs; $scope.data.Scenario.EndDate = endDateMs; $scope.data.Scenario.DateForStartOfChanges = date4StartOfChangesMs; // 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; //todo: maybe set adjustMarginRased and dateForStartOfChangesOld for Log in $scope.data.Scenario $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() { var expendituresInScenario = scenarioDetailsService.getExpenditures(getScenarioId()); if (!expendituresInScenario || !$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 expendituresInScenario) { var category = expendituresInScenario[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 expendituresInScenario) { var category = expendituresInScenario[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 = []; if (!$scope.data.Calendar || !$scope.data.Calendar.Rows || $scope.data.Calendar.Rows.length <= 0) return; 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, 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, 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); }; function refreshAssignResourceSelect(expCatId, teamId) { var control = angular.element('#assign-resource-select-' + expCatId + '-' + teamId); refreshSelect2(control); }; function formatPeopleResourceOption(result, container, query, escapeMarkup) { var $optionScope = angular.element(result.element).scope(); /* $optionScope.resource property is declared in the expression in the view: resource in team.AvailableResources track by $index */ if ($optionScope && $optionScope.resource) { return scenarioDetailsService.getAssignableResourceOptionHtml($optionScope.resource); } }; function formatTeam4SelectOption(result, container, query, escapeMarkup) { var $optionScope = angular.element(result.element).scope(); /* $optionScope.resource property is declared in the expression in the view: resource in team.AvailableResources track by $index */ if ($optionScope && $optionScope.Team) { return scenarioDetailsService.getAssignableTeamOptionHtml($optionScope.Team); } }; $scope.getScenarioWeekEndings = function () { if (!$scope.data.Calendar.WeekHeaders || $scope.data.Calendar.WeekHeaders.length <= 0) return []; var weekEndings = []; 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; weekEndings.push(week.Milliseconds); } return weekEndings; }; $scope.getTotalCostsByMonthes = function () { var totals = {}; if (!$scope.data.Calendar.WeekHeaders || $scope.data.Calendar.WeekHeaders.length <= 0) return totals; var expendituresInScenario = scenarioDetailsService.getExpenditures(getScenarioId()); if (!expendituresInScenario || Object.keys(expendituresInScenario).length <= 0) return totals; angular.forEach($scope.data.Calendar.WeekHeaders, function (header, headerIndex) { if (header.DataType == C_HEADER_DATA_TYPE_ORDINAL) { var isActualsCell = !header.Editable[$scope.data.CalendarFilter.ViewModeName]; var weekEndingMonth = new Date(header.Milliseconds); var monthStart = new Date(weekEndingMonth.getFullYear(), weekEndingMonth.getMonth(), 1).getTime(); if (!totals[monthStart]) { totals[monthStart] = 0; } angular.forEach(expendituresInScenario, function (category, categoryIndex) { var scenarioDetail = category.Details[header.Milliseconds] || {}; totals[monthStart] += ((isActualsCell ? scenarioDetail.ActualsCost : scenarioDetail.ForecastCost) || 0); }); } }); return totals; }; function getScenarioId() { return $scope.data.Scenario.Id; }; function recalculateBUTeamValue(row, team, $index, originalExpCatId) { if (!row || !team) return; var $data = 0; if (team.Resources && team.Resources.length > 0) { for (var i = 0; i < team.Resources.length; i++) { var resource = team.Resources[i]; if (resource.QuantityValues) { $data += resource.QuantityValues[$index] || 0; } } } $scope.checkTeamValue(row, team, $index, $data, false, originalExpCatId); }; function recalculateBUECValue(row, $index) { if (!row) return; var $data = 0; if (row.Teams && row.Teams.length > 0) { for (var i = 0; i < row.Teams.length; i++) { var team = row.Teams[i]; if (team.Resources && team.Resources.length > 0) { for (var j = 0; j < team.Resources.length; j++) { var resource = team.Resources[j]; if (resource.QuantityValues) { $data += resource.QuantityValues[$index] || 0; } } } } } $scope.checkValue($data, row.ExpCatId, $index); }; function updateResourceRestQuantityValue(resourceId, colIndex, restValue) { if (!resourceId || colIndex < 0) { return; } if (!$scope.data.Calendar.Rows || $scope.data.Calendar.Rows.length <= 1) { return; } var weekEnding = $scope.data.Calendar.WeekHeaders[colIndex].Milliseconds; for (var rowIndex = 1; rowIndex < $scope.data.Calendar.Rows.length; rowIndex++) { var row = $scope.data.Calendar.Rows[rowIndex]; if (!row.Teams || row.Teams.length <= 0) { continue; } for (var teamIndex = 0; teamIndex < row.Teams.length; teamIndex++) { var team = row.Teams[teamIndex]; var rawTeam = scenarioDetailsService.getTeamInScenario(getScenarioId(), row.ExpCatId, team.Id); // update rest value in the collection that contains already assigned resources if (team.Resources && team.Resources.length > 0) { for (var resourceIndex = 0; resourceIndex < team.Resources.length; resourceIndex++) { var resource = team.Resources[resourceIndex]; if (resource.Id === resourceId) { resource.RestQuantityValues[colIndex] = restValue || 0; var rawResource = rawTeam.Resources[resource.Id]; if (rawResource) { rawResource.RestQuantityValues[weekEnding] = resource.RestQuantityValues[colIndex] / $scope.getUomMultiplier(rawResource.ExpenditureCategoryId); } break; } } } // update rest value in the collection that contains resources available for assign if (rawTeam.AllResources) { var rawResource = rawTeam.AllResources[resourceId]; if (rawResource) { rawResource.RestQuantityValues[weekEnding] = (restValue || 0) / $scope.getUomMultiplier(rawResource.ExpenditureCategoryId); } } } } }; function updateTeamRestQuantityValue(teamId, expenditureCategoryId, colIndex, deltaValue) { if (!teamId || !expenditureCategoryId || colIndex < 0) { return; } if (!$scope.data.Calendar.Rows || $scope.data.Calendar.Rows.length <= 1) { return; } var weekEnding = $scope.data.Calendar.WeekHeaders[colIndex].Milliseconds; for (var rowIndex = 1; rowIndex < $scope.data.Calendar.Rows.length; rowIndex++) { var row = $scope.data.Calendar.Rows[rowIndex]; if (row.ExpCatId !== expenditureCategoryId) { continue; } if (!row.Teams || row.Teams.length <= 0) { continue; } for (var teamIndex = 0; teamIndex < row.Teams.length; teamIndex++) { var team = row.Teams[teamIndex]; if (team.Id === teamId) { var rawTeam = scenarioDetailsService.getTeamInScenario(getScenarioId(), row.ExpCatId, team.Id); team.RestQuantityValues[colIndex] = ((team.RestQuantityValues[colIndex] || 0) - (deltaValue || 0)); rawTeam.RestQuantityValues[weekEnding] = team.RestQuantityValues[colIndex] / $scope.getUomMultiplier(row.ExpCatId); break; } } } }; function NoteIndex(n) { for (var i = 0; i < $scope.NotesToAdd.length; i++) { if ($scope.NotesToAdd[i].Id == n.Id) { return i; } } return -1 }; }]);