app.controller('teamDetailsCalendarController', ['$scope', '$http', '$location', '$timeout', function ($scope, $http, $location, $timeout) { $scope.CollapsedIcon = 'fa-plus-square'; $scope.NonCollapsedIcon = 'fa-minus-square'; $scope.availableExpenditures = []; $scope.rates = []; $scope.id; $scope.teamId; $scope.dataSection = ""; $scope.calendarFilters = { IsTableModeQuantity: true, IsViewModeMonth: true, ScenarioId: $scope.id, IsUOMHours: false, ShowActuals: false, PreferredTotalsDisplaying: false }; $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.pushpuller = { weeks: 1, push: true, resource: null, startdate: null }; // SA. ENV-815 $scope.init = function (selectedTeamId, dataSection) { $scope.teamId = selectedTeamId; $scope.dataSection = dataSection; } $scope.quickPushPull = function (push, weeks) { for (var i = 1; i < $scope.data.ScenarioCalendar.length; i++) { if ($scope.data.ScenarioCalendar[i].Checked) $scope.pushPullExpCat($scope.data.ScenarioCalendar[i], i, weeks, push); } }; $scope.editTotal = { ExpCat: null, SeatsCost: $scope.typeOptions[0].value, CurrentTotal: null, NewTotal: null, Curve: 'source', OtherCurve: null, }; $scope.formatCells = { ExpCats: [], SelectedExpCats: null, StartDate: null, EndDate: null, DecimalPlaces: 2 }; $scope.data = null; $scope.data2Update = { ScenarioId: $scope.id, ChangedExpCats: [] }; $scope.teamChanged = function (savePreferences) { // SA. ENV-815 if (savePreferences && $scope.dataSection && ($scope.dataSection.length > 0)) { var prefs = collectPreferences($scope.dataSection); saveUserPagePreferences(prefs, $scope.dataSection); } $http.get('/Scenarios/GetTeamCapacityScenarioId?teamId=' + $scope.teamId).success(function (scenarioId) { $scope.id = scenarioId.replace(/"/g, ''); $scope.calendarFilters.ScenarioId = $scope.id; $http.get('/Scenarios/GetScenarioAvailableExpCategories?id=' + $scope.id).success(function (expData) { $scope.availableExpenditures = expData; if ($scope.availableExpenditures != null && $scope.availableExpenditures.length > 0) { $scope.reallocator.SourceExpCat = $scope.availableExpenditures[0].Id; $scope.reallocator.TargetExpCat = $scope.availableExpenditures.length > 1 ? $scope.availableExpenditures[1].Id : null; $scope.reallocator.OtherCurve = $scope.availableExpenditures[0].Id; $scope.editTotal = { ExpCat: $scope.availableExpenditures[0].Id, OtherCurve: $scope.availableExpenditures[0].Id, SeatsCost: $scope.typeOptions[0].value, Curve: 'source' }; } }); $scope.getRates(); $scope.getCalendar(); }); }; $scope.getRates = function () { $http.post('/Scenarios/GetRates', { scenarioId: $scope.id, uomMode: $scope.calendarFilters.IsUOMHours }).success(function (data) { $scope.rates = data; }).error(function (data, status, headers, config) { console.log("an error occurred while loading rates"); }); }; $scope.switchUOMMode = function (value) { var newValue = value != null ? value : !$scope.calendarFilters.IsUOMHours; $scope.calendarFilters.IsUOMHours = newValue; $scope.getCalendar(); $scope.getRates(); }; function getExpCatById(ExpCatId) { for (var i = 0; i < $scope.data.ScenarioCalendar.length; i++) { if ($scope.data.ScenarioCalendar[i].ExpCatId == ExpCatId) { return $scope.data.ScenarioCalendar[i]; } } return null; } function getResourceById(resId) { for (var i = 0; i < $scope.data.AllResources.length; i++) { if ($scope.data.AllResources[i].Id == resId) { return $scope.data.AllResources[i]; } } return null; } function clearResource(expCat, resource) { if (resource != null) { resource.GrandTotalQuantity = 0; var scenarioCalendarRow = null; for (var i = 0; i < $scope.data.ScenarioCalendar.length; i++) { if ($scope.data.ScenarioCalendar[i].ExpCatId === expCat.ExpCatId) { scenarioCalendarRow = $scope.data.ScenarioCalendar[i]; break; } } for (var i = 0; i < $scope.data.Headers.length; i++) { expCat.RestQuantity[i] += resource.QuantityValues[i]; resource.QuantityValues[i] = 0; applyCellChange(expCat.ExpCatId, $scope.data.Headers[i].Milliseconds, scenarioCalendarRow.ScenarioDetailIds[i], resource.QuantityValues[i], 0, resource.Id, false, false); } } } function updateTotals(resource, colIndex, newQuantity, oldQuantity, newCost, oldCost) { for (var i = colIndex + 1; i < $scope.data.Headers.length; i++) { if (!$scope.data.Headers[i].IsMonth) continue; //update month cell resource.QuantityValues[i] += (newQuantity - oldQuantity); resource.GrandTotalQuantity[i] += (newQuantity - oldQuantity); break; } } function allocateResource(resource, expCat, create, allocateRest, reset) { if (resource != null) { resource.GrandTotalQuantity = 0; resource.GrandTotalCost = 0; var scenarioCalendarRow = null; var i; for (i = 0; i < $scope.data.ScenarioCalendar.length; i++) { if ($scope.data.ScenarioCalendar[i].ExpCatId === expCat.ExpCatId) { scenarioCalendarRow = $scope.data.ScenarioCalendar[i]; break; } } for (i = 0; i < $scope.data.Headers.length; i++) { var quantity = create || reset ? 0 : allocateRest ? resource.QuantityValues[i] + expCat.RestQuantity[i] : expCat.QuantityValues[i]; if (create) resource.QuantityValues[i] = 0; expCat.RestQuantity[i] -= quantity - resource.QuantityValues[i]; updateTotals(resource, i, quantity, resource.QuantityValues[i], 0, 0); resource.QuantityValues[i] = quantity; if (!$scope.data.Headers[i].IsMonth) { resource.GrandTotalQuantity += quantity; applyCellChange(expCat.ExpCatId, $scope.data.Headers[i].Milliseconds, scenarioCalendarRow.ScenarioDetailIds[i], resource.QuantityValues[i], resource.CostValues[i], resource.Id, create, false); } } } } $scope.getCalendar = function () { if ($scope.id === undefined || $scope.id == "" || $scope.id == "0") return; blockUI(); $scope.data = null; var postData = JSON.parse(JSON.stringify($scope.calendarFilters)); $http.post('/Scenarios/LoadJsonScenarioCalendar', postData). success(function (data, status, headers, config) { if (data.Headers.length < 1) { unblockUI(); return; } $scope.data = data; $scope.calendarFilters.IsUOMHours = data.IsUOMHours; $scope.calendarFilters.PreferredTotalsDisplaying = data.PreferredTotalsDisplaying; $scope.switchViewMode($scope.calendarFilters.IsViewModeMonth); $scope.data2Update = { ScenarioId: $scope.id, ChangedExpCats: [] }; $scope.reallocator.SourceWeekEnding = $scope.data.Headers[0].Title; $scope.reallocator.TargetWeekEnding = $scope.data.Headers[$scope.data.Headers.length - 2].Title; var dec = Math.pow(10, parseInt($scope.formatCells.DecimalPlaces)); if ($scope.data.ScenarioCalendar.length > 0) { $scope.editTotal.ExpCat = $scope.data.ScenarioCalendar[1].ExpCatId; $scope.editTotal.SeatsCost = 'Quantity'; $scope.editTotal.CurrentTotal = Math.round($scope.data.ScenarioCalendar[1].GrandTotalQuantity * dec) / dec; $scope.editTotal.NewTotal = null; $scope.editTotal.Curve = 'source'; $scope.editTotal.OtherCurve = $scope.data.ScenarioCalendar[1].ExpCatId; } var dt = new Date($scope.data.Headers[0].Milliseconds); $scope.formatCells.StartDate = (dt.getMonth() + 1) + "/" + dt.getDate() + "/" + dt.getFullYear(); if ($scope.data.Headers.length > 3) dt = new Date($scope.data.Headers[$scope.data.Headers.length - 2].Milliseconds); else dt = new Date($scope.data.Headers[$scope.data.Headers.length - 2].Milliseconds); $scope.formatCells.EndDate = (dt.getMonth() + 1) + "/" + dt.getDate() + "/" + dt.getFullYear(); unblockUI(); }). 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(); }); }; $scope.onPercentageChange = function (val) { $scope.reallocator.Percentage = val; }; $scope.checkEditable = function (useType) { if (3 === useType) { return false; } return true; }; $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"; } for (var i = 0; i < $scope.data.ScenarioCalendar.length; i++) { if ($scope.data.ScenarioCalendar[i].ExpCatId === rowId) { if ($scope.calendarFilters.IsTableModeQuantity) { updateQuantityOrCostCell(rowId, i, colIndex, newValue, null, true); } else { updateQuantityOrCostCell(rowId, i, colIndex, null, newValue, false); } break; } } //required to be false by xeditable grid return false; }; $scope.watchKeyInput = function (t) { $timeout(function () { if (t.$editable.inputEl.select) t.$editable.inputEl.select(); else if (t.$editable.inputEl.setSelectionRange) t.$editable.inputEl.setSelectionRange(0, t.$editable.inputEl.val().length); }, 3); t.$editable.inputEl.on('keydown', function (e) { if (e.which == 9) { //when tab key is pressed e.preventDefault(); var tab2Cell; if (e.shiftKey) {// when shift + tab use with 'onblur' set to 'submit' for automatic submission find the parent of the editable before this one in the markup grab the editable and display it tab2Cell = $(this).parentsUntil('table#table').prevAll(":has(.editable:visible):first").find(".editable:visible:last"); t.$form.$submit(); tab2Cell.click(); } else {// when just tab use with 'onblur' set to 'submit' for automatic submission find the parent of the editable after this one in the markup grab the editable and display it tab2Cell = $(this).parentsUntil('table#table').nextAll(":has(.editable:visible):first").find(".editable:visible:first"); t.$form.$submit(); tab2Cell.click(); } } }); }; $scope.onTxtBlur = function (txt) { txt.$form.$submit(); }; 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 */ var oldQuantity = $scope.data.ScenarioCalendar[rowIndex].QuantityValues[colIndex]; var oldCost = $scope.data.ScenarioCalendar[rowIndex].CostValues[colIndex]; if (true === isUpdateQuantity) { // update quantity, recalculate cost based on exp cat rate if (oldQuantity === newQuantity || newQuantity < 0) return; newCost = Math.round(newQuantity * $scope.getRate(expCatId, $scope.data.Headers[colIndex].Milliseconds) * 10000) / 10000; } else if (false === isUpdateQuantity) { // update cost, recalculate quantity based on exp cat rate if (oldCost === newCost || newCost < 0) return; newQuantity = Math.round(newCost / $scope.getRate(expCatId, $scope.data.Headers[colIndex].Milliseconds) * 10000) / 10000; } // update modified cell $scope.data.ScenarioCalendar[rowIndex].RestQuantity[colIndex] += newQuantity - $scope.data.ScenarioCalendar[rowIndex].QuantityValues[colIndex]; $scope.data.ScenarioCalendar[rowIndex].CostValues[colIndex] = newCost; $scope.data.ScenarioCalendar[rowIndex].QuantityValues[colIndex] = newQuantity; // update row grand totals $scope.data.ScenarioCalendar[rowIndex].GrandTotalCost += (newCost - oldCost); $scope.data.ScenarioCalendar[rowIndex].GrandTotalQuantity += (newQuantity - oldQuantity); // update column totals $scope.data.ScenarioCalendar[0].CostValues[colIndex] += (newCost - oldCost); $scope.data.ScenarioCalendar[0].QuantityValues[colIndex] += (newQuantity - oldQuantity); // update table totals $scope.data.ScenarioCalendar[0].GrandTotalCost += (newCost - oldCost); $scope.data.ScenarioCalendar[0].GrandTotalQuantity += (newQuantity - oldQuantity); // update month cell and month total cell for (var i = colIndex + 1; i < $scope.data.Headers.length; i++) { if (!$scope.data.Headers[i].IsMonth) continue; //update month cell $scope.data.ScenarioCalendar[rowIndex].QuantityValues[i] += (newQuantity - oldQuantity); $scope.data.ScenarioCalendar[rowIndex].CostValues[i] += (newCost - oldCost); // update month column totals $scope.data.ScenarioCalendar[0].CostValues[i] += (newCost - oldCost); $scope.data.ScenarioCalendar[0].QuantityValues[i] += (newQuantity - oldQuantity); break; } applyCellChange(expCatId, $scope.data.Headers[colIndex].Milliseconds, $scope.data.ScenarioCalendar[rowIndex].ScenarioDetailIds[colIndex], $scope.data.ScenarioCalendar[rowIndex].QuantityValues[colIndex], $scope.data.ScenarioCalendar[rowIndex].CostValues[colIndex], null, false, false); } function applyCellChange(rowId, cellMilliseconds, cellId, cellQuantity, cellCost, resId, isAdded, isRemoved) { var catIndex = -1; for (var ix = 0; ix < $scope.data2Update.ChangedExpCats.length; ix++) { if ($scope.data2Update.ChangedExpCats[ix].Id == rowId) { catIndex = ix; break; } } if (catIndex == -1) { $scope.data2Update.ChangedExpCats[$scope.data2Update.ChangedExpCats.length] = { Id: rowId, Values: [], Resources: [] }; catIndex = $scope.data2Update.ChangedExpCats.length - 1; } if (resId == null) { var dateIndex = -1; for (var dx = 0; dx < $scope.data2Update.ChangedExpCats[catIndex].Values.length; dx++) { if ($scope.data2Update.ChangedExpCats[catIndex].Values[dx].Id == cellId) { dateIndex = dx; break; } } if (dateIndex == -1) { $scope.data2Update.ChangedExpCats[catIndex].Values[$scope.data2Update.ChangedExpCats[catIndex].Values.length] = { Id: cellId, Milliseconds: cellMilliseconds, Values: [] }; dateIndex = $scope.data2Update.ChangedExpCats[catIndex].Values.length - 1; } $scope.data2Update.ChangedExpCats[catIndex].Values[dateIndex].Cost = cellCost; $scope.data2Update.ChangedExpCats[catIndex].Values[dateIndex].Quantity = cellQuantity; } else { var dateIndex = -1; for (var dx = 0; dx < $scope.data2Update.ChangedExpCats[catIndex].Resources.length; dx++) { if ($scope.data2Update.ChangedExpCats[catIndex].Resources[dx].Id == resId) { dateIndex = dx; break; } } if (dateIndex == -1) { $scope.data2Update.ChangedExpCats[catIndex].Resources[$scope.data2Update.ChangedExpCats[catIndex].Resources.length] = { Id: resId, Values: [], IsAdded: isAdded, IsRemoved: isRemoved }; dateIndex = $scope.data2Update.ChangedExpCats[catIndex].Resources.length - 1; if (isAdded || isRemoved) return; } else { if (isRemoved) { if ($scope.data2Update.ChangedExpCats[catIndex].Resources[dateIndex].IsAdded) { $scope.data2Update.ChangedExpCats[catIndex].Resources.splice(dateIndex, 1); } return; } } var dateIndex1 = -1; for (var dx = 0; dx < $scope.data2Update.ChangedExpCats[catIndex].Resources[dateIndex].Values.length; dx++) { if ($scope.data2Update.ChangedExpCats[catIndex].Resources[dateIndex].Values[dx].Id == cellId) { dateIndex1 = dx; break; } } if (dateIndex1 == -1) { $scope.data2Update.ChangedExpCats[catIndex].Resources[dateIndex].Values[$scope.data2Update.ChangedExpCats[catIndex].Resources[dateIndex].Values.length] = { Id: cellId, Milliseconds: cellMilliseconds, Values: [] }; dateIndex1 = $scope.data2Update.ChangedExpCats[catIndex].Resources[dateIndex].Values.length - 1; } //$scope.data2Update.ChangedExpCats[catIndex].Resources[dateIndex].Values[dateIndex1].Cost = cellCost; $scope.data2Update.ChangedExpCats[catIndex].Resources[dateIndex].Values[dateIndex1].Quantity = cellQuantity; } } $scope.saveChanges = function () { blockUI(); var postData = JSON.parse(JSON.stringify($scope.data2Update)); postData.ScenarioFilters = $scope.calendarFilters; $http.post('/Scenarios/SaveChanges', postData). success(function (data, status, headers, config) { //$scope.data = data; $scope.data2Update = { ScenarioId: $scope.id, ChangedExpCats: [] }; if (typeof InitGraph === 'function') InitGraph($scope.teamId); if (typeof LoadGraphData === 'function') LoadGraphData(); unblockUI(); //var tab = $("#uidemo-tabs-default-demo li.active a").attr("href"); //var retUrl = window.location.href; //if (retUrl.indexOf("&tab=") > 0) { // retUrl = retUrl.substr(0, retUrl.indexOf("&tab=")); //} //window.location.href = retUrl + "&tab=" + tab.replace("#", ""); if (typeof onDataSaved === 'function') onDataSaved(); }). error(function (data, status, headers, config) { console.log("an error occurred while saving calendar changes"); unblockUI(); }); }; $scope.getRate = function (expCatId, date) { if ($scope.rates && $scope.rates.length > 0) { for (var i = 0; i < $scope.rates.length; i++) { if ($scope.rates[i].expCatId === expCatId) { if ($scope.rates[i].rateValues && $scope.rates[i].rateValues.length > 0) { for (var rIndex = 0; rIndex < $scope.rates[i].rateValues.length; rIndex++) { if (date <= $scope.rates[i].rateValues[rIndex].weekEndDate) return $scope.rates[i].rateValues[rIndex].rateValue; } return $scope.rates[i].rateValues[$scope.rates[i].rateValues.length - 1].rateValue; } break; } } } return 0; }; $scope.graphData = []; $scope.maxGraphValue = 0; $scope.showGraph = function () { // prepare data $scope.maxGraphValue = 0; $scope.graphData = []; var startDate = new Date($scope.data.StartDate.replace('/Date(', '').replace(')/', '') * 1); var endDate = new Date($scope.data.EndDate.replace('/Date(', '').replace(')/', '') * 1); var days = (endDate - startDate) / (1000 * 60 * 60 * 24); var i; for (i = 1; i < $scope.data.ScenarioCalendar.length; i++) { // skip this row if it is not checked if (!$scope.data.ScenarioCalendar[i].Checked) continue; var expCatData = new Array(); for (var colIndex = 0; colIndex < $scope.data.Headers.length; colIndex++) { if ($scope.data.Headers[colIndex].IsMonth) continue; expCatData[expCatData.length] = [$scope.data.Headers[colIndex].Milliseconds, $scope.data.ScenarioCalendar[i].QuantityValues[colIndex]]; if (true !== $scope.calendarFilters.IsTableModeQuantity) { expCatData[expCatData.length - 1][1] = $scope.data.ScenarioCalendar[i].CostValues[colIndex]; } if (expCatData[expCatData.length - 1][1] > $scope.maxGraphValue) $scope.maxGraphValue = expCatData[expCatData.length - 1][1]; expCatData[expCatData.length - 1][1] = expCatData[expCatData.length - 1][1].toFixed(2); } $scope.graphData[$scope.graphData.length] = { label: $scope.data.ScenarioCalendar[i].ExpCatName, data: expCatData, lines: { show: true, fill: true, steps: false }, filledPoints: true, stack: true }; } // show graph $timeout(function () { $scope.initGraph() }); }; $scope.initGraph = function () { // Init Chart $('#DetailsGraphContainer').html('
'); $("#modalGraph").modal("show"); // SA. ENV-574 var C_MAX_GRAPH_TICK_LABELS = 8; var maxSeriePoints = 0; if ($scope.graphData && $scope.graphData.length > 0) { $.each($scope.graphData, function (index, data) { if (data.data && (data.data.length > 0)) { maxSeriePoints = Math.max(maxSeriePoints, data.data.length); } }); } var tickStep = Math.round((maxSeriePoints / 4) / C_MAX_GRAPH_TICK_LABELS); tickStep = (tickStep > 0) ? tickStep : 1; $('#divDetailsGraph').pixelPlot($scope.graphData, { series: { points: { show: false }, lines: { show: true, } }, xaxis: { mode: "time", tickSize: [tickStep, "month"] } }, { height: 405, width: 405, tooltipText: "y + ' units at ' + (new Date(x)).toString()" }); }; $scope.anyChecked = function () { if ($scope.data == null || $scope.data.ScenarioCalendar == null) return false; for (var i = 1; i < $scope.data.ScenarioCalendar.length; i++) { if ($scope.data.ScenarioCalendar[i].Checked) return true; } return false; }; $scope.checkedExpenditures = []; $scope.collectchecked = function (obj) { $scope.checkedExpenditures = []; if ($scope.data == null || $scope.data.ScenarioCalendar == null) return; for (var i = 1; i < $scope.data.ScenarioCalendar.length; i++) { if ($scope.data.ScenarioCalendar[i].Checked || (obj != null && !obj.Checked && obj.ExpCatId == $scope.data.ScenarioCalendar[i].ExpCatId)) $scope.checkedExpenditures.push($scope.data.ScenarioCalendar[i]); } }; $scope.resizeFreezAng = function () { setTimeout(function () { resizeFreez(); }, 0); }; $scope.takeAll = function (resId, expCatId) { if (confirm("Are you sure you want to re-assign category quantity and assign everything to this resource?")) { var expCat = getExpCatById(expCatId); var resource = null; for (var i = 0; i < expCat.Resources.length; i++) { if (expCat.Resources[i].Id == resId) { resource = expCat.Resources[i]; } else { clearResource(expCat, expCat.Resources[i]); } } allocateResource(resource, expCat, false, false, false); } }; $scope.takeRemaining = function (resId, expCatId) { var expCat = getExpCatById(expCatId); if (expCat != null) { for (var i = 0; i < expCat.Resources.length; i++) { if (expCat.Resources[i].Id == resId) { allocateResource(expCat.Resources[i], expCat, false, true, false); return; } } } }; $scope.zeroResource = function (resId, expCatId) { if (confirm("Are you sure you want fill this resource quantities with 0s?")) { var expCat = getExpCatById(expCatId); for (var i = 0; i < expCat.Resources.length; i++) { if (expCat.Resources[i].Id == resId) { allocateResource(expCat.Resources[i], expCat, false, false, true); return; } } } }; $scope.assignResource = function (resId, expCatId, $event) { if (resId == null || expCatId == null) return; var resource = getResourceById(resId); if (resource != null) { var expCat = getExpCatById(expCatId); if (expCat != null) { for (var j = 0; j < expCat.Resources.length; j++) { if (expCat.Resources[j].Id == resource.Id) { alert("The " + resource.Name + " has been assigned to the " + expCat.Name); break; } } resource = { 'Id': resource.Id, 'Name': resource.Name, 'QuantityValues': new Array($scope.data.Headers.length), 'CostValues': new Array($scope.data.Headers.length) }; } allocateResource(resource, expCat, true, false, false); expCat.Resources.push(resource); var tr = $($event.target).parentsUntil('table').last().parent().find('thead tr:first'); console.log("tr: " + $(tr).html()); var thName = tr.find('th:eq(1)'); console.log("thName: " + $(thName).html()); resource.width = thName.width() + 'px'; var thTot = tr.find('th:eq(2)'); console.log("thTot: " + $(thTot).html()); resource.left = thTot.css('left'); $scope.resizeFreezAng(); } }; $scope.removeResource = function (resId, expCatId) { if (confirm("Are you sure you want to remove this resource?")) { var expCat = getExpCatById(expCatId); for (var i = 0; i < expCat.Resources.length; i++) { if (expCat.Resources[i].Id == resId) { expCat.Resources.splice(i, 1); applyCellChange(expCatId, 0, 0, 0, 0, resId, false, true); $scope.resizeFreezAng(); return; } } } }; $scope.checkResourceValue = function (data, expCatId, resId, colIndex) { var newValue = parseFloat(data); if (newValue < 0) { return "Value should not be less than zero"; } var expCat = getExpCatById(expCatId); var resource = null; for (var i = 0; i < expCat.Resources.length; i++) { if (expCat.Resources[i].Id == resId) { resource = expCat.Resources[i]; break; } } var scenarioCalendarRow = null; for (var i = 0; i < $scope.data.ScenarioCalendar.length; i++) { if ($scope.data.ScenarioCalendar[i].ExpCatId === expCatId) { scenarioCalendarRow = $scope.data.ScenarioCalendar[i]; break; } } if (expCat != null && resource != null) { if ($scope.calendarFilters.IsTableModeQuantity) { resource.GrandTotalQuantity += newValue - resource.QuantityValues[colIndex]; updateTotals(resource, colIndex, newValue, resource.QuantityValues[colIndex], 0, 0); expCat.RestQuantity[colIndex] -= newValue - resource.QuantityValues[colIndex]; resource.QuantityValues[colIndex] = newValue; } applyCellChange(expCatId, $scope.data.Headers[colIndex].Milliseconds, scenarioCalendarRow.ScenarioDetailIds[colIndex], resource.QuantityValues[colIndex], 0, resId, false, false); } //required to be false by xeditable grid return false; }; $scope.getExpCatResources = function (expCat) { var resources = []; for (var i = 0; i < $scope.data.AllResources.length; i++) { if ($scope.data.AllResources[i].ExpedentureCategoryId == expCat.ExpCatId && $scope.data.AllResources[i] && $scope.data.AllResources[i].IsActiveEmployee) { var isFound = false; for (var j = 0; j < expCat.Resources.length; j++) { if (expCat.Resources[j].Id == $scope.data.AllResources[i].Id) { isFound = true; break; } } if (!isFound) resources.push($scope.data.AllResources[i]); } } return resources; }; $scope.checkAll = function () { $scope.checkedExpenditures = []; var checked = !$scope.data.ScenarioCalendar[0].Checked; if ($scope.data == null || $scope.data.ScenarioCalendar == null) return; for (var i = 1; i < $scope.data.ScenarioCalendar.length; i++) { $scope.data.ScenarioCalendar[i].Checked = checked; if (checked) $scope.checkedExpenditures.push($scope.data.ScenarioCalendar[i]); } }; $scope.switchViewMode = function (value) { var newValue = value != null ? value : !$scope.calendarFilters.IsViewModeMonth; for (var i = 0; i < $scope.data.Headers.length; i++) { if ($scope.data.Headers[i].IsMonth) { $scope.data.Headers[i].Collapsed = newValue; $scope.data.Headers[i].Show = newValue; $scope.data.Headers[i].CollapsedClass = newValue ? $scope.CollapsedIcon : $scope.NonCollapsedIcon; } else { $scope.data.Headers[i].Collapsed = newValue; $scope.data.Headers[i].Show = !newValue; } } $scope.resizeFreezAng(); }; $scope.anyUpdated = function () { return ($scope.data2Update && $scope.data2Update.ChangedExpCats && $scope.data2Update.ChangedExpCats.length > 0); }; $scope.reallocateResource = function () { var sourceQuantity = 0; var sourceCost = 0; if ($scope.reallocator.Percentage == 0) { alert('Percentage is 0, nothing to reallocate.'); return; } var sourceRowIndex, targetRowIndex, curveRowIndex, sourceColIndex, targetColIndex; var i; for (i = 0; i < $scope.data.ScenarioCalendar.length; i++) { if ($scope.data.ScenarioCalendar[i].ExpCatId === $scope.reallocator.SourceExpCat) sourceRowIndex = i; if ($scope.data.ScenarioCalendar[i].ExpCatId === $scope.reallocator.TargetExpCat) targetRowIndex = i; if ($scope.reallocator.CurveType == 'other' && $scope.data.ScenarioCalendar[i].ExpCatId === $scope.reallocator.OtherCurve) curveRowIndex = i; } if (sourceRowIndex == targetRowIndex) { alert('Cant reallocate within same expenditure category.'); return; } for (i = 0; i < $scope.data.Headers.length; i++) { if ($scope.data.Headers[i].IsMonth) continue; if ($scope.data.Headers[i].Title === $scope.reallocator.SourceWeekEnding) sourceColIndex = i; if ($scope.data.Headers[i].Title === $scope.reallocator.TargetWeekEnding) targetColIndex = i; } if (sourceColIndex == null || targetColIndex == null) { alert('Please check selected dates to match valid week endings.'); return; } else if (targetColIndex < sourceColIndex) { alert('Please select To week ending date later than From week ending date.'); return; } var sourceSumCost = 0, sourceSumQuantity = 0, targetSumCost = 0, targetSumQuantity = 0, otherSumCost = 0, otherSumQuantity = 0; var percentageMult = $scope.reallocator.Percentage / 100; for (i = sourceColIndex; i <= targetColIndex ; i++) { if ($scope.data.Headers[i].IsMonth) continue; sourceSumQuantity += $scope.data.ScenarioCalendar[sourceRowIndex].QuantityValues[i]; sourceSumCost += $scope.data.ScenarioCalendar[sourceRowIndex].CostValues[i]; targetSumQuantity += $scope.data.ScenarioCalendar[targetRowIndex].QuantityValues[i]; targetSumCost += $scope.data.ScenarioCalendar[targetRowIndex].CostValues[i]; if ($scope.reallocator.CurveType == 'other') { otherSumQuantity += $scope.data.ScenarioCalendar[curveRowIndex].QuantityValues[i]; otherSumCost += $scope.data.ScenarioCalendar[curveRowIndex].CostValues[i]; } } if ((sourceSumQuantity == 0 || sourceSumCost == 0)) { alert('Source is zero, nothing to reallocate.'); return; } if ((targetSumQuantity == 0 || targetSumCost == 0) && $scope.reallocator.CurveType == 'target') { alert('Target curve is zero. Please select a different curve.'); return; } if ((otherSumQuantity == 0 || otherSumCost == 0) && $scope.reallocator.CurveType == 'other') { 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.Headers[i].IsMonth) continue; if ($scope.reallocator.CurveType == 'source') { sourceQuantity = $scope.data.ScenarioCalendar[sourceRowIndex].QuantityValues[i]; sourceCost = $scope.data.ScenarioCalendar[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.ScenarioCalendar[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.ScenarioCalendar[targetRowIndex].CostValues[i] + sourceCost * percentageMult, false); } } else if ($scope.reallocator.CurveType == 'target') { sourceQuantity = $scope.data.ScenarioCalendar[sourceRowIndex].QuantityValues[i]; sourceCost = $scope.data.ScenarioCalendar[sourceRowIndex].CostValues[i]; quantity2Move = (percentageMult * sourceSumQuantity) * ($scope.data.ScenarioCalendar[targetRowIndex].QuantityValues[i] / targetSumQuantity); cost2Move = (percentageMult * sourceSumCost) * ($scope.data.ScenarioCalendar[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.ScenarioCalendar[sourceRowIndex].QuantityValues[i]) quantity2Move = $scope.data.ScenarioCalendar[sourceRowIndex].QuantityValues[i]; if (quantity2Move > 0) { updateQuantityOrCostCell($scope.reallocator.SourceExpCat, sourceRowIndex, i, sourceQuantity - quantity2Move, null, true); updateQuantityOrCostCell($scope.reallocator.TargetExpCat, targetRowIndex, i, $scope.data.ScenarioCalendar[targetRowIndex].QuantityValues[i] + quantity2Move, null, true); } } else { // we cannot reallocate more than source quantity if (cost2Move > $scope.data.ScenarioCalendar[sourceRowIndex].CostValues[i]) cost2Move = $scope.data.ScenarioCalendar[sourceRowIndex].CostValues[i]; if (cost2Move > 0) { updateQuantityOrCostCell($scope.reallocator.SourceExpCat, sourceRowIndex, i, null, sourceCost - cost2Move, false); updateQuantityOrCostCell($scope.reallocator.TargetExpCat, targetRowIndex, i, null, $scope.data.ScenarioCalendar[targetRowIndex].CostValues[i] + cost2Move, false); } } } else if ($scope.reallocator.CurveType == 'other') { sourceQuantity = $scope.data.ScenarioCalendar[sourceRowIndex].QuantityValues[i]; sourceCost = $scope.data.ScenarioCalendar[sourceRowIndex].CostValues[i]; quantity2Move = (percentageMult * sourceSumQuantity) * ($scope.data.ScenarioCalendar[curveRowIndex].QuantityValues[i] / otherSumQuantity); cost2Move = (percentageMult * sourceSumCost) * ($scope.data.ScenarioCalendar[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.ScenarioCalendar[sourceRowIndex].QuantityValues[i]) quantity2Move = $scope.data.ScenarioCalendar[sourceRowIndex].QuantityValues[i]; if (quantity2Move > 0) { updateQuantityOrCostCell($scope.reallocator.SourceExpCat, sourceRowIndex, i, sourceQuantity - quantity2Move, null, true); updateQuantityOrCostCell($scope.reallocator.TargetExpCat, targetRowIndex, i, $scope.data.ScenarioCalendar[targetRowIndex].QuantityValues[i] + quantity2Move, null, true); } } else { // we cannot reallocate more than source quantity if (cost2Move > $scope.data.ScenarioCalendar[sourceRowIndex].CostValues[i]) cost2Move = $scope.data.ScenarioCalendar[sourceRowIndex].CostValues[i]; if (cost2Move > 0) { updateQuantityOrCostCell($scope.reallocator.SourceExpCat, sourceRowIndex, i, null, sourceCost - cost2Move, false); updateQuantityOrCostCell($scope.reallocator.TargetExpCat, targetRowIndex, i, null, $scope.data.ScenarioCalendar[targetRowIndex].CostValues[i] + cost2Move, false); } } } } $("#reallocator").modal("hide"); }; $scope.pushPull = function () { for (var i = 1; i < $scope.data.ScenarioCalendar.length; i++) { if ($scope.data.ScenarioCalendar[i].Checked) $scope.pushPullExpCat($scope.data.ScenarioCalendar[i], i, $scope.pushpuller.weeks, $scope.pushpuller.push); } $("#push_pull").modal("hide"); }; $scope.pushPullExpCat = function (data, sourceRowIndex, weeks, push) { var oldQuantity = 0; var oldCost = 0; 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.Headers.length; i++) { if ($scope.data.Headers[i].IsMonth) continue; handledWeeks++; if (handledWeeks == weeks) { leftMargin = i + 1; break; } } handledWeeks = 0; for (i = $scope.data.Headers.length - 1; i >= 0; i--) { if ($scope.data.Headers[i].IsMonth) continue; if (i < leftMargin) { alert('Error occurred while calculating right margin of required changes.'); return; } handledWeeks++; if (handledWeeks == weeks) { rightMargin = i - 1; break; } } if (push) { for (i = $scope.data.Headers.length - 1; i >= 0; i--) { if ($scope.data.Headers[i].IsMonth) continue; oldQuantity = 0; oldCost = 0; if (i < leftMargin) { if ($scope.calendarFilters.IsTableModeQuantity) { updateQuantityOrCostCell(data.ExpCatId, sourceRowIndex, i, 0, null, true); } else { updateQuantityOrCostCell(data.ExpCatId, sourceRowIndex, i, null, 0, false); } } else { //copy data from [i+weeks] cell handledWeeks = 0; for (j = i - 1; j >= 0; j--) { if ($scope.data.Headers[j].IsMonth) continue; handledWeeks++; if (handledWeeks == weeks) { oldQuantity = data.QuantityValues[j]; oldCost = data.CostValues[j]; break; } } if ($scope.calendarFilters.IsTableModeQuantity) { updateQuantityOrCostCell(data.ExpCatId, sourceRowIndex, i, oldQuantity, null, true); } else { updateQuantityOrCostCell(data.ExpCatId, sourceRowIndex, i, null, oldCost, false); } } } } else { for (i = 0; i < $scope.data.Headers.length; i++) { if ($scope.data.Headers[i].IsMonth) continue; oldQuantity = 0; oldCost = 0; if (i <= rightMargin) { //copy data from [i+weeks] cell handledWeeks = 0; for (j = i + 1; j < $scope.data.Headers.length; j++) { if ($scope.data.Headers[j].IsMonth) continue; handledWeeks++; if (handledWeeks == weeks) { oldQuantity = data.QuantityValues[j]; oldCost = data.CostValues[j]; break; } } if ($scope.calendarFilters.IsTableModeQuantity) { updateQuantityOrCostCell(data.ExpCatId, sourceRowIndex, i, oldQuantity, null, true); } else { updateQuantityOrCostCell(data.ExpCatId, sourceRowIndex, i, null, oldCost, false); } } else if (i > rightMargin) { if ($scope.calendarFilters.IsTableModeQuantity) { updateQuantityOrCostCell(data.ExpCatId, sourceRowIndex, i, 0, null, true); } else { updateQuantityOrCostCell(data.ExpCatId, sourceRowIndex, i, null, 0, false); } } } } }; $scope.editTotalFunc = function () { var oldQuantity = 0; var oldCost = 0; if ($scope.editTotal.CurrentTotal == $scope.editTotal.NewTotal && $scope.editTotal.Curve != 'other') { $("#editTotal").modal("hide"); return; } var changeMult = $scope.editTotal.NewTotal / $scope.editTotal.CurrentTotal; var sourceRowIndex = null; var curveRowIndex = null; var i; for (i = 0; i < $scope.data.ScenarioCalendar.length; i++) { if ($scope.data.ScenarioCalendar[i].ExpCatId === $scope.editTotal.ExpCat) sourceRowIndex = i; if ($scope.data.ScenarioCalendar[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; for (i = 0; i < $scope.data.Headers.length ; i++) { if ($scope.data.Headers[i].IsMonth) continue; sourceSumQuantity += $scope.data.ScenarioCalendar[sourceRowIndex].QuantityValues[i]; sourceSumCost += $scope.data.ScenarioCalendar[sourceRowIndex].CostValues[i]; curveSumQuantity += $scope.data.ScenarioCalendar[curveRowIndex].QuantityValues[i]; curveSumCost += $scope.data.ScenarioCalendar[curveRowIndex].CostValues[i]; } if (curveSumQuantity == 0 || sourceSumQuantity == 0) { changeMult = $scope.editTotal.NewTotal; } //console.log("sourceSumQuantity: " + sourceSumQuantity + "; curveSumQuantity: " + curveSumQuantity + "; changeMult: " + changeMult); var numberOfWeeks = 0; for (i = 0; i < $scope.data.Headers.length; i++) { if ($scope.data.Headers[i].IsMonth) continue; numberOfWeeks++; } if (curveSumQuantity > 0) for (i = 0; i < $scope.data.Headers.length; i++) { if ($scope.data.Headers[i].IsMonth) continue; oldQuantity = $scope.data.ScenarioCalendar[sourceRowIndex].QuantityValues[i]; oldCost = $scope.data.ScenarioCalendar[sourceRowIndex].CostValues[i]; if ($scope.editTotal.Curve == 'other') { var targetQuantity = $scope.data.ScenarioCalendar[curveRowIndex].QuantityValues[i]; var targetCost = $scope.data.ScenarioCalendar[curveRowIndex].CostValues[i]; if (sourceSumQuantity != 0) 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((targetQuantity / curveSumQuantity) * changeMult), Math.abs(sourceSumCost * (targetCost / curveSumCost) * changeMult), null); } else updateQuantityOrCostCell($scope.editTotal.ExpCat, sourceRowIndex, i, Math.abs(oldQuantity * changeMult), Math.abs(oldCost * changeMult), null); } else { for (i = 0; i < $scope.data.Headers.length; i++) { if ($scope.data.Headers[i].IsMonth) continue; updateQuantityOrCostCell($scope.editTotal.ExpCat, sourceRowIndex, i, Math.abs(changeMult / numberOfWeeks), Math.abs(changeMult * $scope.getRate($scope.editTotal.ExpCat, $scope.data.Headers[i].Milliseconds) / numberOfWeeks), null); } } $scope.editTotal.CurrentTotal = $scope.editTotal.NewTotal; $("#editTotal").modal("hide"); }; $scope.recalcTotal = function () { for (var i = 0; i < $scope.data.ScenarioCalendar.length; i++) { if ($scope.data.ScenarioCalendar[i].ExpCatId === $scope.editTotal.ExpCat) { if ($scope.editTotal.SeatsCost == 'Quantity') $scope.editTotal.CurrentTotal = $scope.data.ScenarioCalendar[i].GrandTotalQuantity; else $scope.editTotal.CurrentTotal = $scope.data.ScenarioCalendar[i].GrandTotalCost; break; } } }; $scope.isOtherSelectedReallocator = function () { return $scope.reallocator.CurveType == 'other'; }; $scope.isOtherSelectedTotal = function () { return $scope.editTotal.Curve == 'other'; }; $scope.prefillFormatCells = function () { $scope.formatCells.ExpCats = []; for (var i = 1; i < $scope.data.ScenarioCalendar.length; i++) { // skip this row if it is not checked if (!$scope.data.ScenarioCalendar[i].Checked) continue; $scope.formatCells.ExpCats[$scope.formatCells.ExpCats.length] = { Id: $scope.data.ScenarioCalendar[i].ExpCatId, Name: $scope.data.ScenarioCalendar[i].ExpCatName }; } if ($scope.formatCells.ExpCats.length > 0) $scope.formatCells.SelectedExpCats = $scope.formatCells.ExpCats[0].Id; }; $scope.submitFormatCells = function () { if ($scope.data.ScenarioType === 9) return false; if (isNaN($scope.formatCells.DecimalPlaces)) { alert("Decimal Places is not set."); return false; } var colIndexes2Update = []; // gather column indexes which we're going to update var dtStart = $scope.formatCells.StartDate.split('/'); var msStart = new Date(dtStart[2], dtStart[0] - 1, dtStart[1]).getTime(); var dtEnd = $scope.formatCells.EndDate.split('/'); var msEnd = new Date(dtEnd[2], dtEnd[0] - 1, dtEnd[1]).getTime(); for (var headerIndex = 0; headerIndex < $scope.data.Headers.length; headerIndex++) { if ($scope.data.Headers[headerIndex].IsMonth) continue; if ($scope.data.Headers[headerIndex].Milliseconds >= msStart && $scope.data.Headers[headerIndex].Milliseconds <= msEnd && $scope.data.Headers[headerIndex].Milliseconds > $scope.data.ActualsEndDateMs) { colIndexes2Update[colIndexes2Update.length] = headerIndex; } } var oldQuantity = 0; var newQuantity = 0; var newCost = 0; var dec = Math.pow(10, parseInt($scope.formatCells.DecimalPlaces)); for (var rowIndex = 0; rowIndex < $scope.data.ScenarioCalendar.length; rowIndex++) { for (var catIndex = 0; catIndex < $scope.formatCells.ExpCats.length; catIndex++) { if ($scope.formatCells.ExpCats[catIndex].Id === $scope.data.ScenarioCalendar[rowIndex].ExpCatId) { for (var colIndex = 0; colIndex < colIndexes2Update.length; colIndex++) { oldQuantity = $scope.data.ScenarioCalendar[rowIndex].QuantityValues[colIndexes2Update[colIndex]]; newQuantity = Math.round(oldQuantity * dec) / dec; newCost = Math.round(newQuantity * $scope.getRate($scope.formatCells.ExpCats[catIndex].Id, $scope.data.Headers[colIndexes2Update[colIndex]].Milliseconds) * dec) / dec; if (oldQuantity === newQuantity) continue; updateQuantityOrCostCell($scope.formatCells.ExpCats[catIndex].Id, rowIndex, colIndexes2Update[colIndex], newQuantity, newCost, null); } break; } } } $("#modalFormatCells").modal("hide"); return true; }; $scope.onMonthHeaderClick = function (header) { header.Collapsed = !header.Collapsed; header.Show = !header.Show; header.CollapsedClass = header.Collapsed ? $scope.CollapsedIcon : $scope.NonCollapsedIcon; if (header.Weeks && header.Weeks.length > 0) for (var i = 0; i < header.Weeks.length; i++) { $scope.data.Headers[header.Weeks[i]].Collapsed = header.Collapsed; $scope.data.Headers[header.Weeks[i]].Show = !header.Collapsed; } $scope.resizeFreezAng(); }; $scope.onExpCatClick = function (row) { row.Collapsed = !row.Collapsed; //row.Show = !row.Show; row.CollapsedClass = row.Collapsed ? $scope.CollapsedIcon : $scope.NonCollapsedIcon; }; }]);