EnVisageOnline/Main-RMO/Source/EnVisage/Scripts/Angular/Controllers/scenarioDetailController.js

3959 lines
198 KiB
JavaScript

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