3959 lines
198 KiB
JavaScript
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;
|
|
}
|
|
|
|
}]); |