4472 lines
220 KiB
JavaScript
4472 lines
220 KiB
JavaScript
'use strict';
|
|
|
|
app
|
|
.factory('timeTracker', ['$timeout', function ($timeout) {
|
|
var ctx = {
|
|
trackerQueue: [],
|
|
startTrack: function (key) {
|
|
if (!(key in ctx.trackerQueue)) {
|
|
ctx.trackerQueue[key] = new Date();
|
|
console.log('tracking of ' + key + ' started: ' + ctx.trackerQueue[key]);
|
|
}
|
|
},
|
|
endTrack: function (key) {
|
|
if (key in ctx.trackerQueue) {
|
|
console.log('tracking of ' + key + ' took: ' + (new Date() - ctx.trackerQueue[key]) + ' ms');
|
|
delete ctx.trackerQueue[key];
|
|
}
|
|
},
|
|
logTrack: function (key) {
|
|
if (key in ctx.trackerQueue) {
|
|
console.log('tracking of ' + key + ' taking: ' + (new Date() - ctx.trackerQueue[key]) + ' ms');
|
|
}
|
|
}
|
|
};
|
|
return ctx;
|
|
}])
|
|
.directive('ngMax', function () {
|
|
return {
|
|
restrict: 'A',
|
|
require: 'ngModel',
|
|
link: function (scope, elem, attr, ctrl) {
|
|
scope.$watch(attr.ngMax, function () {
|
|
ctrl.$setViewValue(ctrl.$viewValue);
|
|
});
|
|
var maxValidator = function (value) {
|
|
var max = scope.$eval(attr.ngMax) || Infinity;
|
|
if (value && value > max) {
|
|
ctrl.$setValidity('ngMax', false);
|
|
return value;
|
|
} else {
|
|
ctrl.$setValidity('ngMax', true);
|
|
return value;
|
|
}
|
|
};
|
|
|
|
ctrl.$parsers.push(maxValidator);
|
|
ctrl.$formatters.push(maxValidator);
|
|
}
|
|
};
|
|
})
|
|
.directive('requireMultiple', function () {
|
|
return {
|
|
require: 'ngModel',
|
|
link: function postLink(scope, element, attrs, ngModel) {
|
|
ngModel.$validators.required = function (value) {
|
|
return angular.isArray(value) && value.length > 0;
|
|
};
|
|
}
|
|
};
|
|
})
|
|
.directive('postRepeatDirective', ['$timeout', 'timeTracker', function ($timeout, timeTracker) {
|
|
return function (scope, element, attrs) {
|
|
if (scope.$last) {
|
|
//var start = new Date();
|
|
//$log.debug('DOM rendering started ' + start);
|
|
$timeout(function () {
|
|
//var end = new Date();
|
|
timeTracker.endTrack('gridRender');
|
|
//$log.debug("## DOM rendering list took: " + (end - start) + " ms");
|
|
//$log.debug('DOM rendering end ' + end);
|
|
});
|
|
}
|
|
};
|
|
}])
|
|
.filter('exceptThis', function () {
|
|
return function (inputArray, filterCriteria) {
|
|
if (null === inputArray || undefined === inputArray)
|
|
return null;
|
|
return inputArray.filter(function (item) {
|
|
// if the value of filterCriteria is "falsy", retain the inputArray as it is
|
|
// then check if the currently checked item in the inputArray is different from the filterCriteria,
|
|
// if so, keep it in the filtered results
|
|
var exists = false;
|
|
if (filterCriteria) {
|
|
for (var i = 0; i < filterCriteria.length; i++) {
|
|
if (filterCriteria[i].Id === item.Id) {
|
|
exists = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return !filterCriteria || !exists;
|
|
});
|
|
};
|
|
})
|
|
.controller('scenarioDetailsCalendarController', ['$scope', '$rootScope', '$http', '$location', '$timeout', '$filter', '$sce', '$document', '$q', 'timeTracker', 'dndKey', 'costSavingService', 'scenarioDetailsService', 'dataSources', function ($scope, $rootScope, $http, $location, $timeout, $filter, $sce, $document, $q, timeTracker, dndKey, costSavingService, scenarioDetailsService, dataSources) {
|
|
|
|
var C_HEADER_PERIOD_MONTH = 1;
|
|
var C_HEADER_PERIOD_WEEK = 2;
|
|
|
|
var C_HEADER_DATA_TYPE_ORDINAL = 1;
|
|
var C_HEADER_DATA_TYPE_TOTALS = 100;
|
|
|
|
var C_CALENDAR_VIEW_MODE_FORECAST = "F";
|
|
var C_CALENDAR_VIEW_MODE_ACTUALS = "A";
|
|
|
|
var AllocationMode = {
|
|
AssignRest: 1,
|
|
AssignAll: 2,
|
|
Reset: 3
|
|
};
|
|
|
|
$scope.Monthes = ['Jun', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
|
|
|
$scope.CollapsedIcon = 'fa-plus-square';
|
|
$scope.NonCollapsedIcon = 'fa-minus-square';
|
|
$scope.CommonErrorMessage = 'An error occurred while processing your request. Please, try again later.';
|
|
|
|
$scope.ExpendituresIsOverAllocated = false;
|
|
$scope.ExpendituresIsUnderAllocated = false;
|
|
$scope.TeamsIsOverAllocated = false;
|
|
$scope.IsPossibleMarginDesignated = true;
|
|
$scope.refreshGraph = false;
|
|
$scope.WorkFlowSchema = null;
|
|
$scope.NotesToAdd = [];
|
|
$scope.NotesToDelete = [];
|
|
$scope.HoldWorkFlowActions = [];
|
|
$scope.HoldWorkFlowSchema = null;
|
|
|
|
|
|
//notification from the Scenario Header controller
|
|
$scope.$on('refreshScenarioDetails', function (event, data) {
|
|
try {
|
|
var sd = new Date(data.startDate);
|
|
var ed = new Date(data.endDate);
|
|
|
|
$scope.data.Scenario.Id = data.id;
|
|
$scope.data.Scenario.Name = data.name;
|
|
$scope.data.Scenario.ParentId = data.parentId;
|
|
$scope.data.Scenario.OldTemplateId = data.oldTemplateId;
|
|
$scope.data.Scenario.TemplateId = data.templateId;
|
|
$scope.data.Scenario.StartDate = Date.UTC(sd.getFullYear(), sd.getMonth(), sd.getDate());
|
|
$scope.data.Scenario.EndDate = Date.UTC(ed.getFullYear(), ed.getMonth(), ed.getDate());
|
|
$scope.data.Scenario.GrowthScenario = data.growthScenario;
|
|
$scope.data.Scenario.ProjectedRevenue = data.revenue;
|
|
$scope.data.Scenario.TDDirectCosts = data.expense;
|
|
$scope.data.Scenario.UseLMMargin = data.useLMMargin;
|
|
$scope.data.Scenario.GrossMargin = data.grossMargin;
|
|
$scope.data.Scenario.LMMargin = data.lmMargin;
|
|
$scope.data.Scenario.IsActiveScenario = data.isActiveScenario;
|
|
$scope.data.Scenario.CostSavings = data.costSaving;
|
|
$scope.data.Scenario.NeedToAdjustMargin = data.needToAdjustMargin;
|
|
$scope.data.Scenario.UserDefinedFields = data.UserDefinedFields;
|
|
|
|
$scope.data.Scenario.AdjustMarginRased = data.adjustMarginRased;
|
|
$scope.data.Scenario.DateForStartOfChangesOld = data.dateForStartOfChangesOld;
|
|
|
|
$scope.refreshGraph = true;
|
|
|
|
if (data.dateForStartOfChanges) {
|
|
var sc = new Date(data.dateForStartOfChanges);
|
|
$scope.data.Scenario.DateForStartOfChanges = Date.UTC(sc.getFullYear(), sc.getMonth(), sc.getDate());
|
|
}
|
|
|
|
if (data.DateForStartOfChangesOld) {
|
|
var sc2 = new Date(data.DateForStartOfChangesOld);
|
|
$scope.data.Scenario.DateForStartOfChangesOld = Date.UTC(sc2.getFullYear(), sc2.getMonth(), sc2.getDate());
|
|
}
|
|
|
|
$scope.data.NeedToRecalculateScenarioDetails = true;
|
|
|
|
if (refreshScenarioStatus && typeof refreshScenarioStatus == 'function')
|
|
refreshScenarioStatus(data.isActiveScenario);
|
|
} catch (e) {
|
|
showErrorModal('Oops!', $scope.CommonErrorMessage);
|
|
return;
|
|
}
|
|
$scope.gridChanged();
|
|
$scope.getCalendar();
|
|
});
|
|
|
|
$scope.$on('refreshFinancialInformation', function (event, data) {
|
|
try {
|
|
$scope.data.Scenario.ProjectedRevenue = data.revenue;
|
|
$scope.data.Scenario.TDDirectCosts = data.expense;
|
|
$scope.data.Scenario.UseLMMargin = data.useLMMargin;
|
|
$scope.data.Scenario.GrossMargin = data.grossMargin;
|
|
$scope.data.Scenario.LMMargin = data.lmMargin;
|
|
$scope.data.Scenario.CostSavings = data.costSaving;
|
|
$scope.data.Scenario.NeedToAdjustMargin = data.needToAdjustMargin;
|
|
$scope.data.NeedToRecalculateScenarioDetails = true;
|
|
|
|
$scope.data.Scenario.AdjustMarginRased = data.adjustMarginRased;
|
|
$scope.data.Scenario.DateForStartOfChangesOld = data.dateForStartOfChangesOld;
|
|
|
|
if (data.dateForStartOfChanges) {
|
|
$scope.data.Scenario.DateForStartOfChanges = DateTimeConverter.stringToMs(data.dateForStartOfChanges);
|
|
$scope.data.ScenarioGeneralInfoEditModel.DateForStartOfChanges = data.dateForStartOfChanges;
|
|
$document.trigger('date-for-start-of-change-changed', $scope.data.ScenarioGeneralInfoEditModel.DateForStartOfChanges);
|
|
}
|
|
if (data.dateForStartOfChangesOld) {
|
|
$scope.data.Scenario.DateForStartOfChangesOld = DateTimeConverter.stringToMs(data.dateForStartOfChangesOld);
|
|
}
|
|
} catch (e) {
|
|
console.error(e);
|
|
showErrorModal('Oops!', $scope.CommonErrorMessage);
|
|
return;
|
|
}
|
|
$scope.gridChanged();
|
|
$scope.getCalendar();
|
|
});
|
|
$scope.$on('changeScenarioDetails', function (event, data) {
|
|
if (!data)
|
|
return;
|
|
|
|
scenarioDetailsService.setExpenditures(getScenarioId(), data.Expenditures || {});
|
|
scenarioDetailsService.setRates(getScenarioId(), data.Rates || []);
|
|
$scope.data.AvailableExpenditures = [];
|
|
$scope.data.LocalRatesLoaded = true;
|
|
if (!!data.CostSaving) {
|
|
costSavingService.init({
|
|
ScenarioId: getScenarioId(),
|
|
ScenarioStartDate: $scope.data.Scenario.StartDate,
|
|
ScenarioEndDate: $scope.data.Scenario.EndDate,
|
|
StartDate: data.CostSaving.CostSavingStartDate ? DateTimeConverter.msFormatAsUtcString(data.CostSaving.CostSavingStartDate) : "",
|
|
EndDate: data.CostSaving.CostSavingEndDate ? DateTimeConverter.msFormatAsUtcString(data.CostSaving.CostSavingEndDate) : "",
|
|
CostSavings: data.CostSaving.CostSavings,
|
|
CostSavingType: data.CostSaving.CostSavingType,
|
|
Description: data.CostSaving.CostSavingDescription,
|
|
Items: !!data.CostSaving.CostSavingItems ? JSON.stringify(data.CostSaving.CostSavingItems) : null
|
|
});
|
|
}
|
|
|
|
for (var expCatId in data.Expenditures) {
|
|
$scope.data.AvailableExpenditures.push({
|
|
Id: expCatId,
|
|
Name: data.Expenditures[expCatId].ExpenditureCategoryName
|
|
});
|
|
}
|
|
|
|
$scope.getCalendar(null, normalizeTeamQuantityValues);
|
|
});
|
|
$scope.$on('addNewExpenditureCategory', function (event, data) {
|
|
if (!data)
|
|
return;
|
|
|
|
if (!data.expenditureCategoryId) {
|
|
if (data.callback)
|
|
data.callback();
|
|
|
|
return;
|
|
}
|
|
|
|
$scope.data.CalendarFilter.ExpCats2Add = [{ Id: data.expenditureCategoryId }];
|
|
$scope.addExpCats(data.callback);
|
|
});
|
|
$scope.$on('deleteExpenditureCategories', function (event, data) {
|
|
if (!data || !angular.isArray(data) || data.length <= 0)
|
|
return;
|
|
|
|
removeExpCatsInternal(data);
|
|
});
|
|
$scope.$on('refreshTeamsList', function (event, data) {
|
|
$scope.RestTeams = getRestTeams();
|
|
});
|
|
$scope.$on('scenarioIsChanged', function (event, data) {
|
|
$scope.gridChanged();
|
|
});
|
|
$scope.$on('NoteAdded', function (event, n) {
|
|
var Nidx = NoteIndex(n);
|
|
if (Nidx < 0)
|
|
$scope.NotesToAdd.push(n);
|
|
else
|
|
$scope.NotesToAdd.splice(Nidx, 1, n);
|
|
|
|
});
|
|
$scope.$on('NoteRemoved', function (event, n) {
|
|
var Nidx = NoteIndex(n);
|
|
if (Nidx > 0)
|
|
$scope.NotesToAdd.splice(Nidx, 1);
|
|
else
|
|
$scope.NotesToDelete.push(n);
|
|
|
|
});
|
|
|
|
$scope.$on('scenarioWorkFlowSchemaChanged', function (event, data) {
|
|
if ($scope.WorkFlowSchema != data.WorkFlowSchema) {
|
|
if ($scope.HoldWorkFlowSchema == null) {
|
|
$scope.HoldWorkFlowSchema = $scope.WorkFlowSchema;
|
|
$scope.data.HoldWorkFlowActions = $scope.data.WorkFlowActions;
|
|
}
|
|
$scope.WorkFlowSchema = data.WorkFlowSchema;
|
|
$scope.data.WorkFlowActions = [];
|
|
if ( $scope.HoldWorkFlowSchema = $scope.WorkFlowSchema)
|
|
$scope.data.WorkFlowActions = $scope.data.HoldWorkFlowActions;
|
|
}
|
|
});
|
|
$scope.$on('dependenciesvalidated', function (event, data) {
|
|
$scope.Dependencies = data;
|
|
});
|
|
$scope.initData = function () {
|
|
$scope.data = {
|
|
Scenario: {
|
|
Id: null,
|
|
Name: null,
|
|
ParentId: null,
|
|
OldTemplateId: null,
|
|
TemplateId: null,
|
|
StartDate: null,
|
|
EndDate: null,
|
|
GrowthScenario: false,
|
|
ProjectedRevenue: null,
|
|
TDDirectCosts: null,
|
|
Type: 0,
|
|
UseLMMargin: false,
|
|
GrossMargin: null,
|
|
LMMargin: null,
|
|
IsActiveScenario: null,
|
|
DateForStartOfChanges: null,
|
|
ActualStartDate: 0,
|
|
ActualEndDate: 0,
|
|
GreenIndicator: null,
|
|
YellowIndicator: null,
|
|
RedIndicator: null,
|
|
SaveAs: null,
|
|
SaveAsDraft: false, // by default need to save full information by scenario
|
|
NeedToAdjustMargin: false,
|
|
CostSavings: null,
|
|
DistributionType: "0" // original distribution type
|
|
},
|
|
ScenarioGeneralInfoEditModel: {
|
|
StartDate: null,
|
|
EndDate: null,
|
|
DateForStartOfChanges: null,
|
|
ProjectDeadline: null,
|
|
ActualStartDate: null,
|
|
ActualEndDate: null,
|
|
HasActuals: false,
|
|
IsChanged: false,
|
|
DistributionType: "0" // original distribution type
|
|
},
|
|
CalendarFilter: {
|
|
CategoryType: null,
|
|
LaborMaterials: null,
|
|
IncomeType: null,
|
|
GLAccount: null,
|
|
CreditDepartment: null,
|
|
SelectedExpCats: [],
|
|
VisibleExpCats: [],
|
|
SecondaryExpCats: [],
|
|
IsTableModeQuantity: true,
|
|
IsViewModeMonth: true,
|
|
ShowActuals: false,
|
|
IsUOMHours: false,
|
|
ScenarioId: null,
|
|
ShowAvgTotals: false,
|
|
ShowFilters: null,
|
|
ViewModeName: "",
|
|
DragNDropReplaceValues: true
|
|
},
|
|
Calendar: {
|
|
Headers: null,
|
|
WeekHeaders: null,
|
|
MonthHeaders: null,
|
|
Rows: null,
|
|
Expenditures: null
|
|
},
|
|
ExpenditureAddGroups: [
|
|
{ Id: 0, Name: "Other Categories" },
|
|
{ Id: 1, Name: "Categories of current Teams" }
|
|
],
|
|
AvailableExpenditures: null,
|
|
Expenditures2Add: null,
|
|
Teams: null,
|
|
CategoryTypes: null,
|
|
CGEFX: null,
|
|
IncomeTypes: null,
|
|
GLAccounts: null,
|
|
CreditDepartments: null,
|
|
Rates: null,
|
|
NeedToRebind: true,
|
|
NeedToRecalculateScenarioDetails: false,
|
|
isDataChanged: false,
|
|
VisibleCellCount: 1,
|
|
Opener: 1, //'details'
|
|
WorkFlowActions: [{ command: null, Selected: false, Id: null, HasChanges:false}],
|
|
WorkFlowStates: [{ state: null, Selected: false, Id: null, HasChanges:false}],
|
|
WorkFlowSchema: null,
|
|
Audit: null
|
|
};
|
|
};
|
|
|
|
$scope.initData();
|
|
|
|
$scope.typeOptions = [
|
|
{ name: 'Quantity', value: 'Quantity' },
|
|
{ name: 'Cost', value: 'Cost' }
|
|
];
|
|
$scope.reallocator = {
|
|
SourceExpCat: null,
|
|
TargetExpCat: null,
|
|
SourceWeekEnding: null,
|
|
TargetWeekEnding: null,
|
|
Percentage: 100,
|
|
ReallocateBy: $scope.typeOptions[0].value,
|
|
CurveType: 'source',
|
|
OtherCurve: null,
|
|
};
|
|
$scope.GraphFilter = {
|
|
ExpCats: [],
|
|
Actuals: false,
|
|
ShowTotal: false,
|
|
ShowTime: false,
|
|
Quantity: false,
|
|
};
|
|
$scope.pushpuller = {
|
|
weeks: 1,
|
|
push: true,
|
|
resource: null,
|
|
startdate: null,
|
|
maxWeeks: null
|
|
};
|
|
|
|
$scope.editTotal = {
|
|
ExpCat: null,
|
|
CurrentTotal: null,
|
|
NewTotal: null,
|
|
OtherCurve: null,
|
|
};
|
|
|
|
$scope.formatCells = {
|
|
ExpCats: [],
|
|
SelectedExpCats: null,
|
|
StartDate: null,
|
|
EndDate: null,
|
|
DecimalCalcCostPlaces: 4,
|
|
DecimalCalcQtyPlaces: 6,
|
|
RoundOptions: [
|
|
{ Name: '10th', Value: 0.1 },
|
|
{ Name: 'Fourth', Value: 0.25 },
|
|
{ Name: 'Half', Value: 0.5 },
|
|
{ Name: 'Full', Value: 1 },
|
|
]
|
|
};
|
|
$scope.formatCells.DecimalPlaces = $scope.formatCells.RoundOptions[1].Value;
|
|
$scope.RoundingBasis = 0.01;
|
|
$scope.RestTeams = [];
|
|
|
|
$scope.$watch("reallocator.SourceExpCat", function (newValue, oldValue) {
|
|
if (newValue != oldValue) {
|
|
$scope.data.CalendarFilter.SecondaryExpCats = [];
|
|
$.each($scope.data.CalendarFilter.VisibleExpCats, function (i, expCat) {
|
|
if (expCat.Id != $scope.reallocator.SourceExpCat) {
|
|
$scope.data.CalendarFilter.SecondaryExpCats.push(expCat);
|
|
}
|
|
});
|
|
|
|
if ($scope.data.CalendarFilter.SecondaryExpCats && ($scope.data.CalendarFilter.SecondaryExpCats.length > 0) &&
|
|
$scope.data.CalendarFilter.SecondaryExpCats[0]) {
|
|
$scope.reallocator.TargetExpCat = $scope.data.CalendarFilter.SecondaryExpCats[0].Id;
|
|
}
|
|
else {
|
|
$scope.reallocator.TargetExpCat = undefined;
|
|
}
|
|
}
|
|
});
|
|
$scope.$watch("data.ScenarioGeneralInfoEditModel.StartDate", function (newValue, oldValue) {
|
|
if (newValue != oldValue) {
|
|
$scope.scenarioEditModelChanged();
|
|
}
|
|
});
|
|
$scope.$watch("data.ScenarioGeneralInfoEditModel.EndDate", function (newValue, oldValue) {
|
|
if (newValue != oldValue) {
|
|
$scope.scenarioEditModelChanged();
|
|
}
|
|
});
|
|
|
|
function updateECTotals(rowIndex, colIndex, newQuantity, oldQuantity, newCost, oldCost) {
|
|
var monthHeaderIndex = $scope.data.Calendar.WeekHeaders[colIndex].MonthHeader;
|
|
var monthHeader = $scope.data.Calendar.MonthHeaders[monthHeaderIndex];
|
|
var monthTotalCellIndex = monthHeader.TotalsHeader;
|
|
|
|
var rowEC = $scope.data.Calendar.Rows[rowIndex];
|
|
var totalQuantity = 0;
|
|
var totalCost = 0;
|
|
var weeksCount = 0;
|
|
|
|
for (var index = 0; index < monthHeader.WeekHeaders.length; index++) {
|
|
var currentWeekHeaderIndex = monthHeader.WeekHeaders[index];
|
|
|
|
if (colIndex == currentWeekHeaderIndex) {
|
|
totalQuantity += newQuantity;
|
|
totalCost += newCost;
|
|
weeksCount++;
|
|
} else {
|
|
if ($scope.data.Calendar.WeekHeaders[currentWeekHeaderIndex].Visible[$scope.data.CalendarFilter.ViewModeName]) {
|
|
totalQuantity += rowEC.QuantityValues[currentWeekHeaderIndex];
|
|
totalCost += rowEC.CostValues[currentWeekHeaderIndex];
|
|
weeksCount++;
|
|
}
|
|
}
|
|
|
|
}
|
|
if (isAvgMode()) {
|
|
newQuantity = Math.round(totalQuantity * 1000 / weeksCount) / 1000;
|
|
oldQuantity = rowEC.QuantityValues[monthTotalCellIndex];
|
|
} else {
|
|
oldQuantity = rowEC.QuantityValues[monthTotalCellIndex];
|
|
oldCost = rowEC.CostValues[monthTotalCellIndex];
|
|
newQuantity = totalQuantity;
|
|
}
|
|
newCost = totalCost;
|
|
|
|
//update month cell
|
|
rowEC.QuantityValues[monthTotalCellIndex] = calcWithSamePresicion(rowEC.QuantityValues[monthTotalCellIndex], (newQuantity - oldQuantity), false);
|
|
rowEC.CostValues[monthTotalCellIndex] = calcWithSamePresicion(rowEC.CostValues[monthTotalCellIndex], (newCost - oldCost), true);
|
|
// update row grand totals
|
|
var grandTotalQuantity = 0;
|
|
var grandTotalCost = 0;
|
|
weeksCount = 0;
|
|
|
|
for (var i = 0; i < $scope.data.Calendar.WeekHeaders.length; i++) {
|
|
if (($scope.data.Calendar.WeekHeaders[i].DataType == C_HEADER_DATA_TYPE_ORDINAL) &&
|
|
($scope.data.Calendar.WeekHeaders[i].Visible[$scope.data.CalendarFilter.ViewModeName])) {
|
|
grandTotalQuantity = calcWithSamePresicion(grandTotalQuantity, rowEC.QuantityValues[i], false);
|
|
grandTotalCost = calcWithSamePresicion(grandTotalCost, rowEC.CostValues[i], true);
|
|
weeksCount++;
|
|
}
|
|
}
|
|
|
|
|
|
if (isAvgMode()) {
|
|
totalQuantity = Math.round(grandTotalQuantity * 1000 / weeksCount) / 1000;
|
|
} else {
|
|
totalQuantity = grandTotalQuantity;
|
|
}
|
|
totalCost = grandTotalCost; //Math.round(grandTotalCost / monthCount * 100) / 100;
|
|
// update column totals
|
|
var rowECTotal = $scope.data.Calendar.Rows[0];
|
|
rowECTotal.CostValues[monthTotalCellIndex] = calcWithSamePresicion(rowECTotal.CostValues[monthTotalCellIndex], (newCost - oldCost), true);
|
|
rowECTotal.QuantityValues[monthTotalCellIndex] = calcWithSamePresicion(rowECTotal.QuantityValues[monthTotalCellIndex], (newQuantity - oldQuantity), false);
|
|
rowECTotal.GrandTotalQuantity = calcWithSamePresicion(rowECTotal.GrandTotalQuantity, totalQuantity - rowEC.GrandTotalQuantity, false);
|
|
rowECTotal.GrandTotalCost = calcWithSamePresicion(rowECTotal.GrandTotalCost, totalCost - rowEC.GrandTotalCost, true);
|
|
setRowGrandTotal(rowECTotal);
|
|
|
|
rowEC.GrandTotalQuantity = totalQuantity;
|
|
rowEC.GrandTotalCost = totalCost;
|
|
setRowGrandTotal(rowEC);
|
|
}
|
|
|
|
// you can send resource or team as entity parameter
|
|
function updateTotals(entity, colIndex, newQuantity) {
|
|
var monthHeaderIndex = $scope.data.Calendar.WeekHeaders[colIndex].MonthHeader;
|
|
var monthHeader = $scope.data.Calendar.MonthHeaders[monthHeaderIndex];
|
|
var monthTotalCellIndex = monthHeader.TotalsHeader;
|
|
|
|
var total = 0,
|
|
weekCount = 0,
|
|
oldQuantity = entity.QuantityValues[monthTotalCellIndex];
|
|
|
|
for (var index = 0; index < monthHeader.WeekHeaders.length; index++) {
|
|
var currentWeekHeaderIndex = monthHeader.WeekHeaders[index];
|
|
|
|
if (colIndex == currentWeekHeaderIndex) {
|
|
total += newQuantity;
|
|
weekCount++;
|
|
} else {
|
|
if ($scope.data.Calendar.WeekHeaders[currentWeekHeaderIndex].Visible[$scope.data.CalendarFilter.ViewModeName]) {
|
|
total += entity.QuantityValues[currentWeekHeaderIndex];
|
|
weekCount++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (isAvgMode()) {
|
|
newQuantity = Math.round(total * 1000 / weekCount) / 1000;
|
|
} else {
|
|
//update month cell
|
|
newQuantity = total;
|
|
}
|
|
|
|
//update month cell
|
|
entity.QuantityValues[monthTotalCellIndex] += (newQuantity - oldQuantity);
|
|
entity.RestQuantityValues[monthTotalCellIndex] -= (newQuantity - oldQuantity);
|
|
}
|
|
|
|
// update row grand totals
|
|
function updateGrandTotals(entity) {
|
|
var grandTotalQuantity = 0;
|
|
var weekCount = 0;
|
|
for (var i = 0; i < $scope.data.Calendar.WeekHeaders.length; i++) {
|
|
if (($scope.data.Calendar.WeekHeaders[i].DataType == C_HEADER_DATA_TYPE_ORDINAL) &&
|
|
($scope.data.Calendar.WeekHeaders[i].Visible[$scope.data.CalendarFilter.ViewModeName])) {
|
|
|
|
grandTotalQuantity += entity.QuantityValues[i];
|
|
weekCount++;
|
|
}
|
|
}
|
|
|
|
if (isAvgMode()) {
|
|
grandTotalQuantity = Math.round(grandTotalQuantity * 1000 / weekCount) / 1000;
|
|
} else {
|
|
grandTotalQuantity = grandTotalQuantity;
|
|
}
|
|
entity.GrandTotalQuantity = grandTotalQuantity;
|
|
}
|
|
|
|
function allocateResource(row, team, resource, mode) {
|
|
if (resource != null) {
|
|
resource.GrandTotalQuantity = 0;
|
|
resource.GrandTotalCost = 0;
|
|
for (var i = 0; i < $scope.data.Calendar.WeekHeaders.length; i++) {
|
|
if ($scope.data.Calendar.WeekHeaders[i].DataType != C_HEADER_DATA_TYPE_ORDINAL)
|
|
continue;
|
|
|
|
// skip all readonly cells except if you click reset button, because we need to reset ALL cells (clear corrupted data)
|
|
if (resource.ReadOnly[i] && !(mode == AllocationMode.Reset && resource.QuantityValues[i] != 0))
|
|
continue;
|
|
|
|
var quantity = 0;
|
|
if (mode == AllocationMode.Reset) {
|
|
quantity = -resource.QuantityValues[i];
|
|
}
|
|
else if (mode == AllocationMode.AssignRest) {
|
|
var needToAssign = Math.max(team.QuantityValues[i] - team.AllocatedByResources[i], 0);
|
|
var canBeAssigned = Math.max(resource.RestQuantityValues[i], 0);
|
|
quantity = Math.min(needToAssign, canBeAssigned);
|
|
}
|
|
else if (mode == AllocationMode.AssignAll) {
|
|
var needToAssign = Math.max(team.QuantityValues[i] - team.AllocatedByResources[i], 0);
|
|
var canBeAssigned = Math.max(resource.CapacityQuantityValues[i] - resource.QuantityValues[i], 0);
|
|
quantity = Math.min(needToAssign, canBeAssigned);
|
|
}
|
|
|
|
var newQuantity = resource.QuantityValues[i] + quantity;
|
|
$scope.checkResourceValue(row, team, resource, i, newQuantity, false);
|
|
}
|
|
$scope.gridChanged();
|
|
$scope.RefreshCSSClasses();
|
|
}
|
|
}
|
|
|
|
function allocateTeam(expCat, team, mode) {
|
|
if (!team || !expCat)
|
|
return;
|
|
|
|
team.GrandTotalQuantity = 0;
|
|
team.GrandTotalCost = 0;
|
|
|
|
for (var i = 0; i < $scope.data.Calendar.WeekHeaders.length; i++) {
|
|
if ($scope.data.Calendar.WeekHeaders[i].DataType != C_HEADER_DATA_TYPE_ORDINAL)
|
|
continue;
|
|
|
|
var quantity = 0;
|
|
if (mode == AllocationMode.Reset) {
|
|
quantity = -team.QuantityValues[i];
|
|
}
|
|
else if (mode == AllocationMode.AssignRest) {
|
|
var needToAssign = Math.max(expCat.RestQuantity[i], 0);
|
|
var canBeAssigned = Math.max(team.RestQuantityValues[i], 0);
|
|
quantity = Math.min(needToAssign, canBeAssigned);
|
|
}
|
|
else if (mode == AllocationMode.AssignAll) {
|
|
var needToAssign = Math.max(expCat.RestQuantity[i], 0);
|
|
var canBeAssigned = Math.max(team.CapacityQuantityValues[i] - team.QuantityValues[i], 0);
|
|
quantity = Math.min(needToAssign, canBeAssigned);
|
|
}
|
|
|
|
var newQuantity = team.QuantityValues[i] + quantity;
|
|
$scope.checkTeamValue(expCat, team, i, newQuantity, false);
|
|
}
|
|
$scope.gridChanged();
|
|
$scope.RefreshCSSClasses();
|
|
}
|
|
|
|
function isAvgMode() {
|
|
return $scope.data.CalendarFilter.ShowAvgTotals && !$scope.data.CalendarFilter.IsUOMHours && $scope.data.CalendarFilter.IsTableModeQuantity;
|
|
}
|
|
|
|
$scope.init = function (initData, fnCallback) {
|
|
$scope.data.Scenario.Id = initData.Id;
|
|
$scope.data.Scenario.Name = initData.Name;
|
|
$scope.data.Scenario.ParentId = initData.ParentId;
|
|
$scope.data.Scenario.TemplateId = initData.TemplateId;
|
|
$scope.data.Scenario.StartDate = initData.StartDate;
|
|
$scope.data.Scenario.EndDate = initData.EndDate;
|
|
$scope.data.Scenario.ProjectedRevenue = initData.ProjectedRevenue;
|
|
$scope.data.Scenario.GrowthScenario = initData.GrowthScenario;
|
|
$scope.data.Scenario.Type = initData.Type;
|
|
$scope.data.Scenario.UseLMMargin = initData.UseLMMargin;
|
|
$scope.data.Scenario.GrossMargin = initData.GrossMargin;
|
|
$scope.data.Scenario.LMMargin = initData.LMMargin;
|
|
$scope.data.Scenario.Duration = initData.Duration;
|
|
$scope.data.Scenario.TDDirectCosts = initData.TDDirectCosts;
|
|
$scope.data.Scenario.CGSplit = initData.CGSplit;
|
|
$scope.data.Scenario.EFXSplit = initData.EFXSplit;
|
|
$scope.data.Scenario.Shots = initData.Shots;
|
|
$scope.data.Scenario.IsActiveScenario = initData.IsActiveScenario;
|
|
$scope.data.Scenario.NeedToAdjustMargin = initData.NeedToAdjustMargin;
|
|
$scope.data.Scenario.IsBottomUp = initData.IsBottomUp;
|
|
$scope.data.Scenario.UserDefinedFields = initData.UserDefinedFields;
|
|
|
|
$scope.data.CalendarFilter.ShowAvgTotals = initData.ShowAvgTotals;
|
|
$scope.data.CalendarFilter.IsUOMHours = initData.IsUOMHours;
|
|
$scope.data.CalendarFilter.ShowActuals = initData.ShowActuals;
|
|
$scope.data.CalendarFilter.IsTableModeQuantity = initData.IsTableModeQuantity;
|
|
$scope.data.CalendarFilter.IsViewModeMonth = initData.IsViewModeMonth;
|
|
$scope.data.CalendarFilter.ShowFilters = initData.ShowFilters;
|
|
$scope.data.AvailableExpenditures = initData.AvailableExpenditures;
|
|
$scope.data.NeedToRecalculateScenarioDetails = initData.NeedToRecalculateScenarioDetails;
|
|
$scope.Opener = initData.Opener;
|
|
|
|
if (initData.StartDate > 0) {
|
|
$scope.data.ScenarioGeneralInfoEditModel.StartDate = DateTimeConverter.msFormatAsUtcString(initData.StartDate);
|
|
}
|
|
if (initData.EndDate > 0) {
|
|
$scope.data.ScenarioGeneralInfoEditModel.EndDate = DateTimeConverter.msFormatAsUtcString(initData.EndDate);
|
|
}
|
|
var currentDateAsUts = DateTimeConverter.getUtcNowMs();
|
|
var dateTimeForStartChanges = (initData.StartDate > currentDateAsUts) ? initData.StartDate :
|
|
(initData.EndDate > currentDateAsUts) ? currentDateAsUts : initData.EndDate;
|
|
|
|
if (dateTimeForStartChanges > 0) {
|
|
$scope.data.ScenarioGeneralInfoEditModel.DateForStartOfChanges = DateTimeConverter.msFormatAsLocalString(dateTimeForStartChanges);
|
|
}
|
|
$scope.data.DataSection = initData.DataSection;
|
|
|
|
if (initData.PagePreferences && initData.PagePreferences.length > 0) {
|
|
var pagePrefArray = JSON.parse(initData.PagePreferences);
|
|
|
|
for (var i = 0; i < pagePrefArray.length; i++) {
|
|
switch (pagePrefArray[i].Key) {
|
|
case "uomMode":
|
|
$scope.data.CalendarFilter.IsUOMHours = pagePrefArray[i].Value;
|
|
break;
|
|
case "chMode":
|
|
$scope.data.CalendarFilter.IsTableModeQuantity = pagePrefArray[i].Value;
|
|
break;
|
|
case "monthWeekMode":
|
|
$scope.data.CalendarFilter.IsViewModeMonth = pagePrefArray[i].Value;
|
|
break;
|
|
case "dragNDropReplaceValues":
|
|
$scope.data.CalendarFilter.DragNDropReplaceValues = pagePrefArray[i].Value;
|
|
break;
|
|
case "showFilters":
|
|
$scope.data.CalendarFilter.ShowFilters = pagePrefArray[i].Value;
|
|
break;
|
|
case "actualsModeGraph":
|
|
$scope.GraphFilter.Actuals = pagePrefArray[i].Value;
|
|
break;
|
|
case "totalGraph":
|
|
$scope.GraphFilter.ShowTotal = pagePrefArray[i].Value;
|
|
break;
|
|
//case "timeGraph":
|
|
// $scope.GraphFilter.ShowTime = pagePrefArray[i].Value;
|
|
// break;
|
|
//case "quantityGraph":
|
|
// $scope.GraphFilter.Quantity = pagePrefArray[i].Value;
|
|
// break;
|
|
default:
|
|
console.log("Restore page preferences (Scenario Details): data key not found in parsing loaded preferences (datakey=" +
|
|
pagePrefArray[i].Key + ", dataSection=" + $scope.dataSection + ")");
|
|
}
|
|
}
|
|
}
|
|
|
|
// init scenario details service
|
|
scenarioDetailsService.setTeamsInScenario(getScenarioId(), initData.TeamsInScenario);
|
|
|
|
var expendituresLoadTask = dataSources.load();
|
|
var dataSourcesLoadTask = scenarioDetailsService.loadDataSources();
|
|
|
|
$q.all([expendituresLoadTask, dataSourcesLoadTask])
|
|
.then(function (asyncResults) {
|
|
$scope.data.Teams = asyncResults[1].teams;
|
|
$scope.data.CGEFX = asyncResults[1].cgefx;
|
|
$scope.data.CategoryTypes = asyncResults[1].categoryTypes;
|
|
$scope.data.CreditDepartments = asyncResults[1].creditDepartments;
|
|
$scope.data.GLAccounts = asyncResults[1].glAccounts;
|
|
$scope.data.IncomeTypes = asyncResults[1].incomeTypes;
|
|
|
|
if (initData.InitOnDemand !== true)
|
|
$scope.getCalendar(fnCallback);
|
|
})
|
|
.then(null, function () { showErrorModal('Oops!', $scope.CommonErrorMessage); });
|
|
};
|
|
$scope.getCalendar = function (fnCallback, fnRightAfterLoadCallback) {
|
|
if ($scope.data.Scenario.StartDate <= 0 || $scope.data.Scenario.EndDate <= 0)
|
|
return;
|
|
blockUI();
|
|
|
|
// maybe it is temp solution
|
|
var dataCopy = angular.copy($scope.data);
|
|
var snapshot = {
|
|
recalculationModel: {
|
|
Scenario: $scope.data.Scenario,
|
|
AvailableExpenditures: $scope.data.AvailableExpenditures,
|
|
TeamsInScenario: scenarioDetailsService.getTeamsInScenarioArray(getScenarioId()),
|
|
Rates: scenarioDetailsService.getScenarioRates(getScenarioId()),
|
|
NeedToRebind: $scope.data.NeedToRebind,
|
|
NeedToRecalculateScenarioDetails: $scope.data.NeedToRecalculateScenarioDetails,
|
|
LocalRatesLoaded: $scope.data.LocalRatesLoaded,
|
|
Calendar: {
|
|
Expenditures: scenarioDetailsService.getExpenditures(getScenarioId())
|
|
},
|
|
filterStartDate: $scope.data.Scenario.StartDate,
|
|
filterEndDate: $scope.data.Scenario.EndDate
|
|
|
|
}};
|
|
|
|
var allExpenditures = dataSources.getExpenditures();
|
|
//$scope.data = null;
|
|
$http.post('/Scenarios/RecalculateCalendar', snapshot).
|
|
success(function (data, status, headers, config) {
|
|
try {
|
|
if (data.NeedToRebind) {
|
|
var adjustMarginRased = data.Scenario.AdjustMarginRased;
|
|
var dateForStartOfChangesOld = $scope.data.Scenario.DateForStartOfChangesOld;
|
|
|
|
$scope.data.AvailableExpenditures = data.AvailableExpenditures;
|
|
$scope.data.Calendar = data.Calendar;
|
|
$scope.data.Scenario = data.Scenario;
|
|
|
|
$scope.data.Scenario.AdjustMarginRased = adjustMarginRased;
|
|
$scope.data.Scenario.DateForStartOfChangesOld = dateForStartOfChangesOld;
|
|
|
|
$scope.data.Expenditures2Add = data.Expenditures2Add;
|
|
$scope.data.NeedToRebind = false;
|
|
|
|
$scope.data.ScenarioGeneralInfoEditModel.ProjectDeadline = data.Scenario.ProjectDeadline;
|
|
$scope.data.ScenarioGeneralInfoEditModel.ActualStartDate = data.Scenario.ActualStartDate;
|
|
$scope.data.ScenarioGeneralInfoEditModel.ActualEndDate = data.Scenario.ActualEndDate;
|
|
$scope.data.ScenarioGeneralInfoEditModel.HasActuals = !!data.Scenario.ActualEndDate ? 1 : 0;
|
|
$scope.data.WorkFlowActions = data.WorkFlowActions;
|
|
$scope.data.WorkFlowStates = data.WorkFlowStates;
|
|
|
|
if ($scope.WorkFlowSchema == null || data.WorkFlowSchema != null)
|
|
$scope.WorkFlowSchema = data.WorkFlowSchema;
|
|
for (var i = 0; i < $scope.data.Expenditures2Add.length; i++) {
|
|
var exp = $scope.data.Expenditures2Add[i];
|
|
exp.GroupElement = $scope.data.ExpenditureAddGroups[exp.IsRelatedToScenario];
|
|
}
|
|
|
|
} else {
|
|
var adjustMarginRased = data.Scenario.AdjustMarginRased;
|
|
var dateForStartOfChangesOld = $scope.data.Scenario.DateForStartOfChangesOld;
|
|
|
|
$scope.data = dataCopy;
|
|
$scope.data.Scenario = data.Scenario;
|
|
|
|
$scope.data.Scenario.AdjustMarginRased = adjustMarginRased;
|
|
$scope.data.Scenario.DateForStartOfChangesOld = dateForStartOfChangesOld;
|
|
|
|
$scope.data.AvailableExpenditures = data.AvailableExpenditures;
|
|
$scope.data.NeedToRecalculateScenarioDetails = data.NeedToRecalculateScenarioDetails;
|
|
$scope.data.Calendar = data.Calendar;
|
|
}
|
|
|
|
if (!data.Calendar || !data.Calendar.Headers || !data.Calendar.Expenditures) {
|
|
$scope.data.Scenario.NeedToAdjustMargin = false;
|
|
unblockUI();
|
|
$document.trigger('sd.dataload.completed', $scope.getTotalCostsByMonthes());
|
|
return;
|
|
}
|
|
|
|
// Loading scenario resources info
|
|
var teamIds = [];
|
|
for (var index = 0; index < data.TeamsInScenario.length; index++) {
|
|
teamIds.push(data.TeamsInScenario[index].TeamId);
|
|
}
|
|
|
|
dataSources.loadResourcesByTeamIds(teamIds).then(function (result) {
|
|
$scope.data.Calendar.WeekHeaders = $scope.data.Calendar.Headers[C_HEADER_PERIOD_WEEK];
|
|
$scope.data.Calendar.MonthHeaders = $scope.data.Calendar.Headers[C_HEADER_PERIOD_MONTH];
|
|
|
|
if ($scope.data.CalendarFilter.ShowActuals)
|
|
$scope.data.CalendarFilter.ViewModeName = C_CALENDAR_VIEW_MODE_ACTUALS;
|
|
else
|
|
$scope.data.CalendarFilter.ViewModeName = C_CALENDAR_VIEW_MODE_FORECAST;
|
|
|
|
if (angular.isFunction(fnRightAfterLoadCallback))
|
|
fnRightAfterLoadCallback();
|
|
|
|
var scenarioWeekendings = $scope.getScenarioWeekEndings();
|
|
scenarioDetailsService.recalculateResourceAvailability4Categories(data.Calendar.Expenditures, scenarioWeekendings, allExpenditures);
|
|
scenarioDetailsService.setTeamsInScenario(getScenarioId(), data.TeamsInScenario);
|
|
scenarioDetailsService.setExpenditures(getScenarioId(), data.Calendar.Expenditures);
|
|
scenarioDetailsService.setRates(getScenarioId(), data.Rates);
|
|
|
|
$scope.switchViewMode();
|
|
initGraphData();
|
|
|
|
$scope.RestTeams = getRestTeams();
|
|
|
|
// if it is top-down scenario
|
|
if (!$scope.data.Scenario.IsBottomUp) {
|
|
$scope.initGridRows();
|
|
$scope.setGridSource();
|
|
initReallocatorModel();
|
|
initFormatCells();
|
|
initEditTotalModel();
|
|
initPushPullerMaxWeeks();
|
|
$scope.RefreshCSSClasses();
|
|
|
|
if ($scope.data.Scenario.NeedToAdjustMargin) {
|
|
if ($scope.data.Scenario.ProjectedRevenue > 0) {
|
|
if ((Math.round($scope.data.Calendar.Rows[0].GrandTotalCost * 100) / 100) > (Math.round($scope.data.Scenario.TDDirectCosts * 100) / 100))
|
|
$scope.IsPossibleMarginDesignated = false;
|
|
}
|
|
$scope.data.Scenario.NeedToAdjustMargin = false;
|
|
}
|
|
|
|
if (angular.isFunction(fnCallback))
|
|
fnCallback();
|
|
}
|
|
else {
|
|
var eventData = {
|
|
ScenarioId: getScenarioId(),
|
|
MonthHeaders: angular.copy($scope.data.Calendar.MonthHeaders),
|
|
WeekHeaders: angular.copy($scope.data.Calendar.WeekHeaders),
|
|
CalendarFilter: angular.copy($scope.data.CalendarFilter)
|
|
};
|
|
|
|
if (angular.isFunction(fnCallback)) {
|
|
var result = fnCallback();
|
|
if (result && angular.isFunction(result.then))
|
|
result.then(function () { $scope.$broadcast('refreshScenarioDetailsGrid', eventData); });
|
|
else
|
|
$scope.$broadcast('refreshScenarioDetailsGrid', eventData);
|
|
}
|
|
else {
|
|
$scope.$broadcast('refreshScenarioDetailsGrid', eventData);
|
|
}
|
|
}
|
|
|
|
$document.trigger('sd.dataload.completed', $scope.getTotalCostsByMonthes());
|
|
unblockUI();
|
|
//console.log('load data ended ' + new Date());
|
|
//timeTracker.startTrack('gridRender');
|
|
});
|
|
} catch (e) {
|
|
console.log(e);
|
|
unblockUI();
|
|
showErrorModal('Oops!', $scope.CommonErrorMessage);
|
|
}
|
|
}).
|
|
error(function (data, status, headers, config) {
|
|
// called asynchronously if an error occurs
|
|
// or server returns response with an error status.
|
|
console.log("an error occurred while loading calendar");
|
|
unblockUI();
|
|
showErrorModal('Oops!', $scope.CommonErrorMessage);
|
|
});
|
|
};
|
|
function initReallocatorModel() {
|
|
if ($scope.data.AvailableExpenditures && $scope.data.AvailableExpenditures.length > 0) {
|
|
$scope.reallocator.SourceExpCat = $scope.data.AvailableExpenditures[0].Id;
|
|
$scope.reallocator.TargetExpCat = $scope.data.AvailableExpenditures.length > 1 ? $scope.data.AvailableExpenditures[1].Id : null;
|
|
$scope.reallocator.OtherCurve = $scope.data.AvailableExpenditures[0].Id;
|
|
}
|
|
};
|
|
function initGraphData() {
|
|
$scope.GraphFilter.ExpCats = {};
|
|
var expenditures = scenarioDetailsService.getExpenditures(getScenarioId());
|
|
$.each(expenditures, function (expCatId, ecg) {
|
|
$scope.GraphFilter.ExpCats[expCatId] = true;
|
|
});
|
|
if ($scope.refreshGraph) {
|
|
$scope.showGraph();
|
|
$scope.refreshGraph = false;
|
|
}
|
|
};
|
|
function initFormatCells() {
|
|
var visibleHeaders = $scope.data.Calendar.WeekHeaders.filter(function (obj) {
|
|
return obj.Visible[$scope.data.CalendarFilter.ViewModeName];
|
|
});
|
|
if (visibleHeaders.length > 0) {
|
|
// apply first column values to modal form's models
|
|
$scope.reallocator.SourceWeekEnding = visibleHeaders[0].Title;
|
|
var dt = new Date(visibleHeaders[0].Milliseconds);
|
|
$scope.formatCells.InitStartDate = (dt.getUTCMonth() + 1) + "/" + dt.getUTCDate() + "/" + dt.getUTCFullYear();
|
|
$scope.formatCells.StartDate = $scope.formatCells.InitStartDate;
|
|
|
|
// apply second column values to modal form's models
|
|
if (visibleHeaders.length > 1) {
|
|
$scope.reallocator.TargetWeekEnding = visibleHeaders[visibleHeaders.length - 2].Title;
|
|
dt = new Date(visibleHeaders[visibleHeaders.length - 2].Milliseconds);
|
|
$scope.formatCells.InitEndDate = (dt.getUTCMonth() + 1) + "/" + dt.getUTCDate() + "/" + dt.getUTCFullYear();
|
|
$scope.formatCells.EndDate = $scope.formatCells.InitEndDate;
|
|
}
|
|
}
|
|
};
|
|
function initEditTotalModel() {
|
|
if ($scope.data.AvailableExpenditures && $scope.data.AvailableExpenditures.length > 0) {
|
|
$scope.editTotal = {
|
|
ExpCat: $scope.data.AvailableExpenditures[0].Id,
|
|
OtherCurve: $scope.data.AvailableExpenditures[0].Id
|
|
};
|
|
}
|
|
else {
|
|
$scope.editTotal = {};
|
|
}
|
|
};
|
|
function initPushPullerMaxWeeks() {
|
|
var numberOfWeeks = 0;
|
|
for (var i = 0; i < $scope.data.Calendar.WeekHeaders.length; i++) {
|
|
if ($scope.data.Calendar.WeekHeaders[i].DataType == C_HEADER_DATA_TYPE_ORDINAL &&
|
|
$scope.data.Calendar.WeekHeaders[i].Visible[$scope.data.CalendarFilter.ViewModeName])
|
|
numberOfWeeks++;
|
|
}
|
|
|
|
$scope.pushpuller.maxWeeks = numberOfWeeks - 1;
|
|
};
|
|
$scope.onPercentageChange = function (val) {
|
|
$scope.reallocator.Percentage = val;
|
|
};
|
|
$scope.checkValue = function (data, rowId, colIndex) {
|
|
var newValue = parseFloat(data);
|
|
if (isNaN(newValue))
|
|
newValue = 0;
|
|
if (newValue < 0) {
|
|
return "Value should not be less than zero";
|
|
}
|
|
|
|
var isMonth = $scope.data.Calendar.WeekHeaders[colIndex].DataType == C_HEADER_DATA_TYPE_TOTALS;
|
|
|
|
for (var i = 0; i < $scope.data.Calendar.Rows.length; i++) {
|
|
if ($scope.data.Calendar.Rows[i].ExpCatId === rowId) {
|
|
var ec = $scope.data.Calendar.Rows[i];
|
|
|
|
if (isMonth) {
|
|
var ec_val = $scope.data.CalendarFilter.IsTableModeQuantity ? ec.QuantityValues[colIndex] : ec.CostValues[colIndex];
|
|
|
|
var weekHeader = $scope.data.Calendar.WeekHeaders[colIndex];
|
|
var monthHeaderIndex = weekHeader.MonthHeader;
|
|
var weeks = $scope.data.Calendar.MonthHeaders[monthHeaderIndex].WeekHeaders.length;
|
|
|
|
var oldValue = ec_val;
|
|
|
|
var avg = ($scope.data.CalendarFilter.IsTableModeQuantity && $scope.data.CalendarFilter.ShowAvgTotals && !$scope.data.CalendarFilter.IsUOMHours);
|
|
var coef = avg ? (newValue / oldValue) : (ec_val > 0 ? newValue / ec_val : 1);
|
|
|
|
for (var j = colIndex - 1; j >= 0 && ($scope.data.Calendar.WeekHeaders[j].DataType == C_HEADER_DATA_TYPE_ORDINAL && $scope.data.Calendar.WeekHeaders[j].Visible[$scope.data.CalendarFilter.ViewModeName]) ; j--) {
|
|
//When old month is zero spread equally, othewise use current curve
|
|
var val = avg ? (ec_val > 0 ? ec.QuantityValues[j] * coef : newValue)
|
|
: (ec_val > 0 ? ($scope.data.CalendarFilter.IsTableModeQuantity ? ec.QuantityValues[j] : ec.CostValues[j]) * coef :
|
|
newValue / weeks);
|
|
if ($scope.data.CalendarFilter.IsTableModeQuantity) {
|
|
updateQuantityOrCostCell(rowId, i, j, val, null, true);
|
|
} else {
|
|
updateQuantityOrCostCell(rowId, i, j, null, val, false);
|
|
}
|
|
}
|
|
} else {
|
|
if ($scope.data.CalendarFilter.IsTableModeQuantity) {
|
|
updateQuantityOrCostCell(rowId, i, colIndex, newValue, null, true);
|
|
} else {
|
|
updateQuantityOrCostCell(rowId, i, colIndex, null, newValue, false);
|
|
}
|
|
}
|
|
|
|
$.each(ec.Teams, function (teamId, team) {
|
|
if (ec.Changed) {
|
|
team.Changed = true;
|
|
}
|
|
});
|
|
break;
|
|
}
|
|
}
|
|
$scope.RefreshCSSClasses();
|
|
//required to be false by xeditable grid
|
|
return false;
|
|
};
|
|
$scope.checkTotalValue = function (data, row, sourceRowIndex) {
|
|
var newValue = parseFloat(data);
|
|
if (isNaN(newValue))
|
|
newValue = 0;
|
|
if (newValue < 0) {
|
|
return "Value should not be less than zero";
|
|
}
|
|
|
|
if (null === sourceRowIndex) {
|
|
alert('Cannot find expenditure category to update.');
|
|
return false;
|
|
}
|
|
|
|
var oldQuantity = 0;
|
|
var oldCost = 0;
|
|
var oldValue = ($scope.data.CalendarFilter.IsTableModeQuantity ? row.GrandTotalQuantity : row.GrandTotalCost);
|
|
var changeMult = oldValue != 0 ? parseFloat(newValue) / oldValue : 0;
|
|
|
|
var sourceSumCost = 0, sourceSumQuantity = 0;
|
|
var numberOfWeeks = 0;
|
|
for (var i = 0; i < $scope.data.Calendar.WeekHeaders.length; i++) {
|
|
if ($scope.data.Calendar.WeekHeaders[i].DataType == C_HEADER_DATA_TYPE_TOTALS || !$scope.data.Calendar.WeekHeaders[i].Visible[$scope.data.CalendarFilter.ViewModeName])
|
|
continue;
|
|
sourceSumQuantity += $scope.data.Calendar.Rows[sourceRowIndex].QuantityValues[i];
|
|
sourceSumCost += $scope.data.Calendar.Rows[sourceRowIndex].CostValues[i];
|
|
numberOfWeeks++;
|
|
}
|
|
|
|
if (isAvgMode() && numberOfWeeks > 0) {
|
|
sourceSumQuantity = sourceSumQuantity / numberOfWeeks;
|
|
}
|
|
|
|
//console.log("sourceSumQuantity: " + sourceSumQuantity + "; curveSumQuantity: " + curveSumQuantity + "; changeMult: " + changeMult);
|
|
if (sourceSumQuantity > 0)
|
|
for (var i = 0; i < $scope.data.Calendar.WeekHeaders.length; i++) {
|
|
if ($scope.data.Calendar.WeekHeaders[i].DataType == C_HEADER_DATA_TYPE_TOTALS ||
|
|
!$scope.data.Calendar.WeekHeaders[i].Visible[$scope.data.CalendarFilter.ViewModeName]) {
|
|
continue;
|
|
}
|
|
oldQuantity = $scope.data.Calendar.Rows[sourceRowIndex].QuantityValues[i];
|
|
oldCost = $scope.data.Calendar.Rows[sourceRowIndex].CostValues[i];
|
|
if ($scope.data.CalendarFilter.IsTableModeQuantity)
|
|
updateQuantityOrCostCell(row.ExpCatId, sourceRowIndex, i, Math.abs(oldQuantity * changeMult), null, true);
|
|
else
|
|
updateQuantityOrCostCell(row.ExpCatId, sourceRowIndex, i, null, Math.abs(oldCost * changeMult), false);
|
|
}
|
|
else {
|
|
for (var i = 0; i < $scope.data.Calendar.WeekHeaders.length; i++) {
|
|
if ($scope.data.Calendar.WeekHeaders[i].DataType == C_HEADER_DATA_TYPE_TOTALS ||
|
|
!$scope.data.Calendar.WeekHeaders[i].Visible[$scope.data.CalendarFilter.ViewModeName]) {
|
|
continue;
|
|
}
|
|
if (isAvgMode()) {
|
|
if ($scope.data.CalendarFilter.IsTableModeQuantity)
|
|
updateQuantityOrCostCell(row.ExpCatId, sourceRowIndex, i, newValue, null, true);
|
|
else
|
|
updateQuantityOrCostCell(row.ExpCatId, sourceRowIndex, i, null, newValue, false);
|
|
} else {
|
|
if ($scope.data.CalendarFilter.IsTableModeQuantity)
|
|
updateQuantityOrCostCell(row.ExpCatId, sourceRowIndex, i, Math.abs(numberOfWeeks == 0 ? newValue : (newValue / numberOfWeeks)), null, true);
|
|
else
|
|
updateQuantityOrCostCell(row.ExpCatId, sourceRowIndex, i, null, Math.abs(numberOfWeeks == 0 ? newValue : (newValue / numberOfWeeks)), false);
|
|
}
|
|
}
|
|
}
|
|
$scope.RefreshCSSClasses();
|
|
//required to be false by xeditable grid
|
|
return false;
|
|
};
|
|
$scope.checkGrandTotalValue = function (data) {
|
|
var newValue = parseFloat(data);
|
|
if (isNaN(newValue))
|
|
newValue = 0;
|
|
if (newValue < 0) {
|
|
return "Value should not be less than zero";
|
|
}
|
|
|
|
// it is incorrect situation when rows collection contains only grand total row
|
|
if (!$scope.data.Calendar.Rows || $scope.data.Calendar.Rows.length <= 1)
|
|
return false;
|
|
|
|
var oldValue = $scope.data.CalendarFilter.IsTableModeQuantity ? $scope.data.Calendar.Rows[0].GrandTotalQuantity : $scope.data.Calendar.Rows[0].GrandTotalCost,
|
|
changed = oldValue <= 0 ? (newValue / ($scope.data.Calendar.Rows.length - 1)) : (newValue / oldValue),
|
|
distributed = 0;
|
|
|
|
for (var i = 1; i < $scope.data.Calendar.Rows.length; i++) {
|
|
var row = $scope.data.Calendar.Rows[i],
|
|
newTotalRowValue = 0;
|
|
|
|
if ((i + 1) === $scope.data.Calendar.Rows.length) {
|
|
newTotalRowValue = newValue - distributed;
|
|
} else {
|
|
if (oldValue <= 0)
|
|
newTotalRowValue = changed;
|
|
else
|
|
newTotalRowValue = (($scope.data.CalendarFilter.IsTableModeQuantity ? row.GrandTotalQuantity : row.GrandTotalCost) || 0) * changed;
|
|
}
|
|
|
|
$scope.checkTotalValue(newTotalRowValue, row, i);
|
|
|
|
distributed += newTotalRowValue;
|
|
}
|
|
|
|
$scope.RefreshCSSClasses();
|
|
//required to be false by xeditable grid
|
|
return false;
|
|
};
|
|
$scope.checkResourceTotalValue = function (data, ec, team, resource) {
|
|
if (!$scope.data.CalendarFilter.IsTableModeQuantity || !ec || !team || !resource)
|
|
return false;
|
|
|
|
var newValue = parseFloat(data);
|
|
if (isNaN(newValue))
|
|
newValue = 0;
|
|
|
|
if (newValue < 0) {
|
|
return "Value should not be less than zero";
|
|
}
|
|
|
|
var oldValue = resource.GrandTotalQuantity;
|
|
var changeMult = oldValue != 0 ? parseFloat(newValue) / oldValue : 0;
|
|
|
|
var sourceSumQuantity = 0,
|
|
numberOfWeeks = 0;
|
|
|
|
for (var i in $scope.data.Calendar.WeekHeaders) {
|
|
if ($scope.data.Calendar.WeekHeaders[i].DataType == C_HEADER_DATA_TYPE_TOTALS ||
|
|
!$scope.data.Calendar.WeekHeaders[i].Visible[$scope.data.CalendarFilter.ViewModeName] ||
|
|
resource.ReadOnly[i])
|
|
continue;
|
|
|
|
sourceSumQuantity += resource.QuantityValues[i];
|
|
numberOfWeeks++;
|
|
}
|
|
|
|
if (isAvgMode() && numberOfWeeks > 0) {
|
|
sourceSumQuantity = sourceSumQuantity / numberOfWeeks;
|
|
}
|
|
|
|
for (var i in $scope.data.Calendar.WeekHeaders) {
|
|
if ($scope.data.Calendar.WeekHeaders[i].DataType == C_HEADER_DATA_TYPE_TOTALS ||
|
|
!$scope.data.Calendar.WeekHeaders[i].Visible[$scope.data.CalendarFilter.ViewModeName] ||
|
|
resource.ReadOnly[i]) {
|
|
continue;
|
|
}
|
|
if (sourceSumQuantity > 0) {
|
|
$scope.checkResourceValue(ec, team, resource, i, Math.abs(resource.QuantityValues[i] * changeMult), false);
|
|
}
|
|
else {
|
|
if (isAvgMode())
|
|
$scope.checkResourceValue(ec, team, resource, i, newValue, false);
|
|
else
|
|
$scope.checkResourceValue(ec, team, resource, i, (numberOfWeeks == 0 ? newValue : (newValue / numberOfWeeks)), false);
|
|
}
|
|
}
|
|
$scope.RefreshCSSClasses();
|
|
//required to be false by xeditable grid
|
|
return false;
|
|
};
|
|
$scope.checkTeamTotalValue = function (data, ec, team) {
|
|
if (!$scope.data.CalendarFilter.IsTableModeQuantity || !ec || !team)
|
|
return false;
|
|
|
|
var newValue = parseFloat(data);
|
|
if (isNaN(newValue))
|
|
newValue = 0;
|
|
|
|
if (newValue < 0) {
|
|
return "Value should not be less than zero";
|
|
}
|
|
|
|
var oldValue = team.GrandTotalQuantity;
|
|
var changeMult = oldValue != 0 ? parseFloat(newValue) / oldValue : 0;
|
|
|
|
var sourceSumQuantity = 0,
|
|
numberOfWeeks = 0;
|
|
|
|
for (var i in $scope.data.Calendar.WeekHeaders) {
|
|
if ($scope.data.Calendar.WeekHeaders[i].DataType == C_HEADER_DATA_TYPE_TOTALS ||
|
|
!$scope.data.Calendar.WeekHeaders[i].Visible[$scope.data.CalendarFilter.ViewModeName])
|
|
continue;
|
|
|
|
sourceSumQuantity += team.QuantityValues[i];
|
|
numberOfWeeks++;
|
|
}
|
|
|
|
if (isAvgMode() && numberOfWeeks > 0) {
|
|
sourceSumQuantity = sourceSumQuantity / numberOfWeeks;
|
|
}
|
|
|
|
for (var i in $scope.data.Calendar.WeekHeaders) {
|
|
if ($scope.data.Calendar.WeekHeaders[i].DataType == C_HEADER_DATA_TYPE_TOTALS ||
|
|
!$scope.data.Calendar.WeekHeaders[i].Visible[$scope.data.CalendarFilter.ViewModeName]) {
|
|
continue;
|
|
}
|
|
if (sourceSumQuantity > 0) {
|
|
$scope.checkTeamValue(ec, team, i, Math.abs(team.QuantityValues[i] * changeMult), false);
|
|
}
|
|
else {
|
|
if (isAvgMode())
|
|
$scope.checkTeamValue(ec, team, i, newValue, false);
|
|
else
|
|
$scope.checkTeamValue(ec, team, i, (numberOfWeeks == 0 ? newValue : (newValue / numberOfWeeks)), false);
|
|
}
|
|
}
|
|
$scope.RefreshCSSClasses();
|
|
//required to be false by xeditable grid
|
|
return false;
|
|
};
|
|
$scope.watchKeyInput = function (t) {
|
|
clearAllSelectedCells();
|
|
|
|
$timeout(function () {
|
|
if (t.$editable.inputEl.select)
|
|
t.$editable.inputEl.select();
|
|
else if (t.$editable.inputEl.setSelectionRange)
|
|
t.$editable.inputEl.setSelectionRange(0, t.$editable.inputEl.val().length);
|
|
}, 3);
|
|
|
|
t.$editable.inputEl.on('keydown', function (e) {
|
|
if (e.which == 9) { //when tab key is pressed
|
|
e.preventDefault();
|
|
var tab2Cell;
|
|
if (e.shiftKey) { // when shift + tab use with 'onblur' set to 'submit' for automatic submission find the parent of the editable before this one in the markup grab the editable and display it
|
|
tab2Cell = $(this).parentsUntil('table#table').prevAll(":has(.editable:visible):first").find(".editable:visible:last");
|
|
t.$form.$submit();
|
|
$timeout(function () {
|
|
tab2Cell.click();
|
|
}, 0);
|
|
} else { // when just tab use with 'onblur' set to 'submit' for automatic submission find the parent of the editable after this one in the markup grab the editable and display it
|
|
tab2Cell = $(this).parentsUntil('table#table').nextAll(":has(.editable:visible):first").find(".editable:visible:first");
|
|
t.$form.$submit();
|
|
$timeout(function () {
|
|
tab2Cell.click();
|
|
}, 0);
|
|
}
|
|
}
|
|
});
|
|
};
|
|
$scope.onTxtBlur = function (txt) {
|
|
txt.$form.$submit();
|
|
};
|
|
$scope.gridChanged = function () {
|
|
$scope.data.isDataChanged = true;
|
|
if (typeof onDataChanged === 'function') {
|
|
onDataChanged();
|
|
}
|
|
$scope.$emit('gridChanged');
|
|
};
|
|
$scope.gridSaved = function () {
|
|
$scope.data.isDataChanged = false;
|
|
if (typeof onDataSaved === 'function') {
|
|
onDataSaved();
|
|
}
|
|
};
|
|
|
|
function updateQuantityOrCostCell(expCatId, rowIndex, colIndex, newQuantity, newCost, isUpdateQuantity) {
|
|
/* isUpdateQuantity - indicates whether to update by quantity or by cost.
|
|
* true - Quantity will be updated from newQuantity, Cost will be recalculated as newQuantity * rate
|
|
* false - Cost will be updated from newCost, Quantity will be recalculated as newCost / rate
|
|
* null - Quantity will be updated from newQuantity, Cost will be updated from newCost
|
|
*/
|
|
|
|
// DO NOT UPDATE ACTUALS CELLS
|
|
if (!$scope.data.Calendar.WeekHeaders[colIndex].Visible[$scope.data.CalendarFilter.ViewModeName])
|
|
return;
|
|
var dec = Math.pow(10, true === isUpdateQuantity ? parseInt($scope.formatCells.DecimalCalcQtyPlaces) : parseInt($scope.formatCells.DecimalCalcCostPlaces));
|
|
|
|
var oldQuantity = $scope.data.Calendar.Rows[rowIndex].QuantityValues[colIndex];
|
|
var oldCost = $scope.data.Calendar.Rows[rowIndex].CostValues[colIndex];
|
|
var weekEnding = $scope.data.Calendar.WeekHeaders[colIndex].Milliseconds;
|
|
var rawCategory = scenarioDetailsService.getCategoryInScenario(getScenarioId(), expCatId);
|
|
|
|
if (true === isUpdateQuantity) { // update quantity, recalculate cost based on exp cat rate
|
|
if (oldQuantity === newQuantity || newQuantity < 0)
|
|
return;
|
|
newCost = Math.round(newQuantity * $scope.getRate(expCatId, weekEnding) * dec) / dec;
|
|
} else if (false === isUpdateQuantity) { // update cost, recalculate quantity based on exp cat rate
|
|
if (oldCost === newCost || newCost < 0)
|
|
return;
|
|
newQuantity = Math.round(newCost / $scope.getRate(expCatId, weekEnding) * dec) / dec;
|
|
}
|
|
// update modified cell
|
|
$scope.data.Calendar.Rows[rowIndex].RestQuantity[colIndex] += newQuantity - $scope.data.Calendar.Rows[rowIndex].QuantityValues[colIndex];
|
|
$scope.data.Calendar.Rows[rowIndex].CostValues[colIndex] = newCost;
|
|
$scope.data.Calendar.Rows[rowIndex].QuantityValues[colIndex] = newQuantity;
|
|
|
|
rawCategory.Details[weekEnding].ForecastQuantity = newQuantity / $scope.getUomMultiplier(expCatId);
|
|
rawCategory.Details[weekEnding].ForecastCost = newCost;
|
|
rawCategory.Details[weekEnding].Changed = true;
|
|
|
|
// update column totals
|
|
$scope.data.Calendar.Rows[0].CostValues[colIndex] += (newCost - oldCost);
|
|
$scope.data.Calendar.Rows[0].QuantityValues[colIndex] += (newQuantity - oldQuantity);
|
|
// update month cell and month total cell
|
|
updateECTotals(rowIndex, colIndex, newQuantity, oldQuantity, newCost, oldCost);
|
|
$scope.gridChanged();
|
|
}
|
|
|
|
function calcWithSamePresicion(val1, val2, isCost) {
|
|
var dec = Math.pow(10, parseInt(isCost ? $scope.formatCells.DecimalCalcCostPlaces : $scope.formatCells.DecimalCalcQtyPlaces));
|
|
return Math.round(val1 * dec) / dec + Math.round(val2 * dec) / dec;
|
|
}
|
|
|
|
function setRowGrandTotal(row) {
|
|
if ($scope.data.CalendarFilter.IsTableModeQuantity) {
|
|
row.GrandTotal = $filter('number')(row.GrandTotalQuantity || 0, 2);
|
|
} else {
|
|
row.GrandTotal = $filter('currency')(row.GrandTotalCost || 0);
|
|
}
|
|
}
|
|
|
|
$scope.prepareWorkFlowOptionsBeforeSaving = function (event, saveAs) {
|
|
var changedExpenditures = {},
|
|
expendituresInScenario = scenarioDetailsService.getExpenditures(getScenarioId());
|
|
|
|
if (!expendituresInScenario)
|
|
return;
|
|
if (!saveAs)
|
|
{
|
|
$scope.data.Scenario.SaveAs = 0;
|
|
if ($scope.data.WorkFlowActions.length <= 1)
|
|
{
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
$scope.saveChanges();
|
|
}
|
|
} else {
|
|
$scope.data.Scenario.SaveAs = 1;
|
|
}
|
|
};
|
|
function prepareExpendituresForSaving() {
|
|
var changedExpenditures = {},
|
|
expendituresInScenario = scenarioDetailsService.getExpenditures(getScenarioId());
|
|
|
|
if (!expendituresInScenario)
|
|
return changedExpenditures;
|
|
|
|
$.each(expendituresInScenario, function (expCatId, expCat) {
|
|
var details = {}, teams = {};
|
|
$.each(expCat.Details, function (milliseconds, detail) {
|
|
if (detail.Changed) {
|
|
details[milliseconds] = detail;
|
|
|
|
}
|
|
});
|
|
$.each(expCat.Teams, function (teamId, team) {
|
|
if (team.Changed) {
|
|
teams[teamId] = {
|
|
Id: team.Id,
|
|
Name: team.Name,
|
|
QuantityValues: team.QuantityValues,
|
|
Changed: team.Changed,
|
|
Resources: {}
|
|
};
|
|
$.each(team.Resources, function (resourceId, resource) {
|
|
if (resource.Changed)
|
|
teams[teamId].Resources[resourceId] = resource;
|
|
});
|
|
}
|
|
|
|
});
|
|
if (Object.keys(details).length > 0 || Object.keys(teams).length > 0) {
|
|
changedExpenditures[expCatId] = angular.copy(expCat);
|
|
changedExpenditures[expCatId].Details = details;
|
|
changedExpenditures[expCatId].Teams = teams;
|
|
}
|
|
|
|
var isAvailable = false;
|
|
|
|
for (var index = 0; index < $scope.data.AvailableExpenditures.length; index++) {
|
|
if ($scope.data.AvailableExpenditures[index].Id == expCatId) {
|
|
isAvailable = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!isAvailable) {
|
|
var ecItem = new Object();
|
|
ecItem.CategoriesCount = 0;
|
|
ecItem.Id = expCatId;
|
|
ecItem.Name = expCat.ExpenditureCategoryName
|
|
|
|
$scope.data.AvailableExpenditures[$scope.data.AvailableExpenditures.length] = ecItem;
|
|
}
|
|
});
|
|
return changedExpenditures;
|
|
};
|
|
$scope.getData4Saving = function () {
|
|
return {
|
|
Scenario: $scope.data.Scenario,
|
|
AvailableExpenditures: $scope.data.AvailableExpenditures,
|
|
TeamsInScenario: scenarioDetailsService.recalculateTeamsAllocation(getScenarioId()),
|
|
Calendar: {
|
|
Expenditures: prepareExpendituresForSaving()
|
|
},
|
|
WorkFlowSchema:$scope.WorkFlowSchema,
|
|
WorkFlowActions: $scope.data.WorkFlowActions,
|
|
WorkFlowStates: $scope.data.WorkFlowStates,
|
|
Notes: $scope.NotesToAdd,
|
|
NotesToDelete: $scope.NotesToDelete,
|
|
Dependencies: $scope.Dependencies,
|
|
Audit: $scope.data.Audit
|
|
};
|
|
};
|
|
$scope.isValidAllocation = function () {
|
|
return !$scope.ExpendituresIsOverAllocated && !$scope.TeamsIsOverAllocated;
|
|
};
|
|
$scope.showWarningBlock = function () {
|
|
return $scope.gridWasChanged() || $scope.ExpendituresIsUnderAllocated || !$scope.IsPossibleMarginDesignated;
|
|
};
|
|
$scope.showMessagePanel = function () {
|
|
return !$scope.isValidAllocation() || $scope.showWarningBlock();
|
|
};
|
|
$scope.saveChanges = function (saveAsDraft, saveAs) {
|
|
|
|
if (saveAs != null && saveAs != undefined)
|
|
$scope.data.Scenario.SaveAs = saveAs;
|
|
|
|
// 0 - Update, 1 - New
|
|
if ($scope.data.Scenario.SaveAs != 0 && $scope.data.Scenario.SaveAs != 1)
|
|
return;
|
|
|
|
// we should allow to save scenario as draft even if it is over allocated, because drafts save without allocations
|
|
if (!saveAsDraft && !$scope.isValidAllocation())
|
|
return;
|
|
|
|
if (saveAsDraft != null && saveAsDraft != undefined)
|
|
$scope.data.Scenario.SaveAsDraft = saveAsDraft;
|
|
|
|
blockUI();
|
|
var dataToSave = $scope.getData4Saving();
|
|
$http.post('/Scenarios/SaveSnapshotChanges', JSON.stringify(dataToSave)).
|
|
success(function (data, status, headers, config) {
|
|
try {
|
|
$scope.gridSaved();
|
|
// 'details'
|
|
var openurl = window.location.href;
|
|
if (1 === $scope.Opener ) {
|
|
if (data && data.ScenarioId)
|
|
window.location.href = window.location.origin + '/Scenarios/Details/' + data.ScenarioId + '?ptab=Details'; // window.location.href.replace(new RegExp(/\w{8}-(\w{4}-){3}\w{12}/), data.ScenarioId) // replace scenario id part with the new scenario id
|
|
// .replace(new RegExp(/#\/?\w*/), ''); // cut the hash part
|
|
else
|
|
window.location.reload(true);
|
|
}
|
|
else if (0 === $scope.Opener && openurl.indexOf('Project') > 0) {
|
|
window.location.href = window.location.origin + '/Scenarios/Details/' + data.ScenarioId + '?ptab=Details';
|
|
}
|
|
else {
|
|
|
|
window.location.href = window.location.href.replace("newscenario", "scenarios").replace(new RegExp(/#\/?\w*/), '');
|
|
}
|
|
unblockUI();
|
|
} catch (e) {
|
|
unblockUI();
|
|
if (typeof (hideModals) == 'function')
|
|
hideModals();
|
|
showErrorModal('Oops!', $scope.CommonErrorMessage);
|
|
}
|
|
}).
|
|
error(function (data, status, headers, config) {
|
|
console.log("an error occurred while saving calendar changes");
|
|
unblockUI();
|
|
if (typeof (hideModals) == 'function')
|
|
hideModals();
|
|
showErrorModal('Oops!', $scope.CommonErrorMessage);
|
|
});
|
|
};
|
|
$scope.getRate = function (expCatId, date) {
|
|
return scenarioDetailsService.getRate(getScenarioId(), expCatId, date) / $scope.getUomMultiplier(expCatId);
|
|
};
|
|
$scope.toggleGraphExpCat = function (id) {
|
|
$scope.GraphFilter.ExpCats[id] = !$scope.GraphFilter.ExpCats[id];
|
|
$scope.showGraph();
|
|
};
|
|
$scope.toggleGraphValue = function () {
|
|
$scope.GraphFilter.ShowTime = !$scope.GraphFilter.ShowTime;
|
|
savePagePreferences();
|
|
$scope.showGraph();
|
|
};
|
|
$scope.toggleGraphActuals = function () {
|
|
savePagePreferences();
|
|
$scope.showGraph();
|
|
};
|
|
$scope.toggleGraphTotal = function () {
|
|
savePagePreferences();
|
|
$scope.showGraph();
|
|
};
|
|
$scope.toggleGraphQuantity = function () {
|
|
$scope.GraphFilter.Quantity = !$scope.GraphFilter.Quantity;
|
|
savePagePreferences();
|
|
$scope.showGraph();
|
|
};
|
|
$scope.graphData = [];
|
|
$scope.maxGraphValue = 0;
|
|
$scope.showGraph = function () {
|
|
// prepare data
|
|
$scope.maxGraphValue = 0;
|
|
$scope.graphData = [];
|
|
|
|
var expendituresInScenario = scenarioDetailsService.getExpenditures(getScenarioId());
|
|
if (!$scope.data.Calendar || !expendituresInScenario || Object.keys(expendituresInScenario).length <= 0)
|
|
return;
|
|
|
|
var sd = $scope.data.Scenario.StartDate;
|
|
if ($scope.data.Scenario.ActualStartDate > 0 && $scope.data.Scenario.ActualStartDate < sd)
|
|
sd = $scope.data.Scenario.ActualStartDate;
|
|
var ed = $scope.data.Scenario.EndDate;
|
|
if ($scope.data.Scenario.ActualEndDate > 0 && $scope.data.Scenario.ActualEndDate < ed)
|
|
ed = $scope.data.Scenario.ActualEndDate;
|
|
var startDate = new Date(sd);
|
|
var endDate = new Date(ed);
|
|
var days = (endDate - startDate) / (1000 * 60 * 60 * 24);
|
|
|
|
var expCatData;
|
|
var expCatActualsData;
|
|
var actualsData = new Array();
|
|
var firstpass = true;
|
|
|
|
var expenditures = Object.keys(expendituresInScenario);
|
|
for (var i = 0; i < expenditures.length; i++) {//ExpCat Iteration
|
|
var ExpCatId = expenditures[i];
|
|
var uomMultiplier = $scope.getUomMultiplier(ExpCatId) || 1;
|
|
|
|
if (expCatData == undefined || !$scope.GraphFilter.ShowTotal) {
|
|
expCatData = new Array();
|
|
}
|
|
if (!expCatActualsData) expCatActualsData = new Array();
|
|
if (!$scope.GraphFilter.ExpCats[ExpCatId]) continue;
|
|
var currentIndex = 0;
|
|
for (var colIndex = 0; colIndex < $scope.data.Calendar.WeekHeaders.length; colIndex++) {//Dates Iteration
|
|
if ($scope.data.Calendar.WeekHeaders[colIndex].DataType == C_HEADER_DATA_TYPE_TOTALS ||
|
|
!$scope.data.Calendar.WeekHeaders[colIndex].Visible[$scope.data.CalendarFilter.ViewModeName])
|
|
continue;
|
|
|
|
if (!$scope.GraphFilter.ShowTotal || firstpass)
|
|
expCatData[currentIndex] = new Array();
|
|
|
|
var weekEnding = $scope.data.Calendar.WeekHeaders[colIndex].Milliseconds;
|
|
expCatData[currentIndex][0] = weekEnding;
|
|
if (!$scope.GraphFilter.ShowTotal || firstpass) {//if we are not calculating totals or it is the first pass
|
|
if (!$scope.data.CalendarFilter.IsTableModeQuantity)
|
|
expCatData[currentIndex][1] = expendituresInScenario[ExpCatId].Details[weekEnding].ForecastCost || 0;
|
|
else if ($scope.data.CalendarFilter.IsUOMHours)
|
|
expCatData[currentIndex][1] = expendituresInScenario[ExpCatId].Details[weekEnding].ForecastQuantity || 0;
|
|
else
|
|
expCatData[currentIndex][1] = expendituresInScenario[ExpCatId].Details[weekEnding].ForecastQuantity * uomMultiplier || 0;
|
|
}
|
|
else {
|
|
if (!$scope.data.CalendarFilter.IsTableModeQuantity)
|
|
expCatData[currentIndex][1] = +expCatData[currentIndex][1] + (+expendituresInScenario[ExpCatId].Details[weekEnding].ForecastCost || 0);
|
|
else if ($scope.data.CalendarFilter.IsUOMHours)
|
|
expCatData[currentIndex][1] = +expCatData[currentIndex][1] + (+expendituresInScenario[ExpCatId].Details[weekEnding].ForecastQuantity || 0);
|
|
else
|
|
expCatData[currentIndex][1] = +expCatData[currentIndex][1] + (+expendituresInScenario[ExpCatId].Details[weekEnding].ForecastQuantity * uomMultiplier || 0);
|
|
}
|
|
if ($scope.GraphFilter.Actuals) {
|
|
if (firstpass || !expCatActualsData[currentIndex]) expCatActualsData[currentIndex] = new Array();
|
|
expCatActualsData[currentIndex][0] = weekEnding;
|
|
var value = (true !== $scope.data.CalendarFilter.IsTableModeQuantity) ?
|
|
expendituresInScenario[ExpCatId].Details[weekEnding].ActualsCost :
|
|
expendituresInScenario[ExpCatId].Details[weekEnding].ActualsQuantity;
|
|
if (firstpass) {
|
|
if (true !== $scope.data.CalendarFilter.IsTableModeQuantity) {
|
|
expCatActualsData[currentIndex][1] = value;
|
|
} else {
|
|
if ($scope.data.CalendarFilter.IsUOMHours)
|
|
expCatActualsData[currentIndex][1] = value;
|
|
else
|
|
expCatActualsData[currentIndex][1] = value ? value * uomMultiplier : null;
|
|
}
|
|
}
|
|
else {
|
|
if (true !== $scope.data.CalendarFilter.IsTableModeQuantity) {
|
|
expCatActualsData[currentIndex][1] = (!expCatActualsData[currentIndex][1] && !value) ? null : +expCatActualsData[currentIndex][1] + value;
|
|
} else {
|
|
if ($scope.data.CalendarFilter.IsUOMHours)
|
|
expCatActualsData[currentIndex][1] = (!expCatActualsData[currentIndex][1] && !value) ? null : +expCatActualsData[currentIndex][1] + value;
|
|
else
|
|
expCatActualsData[currentIndex][1] = (!expCatActualsData[currentIndex][1] && !value) ? null : +expCatActualsData[currentIndex][1] + (value ? value * uomMultiplier : null);
|
|
}
|
|
}
|
|
}
|
|
//todo: ask if we need to show avg data. If yes then we should manually get value but not from display data source (Cells)
|
|
if (expCatData[currentIndex][1] > $scope.maxGraphValue)
|
|
$scope.maxGraphValue = expCatData[currentIndex][1];
|
|
if (expCatData[currentIndex] && expCatData[currentIndex][1])
|
|
expCatData[currentIndex][1] = expCatData[currentIndex][1].toFixed(2);
|
|
if ($scope.GraphFilter.Actuals) {
|
|
if (expCatActualsData[currentIndex] && expCatActualsData[currentIndex][1])
|
|
expCatActualsData[currentIndex][1] = expCatActualsData[currentIndex][1].toFixed(2);
|
|
}
|
|
currentIndex++;
|
|
}
|
|
var lastIndex = -1;
|
|
var firstIndex = -1;
|
|
for (var index in expCatActualsData) {
|
|
if (expCatActualsData[index][1]) {
|
|
if (firstIndex == -1)
|
|
firstIndex = index;
|
|
lastIndex = index;
|
|
}
|
|
}
|
|
if (firstIndex == -1)
|
|
firstIndex = 0;
|
|
if (lastIndex == -1)
|
|
lastIndex = firstIndex;
|
|
for (var index = firstIndex; index <= lastIndex; index++) {
|
|
if (expCatActualsData[index] && expCatActualsData[index].length > 0 && !expCatActualsData[index][1])
|
|
expCatActualsData[index][1] = 0;
|
|
}
|
|
|
|
if (!$scope.GraphFilter.ShowTotal || i == expenditures.length - 1) {
|
|
$scope.graphData[$scope.graphData.length] = {
|
|
label: ($scope.GraphFilter.ShowTotal) ? 'Total' : expendituresInScenario[ExpCatId].ExpenditureCategoryName,
|
|
data: expCatData,
|
|
lines: { show: true, fill: true, steps: false },
|
|
filledPoints: true,
|
|
stack: true
|
|
};
|
|
if ($scope.GraphFilter.Actuals) {
|
|
$scope.graphData[$scope.graphData.length] = {
|
|
label: ($scope.GraphFilter.ShowTotal) ? 'Total - Actuals' : expendituresInScenario[ExpCatId].ExpenditureCategoryName + ' - Actuals',
|
|
data: jQuery.extend(true, [], expCatActualsData),
|
|
lines: { show: true, fill: false, steps: false },
|
|
filledPoints: true,
|
|
stack: false
|
|
};
|
|
}
|
|
}
|
|
firstpass = false;
|
|
}
|
|
|
|
|
|
$timeout(function () {
|
|
$scope.initGraph()
|
|
});
|
|
};
|
|
|
|
$scope.initGraph = function () {
|
|
// Clear graph placeholder
|
|
$('#DetailsGraphContainer').html('<div id="divDetailsGraph"></div>');
|
|
var tickStep = 1;
|
|
if ($scope.graphData && $scope.graphData.length > 0) CalculateMonthSpread('DetailsGraphContainer', $scope.graphData[0].data);//$scope.calculateMonthSpread();
|
|
|
|
// Init Chart
|
|
$('#divDetailsGraph').pixelPlot($scope.graphData, {
|
|
//var plot = $.plot($('#jq-flot-graph2'), graphData, {
|
|
series: {
|
|
points: {
|
|
show: false
|
|
},
|
|
lines: {
|
|
show: true,
|
|
}
|
|
},
|
|
xaxis: {
|
|
mode: "time",
|
|
tickSize: [tickStep, "month"],
|
|
timeformat: "%b %Y"
|
|
}
|
|
}, {
|
|
height: 255,
|
|
width: 405,
|
|
// SA. ENV-574. Tooltips for time points. formatDateMY func is defined in _scenarioCalendar.cshtml
|
|
tooltipText: "y + ' units at ' + formatDateMY(new Date(x))"
|
|
});
|
|
};
|
|
|
|
$scope.assignRestForResource = function (row, team, resource) {
|
|
allocateResource(row, team, resource, AllocationMode.AssignRest);
|
|
};
|
|
$scope.assignAllForResource = function (row, team, resource) {
|
|
allocateResource(row, team, resource, AllocationMode.AssignAll);
|
|
};
|
|
$scope.zeroResource = function (row, team, resource) {
|
|
bootbox.confirm({
|
|
message: "Are you sure you want fill this resource quantities with 0s?",
|
|
callback: function (result) {
|
|
if (result) {
|
|
$scope.$apply(function () {
|
|
allocateResource(row, team, resource, AllocationMode.Reset);
|
|
});
|
|
}
|
|
}
|
|
});
|
|
};
|
|
$scope.removeResource = function (row, team, resource, $index) {
|
|
|
|
bootbox.confirm({
|
|
message: "Are you sure you want to remove this resource?",
|
|
callback: function (result) {
|
|
$scope.$apply(function () {
|
|
if (result) {
|
|
var resId = resource.Id;
|
|
var rawTeam = scenarioDetailsService.getTeamInScenario(getScenarioId(), row.ExpCatId, team.Id);
|
|
|
|
//Zero resource out
|
|
allocateResource(row, team, resource, AllocationMode.Reset);
|
|
|
|
//Remove resource
|
|
rawTeam.Resources[resId].Changed = true;
|
|
rawTeam.Resources[resId].Deleted = true;
|
|
rawTeam.Changed = true;
|
|
|
|
team.Resources.splice($index, 1);
|
|
team.AvailableResources = getAvailableResources(rawTeam);
|
|
refreshAssignResourceSelect(row.ExpCatId, team.Id);
|
|
};
|
|
$scope.gridChanged();
|
|
$scope.setGridSource();
|
|
});
|
|
}
|
|
});
|
|
};
|
|
$scope.assignResource = function (expCat, team, $event) {
|
|
if (expCat == null)
|
|
return;
|
|
|
|
if (!team || !team.ResourceToAssignId)
|
|
return;
|
|
|
|
var rawTeam = scenarioDetailsService.getTeamInScenario(getScenarioId(), expCat.ExpCatId, team.Id);
|
|
// first of all we should check for existence of resource in Resources and then in AllResources because if resource was removed it contains in Resources object
|
|
var rawResource = rawTeam.Resources[team.ResourceToAssignId] || rawTeam.AllResources[team.ResourceToAssignId];
|
|
if (!rawResource)
|
|
return;
|
|
|
|
for (var j = 0; j < team.Resources.length; j++) {
|
|
if (team.Resources[j].Id == rawResource.Id) {
|
|
alert("The " + rawResource.Name + " has been assigned to the " + expCat.ExpCatName);
|
|
break;
|
|
}
|
|
}
|
|
|
|
var resource = {
|
|
Id: rawResource.Id,
|
|
ExpenditureCategoryId: rawResource.ExpenditureCategoryId,
|
|
Name: rawResource.Name,
|
|
GrandTotalCost: 0,
|
|
GrandTotalQuantity: 0,
|
|
IsActiveEmployee: rawResource.IsActiveEmployee,
|
|
CapacityQuantityValues: [],
|
|
ReadOnly: [],
|
|
RestQuantityValues: [],
|
|
QuantityValues: [],
|
|
Cells: []
|
|
};
|
|
|
|
rawResource.Changed = true;
|
|
rawResource.Deleted = false;
|
|
rawTeam.Resources[rawResource.Id] = angular.copy(rawResource);
|
|
rawTeam.Changed = true;
|
|
team.Resources.push(resource);
|
|
|
|
team.ResourceToAssignId = null;
|
|
team.AvailableResources = getAvailableResources(rawTeam);
|
|
refreshAssignResourceSelect(expCat.ExpCatId, team.Id);
|
|
$scope.gridChanged();
|
|
$scope.setGridSource();
|
|
};
|
|
$scope.assignTeam = function (expCat, $event) {
|
|
if (expCat == null || expCat.TeamToAssignId == null)
|
|
return;
|
|
var teamId = expCat.TeamToAssignId;
|
|
scenarioDetailsService.addTeamInScenario(getScenarioId(), teamId);
|
|
$scope.data.NeedToRecalculateScenarioDetails = true;
|
|
$scope.data.isDataChanged = true;
|
|
$scope.gridChanged();
|
|
$scope.getCalendar();
|
|
};
|
|
|
|
|
|
$scope.checkResourceValue = function (row, team, resource, colIndex, data, refreshCss) {
|
|
var uomMultiplier = $scope.getUomMultiplier(resource.ExpenditureCategoryId);
|
|
|
|
var newValue = parseFloat(data);
|
|
if (isNaN(newValue))
|
|
newValue = 0;
|
|
|
|
if (newValue < 0) {
|
|
return "Value should not be less than zero";
|
|
}
|
|
|
|
var affectedCells = [];
|
|
var isMonth = $scope.data.Calendar.WeekHeaders[colIndex].DataType == C_HEADER_DATA_TYPE_TOTALS;
|
|
if ($scope.data.CalendarFilter.IsTableModeQuantity) {
|
|
if (isMonth) {
|
|
var ec_val = resource.QuantityValues[colIndex];
|
|
var weeks = 0;
|
|
for (var j = colIndex - 1; j >= 0 && $scope.data.Calendar.WeekHeaders[j].DataType == C_HEADER_DATA_TYPE_ORDINAL && $scope.data.Calendar.WeekHeaders[j].Visible[$scope.data.CalendarFilter.ViewModeName]; j--) {
|
|
weeks++;
|
|
}
|
|
|
|
var oldValue = ec_val;
|
|
|
|
var avg = (isAvgMode());
|
|
var coef = avg ? (newValue / oldValue) : (ec_val > 0 ? newValue / ec_val : 1);
|
|
|
|
for (var j = colIndex - 1; j >= 0 && $scope.data.Calendar.WeekHeaders[j].DataType == C_HEADER_DATA_TYPE_ORDINAL && $scope.data.Calendar.WeekHeaders[j].Visible[$scope.data.CalendarFilter.ViewModeName]; j--) {
|
|
var val = avg ? (ec_val > 0 ? resource.QuantityValues[j] * coef : newValue)
|
|
: (ec_val > 0 ? resource.QuantityValues[j] * coef : newValue / weeks);
|
|
|
|
affectedCells.push({
|
|
WeekIndex: j,
|
|
OldValue: resource.QuantityValues[j],
|
|
NewValue: val
|
|
});
|
|
}
|
|
} else {
|
|
affectedCells.push({
|
|
WeekIndex: colIndex,
|
|
OldValue: resource.QuantityValues[colIndex],
|
|
NewValue: newValue
|
|
});
|
|
}
|
|
}
|
|
|
|
if (affectedCells.length > 0) {
|
|
|
|
var rawTeam = scenarioDetailsService.getTeamInScenario(getScenarioId(), row.ExpCatId, team.Id);
|
|
// if any resource has been changed we need to mark team as changed also
|
|
rawTeam.Changed = true;
|
|
|
|
var rawResource = rawTeam.Resources[resource.Id];
|
|
for (var i = 0; i < affectedCells.length; i++) {
|
|
var cell = affectedCells[i];
|
|
|
|
resource.QuantityValues[cell.WeekIndex] = cell.NewValue;
|
|
team.AllocatedByResources[cell.WeekIndex] += (cell.NewValue - cell.OldValue);
|
|
updateTotals(resource, cell.WeekIndex, cell.NewValue);
|
|
|
|
var weekEnding = $scope.data.Calendar.WeekHeaders[cell.WeekIndex].Milliseconds;
|
|
rawResource.QuantityValues[weekEnding] = resource.QuantityValues[cell.WeekIndex] / uomMultiplier;
|
|
rawResource.Changed = true;
|
|
|
|
updateResourceRestQuantityValue(resource.Id, cell.WeekIndex, resource.RestQuantityValues[cell.WeekIndex] - (cell.NewValue - cell.OldValue));
|
|
//env-2208 start
|
|
// if it is super EC we should recalculate team and EC quantity according to resource allocations as in the case of BU scenario
|
|
//if (!row.AllowResourceAssignment) {
|
|
// recalculateBUTeamValue(row, team, cell.WeekIndex, resource.ExpenditureCategoryId);
|
|
// recalculateBUECValue(row, cell.WeekIndex);
|
|
//}
|
|
//env-2208 End
|
|
}
|
|
|
|
updateGrandTotals(resource);
|
|
}
|
|
|
|
$scope.gridChanged();
|
|
if (refreshCss)
|
|
$scope.RefreshCSSClasses();
|
|
//required to be false by xeditable grid
|
|
return false;
|
|
};
|
|
$scope.checkTeamValue = function (expCat, team, colIndex, data, refreshCss, originalExpCatId) {
|
|
var uomMultiplier = $scope.getUomMultiplier(expCat.ExpCatId);
|
|
var rawTeam = scenarioDetailsService.getTeamInScenario(getScenarioId(), expCat.ExpCatId, team.Id);
|
|
|
|
var newValue = parseFloat(data);
|
|
if (isNaN(newValue))
|
|
newValue = 0;
|
|
|
|
if (newValue < 0) {
|
|
return "Value should not be less than zero";
|
|
}
|
|
|
|
var isMonth = $scope.data.Calendar.WeekHeaders[colIndex].DataType == C_HEADER_DATA_TYPE_TOTALS;
|
|
if ($scope.data.CalendarFilter.IsTableModeQuantity) {
|
|
if (isMonth) {
|
|
var ec_val = team.QuantityValues[colIndex];
|
|
var weeks = 0;
|
|
for (var j = colIndex - 1; j >= 0 && $scope.data.Calendar.WeekHeaders[j].DataType == C_HEADER_DATA_TYPE_ORDINAL && $scope.data.Calendar.WeekHeaders[j].Visible[$scope.data.CalendarFilter.ViewModeName]; j--) {
|
|
weeks++;
|
|
}
|
|
|
|
var oldValue = ec_val;
|
|
|
|
var avg = (isAvgMode());
|
|
var coef = avg ? (newValue / oldValue) : (ec_val > 0 ? newValue / ec_val : 1);
|
|
|
|
for (var j = colIndex - 1; j >= 0 && $scope.data.Calendar.WeekHeaders[j].DataType == C_HEADER_DATA_TYPE_ORDINAL && $scope.data.Calendar.WeekHeaders[j].Visible[$scope.data.CalendarFilter.ViewModeName]; j--) {
|
|
var val = avg ? (ec_val > 0 ? team.QuantityValues[j] * coef : newValue)
|
|
: (ec_val > 0 ? team.QuantityValues[j] * coef : newValue / weeks);
|
|
updateTotals(team, j, val);
|
|
var deltaValue = val - team.QuantityValues[j];
|
|
expCat.RestQuantity[j] -= deltaValue;
|
|
team.QuantityValues[j] = val;
|
|
|
|
updateGrandTotals(team);
|
|
|
|
var weekEnding = $scope.data.Calendar.WeekHeaders[j].Milliseconds;
|
|
rawTeam.QuantityValues[weekEnding] = team.QuantityValues[j] / uomMultiplier;
|
|
rawTeam.Changed = true;
|
|
|
|
updateTeamRestQuantityValue(team.Id, originalExpCatId || expCat.ExpCatId, j, deltaValue);
|
|
}
|
|
} else {
|
|
updateTotals(team, colIndex, newValue);
|
|
var deltaValue = newValue - team.QuantityValues[colIndex];
|
|
expCat.RestQuantity[colIndex] -= deltaValue;
|
|
team.QuantityValues[colIndex] = newValue;
|
|
|
|
updateGrandTotals(team);
|
|
|
|
var weekEnding = $scope.data.Calendar.WeekHeaders[colIndex].Milliseconds;
|
|
rawTeam.QuantityValues[weekEnding] = team.QuantityValues[colIndex] / uomMultiplier;
|
|
rawTeam.Changed = true;
|
|
|
|
updateTeamRestQuantityValue(team.Id, originalExpCatId || expCat.ExpCatId, colIndex, deltaValue);
|
|
}
|
|
}
|
|
|
|
$scope.gridChanged();
|
|
if (refreshCss)
|
|
$scope.RefreshCSSClasses();
|
|
//required to be false by xeditable grid
|
|
return false;
|
|
};
|
|
function getAvailableResources(team) {
|
|
var resources = [];
|
|
if (!team || !team.AllResources)
|
|
return resources;
|
|
|
|
$.each(team.AllResources, function (resourceId, resource) {
|
|
var isExists = !!team.Resources && !!team.Resources[resourceId] && !team.Resources[resourceId].Deleted;
|
|
if (!isExists) {
|
|
var resourceExt = scenarioDetailsService.extendResourceModelWithAttributes(resource, team.Id);
|
|
|
|
if (resourceExt.IsVisible) {
|
|
resources.push({
|
|
id: resourceExt.Id,
|
|
name: resourceExt.Name,
|
|
minAvailability: resourceExt.MinAvailability,
|
|
maxAvailability: resourceExt.MaxAvailability,
|
|
avgAvailability: resourceExt.AvgAvailability
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
return resources;
|
|
};
|
|
$scope.assignRestForTeam = function (expCat, team, $event) {
|
|
$event.stopPropagation();
|
|
|
|
clearAllSelectedCells();
|
|
allocateTeam(expCat, team, AllocationMode.AssignRest);
|
|
};
|
|
$scope.assignAllForTeam = function (expCat, team, $event) {
|
|
$event.stopPropagation();
|
|
|
|
clearAllSelectedCells();
|
|
allocateTeam(expCat, team, AllocationMode.AssignAll);
|
|
};
|
|
$scope.zeroTeam = function (expCat, team, $event) {
|
|
$event.stopPropagation();
|
|
|
|
clearAllSelectedCells();
|
|
|
|
bootbox.confirm({
|
|
message: "Are you sure you want fill this team quantities with 0s?",
|
|
callback: function (result) {
|
|
if (result) {
|
|
$scope.$apply(function () {
|
|
allocateTeam(expCat, team, AllocationMode.Reset);
|
|
});
|
|
}
|
|
}
|
|
});
|
|
};
|
|
function getVisibleCellCount() {
|
|
var count = 0;
|
|
for (var i = 0; i < $scope.data.Calendar.WeekHeaders.length; i++) {
|
|
var header = $scope.data.Calendar.WeekHeaders[i];
|
|
if (header.Show)
|
|
count++;
|
|
}
|
|
$scope.data.VisibleCellCount = count;
|
|
}
|
|
$scope.getUomMultiplier = function (expCatId) {
|
|
if ($scope.data.CalendarFilter.IsUOMHours)
|
|
return 1;
|
|
|
|
var category = dataSources.getExpenditureById(expCatId);
|
|
if (!category)
|
|
return 1;
|
|
|
|
return 1.0 / category.UOMValue;
|
|
};
|
|
$scope.setGridHeaderState = function () {
|
|
if (!$scope.data.Calendar.MonthHeaders || !$scope.data.Calendar.WeekHeaders) {
|
|
return;
|
|
}
|
|
|
|
var isGridMonthViewMode = $scope.data.CalendarFilter.IsViewModeMonth;
|
|
var header;
|
|
var i;
|
|
|
|
for (i = 0; i < $scope.data.Calendar.MonthHeaders.length; i++) {
|
|
header = $scope.data.Calendar.MonthHeaders[i];
|
|
header.Show = $scope.getMonthHeaderShowStatus(header);
|
|
header.IsCollapsed = isGridMonthViewMode;
|
|
header.CollapsedClass = isGridMonthViewMode ? $scope.CollapsedIcon : $scope.NonCollapsedIcon;
|
|
header.Initialized = header.Initialized || header.Show;
|
|
}
|
|
|
|
for (i = 0; i < $scope.data.Calendar.WeekHeaders.length; i++) {
|
|
header = $scope.data.Calendar.WeekHeaders[i];
|
|
header.Show = $scope.getWeekHeaderShowStatus(header);
|
|
header.Initialized = header.Initialized || header.Show;
|
|
}
|
|
getVisibleCellCount();
|
|
};
|
|
$scope.getWeekHeaderShowStatus = function (header) {
|
|
var parentMonthHeader = $scope.data.Calendar.MonthHeaders[header.MonthHeader];
|
|
var isDisplayed = true;
|
|
|
|
if (header.DataType == C_HEADER_DATA_TYPE_ORDINAL)
|
|
// Ordinal data column
|
|
isDisplayed = header.Visible[$scope.data.CalendarFilter.ViewModeName] &&
|
|
parentMonthHeader.Visible[$scope.data.CalendarFilter.ViewModeName] &&
|
|
!parentMonthHeader.IsCollapsed;
|
|
else
|
|
// Month totals data column
|
|
isDisplayed = header.Visible[$scope.data.CalendarFilter.ViewModeName] &&
|
|
parentMonthHeader.Visible[$scope.data.CalendarFilter.ViewModeName] &&
|
|
parentMonthHeader.IsCollapsed;
|
|
return isDisplayed;
|
|
};
|
|
$scope.getMonthHeaderShowStatus = function (header) {
|
|
return header.Visible[$scope.data.CalendarFilter.ViewModeName];
|
|
};
|
|
$scope.$watch('data.CalendarFilter.IsUOMHours', function (newValue, oldValue) {
|
|
if (oldValue != newValue) {
|
|
$scope.switchUOMMode();
|
|
$scope.$broadcast('refreshUOMMode', newValue);
|
|
}
|
|
});
|
|
$scope.switchUOMMode = function () {
|
|
$scope.setGridSource();
|
|
$scope.showGraph();
|
|
};
|
|
$scope.$watch('data.CalendarFilter.IsTableModeQuantity', function (newValue, oldValue) {
|
|
if (oldValue != newValue) {
|
|
$scope.switchTableMode();
|
|
$scope.$broadcast('refreshTableMode', newValue);
|
|
}
|
|
});
|
|
$scope.switchTableMode = function () {
|
|
$scope.setGridSource();
|
|
$scope.RefreshCSSClasses();
|
|
$scope.showGraph();
|
|
//timeTracker.startTrack('gridRender');
|
|
};
|
|
$scope.$watch('data.CalendarFilter.IsViewModeMonth', function (newValue, oldValue) {
|
|
if (oldValue != newValue)
|
|
$scope.switchViewMode();
|
|
});
|
|
$scope.switchViewMode = function () {
|
|
// SA. ENV-667. Begin
|
|
$scope.setGridHeaderState();
|
|
// SA. ENV-667. End
|
|
};
|
|
$scope.$watch('data.CalendarFilter.ShowActuals', function (newValue, oldValue) {
|
|
if (oldValue != newValue) {
|
|
// SA. ENV-667
|
|
if ($scope.data.CalendarFilter.ShowActuals)
|
|
$scope.data.CalendarFilter.ViewModeName = C_CALENDAR_VIEW_MODE_ACTUALS;
|
|
else
|
|
$scope.data.CalendarFilter.ViewModeName = C_CALENDAR_VIEW_MODE_FORECAST;
|
|
|
|
$scope.switchActualsMode();
|
|
var eventData = {
|
|
ShowActuals: $scope.data.CalendarFilter.ShowActuals,
|
|
ViewModeName: $scope.data.CalendarFilter.ViewModeName
|
|
};
|
|
$scope.$broadcast('refreshActualsMode', eventData);
|
|
}
|
|
});
|
|
$scope.switchActualsMode = function () {
|
|
$scope.setGridHeaderState();
|
|
$scope.setGridSource();
|
|
$scope.RefreshCSSClasses();
|
|
};
|
|
$scope.initGridRows = function () {
|
|
$scope.data.Calendar.Rows = [];
|
|
var totalRow = {
|
|
ExpCatId: C_EMPTY_GUID,
|
|
ExpCatName: "Totals",
|
|
UseType: 3, // calculated
|
|
Visible: true
|
|
};
|
|
$scope.data.Calendar.Rows.push(totalRow);
|
|
|
|
var expendituresInScenario = scenarioDetailsService.getExpenditures(getScenarioId());
|
|
if (expendituresInScenario) {
|
|
$.each(expendituresInScenario, function (expCatId, ecg) {
|
|
|
|
var row = {
|
|
Collapsed: ecg.Collapsed,
|
|
CollapsedClass: ecg.CollapsedClass,
|
|
ExpCatId: expCatId,
|
|
ExpCatName: ecg.ExpenditureCategoryName,
|
|
Teams: [],
|
|
AvailableTeams: [],
|
|
UseType: 1,
|
|
Visible: true,
|
|
AllowResourceAssignment: ecg.AllowResourceAssignment
|
|
};
|
|
$.each(ecg.Teams, function (teamId, team) {
|
|
var rowTeam = {
|
|
Id: teamId,
|
|
Name: team.Name,
|
|
Changed: team.Changed,
|
|
CanBeDeleted: team.CanBeDeleted,
|
|
IsAccessible: team.IsAccessible,
|
|
ResourceToAssignId: null,
|
|
Resources: [],
|
|
AvailableResources: getAvailableResources(team),
|
|
Collapsed: team.Collapsed,
|
|
CollapsedClass: team.CollapsedClass,
|
|
};
|
|
|
|
$.each(team.Resources, function (resourceId, resource) {
|
|
if (!resource.Deleted) {
|
|
var rowResource = {
|
|
Id: resourceId,
|
|
ExpenditureCategoryId: resource.ExpenditureCategoryId,
|
|
IsActiveEmployee: resource.IsActiveEmployee,
|
|
Name: resource.Name,
|
|
CapacityQuantityValues: [],
|
|
ReadOnly: [],
|
|
RestQuantityValues: [],
|
|
QuantityValues: [],
|
|
GrandTotalQuantity: 0
|
|
};
|
|
rowTeam.Resources.push(rowResource);
|
|
}
|
|
});
|
|
row.Teams.push(rowTeam);
|
|
});
|
|
$scope.data.Calendar.Rows.push(row);
|
|
});
|
|
|
|
refreshAvailableTeamsList();
|
|
}
|
|
};
|
|
$scope.isVisibleGridRow = function (expCat) {
|
|
if (!expCat)
|
|
return false;
|
|
|
|
return dataSources.checkExpenditureCategory(expCat.ExpenditureCategoryId,
|
|
$scope.data.CalendarFilter.CategoryType,
|
|
$scope.data.CalendarFilter.GLAccount,
|
|
$scope.data.CalendarFilter.CreditDepartment,
|
|
$scope.data.CalendarFilter.SelectedExpCats);
|
|
};
|
|
$scope.applyGridFilter = function () {
|
|
if ($scope.data.Scenario.IsBottomUp) {
|
|
var eventData = {
|
|
CategoryType: $scope.data.CalendarFilter.CategoryType,
|
|
GLAccount: $scope.data.CalendarFilter.GLAccount,
|
|
CreditDepartment: $scope.data.CalendarFilter.CreditDepartment,
|
|
SelectedExpCats: angular.copy($scope.data.CalendarFilter.SelectedExpCats)
|
|
};
|
|
$scope.$broadcast('applyGridFilter', eventData);
|
|
} else {
|
|
$scope.setGridSource();
|
|
}
|
|
};
|
|
$scope.setGridSource = function () {
|
|
$scope.data.CalendarFilter.VisibleExpCats = [];
|
|
$scope.data.CalendarFilter.SecondaryExpCats = [];
|
|
|
|
var expendituresInScenario = scenarioDetailsService.getExpenditures(getScenarioId());
|
|
if (expendituresInScenario && $scope.data.Calendar.Rows) {
|
|
var totalRow = $scope.data.Calendar.Rows[0];
|
|
totalRow.GrandTotalCost = 0;
|
|
totalRow.GrandTotalQuantity = 0;
|
|
totalRow.CostValues = [];
|
|
totalRow.QuantityValues = [];
|
|
totalRow.Visible = false;
|
|
totalRow.IsEditable = true;
|
|
$.each(expendituresInScenario, function (expCatId, ecg) {
|
|
var row = null, rowIndex = -1;
|
|
var ecUomMultiplier = $scope.getUomMultiplier(expCatId);
|
|
|
|
for (var i = 1; i < $scope.data.Calendar.Rows.length; i++) {
|
|
var rowItem = $scope.data.Calendar.Rows[i];
|
|
if (rowItem.ExpCatId == expCatId) {
|
|
rowIndex = i;
|
|
row = rowItem;
|
|
|
|
row.CostValues = [];
|
|
row.ForecastCompletedPercent = 0;
|
|
row.ForecastCostTotalInActualsRange = 0;
|
|
row.ActualsCostTotal = 0;
|
|
row.GrandTotalCost = 0;
|
|
row.GrandTotalQuantity = 0;
|
|
row.QuantityValues = [];
|
|
row.RestQuantity = [];
|
|
row.Visible = $scope.isVisibleGridRow(ecg);
|
|
// needs to dnd module for storing selected cells
|
|
row.SelectedCells = new Array($scope.data.Calendar.WeekHeaders.length);
|
|
// array with target droppable elements
|
|
row.ActiveDroppables = new Array($scope.data.Calendar.WeekHeaders.length);
|
|
row.Editable = [];
|
|
row.HasActuals = false;
|
|
//env-843 start
|
|
|
|
if (row.Visible) {
|
|
$scope.data.CalendarFilter.VisibleExpCats.push({
|
|
Id: expCatId,
|
|
Name: ecg.ExpenditureCategoryName,
|
|
AllowResourceAssignment: ecg.AllowResourceAssignment
|
|
});
|
|
if (i > 1) {
|
|
$scope.data.CalendarFilter.SecondaryExpCats.push({
|
|
Id: expCatId,
|
|
Name: ecg.ExpenditureCategoryName,
|
|
AllowResourceAssignment: ecg.AllowResourceAssignment
|
|
});
|
|
}
|
|
}
|
|
//env-843 end
|
|
$.each(row.Teams, function (index, team) {
|
|
team.QuantityValues = [];
|
|
team.RestQuantityValues = [];
|
|
team.CapacityQuantityValues = [];
|
|
team.AllocatedByResources = [];
|
|
team.GrandTotalQuantity = 0;
|
|
|
|
$.each(team.Resources, function (resourceId, resource) {
|
|
resource.CapacityQuantityValues = [];
|
|
resource.RestQuantityValues = [];
|
|
resource.QuantityValues = [];
|
|
resource.ReadOnly = [];
|
|
resource.GrandTotalQuantity = 0;
|
|
});
|
|
});
|
|
|
|
// if any row is visible we need to show total row
|
|
totalRow.Visible = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (row.Visible) {
|
|
var monthCost = 0,
|
|
monthQuantity = 0,
|
|
weeks = 0, resourcesMonth = [], teamMonth = [],
|
|
//TODO: do we need to also check for Quantity/Cost switcher?
|
|
showAvg = $scope.data.CalendarFilter.ShowAvgTotals && !$scope.data.CalendarFilter.IsUOMHours;
|
|
|
|
$.each($scope.data.Calendar.WeekHeaders, function (index, header) {
|
|
if (header.DataType == C_HEADER_DATA_TYPE_ORDINAL) {
|
|
var isActualsCell = !header.Editable[$scope.data.CalendarFilter.ViewModeName];
|
|
var scenarioDetail = ecg.Details[header.Milliseconds];
|
|
|
|
if (isActualsCell) {
|
|
row.CostValues.push(scenarioDetail.ActualsCost);
|
|
row.QuantityValues.push(scenarioDetail.ActualsQuantity * ecUomMultiplier);
|
|
|
|
row.ForecastCostTotalInActualsRange += scenarioDetail.ForecastCost;
|
|
row.ActualsCostTotal += scenarioDetail.ActualsCost;
|
|
row.HasActuals = true;
|
|
} else {
|
|
row.CostValues.push(scenarioDetail.ForecastCost);
|
|
row.QuantityValues.push(scenarioDetail.ForecastQuantity * ecUomMultiplier);
|
|
}
|
|
|
|
row.Editable.push({
|
|
//env-2208 start
|
|
//"F": header.Editable[C_CALENDAR_VIEW_MODE_FORECAST] && row.AllowResourceAssignment,
|
|
//"A": header.Editable[C_CALENDAR_VIEW_MODE_ACTUALS] && row.AllowResourceAssignment
|
|
"F": header.Editable[C_CALENDAR_VIEW_MODE_FORECAST],
|
|
"A": header.Editable[C_CALENDAR_VIEW_MODE_ACTUALS]
|
|
//env-2208 end
|
|
});
|
|
row.RestQuantity.push(scenarioDetail.ForecastQuantity * ecUomMultiplier);
|
|
|
|
if (header.Visible[$scope.data.CalendarFilter.ViewModeName]) {
|
|
monthCost += row.CostValues[index];
|
|
monthQuantity += row.QuantityValues[index];
|
|
|
|
row.GrandTotalCost += row.CostValues[index];
|
|
row.GrandTotalQuantity += row.QuantityValues[index];
|
|
weeks++;
|
|
}
|
|
} else {
|
|
var parentMonthHeaderIndex = header.MonthHeader;
|
|
var parentMonthHeader = $scope.data.Calendar.MonthHeaders[parentMonthHeaderIndex];
|
|
var weekCount = parentMonthHeader.ColspanValues[$scope.data.CalendarFilter.ViewModeName];
|
|
|
|
row.CostValues.push(monthCost);
|
|
row.QuantityValues.push(monthQuantity / (showAvg ? weekCount : 1));
|
|
row.RestQuantity.push(0);
|
|
|
|
row.Editable.push({
|
|
//env-2208 start
|
|
//"F": header.Editable[C_CALENDAR_VIEW_MODE_FORECAST] && row.AllowResourceAssignment,
|
|
//"A": header.Editable[C_CALENDAR_VIEW_MODE_ACTUALS] && row.AllowResourceAssignment
|
|
"F": header.Editable[C_CALENDAR_VIEW_MODE_FORECAST],
|
|
"A": header.Editable[C_CALENDAR_VIEW_MODE_ACTUALS]
|
|
//env-2208 end
|
|
});
|
|
|
|
monthCost = monthQuantity = 0;
|
|
}
|
|
|
|
if (totalRow.CostValues.length == index)
|
|
totalRow.CostValues.push(0);
|
|
if (totalRow.QuantityValues.length == index)
|
|
totalRow.QuantityValues.push(0);
|
|
|
|
totalRow.CostValues[index] += row.CostValues[index];
|
|
totalRow.QuantityValues[index] += row.QuantityValues[index];
|
|
|
|
var allocatedQuantityByTeams = 0;
|
|
$.each(row.Teams, function (index, team) {
|
|
if (header.DataType == C_HEADER_DATA_TYPE_ORDINAL) {
|
|
|
|
var qty = (ecg.Teams[team.Id].QuantityValues[header.Milliseconds] || 0) * ecUomMultiplier;
|
|
var restQty = (ecg.Teams[team.Id].RestQuantityValues[header.Milliseconds] || 0) * ecUomMultiplier;
|
|
var capacityQty = (ecg.Teams[team.Id].CapacityQuantityValues[header.Milliseconds] || 0) * ecUomMultiplier;
|
|
|
|
allocatedQuantityByTeams += qty;
|
|
team.QuantityValues.push(qty);
|
|
team.RestQuantityValues.push(restQty);
|
|
team.CapacityQuantityValues.push(capacityQty);
|
|
|
|
if (!teamMonth[team.Id]) {
|
|
teamMonth[team.Id] = {
|
|
Quantity: 0,
|
|
RestQuantity: 0,
|
|
CapacityQuantity: 0,
|
|
Resources: []
|
|
};
|
|
}
|
|
|
|
team.GrandTotalQuantity += qty;
|
|
teamMonth[team.Id].Quantity += qty;
|
|
teamMonth[team.Id].RestQuantity += restQty;
|
|
teamMonth[team.Id].CapacityQuantity += capacityQty;
|
|
}
|
|
else {
|
|
var parentMonthHeader = $scope.data.Calendar.MonthHeaders[header.MonthHeader];
|
|
var weekCount = parentMonthHeader.ColspanValues[$scope.data.CalendarFilter.ViewModeName];
|
|
|
|
var monthQuantity = teamMonth[team.Id].Quantity / (showAvg ? weekCount : 1);
|
|
var monthRestQuantity = teamMonth[team.Id].RestQuantity / (showAvg ? weekCount : 1);
|
|
var monthCapacityQuantity = teamMonth[team.Id].CapacityQuantity / (showAvg ? weekCount : 1);
|
|
|
|
team.QuantityValues.push(monthQuantity);
|
|
team.RestQuantityValues.push(monthRestQuantity);
|
|
team.CapacityQuantityValues.push(monthCapacityQuantity);
|
|
}
|
|
|
|
|
|
var allocatedByResources = 0;
|
|
$.each(team.Resources, function (rIndex, resource) {
|
|
if (header.DataType == C_HEADER_DATA_TYPE_ORDINAL) {
|
|
var rawResource = ecg.Teams[team.Id].Resources[resource.Id];
|
|
var uomMultiplier = $scope.getUomMultiplier(rawResource.ExpenditureCategoryId);
|
|
|
|
var cqty = (rawResource.CapacityQuantityValues[header.Milliseconds] || 0) * uomMultiplier;
|
|
var qty = (rawResource.QuantityValues[header.Milliseconds] || 0) * uomMultiplier;
|
|
var restQty = (rawResource.RestQuantityValues[header.Milliseconds] || 0) * uomMultiplier;
|
|
var readOnly = rawResource.ReadOnly[header.Milliseconds];
|
|
|
|
resource.CapacityQuantityValues.push(cqty);
|
|
resource.QuantityValues.push(qty);
|
|
resource.RestQuantityValues.push(restQty);
|
|
resource.ReadOnly.push(readOnly);
|
|
|
|
if (!teamMonth[team.Id].Resources[resource.Id]) {
|
|
teamMonth[team.Id].Resources[resource.Id] = {
|
|
CapacityQuantity: 0,
|
|
RestQuantity: 0,
|
|
Quantity: 0,
|
|
ReadOnly: false
|
|
};
|
|
}
|
|
|
|
resource.GrandTotalQuantity += qty;
|
|
teamMonth[team.Id].Resources[resource.Id].CapacityQuantity += cqty;
|
|
teamMonth[team.Id].Resources[resource.Id].RestQuantity += restQty;
|
|
teamMonth[team.Id].Resources[resource.Id].Quantity += qty;
|
|
teamMonth[team.Id].Resources[resource.Id].ReadOnly |= readOnly;
|
|
allocatedByResources += qty;
|
|
} else {
|
|
var parentMonthHeaderIndex = header.MonthHeader;
|
|
var parentMonthHeader = $scope.data.Calendar.MonthHeaders[parentMonthHeaderIndex];
|
|
var weekCount = parentMonthHeader.ColspanValues[$scope.data.CalendarFilter.ViewModeName];
|
|
|
|
var monthCapacityQuantity = teamMonth[team.Id].Resources[resource.Id].CapacityQuantity / (showAvg ? weekCount : 1);
|
|
var monthRestQuantity = teamMonth[team.Id].Resources[resource.Id].RestQuantity / (showAvg ? weekCount : 1);
|
|
var monthQuantity = teamMonth[team.Id].Resources[resource.Id].Quantity / (showAvg ? weekCount : 1);
|
|
var readOnly = teamMonth[team.Id].Resources[resource.Id].ReadOnly;
|
|
|
|
resource.CapacityQuantityValues.push(monthCapacityQuantity);
|
|
resource.RestQuantityValues.push(monthRestQuantity);
|
|
resource.QuantityValues.push(monthQuantity);
|
|
resource.ReadOnly.push(readOnly);
|
|
allocatedByResources += monthQuantity;
|
|
|
|
delete teamMonth[team.Id].Resources[resource.Id];
|
|
}
|
|
});
|
|
// clear teamMonth summary variable after it has been used to fill teams and resources
|
|
if (header.DataType != C_HEADER_DATA_TYPE_ORDINAL) {
|
|
delete teamMonth[team.Id];
|
|
}
|
|
team.AllocatedByResources.push(allocatedByResources);
|
|
});
|
|
|
|
row.RestQuantity[index] -= allocatedQuantityByTeams;
|
|
});
|
|
|
|
row.ForecastCompletedPercent = row.ForecastCostTotalInActualsRange == 0 ? 0 : Math.abs(row.ActualsCostTotal / row.ForecastCostTotalInActualsRange - 1.0);
|
|
if (showAvg) {
|
|
row.GrandTotalQuantity /= weeks;
|
|
$.each(row.Teams, function (i, team) {
|
|
team.GrandTotalQuantity /= weeks;
|
|
$.each(team.Resources, function (j, resource) {
|
|
resource.GrandTotalQuantity /= weeks;
|
|
});
|
|
});
|
|
}
|
|
//env-2208 start
|
|
row.IsEditable = row.UseType != 3 && !row.HasActuals; // && row.AllowResourceAssignment;
|
|
//env-2208 end
|
|
if (!row.IsEditable) {
|
|
totalRow.IsEditable = false;
|
|
}
|
|
}
|
|
|
|
totalRow.GrandTotalCost += row.GrandTotalCost;
|
|
totalRow.GrandTotalQuantity += row.GrandTotalQuantity;
|
|
$scope.data.Calendar.Rows[rowIndex] = row;
|
|
});
|
|
$scope.data.Calendar.Rows[0] = totalRow;
|
|
}
|
|
|
|
if ($scope.data.Calendar.Rows) {
|
|
$.each($scope.data.Calendar.Rows, function (i, cat) {
|
|
if ($scope.data.CalendarFilter.IsTableModeQuantity)
|
|
cat.Cells = cat.QuantityValues;
|
|
else
|
|
cat.Cells = cat.CostValues;
|
|
|
|
if ($scope.data.Calendar.Rows[i].Teams) {
|
|
$.each($scope.data.Calendar.Rows[i].Teams, function (rIndex, team) {
|
|
team.Cells = team.QuantityValues;
|
|
|
|
if (team.Resources) {
|
|
$.each(team.Resources, function (rIndex, res) {
|
|
res.Cells = res.QuantityValues;
|
|
});
|
|
}
|
|
});
|
|
}
|
|
setRowGrandTotal(cat);
|
|
});
|
|
}
|
|
|
|
$timeout(function () {
|
|
angular.element('[ng-model="team.ResourceToAssignId"]').select2({
|
|
allowClear: true,
|
|
placeholder: 'Select a person',
|
|
dropdownAutoWidth: true,
|
|
dropdownCss: { 'font-size': '9pt' },
|
|
minimumResultsForSearch: 5,
|
|
formatResult: formatPeopleResourceOption
|
|
});
|
|
|
|
angular.element('[ng-model="row.TeamToAssignId"]').select2({
|
|
allowClear: true,
|
|
placeholder: 'Select a Team',
|
|
dropdownAutoWidth: true,
|
|
dropdownCss: { 'font-size': '9pt' },
|
|
minimumResultsForSearch: 5
|
|
});
|
|
});
|
|
};
|
|
|
|
$scope.onOpenReallocate = function (row) {
|
|
// SA. Fixed datepicker problems
|
|
var srcDate = new Date($scope.reallocator.SourceWeekEnding);
|
|
var trgDate = new Date($scope.reallocator.TargetWeekEnding);
|
|
$('[name=SourceWeekEnding]').parent('div').data('datepicker').update(srcDate);
|
|
$('[name=TargetWeekEnding]').parent('div').data('datepicker').update(trgDate);
|
|
|
|
if (null == row)
|
|
return;
|
|
|
|
$scope.reallocator.SourceExpCat = row.ExpCatId;
|
|
for (var i = 0; i < $scope.data.AvailableExpenditures.length; i++) {
|
|
if ($scope.data.AvailableExpenditures[i].Id != row.ExpCatId) {
|
|
$scope.reallocator.TargetExpCat = $scope.data.AvailableExpenditures[i].Id;
|
|
return;
|
|
}
|
|
}
|
|
};
|
|
$scope.reallocateResource = function () {
|
|
var sourceQuantity = 0;
|
|
var sourceCost = 0;
|
|
if ($scope.reallocator.Percentage == 0) {
|
|
alert('Percentage is 0, nothing to reallocate.');
|
|
return;
|
|
}
|
|
var sourceRowIndex, targetRowIndex, curveRowIndex, sourceColIndex, targetColIndex;
|
|
var sourceDateMs, targetDateMs;
|
|
|
|
var i;
|
|
for (i = 0; i < $scope.data.Calendar.Rows.length; i++) {
|
|
if ($scope.data.Calendar.Rows[i].ExpCatId === $scope.reallocator.SourceExpCat)
|
|
sourceRowIndex = i;
|
|
if ($scope.data.Calendar.Rows[i].ExpCatId === $scope.reallocator.TargetExpCat)
|
|
targetRowIndex = i;
|
|
if ($scope.reallocator.CurveType == 'other' && $scope.data.Calendar.Rows[i].ExpCatId === $scope.reallocator.OtherCurve)
|
|
curveRowIndex = i;
|
|
}
|
|
if (sourceRowIndex == targetRowIndex) {
|
|
alert('Cant reallocate within same expenditure category.');
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < $scope.data.Calendar.WeekHeaders.length; i++) {
|
|
if ($scope.data.Calendar.WeekHeaders[i].DataType == C_HEADER_DATA_TYPE_TOTALS ||
|
|
!$scope.data.Calendar.WeekHeaders[i].Visible[$scope.data.CalendarFilter.ViewModeName])
|
|
continue;
|
|
if ($scope.data.Calendar.WeekHeaders[i].Title === $scope.reallocator.SourceWeekEnding) {
|
|
sourceColIndex = i;
|
|
sourceDateMs = $scope.data.Calendar.WeekHeaders[i].Milliseconds;
|
|
}
|
|
if ($scope.data.Calendar.WeekHeaders[i].Title === $scope.reallocator.TargetWeekEnding) {
|
|
targetColIndex = i;
|
|
targetDateMs = $scope.data.Calendar.WeekHeaders[i].Milliseconds;
|
|
}
|
|
}
|
|
|
|
if (sourceColIndex == null || targetColIndex == null) {
|
|
alert('Please check selected dates to match valid week endings.');
|
|
return;
|
|
} else if (targetColIndex < sourceColIndex) {
|
|
alert('Please select To week ending date later than From week ending date.');
|
|
return;
|
|
}
|
|
|
|
var sourceSumCost = 0, sourceSumQuantity = 0, targetSumCost = 0, targetSumQuantity = 0, otherSumCost = 0, otherSumQuantity = 0;
|
|
var percentageMult = $scope.reallocator.Percentage / 100;
|
|
for (i = sourceColIndex; i <= targetColIndex; i++) {
|
|
if ($scope.data.Calendar.WeekHeaders[i].DataType == C_HEADER_DATA_TYPE_TOTALS ||
|
|
!$scope.data.Calendar.WeekHeaders[i].Visible[$scope.data.CalendarFilter.ViewModeName])
|
|
continue;
|
|
sourceSumQuantity += $scope.data.Calendar.Rows[sourceRowIndex].QuantityValues[i];
|
|
sourceSumCost += $scope.data.Calendar.Rows[sourceRowIndex].CostValues[i];
|
|
targetSumQuantity += $scope.data.Calendar.Rows[targetRowIndex].QuantityValues[i];
|
|
targetSumCost += $scope.data.Calendar.Rows[targetRowIndex].CostValues[i];
|
|
if ($scope.reallocator.CurveType == 'other') {
|
|
otherSumQuantity += $scope.data.Calendar.Rows[curveRowIndex].QuantityValues[i];
|
|
otherSumCost += $scope.data.Calendar.Rows[curveRowIndex].CostValues[i];
|
|
}
|
|
}
|
|
|
|
if ((sourceSumQuantity == 0 && $scope.reallocator.ReallocateBy == 'Quantity') || (sourceSumCost == 0 && $scope.reallocator.ReallocateBy == 'Cost')) {
|
|
alert('Source is zero, nothing to reallocate.');
|
|
return;
|
|
}
|
|
if ($scope.reallocator.CurveType == 'target')
|
|
if ((targetSumQuantity == 0 && $scope.reallocator.ReallocateBy == 'Quantity') || (targetSumCost == 0 && $scope.reallocator.ReallocateBy == 'Cost')) {
|
|
alert('Target curve is zero. Please select a different curve.');
|
|
return;
|
|
}
|
|
if ($scope.reallocator.CurveType == 'other')
|
|
if ((otherSumQuantity == 0 && $scope.reallocator.ReallocateBy == 'Quantity') || (otherSumCost == 0 && $scope.reallocator.ReallocateBy == 'Cost')) {
|
|
alert('Different curve is zero. Please select a another curve.');
|
|
return;
|
|
}
|
|
var quantity2Move = 0;
|
|
var cost2Move = 0;
|
|
for (i = sourceColIndex; i <= targetColIndex; i++) {
|
|
if ($scope.data.Calendar.WeekHeaders[i].DataType == C_HEADER_DATA_TYPE_TOTALS ||
|
|
!$scope.data.Calendar.WeekHeaders[i].Visible[$scope.data.CalendarFilter.ViewModeName]) {
|
|
continue;
|
|
}
|
|
if ($scope.reallocator.CurveType == 'source') {
|
|
sourceQuantity = $scope.data.Calendar.Rows[sourceRowIndex].QuantityValues[i];
|
|
sourceCost = $scope.data.Calendar.Rows[sourceRowIndex].CostValues[i];
|
|
// update column values in model
|
|
if ($scope.reallocator.ReallocateBy == 'Quantity') {
|
|
updateQuantityOrCostCell($scope.reallocator.SourceExpCat, sourceRowIndex, i,
|
|
sourceQuantity * (1 - percentageMult), sourceCost * (1 - percentageMult), null);
|
|
updateQuantityOrCostCell($scope.reallocator.TargetExpCat, targetRowIndex, i,
|
|
$scope.data.Calendar.Rows[targetRowIndex].QuantityValues[i] + sourceQuantity * percentageMult, null, true);
|
|
} else {
|
|
updateQuantityOrCostCell($scope.reallocator.SourceExpCat, sourceRowIndex, i,
|
|
sourceQuantity * (1 - percentageMult), sourceCost * (1 - percentageMult), null);
|
|
updateQuantityOrCostCell($scope.reallocator.TargetExpCat, targetRowIndex, i,
|
|
null, $scope.data.Calendar.Rows[targetRowIndex].CostValues[i] + sourceCost * percentageMult, false);
|
|
}
|
|
} else if ($scope.reallocator.CurveType == 'target') {
|
|
sourceQuantity = $scope.data.Calendar.Rows[sourceRowIndex].QuantityValues[i];
|
|
sourceCost = $scope.data.Calendar.Rows[sourceRowIndex].CostValues[i];
|
|
quantity2Move = (percentageMult * sourceSumQuantity) * ($scope.data.Calendar.Rows[targetRowIndex].QuantityValues[i] / targetSumQuantity);
|
|
cost2Move = (percentageMult * sourceSumCost) * ($scope.data.Calendar.Rows[targetRowIndex].CostValues[i] / targetSumCost);
|
|
// update column values in model
|
|
if ($scope.reallocator.ReallocateBy == 'Quantity') {
|
|
// we cannot reallocate more than source quantity
|
|
if (quantity2Move > $scope.data.Calendar.Rows[sourceRowIndex].QuantityValues[i])
|
|
quantity2Move = $scope.data.Calendar.Rows[sourceRowIndex].QuantityValues[i];
|
|
if (quantity2Move > 0) {
|
|
updateQuantityOrCostCell($scope.reallocator.SourceExpCat, sourceRowIndex, i,
|
|
sourceQuantity * (1 - percentageMult), null, true);
|
|
updateQuantityOrCostCell($scope.reallocator.TargetExpCat, targetRowIndex, i,
|
|
$scope.data.Calendar.Rows[targetRowIndex].QuantityValues[i] + quantity2Move, null, true);
|
|
}
|
|
} else {
|
|
// we cannot reallocate more than source quantity
|
|
if (cost2Move > $scope.data.Calendar.Rows[sourceRowIndex].CostValues[i])
|
|
cost2Move = $scope.data.Calendar.Rows[sourceRowIndex].CostValues[i];
|
|
if (cost2Move > 0) {
|
|
updateQuantityOrCostCell($scope.reallocator.SourceExpCat, sourceRowIndex, i,
|
|
null, sourceCost * (1 - percentageMult), false);
|
|
updateQuantityOrCostCell($scope.reallocator.TargetExpCat, targetRowIndex, i,
|
|
null, $scope.data.Calendar.Rows[targetRowIndex].CostValues[i] + cost2Move, false);
|
|
}
|
|
}
|
|
} else if ($scope.reallocator.CurveType == 'other') {
|
|
sourceQuantity = $scope.data.Calendar.Rows[sourceRowIndex].QuantityValues[i];
|
|
sourceCost = $scope.data.Calendar.Rows[sourceRowIndex].CostValues[i];
|
|
quantity2Move = (percentageMult * sourceSumQuantity) * ($scope.data.Calendar.Rows[curveRowIndex].QuantityValues[i] / otherSumQuantity);
|
|
cost2Move = (percentageMult * sourceSumCost) * ($scope.data.Calendar.Rows[curveRowIndex].CostValues[i] / otherSumCost);
|
|
// update column values in model
|
|
if ($scope.reallocator.ReallocateBy == 'Quantity') {
|
|
// we cannot reallocate more than source quantity
|
|
if (quantity2Move > $scope.data.Calendar.Rows[sourceRowIndex].QuantityValues[i])
|
|
quantity2Move = $scope.data.Calendar.Rows[sourceRowIndex].QuantityValues[i];
|
|
if (quantity2Move > 0) {
|
|
updateQuantityOrCostCell($scope.reallocator.SourceExpCat, sourceRowIndex, i,
|
|
sourceQuantity * (1 - percentageMult), null, true);
|
|
updateQuantityOrCostCell($scope.reallocator.TargetExpCat, targetRowIndex, i,
|
|
$scope.data.Calendar.Rows[targetRowIndex].QuantityValues[i] + quantity2Move, null, true);
|
|
}
|
|
} else {
|
|
// we cannot reallocate more than source quantity
|
|
if (cost2Move > $scope.data.Calendar.Rows[sourceRowIndex].CostValues[i])
|
|
cost2Move = $scope.data.Calendar.Rows[sourceRowIndex].CostValues[i];
|
|
if (cost2Move > 0) {
|
|
updateQuantityOrCostCell($scope.reallocator.SourceExpCat, sourceRowIndex, i,
|
|
null, sourceCost * (1 - percentageMult), false);
|
|
updateQuantityOrCostCell($scope.reallocator.TargetExpCat, targetRowIndex, i,
|
|
null, $scope.data.Calendar.Rows[targetRowIndex].CostValues[i] + cost2Move, false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Audit for client-side actions
|
|
if (!$scope.data.Audit)
|
|
$scope.data.Audit = {};
|
|
|
|
if (!$scope.data.Audit.Reallocations)
|
|
$scope.data.Audit.Reallocations = {};
|
|
|
|
var auditRecord = {
|
|
SourceExpCat: $scope.reallocator.SourceExpCat,
|
|
TargetExpCat: $scope.reallocator.TargetExpCat,
|
|
SourceWeekEnding: sourceDateMs,
|
|
TargetWeekEnding: targetDateMs,
|
|
Percentage: $scope.reallocator.Percentage,
|
|
ReallocateBy: $scope.reallocator.ReallocateBy
|
|
}
|
|
|
|
if ($scope.reallocator.CurveType == 'source')
|
|
auditRecord.UseCurveFromExpenditureId = $scope.reallocator.SourceExpCat;
|
|
|
|
if ($scope.reallocator.CurveType == 'target')
|
|
auditRecord.UseCurveFromExpenditureId = $scope.reallocator.TargetExpCat;
|
|
|
|
if ($scope.reallocator.CurveType == 'other')
|
|
auditRecord.UseCurveFromExpenditureId = $scope.reallocator.OtherCurve;
|
|
|
|
var currentTime = DateTimeConverter.getUtcNowMs();
|
|
$scope.data.Audit.Reallocations[currentTime] = auditRecord;
|
|
|
|
$scope.RefreshCSSClasses();
|
|
resetRTDataChanged();
|
|
$("#reallocator").modal("hide");
|
|
};
|
|
$scope.pushPull = function () {
|
|
if (!$scope.pushpuller.weeks) {
|
|
alert('Number of weeks is required');
|
|
return;
|
|
}
|
|
if ($scope.pushpuller.weeks > ($scope.pushpuller.maxWeeks)) {
|
|
alert('You cannot set the number of weeks to push/pull that exceeds scenario duration.');
|
|
return;
|
|
}
|
|
if (null != $scope.pushpuller.resource && $scope.pushpuller.resource.length > 0) {
|
|
for (var i = 1; i < $scope.data.Calendar.Rows.length; i++) {
|
|
var catIndex = -1;
|
|
for (var j = 0; j < $scope.pushpuller.resource.length; j++) {
|
|
if ($scope.pushpuller.resource[j] === $scope.data.Calendar.Rows[i].ExpCatId) {
|
|
catIndex = j;
|
|
break;
|
|
}
|
|
}
|
|
if (catIndex >= 0)
|
|
$scope.pushPullExpCat($scope.data.Calendar.Rows[i], i, $scope.pushpuller.weeks, $scope.pushpuller.push);
|
|
}
|
|
}
|
|
$scope.pushpuller.weeks = 1;
|
|
$scope.pushpuller.resource = null;
|
|
$('#pushPullExpCats').select2('val', '');
|
|
resetPPDataChanged();
|
|
$("#push_pull").modal("hide");
|
|
};
|
|
$scope.pushPullExpCat = function (data, sourceRowIndex, weeks, push) {
|
|
var expCat = $scope.data.Calendar.Rows[sourceRowIndex];
|
|
var i;
|
|
var j;
|
|
var handledWeeks = 0;
|
|
var leftMargin; // index of the cell next to the last modified cell from the left side of the table. E.g. if we push 2 weeks, then it will be [2], or [3] if there is only one week in the first month
|
|
var rightMargin; // index of the cell previous to the first modified cell from the right side of the table. E.g. if we pull 2 weeks, then it will be [length-3], or [length-4] if there is only one week in the last month
|
|
for (i = 0; i < $scope.data.Calendar.WeekHeaders.length; i++) {
|
|
if ($scope.data.Calendar.WeekHeaders[i].DataType == C_HEADER_DATA_TYPE_TOTALS ||
|
|
!$scope.data.Calendar.WeekHeaders[i].Visible[$scope.data.CalendarFilter.ViewModeName])
|
|
continue;
|
|
handledWeeks++;
|
|
if (handledWeeks == weeks) {
|
|
leftMargin = i + 1;
|
|
break;
|
|
}
|
|
}
|
|
handledWeeks = 0;
|
|
for (i = $scope.data.Calendar.WeekHeaders.length - 1; i >= 0; i--) {
|
|
if ($scope.data.Calendar.WeekHeaders[i].DataType == C_HEADER_DATA_TYPE_TOTALS ||
|
|
!$scope.data.Calendar.WeekHeaders[i].Visible[$scope.data.CalendarFilter.ViewModeName])
|
|
continue;
|
|
handledWeeks++;
|
|
if (handledWeeks == weeks) {
|
|
rightMargin = i - 1;
|
|
break;
|
|
}
|
|
}
|
|
if (push) {
|
|
for (i = $scope.data.Calendar.WeekHeaders.length - 1; i >= 0; i--) {
|
|
if ($scope.data.Calendar.WeekHeaders[i].DataType == C_HEADER_DATA_TYPE_TOTALS ||
|
|
!$scope.data.Calendar.WeekHeaders[i].Visible[$scope.data.CalendarFilter.ViewModeName]) {
|
|
continue;
|
|
}
|
|
|
|
if (i < leftMargin) {
|
|
if ($scope.data.CalendarFilter.IsTableModeQuantity) {
|
|
updateQuantityOrCostCell(data.ExpCatId, sourceRowIndex, i, 0, null, true);
|
|
} else {
|
|
updateQuantityOrCostCell(data.ExpCatId, sourceRowIndex, i, null, 0, false);
|
|
}
|
|
if (expCat.Teams && expCat.Teams.length > 0) {
|
|
for (var tIndex in expCat.Teams) {
|
|
$scope.checkTeamValue(expCat, expCat.Teams[tIndex], i, 0, false);
|
|
if (expCat.Teams[tIndex].Resources && expCat.Teams[tIndex].Resources.length > 0) {
|
|
for (var rIndex in expCat.Teams[tIndex].Resources)
|
|
$scope.checkResourceValue(expCat, expCat.Teams[tIndex], expCat.Teams[tIndex].Resources[rIndex], i, 0, false);
|
|
}
|
|
}
|
|
}
|
|
} else { //copy data from [i+weeks] cell
|
|
handledWeeks = 0;
|
|
for (j = i - 1; j >= 0; j--) {
|
|
if ($scope.data.Calendar.WeekHeaders[j].DataType == C_HEADER_DATA_TYPE_TOTALS ||
|
|
!$scope.data.Calendar.WeekHeaders[j].Visible[$scope.data.CalendarFilter.ViewModeName])
|
|
continue;
|
|
|
|
if (++handledWeeks == weeks)
|
|
break;
|
|
}
|
|
if ($scope.data.CalendarFilter.IsTableModeQuantity) {
|
|
updateQuantityOrCostCell(data.ExpCatId, sourceRowIndex, i, data.QuantityValues[j], null, true);
|
|
} else {
|
|
updateQuantityOrCostCell(data.ExpCatId, sourceRowIndex, i, null, data.CostValues[j], false);
|
|
}
|
|
|
|
if (expCat.Teams && expCat.Teams.length > 0) {
|
|
for (var tIndex in expCat.Teams) {
|
|
$scope.checkTeamValue(expCat, expCat.Teams[tIndex], i, data.Teams[tIndex].QuantityValues[j], false);
|
|
if (expCat.Teams[tIndex].Resources && expCat.Teams[tIndex].Resources.length > 0) {
|
|
for (var rIndex in expCat.Teams[tIndex].Resources)
|
|
$scope.checkResourceValue(expCat, expCat.Teams[tIndex], expCat.Teams[tIndex].Resources[rIndex], i, expCat.Teams[tIndex].Resources[rIndex].QuantityValues[j], false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
for (i = 0; i < $scope.data.Calendar.WeekHeaders.length; i++) {
|
|
if ($scope.data.Calendar.WeekHeaders[i].DataType == C_HEADER_DATA_TYPE_TOTALS ||
|
|
!$scope.data.Calendar.WeekHeaders[i].Visible[$scope.data.CalendarFilter.ViewModeName]) {
|
|
continue;
|
|
}
|
|
|
|
if (i <= rightMargin) { //copy data from [i+weeks] cell
|
|
handledWeeks = 0;
|
|
for (j = i + 1; j < $scope.data.Calendar.WeekHeaders.length; j++) {
|
|
if ($scope.data.Calendar.WeekHeaders[j].DataType == C_HEADER_DATA_TYPE_TOTALS ||
|
|
!$scope.data.Calendar.WeekHeaders[j].Visible[$scope.data.CalendarFilter.ViewModeName])
|
|
continue;
|
|
|
|
if (++handledWeeks == weeks)
|
|
break;
|
|
}
|
|
if ($scope.data.CalendarFilter.IsTableModeQuantity) {
|
|
updateQuantityOrCostCell(data.ExpCatId, sourceRowIndex, i, data.QuantityValues[j], null, true);
|
|
} else {
|
|
updateQuantityOrCostCell(data.ExpCatId, sourceRowIndex, i, null, data.CostValues[j], false);
|
|
}
|
|
|
|
if (expCat.Teams && expCat.Teams.length > 0) {
|
|
for (var tIndex in expCat.Teams) {
|
|
$scope.checkTeamValue(expCat, expCat.Teams[tIndex], i, data.Teams[tIndex].QuantityValues[j], false);
|
|
if (expCat.Teams[tIndex].Resources && expCat.Teams[tIndex].Resources.length > 0) {
|
|
for (var rIndex in expCat.Teams[tIndex].Resources)
|
|
$scope.checkResourceValue(expCat, expCat.Teams[tIndex], expCat.Teams[tIndex].Resources[rIndex], i, expCat.Teams[tIndex].Resources[rIndex].QuantityValues[j], false);
|
|
}
|
|
}
|
|
}
|
|
} else if (i > rightMargin) {
|
|
if ($scope.data.CalendarFilter.IsTableModeQuantity) {
|
|
updateQuantityOrCostCell(data.ExpCatId, sourceRowIndex, i, 0, null, true);
|
|
} else {
|
|
updateQuantityOrCostCell(data.ExpCatId, sourceRowIndex, i, null, 0, false);
|
|
}
|
|
|
|
if (expCat.Teams && expCat.Teams.length > 0) {
|
|
for (var tIndex in expCat.Teams) {
|
|
$scope.checkTeamValue(expCat, expCat.Teams[tIndex], i, 0, false);
|
|
if (expCat.Teams[tIndex].Resources && expCat.Teams[tIndex].Resources.length > 0) {
|
|
for (var rIndex in expCat.Teams[tIndex].Resources)
|
|
$scope.checkResourceValue(expCat, expCat.Teams[tIndex], expCat.Teams[tIndex].Resources[rIndex], i, 0, false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
$scope.RefreshCSSClasses();
|
|
};
|
|
$scope.hidePushPull = function () {
|
|
$scope.pushpuller.weeks = 1;
|
|
};
|
|
$scope.changeCurve = function () {
|
|
var oldQuantity = 0;
|
|
var oldCost = 0;
|
|
var changeMult = parseFloat($scope.editTotal.NewTotal) / parseFloat($scope.editTotal.CurrentTotal);
|
|
var sourceRowIndex = null;
|
|
var curveRowIndex = null;
|
|
var i;
|
|
for (i = 0; i < $scope.data.Calendar.Rows.length; i++) {
|
|
if ($scope.data.Calendar.Rows[i].ExpCatId === $scope.editTotal.ExpCat)
|
|
sourceRowIndex = i;
|
|
if ($scope.data.Calendar.Rows[i].ExpCatId === $scope.editTotal.OtherCurve)
|
|
curveRowIndex = i;
|
|
}
|
|
if (null === sourceRowIndex) {
|
|
alert('Cannot find expenditure category to update.');
|
|
return;
|
|
}
|
|
if (null === curveRowIndex) {
|
|
alert('Cannot find expenditure category to use as a curve.');
|
|
return;
|
|
}
|
|
|
|
var sourceSumCost = 0, sourceSumQuantity = 0, curveSumCost = 0, curveSumQuantity = 0;
|
|
var numberOfWeeks = 0;
|
|
for (i = 0; i < $scope.data.Calendar.WeekHeaders.length; i++) {
|
|
if ($scope.data.Calendar.WeekHeaders[i].DataType == C_HEADER_DATA_TYPE_TOTALS ||
|
|
!$scope.data.Calendar.WeekHeaders[i].Visible[$scope.data.CalendarFilter.ViewModeName])
|
|
continue;
|
|
sourceSumQuantity += $scope.data.Calendar.Rows[sourceRowIndex].QuantityValues[i];
|
|
sourceSumCost += $scope.data.Calendar.Rows[sourceRowIndex].CostValues[i];
|
|
curveSumQuantity += $scope.data.Calendar.Rows[curveRowIndex].QuantityValues[i];
|
|
curveSumCost += $scope.data.Calendar.Rows[curveRowIndex].CostValues[i];
|
|
numberOfWeeks++;
|
|
}
|
|
if (curveSumQuantity == 0 || sourceSumQuantity == 0) {
|
|
changeMult = parseFloat($scope.editTotal.NewTotal);
|
|
}
|
|
if (isAvgMode() && numberOfWeeks > 0) {
|
|
sourceSumQuantity = sourceSumQuantity / numberOfWeeks;
|
|
curveSumQuantity = curveSumQuantity / numberOfWeeks;
|
|
}
|
|
|
|
if (curveSumQuantity > 0)
|
|
for (i = 0; i < $scope.data.Calendar.WeekHeaders.length; i++) {
|
|
if ($scope.data.Calendar.WeekHeaders[i].DataType == C_HEADER_DATA_TYPE_TOTALS ||
|
|
!$scope.data.Calendar.WeekHeaders[i].Visible[$scope.data.CalendarFilter.ViewModeName]) {
|
|
continue;
|
|
}
|
|
oldQuantity = $scope.data.Calendar.Rows[sourceRowIndex].QuantityValues[i];
|
|
oldCost = $scope.data.Calendar.Rows[sourceRowIndex].CostValues[i];
|
|
var targetQuantity = $scope.data.Calendar.Rows[curveRowIndex].QuantityValues[i];
|
|
var targetCost = $scope.data.Calendar.Rows[curveRowIndex].CostValues[i];
|
|
if (sourceSumQuantity != 0)
|
|
if (isAvgMode()) {
|
|
updateQuantityOrCostCell($scope.editTotal.ExpCat, sourceRowIndex, i, Math.abs(sourceSumQuantity * (targetQuantity / curveSumQuantity) * changeMult), Math.abs(sourceSumCost * (targetCost / curveSumCost) * changeMult), null);
|
|
} else {
|
|
updateQuantityOrCostCell($scope.editTotal.ExpCat, sourceRowIndex, i, Math.abs(sourceSumQuantity * (targetQuantity / curveSumQuantity) * changeMult), Math.abs(sourceSumCost * (targetCost / curveSumCost) * changeMult), null);
|
|
}
|
|
else {
|
|
if (isAvgMode()) {
|
|
updateQuantityOrCostCell($scope.editTotal.ExpCat, sourceRowIndex, i, Math.abs((targetQuantity / curveSumQuantity) * changeMult), Math.abs(sourceSumCost * (targetCost / curveSumCost) * changeMult), null);
|
|
} else {
|
|
updateQuantityOrCostCell($scope.editTotal.ExpCat, sourceRowIndex, i, Math.abs((targetQuantity / curveSumQuantity) * changeMult), Math.abs(sourceSumCost * (targetCost / curveSumCost) * changeMult), null);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
for (i = 0; i < $scope.data.Calendar.WeekHeaders.length; i++) {
|
|
if ($scope.data.Calendar.WeekHeaders[i].DataType == C_HEADER_DATA_TYPE_TOTALS ||
|
|
!$scope.data.Calendar.WeekHeaders[i].Visible[$scope.data.CalendarFilter.ViewModeName]) {
|
|
continue;
|
|
}
|
|
if (isAvgMode()) {
|
|
updateQuantityOrCostCell($scope.editTotal.ExpCat, sourceRowIndex, i, changeMult, changeMult * $scope.getRate($scope.editTotal.ExpCat, $scope.data.Calendar.WeekHeaders[i].Milliseconds), null);
|
|
} else {
|
|
updateQuantityOrCostCell($scope.editTotal.ExpCat, sourceRowIndex, i, Math.abs(changeMult / numberOfWeeks), Math.abs(changeMult * $scope.getRate($scope.editTotal.ExpCat, $scope.data.Calendar.WeekHeaders[i].Milliseconds) / numberOfWeeks), null);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Audit for client-side actions
|
|
if (!$scope.data.Audit)
|
|
$scope.data.Audit = {};
|
|
|
|
if (!$scope.data.Audit.CurveChanges)
|
|
$scope.data.Audit.CurveChanges = {};
|
|
|
|
var auditRecord = {
|
|
FromExpenditureId: $scope.editTotal.OtherCurve,
|
|
ToExpenditureId: $scope.editTotal.ExpCat
|
|
}
|
|
|
|
var currentTime = DateTimeConverter.getUtcNowMs();
|
|
$scope.data.Audit.CurveChanges[currentTime] = auditRecord;
|
|
|
|
$scope.RefreshCSSClasses();
|
|
resetETDataChanged();
|
|
$("#editTotal").modal("hide");
|
|
};
|
|
$scope.openChangeCurveModal = function (row) {
|
|
if (null != row) {
|
|
$scope.editTotal.ExpCat = row.ExpCatId;
|
|
}
|
|
|
|
for (var i = 0; i < $scope.data.Calendar.Rows.length; i++) {
|
|
if ($scope.data.Calendar.Rows[i].ExpCatId === $scope.editTotal.ExpCat) {
|
|
if ($scope.data.CalendarFilter.IsTableModeQuantity) {
|
|
$scope.editTotal.CurrentTotal = roundToNearest($scope.data.Calendar.Rows[i].GrandTotalQuantity, $scope.RoundingBasis);
|
|
} else {
|
|
$scope.editTotal.CurrentTotal = roundToNearest($scope.data.Calendar.Rows[i].GrandTotalCost, $scope.RoundingBasis);
|
|
}
|
|
$scope.editTotal.NewTotal = $scope.editTotal.CurrentTotal;
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
$scope.isOtherSelectedReallocator = function () {
|
|
return $scope.reallocator.CurveType == 'other';
|
|
};
|
|
$scope.prefillFormatCells = function (row) {
|
|
if (null != row)
|
|
$scope.formatCells.SelectedExpCats = [row.ExpCatId];
|
|
else if ($scope.data.AvailableExpenditures.length > 0)
|
|
$scope.formatCells.SelectedExpCats = [$scope.data.AvailableExpenditures[0].Id];
|
|
$scope.formatCells.StartDate = $scope.formatCells.InitStartDate;
|
|
$scope.formatCells.EndDate = $scope.formatCells.InitEndDate;
|
|
var dp = $('#modalFormatCells #fs-datepicker-range').data('datepicker').pickers;
|
|
dp[0].setDate($scope.formatCells.StartDate);
|
|
dp[1].setDate($scope.formatCells.EndDate);
|
|
$('#roundExpCats').select2('val', $scope.formatCells.SelectedExpCats.join());
|
|
refreshSelect2($('#formatCellsDecimalPlaces'));
|
|
};
|
|
$scope.submitFormatCells = function () {
|
|
if ($scope.data.ScenarioType === 9)
|
|
return false;
|
|
|
|
if (isNaN($scope.formatCells.DecimalPlaces)) {
|
|
alert("Decimal Places is not set.");
|
|
return false;
|
|
}
|
|
if ($scope.formatCells.SelectedExpCats.length <= 0) {
|
|
return false;
|
|
}
|
|
var colIndexes2Update = [];
|
|
// gather column indexes which we're going to update
|
|
var dtStart = $scope.formatCells.StartDate.split('/');
|
|
var msStart = Date.UTC(dtStart[2], dtStart[0] - 1, dtStart[1]);
|
|
var dtEnd = $scope.formatCells.EndDate.split('/');
|
|
var msEnd = Date.UTC(dtEnd[2], dtEnd[0] - 1, dtEnd[1]);
|
|
for (var headerIndex = 0; headerIndex < $scope.data.Calendar.WeekHeaders.length; headerIndex++) {
|
|
if ($scope.data.Calendar.WeekHeaders[headerIndex].DataType == C_HEADER_DATA_TYPE_TOTALS ||
|
|
!$scope.data.Calendar.WeekHeaders[headerIndex].Visible[$scope.data.CalendarFilter.ViewModeName])
|
|
continue;
|
|
if ($scope.data.Calendar.WeekHeaders[headerIndex].Milliseconds >= msStart &&
|
|
$scope.data.Calendar.WeekHeaders[headerIndex].Milliseconds <= msEnd) {
|
|
colIndexes2Update[colIndexes2Update.length] = headerIndex;
|
|
}
|
|
}
|
|
var oldQuantity = 0;
|
|
var newQuantity = 0;
|
|
|
|
for (var rowIndex = 0; rowIndex < $scope.data.Calendar.Rows.length; rowIndex++) {
|
|
for (var catIndex = 0; catIndex < $scope.formatCells.SelectedExpCats.length; catIndex++) {
|
|
if ($scope.formatCells.SelectedExpCats[catIndex] === $scope.data.Calendar.Rows[rowIndex].ExpCatId) {
|
|
for (var colIndex = 0; colIndex < colIndexes2Update.length; colIndex++) {
|
|
oldQuantity = $scope.data.Calendar.Rows[rowIndex].QuantityValues[colIndexes2Update[colIndex]];
|
|
newQuantity = roundToNearest(oldQuantity, $scope.formatCells.DecimalPlaces);
|
|
if (oldQuantity === newQuantity)
|
|
continue;
|
|
updateQuantityOrCostCell($scope.data.Calendar.Rows[rowIndex].ExpCatId, rowIndex, colIndexes2Update[colIndex], newQuantity, null, true);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Audit for client-side actions
|
|
if (!$scope.data.Audit)
|
|
$scope.data.Audit = {};
|
|
|
|
if (!$scope.data.Audit.Roundings)
|
|
$scope.data.Audit.Roundings = {};
|
|
|
|
var auditRecord = {
|
|
ExpCats: angular.copy($scope.formatCells.SelectedExpCats),
|
|
StartDate: msStart,
|
|
EndDate: msEnd,
|
|
}
|
|
|
|
for (var index = 0; index < $scope.formatCells.RoundOptions.length; index++) {
|
|
if ($scope.formatCells.RoundOptions[index].Value == $scope.formatCells.DecimalPlaces) {
|
|
auditRecord.DecimalPlaces = $scope.formatCells.RoundOptions[index].Name;
|
|
break;
|
|
}
|
|
}
|
|
|
|
var currentTime = DateTimeConverter.getUtcNowMs();
|
|
$scope.data.Audit.Roundings[currentTime] = auditRecord;
|
|
|
|
$scope.RefreshCSSClasses();
|
|
$scope.formatCells.SelectedExpCats = null;
|
|
$('#roundExpCats').select2('val', '');
|
|
refreshSelect2($('#formatCellsDecimalPlaces'));
|
|
resetFCDataChanged();
|
|
$("#modalFormatCells").modal("hide");
|
|
return true;
|
|
};
|
|
$scope.onMonthHeaderClick = function (header, headerIndex, $event) {
|
|
$event.stopPropagation();
|
|
|
|
header.IsCollapsed = !header.IsCollapsed;
|
|
header.CollapsedClass = header.IsCollapsed ? $scope.CollapsedIcon : $scope.NonCollapsedIcon;
|
|
|
|
if (header.WeekHeaders && header.WeekHeaders.length > 0) {
|
|
for (var i = 0; i < header.WeekHeaders.length; i++) {
|
|
var weekendingHeader = $scope.data.Calendar.WeekHeaders[header.WeekHeaders[i]];
|
|
weekendingHeader.Show = $scope.getWeekHeaderShowStatus(weekendingHeader);
|
|
weekendingHeader.Initialized = weekendingHeader.Initialized || weekendingHeader.Show;
|
|
}
|
|
|
|
// SA. Month header visibility
|
|
var monthHeader = $scope.data.Calendar.WeekHeaders[header.TotalsHeader];
|
|
monthHeader.Show = $scope.getWeekHeaderShowStatus(monthHeader);
|
|
monthHeader.Initialized = monthHeader.Initialized || monthHeader.Show;
|
|
}
|
|
getVisibleCellCount();
|
|
};
|
|
$scope.onExpCatCollapseClick = function (row, $event) {
|
|
$event.stopPropagation();
|
|
$event.preventDefault();
|
|
|
|
var rawEC = scenarioDetailsService.getCategoryInScenario(getScenarioId(), row.ExpCatId);
|
|
onCollapseClick(row, rawEC);
|
|
};
|
|
$scope.onTeamCollapseClick = function (expCatId, team, $event) {
|
|
$event.stopPropagation();
|
|
$event.preventDefault();
|
|
|
|
var rawTeam = scenarioDetailsService.getTeamInScenario(getScenarioId(), expCatId, team.Id);
|
|
onCollapseClick(team, rawTeam);
|
|
};
|
|
// row - collapsable row, rawCollapsable - collapsable model that will be sent to the server
|
|
function onCollapseClick(row, rawCollapsable) {
|
|
// skip collapsing in actuals mode
|
|
if ($scope.data.CalendarFilter.ShowActuals)
|
|
return;
|
|
|
|
row.Collapsed = rawCollapsable.Collapsed = !row.Collapsed;
|
|
row.CollapsedClass = rawCollapsable.CollapsedClass = row.Collapsed ? $scope.CollapsedIcon : $scope.NonCollapsedIcon;
|
|
};
|
|
|
|
$scope.goToTeamBoard = function (Id, $event) {
|
|
// SA. Rewrited to open in new window
|
|
var url = "/Team/Board/?teamId=" + Id + "&#teamCalendar";
|
|
window.open(url);
|
|
$event.stopPropagation();
|
|
}
|
|
|
|
$scope.removeExpCat = function (row) {
|
|
if (null == row)
|
|
return;
|
|
|
|
if (row.HasActuals || $scope.data.CalendarFilter.VisibleExpCats.length <= 1)
|
|
return;
|
|
|
|
bootbox.confirm({
|
|
message: 'Do you really want to delete ' + row.ExpCatName + '?',
|
|
callback: function (result) {
|
|
if (result) {
|
|
$scope.$apply(function () {
|
|
removeExpCatsInternal([row.ExpCatId]);
|
|
});
|
|
}
|
|
}
|
|
});
|
|
};
|
|
function removeExpCatsInternal(expCats) {
|
|
if (!expCats || expCats.length <= 0)
|
|
return;
|
|
|
|
$scope.data.AvailableExpenditures = $scope.data.AvailableExpenditures.filter(function (obj) {
|
|
return expCats.indexOf(obj.Id) < 0;
|
|
});
|
|
$scope.data.NeedToRecalculateScenarioDetails = true;
|
|
$scope.gridChanged();
|
|
$scope.getCalendar();
|
|
for (var i = 0; i < expCats.lenght; i++) {
|
|
var data = { DomainId: null, ParentId: expCats[i] };
|
|
$rootScope.$broadcast('removeNoteCB', data);
|
|
}
|
|
};
|
|
$scope.addExpCats = function (callback) {
|
|
if (null == $scope.data.CalendarFilter.ExpCats2Add || $scope.data.CalendarFilter.ExpCats2Add.length == 0) {
|
|
$('#selExpCats2Add').select2('val', '');
|
|
return;
|
|
}
|
|
for (var i = 0; i < $scope.data.CalendarFilter.ExpCats2Add.length; i++) {
|
|
var item2Add = $scope.data.CalendarFilter.ExpCats2Add[i];
|
|
for (var j = 0; j < $scope.data.Expenditures2Add.length; j++) {
|
|
var item = $scope.data.Expenditures2Add[j];
|
|
if (item.Id === item2Add.Id) {
|
|
$scope.data.AvailableExpenditures.push({
|
|
Id: item.Id,
|
|
Name: item.Name
|
|
});
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
$scope.data.CalendarFilter.ExpCats2Add = null;
|
|
$('#selExpCats2Add').select2('val', '');
|
|
$scope.data.NeedToRecalculateScenarioDetails = true;
|
|
$scope.refreshGraph = true;
|
|
$scope.gridChanged();
|
|
$scope.getCalendar(callback);
|
|
};
|
|
$scope.addTeams = function () {
|
|
scenarioDetailsService.addTeamsInScenario(getScenarioId(), $scope.data.CalendarFilter.Teams2Add);
|
|
$scope.data.CalendarFilter.Teams2Add = null;
|
|
$('#selTeams2Add').select2('val', '');
|
|
$scope.data.NeedToRecalculateScenarioDetails = true;
|
|
$scope.gridChanged();
|
|
$scope.getCalendar();
|
|
};
|
|
$scope.removeTeam = function (team) {
|
|
if (null == team)
|
|
return;
|
|
|
|
bootbox.confirm({
|
|
message: 'Do you really want to delete ' + team.Name + '?',
|
|
callback: function (result) {
|
|
if (result) {
|
|
$scope.$apply(function () {
|
|
removeTeamInternal(team.Id);
|
|
$scope.RestTeams = getRestTeams();
|
|
refreshAvailableTeamsList();
|
|
|
|
$scope.gridChanged();
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
};
|
|
function removeTeamInternal(teamId) {
|
|
if (!teamId)
|
|
return;
|
|
|
|
if ($scope.data) {
|
|
|
|
if ($scope.data.Calendar) {
|
|
|
|
if ($scope.data.Calendar.Rows) {
|
|
for (var i in $scope.data.Calendar.Rows) {
|
|
for (var j in $scope.data.Calendar.Rows[i].Teams) {
|
|
if ($scope.data.Calendar.Rows[i].Teams[j].Id == teamId) {
|
|
for (var z in $scope.data.Calendar.WeekHeaders) {
|
|
if ($scope.data.Calendar.WeekHeaders[z].DataType != C_HEADER_DATA_TYPE_ORDINAL)
|
|
continue;
|
|
|
|
$scope.data.Calendar.Rows[i].RestQuantity[z] += $scope.data.Calendar.Rows[i].Teams[j].QuantityValues[z];
|
|
}
|
|
|
|
$scope.data.Calendar.Rows[i].Teams.splice(j, 1);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
scenarioDetailsService.deleteTeamsFromScenario(getScenarioId(), teamId);
|
|
}
|
|
}
|
|
}
|
|
function refreshAvailableTeamsList() {
|
|
if ($scope.data && $scope.data.Calendar && $scope.data.Calendar.Rows) {
|
|
for (var i = 0; i < $scope.data.Calendar.Rows.length; i++) {
|
|
var category = $scope.data.Calendar.Rows[i];
|
|
if (category.AllowResourceAssignment === false) {
|
|
category.AvailableTeams = getRestTeams();
|
|
}
|
|
else {
|
|
category.AvailableTeams = getRestTeams(category.ExpCatId);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
function getRestTeams(expenditureCategoryId) {
|
|
if (!$scope.data || !$scope.data.Teams)
|
|
return null;
|
|
|
|
var rest = $scope.data.Teams.filter(function (team) {
|
|
var isAssignedToScenario = scenarioDetailsService.checkTeamAssignedToScenario(getScenarioId(), team.Id);
|
|
var containsCategory = !expenditureCategoryId || (team.Expenditures && team.Expenditures.indexOf(expenditureCategoryId) >= 0);
|
|
|
|
return !isAssignedToScenario && containsCategory;
|
|
});
|
|
|
|
return rest;
|
|
};
|
|
// refresh CSS classes of the category or resource cell
|
|
$scope.updateCSSClass = function (row, teamRow, resRow, colIndex) {
|
|
if (colIndex == null || $scope.data.Calendar.WeekHeaders == null)
|
|
return;
|
|
|
|
if (row != null && teamRow != null && resRow != null) { // updating resource row
|
|
resRow.CSSClass[colIndex] = '';
|
|
if (resRow.QuantityValues == null || resRow.QuantityValues.length <= colIndex)
|
|
return;
|
|
|
|
if (($scope.data.Calendar.WeekHeaders[colIndex].DataType == C_HEADER_DATA_TYPE_ORDINAL) && $scope.data.CalendarFilter.IsTableModeQuantity) {
|
|
var quantity = Math.round(resRow.QuantityValues[colIndex] * 100) / 100;
|
|
var restCapacity = Math.round(resRow.RestQuantityValues[colIndex] * 100) / 100;
|
|
|
|
if (quantity == 0)
|
|
return;
|
|
// highlight with red if resource is overallocated against self capacity
|
|
if (restCapacity < 0)
|
|
resRow.CSSClass[colIndex] = 'cellover';
|
|
}
|
|
}
|
|
else if (row != null && teamRow != null) { // updating team row
|
|
teamRow.CSSClass[colIndex] = '';
|
|
if (teamRow.QuantityValues == null || teamRow.QuantityValues.length < colIndex ||
|
|
teamRow.AllocatedByResources == null || teamRow.AllocatedByResources.length < colIndex)
|
|
return;
|
|
|
|
if (($scope.data.Calendar.WeekHeaders[colIndex].DataType == C_HEADER_DATA_TYPE_ORDINAL) && $scope.data.CalendarFilter.IsTableModeQuantity) {
|
|
|
|
var allocatedByResources = (Math.round(teamRow.AllocatedByResources[colIndex] * 100) / 100);
|
|
var quantity = (Math.round(teamRow.QuantityValues[colIndex] * 100) / 100);
|
|
|
|
if (allocatedByResources > quantity) {
|
|
$scope.TeamsIsOverAllocated = true;
|
|
teamRow.CSSClass[colIndex] = 'cellover';
|
|
}
|
|
else if (allocatedByResources < quantity) {
|
|
//$scope.TeamsIsUnderAllocated = true;
|
|
teamRow.CSSClass[colIndex] = 'cellless';
|
|
}
|
|
else if (allocatedByResources == quantity)
|
|
teamRow.CSSClass[colIndex] = 'cellequal';
|
|
}
|
|
}
|
|
else if (row != null) { // updating category row
|
|
row.CSSClass[colIndex] = '';
|
|
if (row.RestQuantity == null || row.RestQuantity.length <= colIndex)
|
|
return;
|
|
if ($scope.data.CalendarFilter.ShowActuals) {
|
|
if ($scope.data.Scenario.RedIndicator != null && row.ForecastCompletedPercent >= $scope.data.Scenario.RedIndicator) {
|
|
row.CSSClass[colIndex] = 'red';
|
|
} else if ($scope.data.Scenario.YellowIndicator != null && row.ForecastCompletedPercent >= $scope.data.Scenario.YellowIndicator) {
|
|
row.CSSClass[colIndex] = 'yellow';
|
|
} else if ($scope.data.Scenario.RedIndicator != null && $scope.data.Scenario.YellowIndicator != null) { //($scope.data.Scenario.GreenIndicator != null && row.ForecastCompletedPercent >= $scope.data.Scenario.GreenIndicator) {
|
|
row.CSSClass[colIndex] = 'green';
|
|
}
|
|
} else {
|
|
if (($scope.data.Calendar.WeekHeaders[colIndex].DataType == C_HEADER_DATA_TYPE_ORDINAL) && $scope.data.CalendarFilter.IsTableModeQuantity) {
|
|
var ecRestQuantity = Math.round(row.RestQuantity[colIndex] * 100) / 100;
|
|
if (ecRestQuantity < 0) {
|
|
$scope.ExpendituresIsOverAllocated = true;
|
|
row.CSSClass[colIndex] = 'cellover';
|
|
}
|
|
else if (ecRestQuantity > 0) {
|
|
$scope.ExpendituresIsUnderAllocated = true;
|
|
row.CSSClass[colIndex] = 'cellless';
|
|
}
|
|
else
|
|
row.CSSClass[colIndex] = 'cellequal';
|
|
}
|
|
}
|
|
}
|
|
};
|
|
$scope.updateMonthCSSClass = function (CSSClass, monthWeeks, colIndex) {
|
|
if (!CSSClass || !monthWeeks || !colIndex || CSSClass.length <= colIndex)
|
|
return;
|
|
|
|
var isExistsOverAllocated = false,
|
|
isExistsUnderAllocated = false,
|
|
isEquals = true;
|
|
|
|
for (var i in monthWeeks) {
|
|
if (CSSClass[monthWeeks[i]] == 'cellover')
|
|
isExistsOverAllocated = true;
|
|
|
|
if (CSSClass[monthWeeks[i]] == 'cellless')
|
|
isExistsUnderAllocated = true;
|
|
|
|
if (CSSClass[monthWeeks[i]] != 'cellequal')
|
|
isEquals = false;
|
|
}
|
|
|
|
if (isExistsOverAllocated)
|
|
CSSClass[colIndex] = 'cellover';
|
|
else if (isExistsUnderAllocated)
|
|
CSSClass[colIndex] = 'cellless';
|
|
else if (isEquals)
|
|
CSSClass[colIndex] = 'cellequal';
|
|
else
|
|
CSSClass[colIndex] = '';
|
|
};
|
|
// refresh CSS classes for all cells
|
|
$scope.RefreshCSSClasses = function () {
|
|
$scope.ExpendituresIsOverAllocated = $scope.ExpendituresIsUnderAllocated = $scope.TeamsIsOverAllocated = false;
|
|
// iterate through collection of all expenditure categories (except total)
|
|
if ($scope.data.Calendar.Rows)
|
|
for (var rowIndex = 1; rowIndex < $scope.data.Calendar.Rows.length; rowIndex++) {
|
|
var expCat = $scope.data.Calendar.Rows[rowIndex];
|
|
// init CSSClass array if necessary
|
|
if (expCat.CSSClass == null)
|
|
expCat.CSSClass = new Array($scope.data.Calendar.WeekHeaders.length);
|
|
// iterate through each cell of selected expenditure category row
|
|
for (var colIndex = 0; colIndex < $scope.data.Calendar.WeekHeaders.length; colIndex++) {
|
|
var weekHeader = $scope.data.Calendar.WeekHeaders[colIndex];
|
|
var monthWeeks = $scope.data.Calendar.MonthHeaders[weekHeader.MonthHeader].WeekHeaders;
|
|
|
|
if ($scope.data.Calendar.WeekHeaders[colIndex].DataType == C_HEADER_DATA_TYPE_TOTALS)
|
|
// update CSS class of selected expenditure category month cell
|
|
$scope.updateMonthCSSClass(expCat.CSSClass, monthWeeks, colIndex);
|
|
else
|
|
// update CSS of the selected expenditure category cell
|
|
$scope.updateCSSClass(expCat, null, null, colIndex);
|
|
|
|
// if expenditure category has assigned teams then we need to color teams as well
|
|
if (expCat.Teams != null && expCat.Teams.length > 0) {
|
|
// iterate through each team row cells
|
|
for (var teamIndex = 0; teamIndex < expCat.Teams.length; teamIndex++) {
|
|
var team = expCat.Teams[teamIndex];
|
|
// init CSSClass array if necessary
|
|
if (team.CSSClass == null)
|
|
team.CSSClass = new Array($scope.data.Calendar.WeekHeaders.length);
|
|
|
|
if ($scope.data.Calendar.WeekHeaders[colIndex].DataType == C_HEADER_DATA_TYPE_TOTALS)
|
|
// update CSS class of selected team month cell
|
|
$scope.updateMonthCSSClass(team.CSSClass, monthWeeks, colIndex);
|
|
else
|
|
// update CSS classes of selected team cell
|
|
$scope.updateCSSClass(expCat, team, null, colIndex);
|
|
|
|
// if team has assigned resources then we need to color resources as well
|
|
if (team.Resources != null && team.Resources.length > 0) {
|
|
// iterate through each resource row cells
|
|
for (var resIndex = 0; resIndex < team.Resources.length; resIndex++) {
|
|
var resource = team.Resources[resIndex];
|
|
// init CSSClass array if necessary
|
|
if (resource.CSSClass == null)
|
|
resource.CSSClass = new Array($scope.data.Calendar.WeekHeaders.length);
|
|
|
|
if ($scope.data.Calendar.WeekHeaders[colIndex].DataType == C_HEADER_DATA_TYPE_TOTALS)
|
|
// update CSS class of selected resource month cell
|
|
$scope.updateMonthCSSClass(resource.CSSClass, monthWeeks, colIndex);
|
|
else
|
|
// update CSS classes of selected resource cell
|
|
$scope.updateCSSClass(expCat, team, resource, colIndex);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
$scope.gridWasChanged = function () {
|
|
return $scope.data.isDataChanged;
|
|
};
|
|
$scope.canSaveScenario = function () {
|
|
if ($scope.data.WorkFlowActions.length > 1 && $scope.isScenarioValid())
|
|
return true;
|
|
if( $scope.gridWasChanged() && $scope.isScenarioValid())
|
|
return true;
|
|
return false;
|
|
};
|
|
|
|
$scope.isScenarioValid = function () {
|
|
var expendituresInScenario = scenarioDetailsService.getExpenditures(getScenarioId());
|
|
var isValid = $scope.isValidAllocation() &&
|
|
expendituresInScenario && Object.keys(expendituresInScenario).length > 0;
|
|
|
|
return isValid;
|
|
};
|
|
$scope.renderHtml = function (html) {
|
|
return $sce.trustAsHtml(html);
|
|
};
|
|
$scope.getScenarioDetailsData = function () {
|
|
return {
|
|
AvailableExpenditures: $scope.data.AvailableExpenditures,
|
|
TeamsInScenario: scenarioDetailsService.recalculateTeamsAllocation(getScenarioId())
|
|
};
|
|
};
|
|
$scope.setFinInfo = function (finInfoStr) {
|
|
if (!finInfoStr || finInfoStr.trim().length <= 0)
|
|
return;
|
|
|
|
var finInfo = JSON.parse(finInfoStr);
|
|
if (finInfo.IsRevenueGenerating)
|
|
$scope.data.Scenario.ProjectedRevenue = finInfo.ProjectedRevenue;
|
|
else
|
|
$scope.data.Scenario.TDDirectCosts = finInfo.ProjectedExpense;
|
|
|
|
if (finInfo.UseLMMargin) {
|
|
$scope.data.Scenario.UseLMMargin = true;
|
|
$scope.data.Scenario.LMMargin = finInfo.Margin;
|
|
}
|
|
else {
|
|
$scope.data.Scenario.UseLMMargin = false;
|
|
$scope.data.Scenario.GrossMargin = finInfo.Margin;
|
|
}
|
|
|
|
if (finInfo.CostSavings) {
|
|
var startDate = new Date(finInfo.CostSavings.CostSavingStartDate),
|
|
endDate = new Date(finInfo.CostSavings.CostSavingEndDate),
|
|
costSavingItems = (finInfo.CostSavings.CostSavingItems || "").trim().length > 0 ? JSON.parse(finInfo.CostSavings.CostSavingItems) : null;
|
|
|
|
$scope.data.Scenario.CostSavings = {
|
|
CostSavingItems: costSavingItems,
|
|
CostSavingStartDate: Date.UTC(startDate.getFullYear(), startDate.getMonth(), startDate.getDate()),
|
|
CostSavingEndDate: Date.UTC(endDate.getFullYear(), endDate.getMonth(), endDate.getDate()),
|
|
CostSavings: finInfo.CostSavings.CostSavings,
|
|
CostSavingType: finInfo.CostSavings.CostSavingType,
|
|
CostSavingDescription: finInfo.CostSavings.CostSavingDescription
|
|
};
|
|
}
|
|
};
|
|
$scope.clearSelection = function () {
|
|
clearAllSelectedCells();
|
|
};
|
|
$scope.saveScenarioDetails = function ($event) {
|
|
var scenario = {
|
|
Id: getScenarioId(),
|
|
ProjectId: $scope.data.Scenario.ParentId,
|
|
StartDate: $scope.data.Scenario.StartDate,
|
|
EndDate: $scope.data.Scenario.EndDate,
|
|
ProjectedRevenue: $scope.data.Scenario.ProjectedRevenue,
|
|
TDDirectCosts: $scope.data.Scenario.TDDirectCosts,
|
|
UseLMMargin: $scope.data.Scenario.UseLMMargin,
|
|
GrossMargin: $scope.data.Scenario.GrossMargin,
|
|
LMMargin: $scope.data.Scenario.LMMargin,
|
|
CostSavings: $scope.data.Scenario.CostSavings,
|
|
Expenditures: scenarioDetailsService.getExpenditures(getScenarioId())
|
|
};
|
|
var args = {
|
|
scenario: scenario,
|
|
currentTarget: $event.currentTarget
|
|
};
|
|
$rootScope.$broadcast('saveScenarioDetails', args);
|
|
};
|
|
$scope.updateWorkFlowAction = function ($scope2) {
|
|
var i = 0;
|
|
var len = $scope.data.WorkFlowActions.length;
|
|
for (; i < len; i++) {
|
|
if ($scope.data.WorkFlowActions[i].command == $scope2.command) {
|
|
$scope.data.WorkFlowActions[i].Selected = true;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
$scope.updateWorkFlowState = function ($scope2) {
|
|
var i = 0;
|
|
var len = $scope.data.WorkFlowStates.length;
|
|
for (; i < len; i++) {
|
|
if ($scope.data.WorkFlowStates[i].state == $scope2.state) {
|
|
$scope.data.WorkFlowStates[i].Selected = true;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
$scope.cancelScenarioDetails = function ($event) {
|
|
var args = {
|
|
currentTarget: $event.currentTarget
|
|
};
|
|
$rootScope.$broadcast('cancelScenarioDetails', args);
|
|
};
|
|
$scope.scenarioEditModelChanged = function () {
|
|
$scope.data.ScenarioGeneralInfoEditModel.IsChanged = true;
|
|
};
|
|
$scope.recalculateScenarioDetails = function () {
|
|
if ($scope.data.ScenarioGeneralInfoEditModel.IsChanged === true) {
|
|
if (angular.isFunction(scenarioDetailsEditFormIsValid) && scenarioDetailsEditFormIsValid()) {
|
|
var startDateMs = DateTimeConverter.stringToMs($scope.data.ScenarioGeneralInfoEditModel.StartDate);
|
|
var endDateMs = DateTimeConverter.stringToMs($scope.data.ScenarioGeneralInfoEditModel.EndDate);
|
|
var date4StartOfChangesMs = DateTimeConverter.stringToMs($scope.data.ScenarioGeneralInfoEditModel.DateForStartOfChanges);
|
|
|
|
var costSavingBackup = costSavingService.createBackup(getScenarioId());
|
|
try {
|
|
var range = costSavingService.changeScenarioRange(
|
|
getScenarioId(),
|
|
$scope.data.ScenarioGeneralInfoEditModel.StartDate,
|
|
$scope.data.ScenarioGeneralInfoEditModel.EndDate);
|
|
|
|
if (!!range) {
|
|
var isChanged = costSavingService.setRange(getScenarioId(), range.startDate, range.endDate);
|
|
if (isChanged)
|
|
$scope.data.Scenario.CostSavings = costSavingService.getCostSavings4Save(getScenarioId());
|
|
}
|
|
} catch (e) {
|
|
if (!!costSavingBackup) {
|
|
costSavingService.restoreBackup(getScenarioId(), costSavingBackup);
|
|
}
|
|
showErrorModal('Oops!', $scope.CommonErrorMessage);
|
|
return;
|
|
}
|
|
|
|
$scope.data.Scenario.DistributionType = $scope.data.ScenarioGeneralInfoEditModel.DistributionType;
|
|
$scope.data.Scenario.StartDate = startDateMs;
|
|
$scope.data.Scenario.EndDate = endDateMs;
|
|
$scope.data.Scenario.DateForStartOfChanges = date4StartOfChangesMs;
|
|
// need to adjust margin for original or keep total modes
|
|
$scope.data.Scenario.NeedToAdjustMargin = $scope.data.Scenario.DistributionType == "0" || $scope.data.Scenario.DistributionType == "1";
|
|
$scope.data.ScenarioGeneralInfoEditModel.IsChanged = false;
|
|
$scope.data.NeedToRecalculateScenarioDetails = true;
|
|
//todo: maybe set adjustMarginRased and dateForStartOfChangesOld for Log in $scope.data.Scenario
|
|
|
|
$scope.gridChanged();
|
|
$scope.getCalendar(null, normalizeTeamQuantityValues);
|
|
}
|
|
}
|
|
};
|
|
// It is usefull for RMO when we have ALL capacity for each team and we should only extend or cut
|
|
// quantity values for teams and their resources. Quantity values for expenditures are actual because thay came from the server
|
|
function normalizeTeamQuantityValues() {
|
|
var expendituresInScenario = scenarioDetailsService.getExpenditures(getScenarioId());
|
|
if (!expendituresInScenario || !$scope.data.Calendar.WeekHeaders)
|
|
return;
|
|
|
|
var weekEndings = [];
|
|
$scope.data.Calendar.WeekHeaders.forEach(function (item) {
|
|
if (item.DataType === C_HEADER_DATA_TYPE_ORDINAL)
|
|
weekEndings.push(item.Milliseconds);
|
|
});
|
|
|
|
// extend with 0-values
|
|
for (var weekIndex = 0; weekIndex < weekEndings.length; weekIndex++) {
|
|
var weekEnding = weekEndings[weekIndex];
|
|
for (var expCatId in expendituresInScenario) {
|
|
var category = expendituresInScenario[expCatId];
|
|
if (!category.Teams)
|
|
continue;
|
|
|
|
for (var teamId in category.Teams) {
|
|
var team = category.Teams[teamId];
|
|
if (!team.QuantityValues)
|
|
team.QuantityValues = {};
|
|
|
|
if (!team.QuantityValues[weekEnding])
|
|
team.QuantityValues[weekEnding] = 0;
|
|
|
|
if (!team.Resources)
|
|
continue;
|
|
|
|
for (var resourceId in team.Resources) {
|
|
var resource = team.Resources[resourceId];
|
|
if (!resource.QuantityValues)
|
|
resource.QuantityValues = {};
|
|
|
|
if (!resource.QuantityValues[weekEnding])
|
|
resource.QuantityValues[weekEnding] = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// delete redundant
|
|
for (var expCatId in expendituresInScenario) {
|
|
var category = expendituresInScenario[expCatId];
|
|
if (!category.Teams)
|
|
continue;
|
|
|
|
for (var teamId in category.Teams) {
|
|
var team = category.Teams[teamId];
|
|
if (!!team.QuantityValues) {
|
|
var tkeys = Object.keys(team.QuantityValues);
|
|
for (var i = 0; i < tkeys.length; i++) {
|
|
var tkey = parseFloat(tkeys[i]);
|
|
if (weekEndings.indexOf(tkey) < 0)
|
|
delete team.QuantityValues[tkey];
|
|
}
|
|
}
|
|
|
|
if (!team.Resources)
|
|
continue;
|
|
|
|
for (var resourceId in team.Resources) {
|
|
var resource = team.Resources[resourceId];
|
|
if (!!resource.QuantityValues) {
|
|
var rkeys = Object.keys(resource.QuantityValues);
|
|
for (var i = 0; i < rkeys.length; i++) {
|
|
var rkey = parseFloat(rkeys[i]);
|
|
if (weekEndings.indexOf(rkey) < 0)
|
|
delete resource.QuantityValues[rkey];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
/* Drag and drop methods */
|
|
$scope.dragStart = function ($dragmodel) {
|
|
if (!$dragmodel.ExpCat.SelectedCells[$dragmodel.Position]) {
|
|
clearAllSelectedCells();
|
|
clearActiveDroppables($dragmodel.ExpCat);
|
|
checkAndSelectMonthCell($dragmodel.ExpCat, $dragmodel.Position, true);
|
|
}
|
|
};
|
|
$scope.drag = function ($api) {
|
|
if (!scrollGrid || typeof scrollGrid != 'function')
|
|
return;
|
|
|
|
var axis = $api.getAxis();
|
|
var coords = {
|
|
x: axis.x,
|
|
y: axis.y
|
|
};
|
|
scrollGrid(coords);
|
|
}
|
|
$scope.dragEnd = function ($dragmodel) {
|
|
clearActiveDroppables($dragmodel.ExpCat);
|
|
clearSelectedCells($dragmodel.ExpCat);
|
|
};
|
|
$scope.dragEnter = function ($dropmodel, $dragmodel) {
|
|
recalculateActiveDroppables($dropmodel.ExpCat, calculateShift($dropmodel.Position, $dragmodel.Position));
|
|
};
|
|
$scope.drop = function ($dropmodel, $dragmodel) {
|
|
copyRange($dropmodel.ExpCat, $dropmodel.ExpCatSourcePosition, calculateShift($dropmodel.Position, $dragmodel.Position));
|
|
clearActiveDroppables($dropmodel.ExpCat);
|
|
clearSelectedCells($dropmodel.ExpCat);
|
|
|
|
$scope.RefreshCSSClasses();
|
|
};
|
|
$scope.selectCell = function (expCat, index, $event) {
|
|
$event.stopPropagation();
|
|
|
|
if ($scope.data.CalendarFilter.ShowActuals ||
|
|
// need to select cell only if user clicks by cell but not by any child inside cell
|
|
$event.target != $event.currentTarget) {
|
|
return;
|
|
}
|
|
|
|
var keys = dndKey.get();
|
|
if (keys && keys.length > 0) {
|
|
var excludeExpCats = [];
|
|
excludeExpCats.push(expCat.ExpCatId);
|
|
clearAllSelectedCells(excludeExpCats);
|
|
|
|
// with pressed Caps Lock user can select any cells from grid without range - we need to prohibit this ability
|
|
if (dndKey.isset(20))
|
|
return;
|
|
|
|
// Ctrl
|
|
if (dndKey.isset(17)) {
|
|
var needToSelectCell = !expCat.SelectedCells[index];
|
|
|
|
// check ability to select cell with pressed Ctrl key
|
|
if (needToSelectCell && !checkCtrlSelection(expCat, index))
|
|
return;
|
|
|
|
// check ability to unselect cell with pressed Ctrl key
|
|
if (!needToSelectCell && !checkCtrlUnselection(expCat, index))
|
|
return;
|
|
}
|
|
// Shift
|
|
else if (dndKey.isset(16)) {
|
|
selectRange(expCat, index);
|
|
return;
|
|
}
|
|
}
|
|
else {
|
|
clearAllSelectedCells();
|
|
}
|
|
|
|
checkAndSelectMonthCell(expCat, index, !expCat.SelectedCells[index]);
|
|
};
|
|
|
|
function checkAndSelectMonthCell(expCat, index, isSelected) {
|
|
if (!isSelectableWeekEnding(index))
|
|
return;
|
|
|
|
expCat.SelectedCells[index] = isSelected;
|
|
|
|
// if user clicked month cell we need to select/unselect all weeks from this month
|
|
var weekHeader = $scope.data.Calendar.WeekHeaders[index];
|
|
var monthHeader = $scope.data.Calendar.MonthHeaders[weekHeader.MonthHeader];
|
|
|
|
if (weekHeader.DataType == C_HEADER_DATA_TYPE_TOTALS) {
|
|
for (var i in monthHeader.WeekHeaders) {
|
|
if (isSelectableWeekEnding(monthHeader.WeekHeaders[i]))
|
|
expCat.SelectedCells[monthHeader.WeekHeaders[i]] = isSelected;
|
|
}
|
|
}
|
|
else {
|
|
var monthIndexInWeekHeaders = Math.max.apply(null, monthHeader.WeekHeaders) + 1;
|
|
if (isSelected) {
|
|
if (areAllWeeksSelected(expCat, monthHeader))
|
|
expCat.SelectedCells[monthIndexInWeekHeaders] = true;
|
|
}
|
|
else {
|
|
expCat.SelectedCells[monthIndexInWeekHeaders] = false;
|
|
}
|
|
}
|
|
};
|
|
function clearSelectedCells(expCat) {
|
|
if (!expCat || !expCat.SelectedCells || expCat.SelectedCells.indexOf(true) < 0)
|
|
return;
|
|
|
|
for (var i = 0; i < expCat.SelectedCells.length; i++)
|
|
expCat.SelectedCells[i] = false;
|
|
}
|
|
function clearAllSelectedCells(excludeExpCats) {
|
|
if (!excludeExpCats)
|
|
excludeExpCats = [];
|
|
|
|
if (!$scope.data.Calendar || !$scope.data.Calendar.Rows || $scope.data.Calendar.Rows.length <= 0)
|
|
return;
|
|
|
|
for (var i = 0; i < $scope.data.Calendar.Rows.length; i++) {
|
|
if (excludeExpCats.indexOf($scope.data.Calendar.Rows[i].ExpCatId) >= 0)
|
|
continue;
|
|
|
|
clearSelectedCells($scope.data.Calendar.Rows[i]);
|
|
}
|
|
}
|
|
function checkCtrlSelection(expCat, selectingIndex) {
|
|
if (!expCat || !expCat.SelectedCells)
|
|
return false;
|
|
|
|
var firstSelectedCellIndex = expCat.SelectedCells.indexOf(true);
|
|
var lastSelectedCellIndex = expCat.SelectedCells.lastIndexOf(true);
|
|
|
|
if (firstSelectedCellIndex < 0 && lastSelectedCellIndex < 0)
|
|
return true;
|
|
|
|
var prevIndexes = getPrevSelectableWeekEndings(firstSelectedCellIndex - 1);
|
|
var nextIndexes = getNextSelectableWeekEndings(lastSelectedCellIndex + 1);
|
|
|
|
return prevIndexes.indexOf(selectingIndex) >= 0 || nextIndexes.indexOf(selectingIndex) >= 0;
|
|
}
|
|
|
|
// check ability to unselect cell with pressed Ctrl key
|
|
function checkCtrlUnselection(expCat, selectingIndex) {
|
|
if (!expCat || !expCat.SelectedCells)
|
|
return false;
|
|
|
|
// if it is leftmost week's cell - can be unselected
|
|
var firstSelectedCellIndex = expCat.SelectedCells.indexOf(true);
|
|
if (firstSelectedCellIndex == selectingIndex)
|
|
return true;
|
|
|
|
// if it is leftmost month's cell - can be unselected
|
|
var firstSelectedMonth = getMonthIndex(firstSelectedCellIndex);
|
|
if (firstSelectedMonth == selectingIndex)
|
|
return true;
|
|
|
|
// if it is rightmost week's cell - can be unselected
|
|
var lastSelectedWeekEndingIndex = getLastSelectedWeekEnding(expCat);
|
|
if (lastSelectedWeekEndingIndex == selectingIndex)
|
|
return true;
|
|
|
|
// if it is rightmost month's cell - can be unselected
|
|
var lastSelectedMonth = getMonthIndex(lastSelectedWeekEndingIndex);
|
|
if (lastSelectedMonth == selectingIndex)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
function areAllWeeksSelected(expCat, monthHeader) {
|
|
if (!expCat || !expCat.SelectedCells)
|
|
return false;
|
|
|
|
if (!monthHeader || !monthHeader.WeekHeaders)
|
|
return false;
|
|
|
|
var areAllSelected = true;
|
|
for (var i in monthHeader.WeekHeaders) {
|
|
if (isSelectableWeekEnding(monthHeader.WeekHeaders[i]) && !expCat.SelectedCells[monthHeader.WeekHeaders[i]]) {
|
|
areAllSelected = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return areAllSelected;
|
|
};
|
|
function getPrevSelectableWeekEndings(toIndex) {
|
|
var weekEndings = [];
|
|
|
|
for (var i = toIndex; i >= 0; i--) {
|
|
if ($scope.data.Calendar.WeekHeaders[i].DataType == C_HEADER_DATA_TYPE_ORDINAL &&
|
|
$scope.data.Calendar.WeekHeaders[i].Visible[$scope.data.CalendarFilter.ViewModeName]) {
|
|
weekEndings.push(i);
|
|
weekEndings.push(getMonthIndex(i));
|
|
break;
|
|
}
|
|
}
|
|
|
|
return weekEndings;
|
|
}
|
|
function getNextSelectableWeekEndings(startIndex) {
|
|
var weekEndings = [];
|
|
|
|
for (var i = startIndex; i < $scope.data.Calendar.WeekHeaders.length; i++) {
|
|
if ($scope.data.Calendar.WeekHeaders[i].DataType == C_HEADER_DATA_TYPE_ORDINAL &&
|
|
$scope.data.Calendar.WeekHeaders[i].Visible[$scope.data.CalendarFilter.ViewModeName]) {
|
|
weekEndings.push(i);
|
|
weekEndings.push(getMonthIndex(i));
|
|
break;
|
|
}
|
|
}
|
|
|
|
return weekEndings;
|
|
}
|
|
// gets the last selected week ending, not month
|
|
function getLastSelectedWeekEnding(expCat) {
|
|
var lastWeekEnding = -1;
|
|
|
|
for (var i = expCat.SelectedCells.lastIndexOf(true) ; i >= 0; i--) {
|
|
if ($scope.data.Calendar.WeekHeaders[i].DataType == C_HEADER_DATA_TYPE_ORDINAL &&
|
|
$scope.data.Calendar.WeekHeaders[i].Visible[$scope.data.CalendarFilter.ViewModeName] &&
|
|
expCat.SelectedCells[i]) {
|
|
lastWeekEnding = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return lastWeekEnding;
|
|
}
|
|
function isSelectableWeekEnding(index) {
|
|
return $scope.data.Calendar.WeekHeaders[index].Visible[$scope.data.CalendarFilter.ViewModeName];
|
|
}
|
|
function getMonthIndex(weekEndingIndex) {
|
|
var weekHeader = $scope.data.Calendar.WeekHeaders[weekEndingIndex];
|
|
if (!weekHeader)
|
|
return -1;
|
|
|
|
var monthHeader = $scope.data.Calendar.MonthHeaders[weekHeader.MonthHeader];
|
|
if (!monthHeader)
|
|
return -1;
|
|
|
|
return Math.max.apply(null, monthHeader.WeekHeaders) + 1;
|
|
}
|
|
function selectRange(expCat, upToIndex) {
|
|
if (!expCat || !expCat.SelectedCells)
|
|
return false;
|
|
|
|
var startIndex = expCat.SelectedCells.indexOf(true);
|
|
// if there are no selected cells yet we need to select only single cell
|
|
if (startIndex < 0) {
|
|
checkAndSelectMonthCell(expCat, upToIndex, true);
|
|
return;
|
|
}
|
|
else {
|
|
clearSelectedCells(expCat);
|
|
for (var i = startIndex; i < upToIndex; i++) {
|
|
if (!isSelectableWeekEnding(i))
|
|
continue;
|
|
|
|
expCat.SelectedCells[i] = true;
|
|
}
|
|
// need to check only last index
|
|
checkAndSelectMonthCell(expCat, upToIndex, true);
|
|
}
|
|
}
|
|
function clearActiveDroppables(expCat) {
|
|
for (var i = 0; i < $scope.data.Calendar.WeekHeaders.length; i++)
|
|
expCat.ActiveDroppables[i] = false;
|
|
}
|
|
function recalculateActiveDroppables(expCat, shift) {
|
|
if (!expCat || !expCat.ActiveDroppables || !expCat.SelectedCells)
|
|
return;
|
|
|
|
clearActiveDroppables(expCat);
|
|
if (shift > 0) {
|
|
for (var i = 0; i < expCat.SelectedCells.length; i++) {
|
|
if (!expCat.SelectedCells[i])
|
|
continue;
|
|
|
|
if ($scope.data.Calendar.WeekHeaders[i].DataType != C_HEADER_DATA_TYPE_ORDINAL ||
|
|
!$scope.data.Calendar.WeekHeaders[i].Visible[$scope.data.CalendarFilter.ViewModeName]) {
|
|
continue;
|
|
}
|
|
|
|
var activeIndex = -1;
|
|
for (var j = i + 1, z = 0; j < expCat.SelectedCells.length; j++) {
|
|
if ($scope.data.Calendar.WeekHeaders[j].DataType == C_HEADER_DATA_TYPE_ORDINAL &&
|
|
$scope.data.Calendar.WeekHeaders[j].Visible[$scope.data.CalendarFilter.ViewModeName]) {
|
|
z++;
|
|
}
|
|
|
|
if (z == shift) {
|
|
activeIndex = j;
|
|
break;
|
|
}
|
|
}
|
|
if (activeIndex < 0)
|
|
continue;
|
|
|
|
var monthIndex = $scope.data.Calendar.WeekHeaders[activeIndex].MonthHeader;
|
|
var monthIndexInWeeksArray = Math.max.apply(null, $scope.data.Calendar.MonthHeaders[monthIndex].WeekHeaders) + 1;
|
|
|
|
expCat.ActiveDroppables[activeIndex] = true;
|
|
expCat.ActiveDroppables[monthIndexInWeeksArray] = true;
|
|
}
|
|
}
|
|
else {
|
|
for (var i = expCat.SelectedCells.length; i > 0; i--) {
|
|
if (!expCat.SelectedCells[i])
|
|
continue;
|
|
|
|
if ($scope.data.Calendar.WeekHeaders[i].DataType != C_HEADER_DATA_TYPE_ORDINAL ||
|
|
!$scope.data.Calendar.WeekHeaders[i].Visible[$scope.data.CalendarFilter.ViewModeName]) {
|
|
continue;
|
|
}
|
|
|
|
var activeIndex = -1;
|
|
for (var j = i - 1, z = 0; j >= 0; j--) {
|
|
if ($scope.data.Calendar.WeekHeaders[j].DataType == C_HEADER_DATA_TYPE_ORDINAL &&
|
|
$scope.data.Calendar.WeekHeaders[j].Visible[$scope.data.CalendarFilter.ViewModeName]) {
|
|
z++;
|
|
}
|
|
|
|
if (z == Math.abs(shift)) {
|
|
activeIndex = j;
|
|
break;
|
|
}
|
|
}
|
|
if (activeIndex < 0)
|
|
continue;
|
|
|
|
var monthIndex = $scope.data.Calendar.WeekHeaders[activeIndex].MonthHeader;
|
|
var monthIndexInWeeksArray = Math.max.apply(null, $scope.data.Calendar.MonthHeaders[monthIndex].WeekHeaders) + 1;
|
|
|
|
expCat.ActiveDroppables[activeIndex] = true;
|
|
expCat.ActiveDroppables[monthIndexInWeeksArray] = true;
|
|
}
|
|
}
|
|
}
|
|
function calculateShift(dropModelIndex, dragModelIndex) {
|
|
var shift = 0,
|
|
dragModelHeader = $scope.data.Calendar.WeekHeaders[dragModelIndex],
|
|
dropModelHeader = $scope.data.Calendar.WeekHeaders[dropModelIndex];
|
|
|
|
if (dragModelHeader.DataType == C_HEADER_DATA_TYPE_TOTALS) {
|
|
dragModelIndex = Math.min.apply(null, $scope.data.Calendar.MonthHeaders[dragModelHeader.MonthHeader].WeekHeaders);
|
|
}
|
|
|
|
if (dropModelHeader.DataType == C_HEADER_DATA_TYPE_TOTALS) {
|
|
dropModelIndex = Math.min.apply(null, $scope.data.Calendar.MonthHeaders[dropModelHeader.MonthHeader].WeekHeaders);
|
|
}
|
|
|
|
// push
|
|
if (dropModelIndex > dragModelIndex) {
|
|
for (var i = dragModelIndex; i < dropModelIndex; i++) {
|
|
if ($scope.data.Calendar.WeekHeaders[i].DataType == C_HEADER_DATA_TYPE_ORDINAL &&
|
|
$scope.data.Calendar.WeekHeaders[i].Visible[$scope.data.CalendarFilter.ViewModeName]) {
|
|
shift++;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
for (var i = dropModelIndex; i < dragModelIndex; i++) {
|
|
if ($scope.data.Calendar.WeekHeaders[i].DataType == C_HEADER_DATA_TYPE_ORDINAL &&
|
|
$scope.data.Calendar.WeekHeaders[i].Visible[$scope.data.CalendarFilter.ViewModeName]) {
|
|
shift--;
|
|
}
|
|
}
|
|
}
|
|
//console.log(shift);
|
|
return shift;
|
|
}
|
|
function getActiveDroppablesCount(expCat) {
|
|
var count = 0;
|
|
for (var i = expCat.ActiveDroppables.lastIndexOf(true) ; i >= expCat.ActiveDroppables.indexOf(true) ; i--) {
|
|
if ($scope.data.Calendar.WeekHeaders[i].DataType == C_HEADER_DATA_TYPE_TOTALS ||
|
|
!$scope.data.Calendar.WeekHeaders[i].Visible[$scope.data.CalendarFilter.ViewModeName]) {
|
|
continue;
|
|
}
|
|
|
|
count++;
|
|
}
|
|
return count;
|
|
}
|
|
$scope.getSelectedCellsCount = function (expCat) {
|
|
var count = 0;
|
|
for (var i = expCat.SelectedCells.lastIndexOf(true) ; i >= expCat.SelectedCells.indexOf(true) ; i--) {
|
|
if (!$scope.data.Calendar.WeekHeaders[i] ||
|
|
$scope.data.Calendar.WeekHeaders[i].DataType == C_HEADER_DATA_TYPE_TOTALS ||
|
|
!$scope.data.Calendar.WeekHeaders[i].Visible[$scope.data.CalendarFilter.ViewModeName]) {
|
|
continue;
|
|
}
|
|
|
|
count++;
|
|
}
|
|
return count;
|
|
}
|
|
function copyRange(expCat, position, shift) {
|
|
if (!expCat || !expCat.ActiveDroppables || !expCat.SelectedCells) {
|
|
console.log('pushRange - incorrect data');
|
|
return;
|
|
}
|
|
|
|
// e.g. we have array: [1, 2, 3, 4, 5, 6]
|
|
// we select [2, 3, 4, 5] and try to move 3 steps to the right
|
|
// so we can place only 2 cells: 2->5, 3->6 and other cells (4, 5) will be removed
|
|
var needToSkip = Math.max($scope.getSelectedCellsCount(expCat) - getActiveDroppablesCount(expCat), 0);
|
|
|
|
var startActiveDroppableIndex = expCat.ActiveDroppables.indexOf(true);
|
|
var endActiveDroppableIndex = expCat.ActiveDroppables.lastIndexOf(true);
|
|
var startSelectedCellIndex = expCat.SelectedCells.indexOf(true);
|
|
var endSelectedCellIndex = expCat.SelectedCells.lastIndexOf(true);
|
|
|
|
if (shift > 0) {
|
|
for (var i = endActiveDroppableIndex; i >= startActiveDroppableIndex; i--) {
|
|
if ($scope.data.Calendar.WeekHeaders[i].DataType == C_HEADER_DATA_TYPE_TOTALS ||
|
|
!$scope.data.Calendar.WeekHeaders[i].Visible[$scope.data.CalendarFilter.ViewModeName] ||
|
|
!expCat.ActiveDroppables[i]) {
|
|
continue;
|
|
}
|
|
|
|
for (var j = endSelectedCellIndex; j >= startSelectedCellIndex; j--) {
|
|
if ($scope.data.Calendar.WeekHeaders[j].DataType == C_HEADER_DATA_TYPE_TOTALS ||
|
|
!$scope.data.Calendar.WeekHeaders[j].Visible[$scope.data.CalendarFilter.ViewModeName] ||
|
|
!expCat.SelectedCells[j]) {
|
|
continue;
|
|
}
|
|
|
|
if (needToSkip > 0) {
|
|
needToSkip--;
|
|
continue;
|
|
}
|
|
|
|
endSelectedCellIndex = j;
|
|
break;
|
|
}
|
|
|
|
if (endSelectedCellIndex >= 0 && endSelectedCellIndex < $scope.data.Calendar.WeekHeaders.length) {
|
|
copyColumn(expCat, position, endSelectedCellIndex, i);
|
|
}
|
|
endSelectedCellIndex--;
|
|
}
|
|
|
|
// clear shifted cells
|
|
for (var i = startSelectedCellIndex; i <= expCat.SelectedCells.lastIndexOf(true) ; i++) {
|
|
if ($scope.data.Calendar.WeekHeaders[i].DataType == C_HEADER_DATA_TYPE_TOTALS ||
|
|
!$scope.data.Calendar.WeekHeaders[i].Visible[$scope.data.CalendarFilter.ViewModeName] ||
|
|
!expCat.SelectedCells[i]) {
|
|
continue;
|
|
}
|
|
|
|
clearColumn(expCat, position, i);
|
|
|
|
if (--shift == 0)
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
for (var i = startActiveDroppableIndex; i <= endActiveDroppableIndex; i++) {
|
|
if ($scope.data.Calendar.WeekHeaders[i].DataType == C_HEADER_DATA_TYPE_TOTALS ||
|
|
!$scope.data.Calendar.WeekHeaders[i].Visible[$scope.data.CalendarFilter.ViewModeName] ||
|
|
!expCat.ActiveDroppables[i]) {
|
|
continue;
|
|
}
|
|
|
|
for (var j = startSelectedCellIndex; j <= endSelectedCellIndex; j++) {
|
|
if ($scope.data.Calendar.WeekHeaders[j].DataType == C_HEADER_DATA_TYPE_TOTALS ||
|
|
!$scope.data.Calendar.WeekHeaders[j].Visible[$scope.data.CalendarFilter.ViewModeName] ||
|
|
!expCat.SelectedCells[j]) {
|
|
continue;
|
|
}
|
|
|
|
if (needToSkip > 0) {
|
|
needToSkip--;
|
|
continue;
|
|
}
|
|
|
|
startSelectedCellIndex = j;
|
|
break;
|
|
}
|
|
|
|
if (startSelectedCellIndex >= 0 && startSelectedCellIndex < $scope.data.Calendar.WeekHeaders.length) {
|
|
copyColumn(expCat, position, startSelectedCellIndex, i);
|
|
}
|
|
startSelectedCellIndex++;
|
|
}
|
|
|
|
// clear shifted cells
|
|
for (var i = endSelectedCellIndex; i >= expCat.SelectedCells.indexOf(true) ; i--) {
|
|
if ($scope.data.Calendar.WeekHeaders[i].DataType == C_HEADER_DATA_TYPE_TOTALS ||
|
|
!$scope.data.Calendar.WeekHeaders[i].Visible[$scope.data.CalendarFilter.ViewModeName] ||
|
|
!expCat.SelectedCells[i]) {
|
|
continue;
|
|
}
|
|
|
|
clearColumn(expCat, position, i);
|
|
|
|
if (++shift == 0)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
function copyColumn(expCat, sourceIndex, copyFromIndex, copyToIndex) {
|
|
if (!expCat)
|
|
return;
|
|
|
|
if ($scope.data.CalendarFilter.IsTableModeQuantity) {
|
|
var newValue = $scope.data.CalendarFilter.DragNDropReplaceValues ? expCat.QuantityValues[copyFromIndex] : (expCat.QuantityValues[copyFromIndex] + expCat.QuantityValues[copyToIndex]);
|
|
updateQuantityOrCostCell(expCat.ExpCatId, sourceIndex, copyToIndex, newValue, null, true);
|
|
} else {
|
|
var newValue = $scope.data.CalendarFilter.DragNDropReplaceValues ? expCat.CostValues[copyFromIndex] : (expCat.CostValues[copyFromIndex] + expCat.CostValues[copyToIndex]);
|
|
updateQuantityOrCostCell(expCat.ExpCatId, sourceIndex, copyToIndex, null, newValue, false);
|
|
}
|
|
|
|
if (expCat.Teams && expCat.Teams.length > 0) {
|
|
for (var tIndex in expCat.Teams) {
|
|
var newTeamValue = $scope.data.CalendarFilter.DragNDropReplaceValues ? expCat.Teams[tIndex].QuantityValues[copyFromIndex] : (expCat.Teams[tIndex].QuantityValues[copyFromIndex] + expCat.Teams[tIndex].QuantityValues[copyToIndex]);
|
|
$scope.checkTeamValue(expCat, expCat.Teams[tIndex], copyToIndex, newTeamValue, false);
|
|
if (expCat.Teams[tIndex].Resources && expCat.Teams[tIndex].Resources.length > 0) {
|
|
for (var rIndex in expCat.Teams[tIndex].Resources) {
|
|
var newResourceValue = $scope.data.CalendarFilter.DragNDropReplaceValues ? expCat.Teams[tIndex].Resources[rIndex].QuantityValues[copyFromIndex] : (expCat.Teams[tIndex].Resources[rIndex].QuantityValues[copyFromIndex] + expCat.Teams[tIndex].Resources[rIndex].QuantityValues[copyToIndex]);
|
|
$scope.checkResourceValue(expCat, expCat.Teams[tIndex], expCat.Teams[tIndex].Resources[rIndex], copyToIndex, newResourceValue, false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
function clearColumn(expCat, sourceIndex, index) {
|
|
if (!expCat || index < 0)
|
|
return;
|
|
|
|
if ($scope.data.CalendarFilter.IsTableModeQuantity) {
|
|
updateQuantityOrCostCell(expCat.ExpCatId, sourceIndex, index, 0, null, true);
|
|
} else {
|
|
updateQuantityOrCostCell(expCat.ExpCatId, sourceIndex, index, null, 0, false);
|
|
}
|
|
|
|
if (expCat.Teams && expCat.Teams.length > 0) {
|
|
for (var tIndex in expCat.Teams) {
|
|
$scope.checkTeamValue(expCat, expCat.Teams[tIndex], index, 0, false);
|
|
if (expCat.Teams[tIndex].Resources && expCat.Teams[tIndex].Resources.length > 0) {
|
|
for (var rIndex in expCat.Teams[tIndex].Resources)
|
|
$scope.checkResourceValue(expCat, expCat.Teams[tIndex], expCat.Teams[tIndex].Resources[rIndex], index, 0, false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// rounds "numToRound" to nearest "roundCoeff".
|
|
// e.g.:roundToNearest(0.6, 0.25)=0.5
|
|
// roundToNearest(0.8, 0.25)=0.75
|
|
// roundToNearest(1.49999, 1)=1
|
|
// roundToNearest(1.5, 1)=2
|
|
function roundToNearest(numToRound, roundCoeff) {
|
|
if (roundCoeff == 0)
|
|
return numToRound;
|
|
roundCoeff = 1 / (roundCoeff);
|
|
|
|
return Math.round(numToRound * roundCoeff) / roundCoeff;
|
|
}
|
|
function refreshSelect2(obj) {
|
|
$timeout(function () { // You might need this timeout to be sure its run after DOM render.
|
|
obj.trigger("change");
|
|
}, 0, false);
|
|
};
|
|
function refreshAssignResourceSelect(expCatId, teamId) {
|
|
var control = angular.element('#assign-resource-select-' + expCatId + '-' + teamId);
|
|
refreshSelect2(control);
|
|
};
|
|
function formatPeopleResourceOption(result, container, query, escapeMarkup) {
|
|
var $optionScope = angular.element(result.element).scope();
|
|
/* $optionScope.resource property is declared in the expression in the view: resource in team.AvailableResources track by $index */
|
|
if ($optionScope && $optionScope.resource) {
|
|
return scenarioDetailsService.getAssignableResourceOptionHtml($optionScope.resource);
|
|
}
|
|
};
|
|
function formatTeam4SelectOption(result, container, query, escapeMarkup) {
|
|
var $optionScope = angular.element(result.element).scope();
|
|
/* $optionScope.resource property is declared in the expression in the view: resource in team.AvailableResources track by $index */
|
|
if ($optionScope && $optionScope.Team) {
|
|
return scenarioDetailsService.getAssignableTeamOptionHtml($optionScope.Team);
|
|
}
|
|
};
|
|
$scope.getScenarioWeekEndings = function () {
|
|
if (!$scope.data.Calendar.WeekHeaders || $scope.data.Calendar.WeekHeaders.length <= 0)
|
|
return [];
|
|
|
|
var weekEndings = [];
|
|
for (var i = 0; i < $scope.data.Calendar.WeekHeaders.length; i++) {
|
|
var week = $scope.data.Calendar.WeekHeaders[i];
|
|
if (week.DataType != C_HEADER_DATA_TYPE_ORDINAL)
|
|
continue;
|
|
|
|
weekEndings.push(week.Milliseconds);
|
|
}
|
|
|
|
return weekEndings;
|
|
};
|
|
|
|
$scope.getTotalCostsByMonthes = function () {
|
|
var totals = {};
|
|
if (!$scope.data.Calendar.WeekHeaders || $scope.data.Calendar.WeekHeaders.length <= 0)
|
|
return totals;
|
|
|
|
var expendituresInScenario = scenarioDetailsService.getExpenditures(getScenarioId());
|
|
if (!expendituresInScenario || Object.keys(expendituresInScenario).length <= 0)
|
|
return totals;
|
|
|
|
angular.forEach($scope.data.Calendar.WeekHeaders, function (header, headerIndex) {
|
|
if (header.DataType == C_HEADER_DATA_TYPE_ORDINAL) {
|
|
var isActualsCell = !header.Editable[$scope.data.CalendarFilter.ViewModeName];
|
|
var weekEndingMonth = new Date(header.Milliseconds);
|
|
var monthStart = new Date(weekEndingMonth.getFullYear(), weekEndingMonth.getMonth(), 1).getTime();
|
|
if (!totals[monthStart]) {
|
|
totals[monthStart] = 0;
|
|
}
|
|
|
|
angular.forEach(expendituresInScenario, function (category, categoryIndex) {
|
|
var scenarioDetail = category.Details[header.Milliseconds] || {};
|
|
totals[monthStart] += ((isActualsCell ? scenarioDetail.ActualsCost : scenarioDetail.ForecastCost) || 0);
|
|
});
|
|
}
|
|
});
|
|
|
|
return totals;
|
|
};
|
|
function getScenarioId() {
|
|
return $scope.data.Scenario.Id;
|
|
};
|
|
function recalculateBUTeamValue(row, team, $index, originalExpCatId) {
|
|
if (!row || !team)
|
|
return;
|
|
|
|
var $data = 0;
|
|
if (team.Resources && team.Resources.length > 0) {
|
|
for (var i = 0; i < team.Resources.length; i++) {
|
|
var resource = team.Resources[i];
|
|
if (resource.QuantityValues) {
|
|
$data += resource.QuantityValues[$index] || 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
$scope.checkTeamValue(row, team, $index, $data, false, originalExpCatId);
|
|
};
|
|
function recalculateBUECValue(row, $index) {
|
|
if (!row)
|
|
return;
|
|
|
|
var $data = 0;
|
|
if (row.Teams && row.Teams.length > 0) {
|
|
for (var i = 0; i < row.Teams.length; i++) {
|
|
var team = row.Teams[i];
|
|
if (team.Resources && team.Resources.length > 0) {
|
|
for (var j = 0; j < team.Resources.length; j++) {
|
|
var resource = team.Resources[j];
|
|
if (resource.QuantityValues) {
|
|
$data += resource.QuantityValues[$index] || 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
$scope.checkValue($data, row.ExpCatId, $index);
|
|
};
|
|
function updateResourceRestQuantityValue(resourceId, colIndex, restValue) {
|
|
if (!resourceId || colIndex < 0) {
|
|
return;
|
|
}
|
|
|
|
if (!$scope.data.Calendar.Rows || $scope.data.Calendar.Rows.length <= 1) {
|
|
return;
|
|
}
|
|
|
|
var weekEnding = $scope.data.Calendar.WeekHeaders[colIndex].Milliseconds;
|
|
for (var rowIndex = 1; rowIndex < $scope.data.Calendar.Rows.length; rowIndex++) {
|
|
var row = $scope.data.Calendar.Rows[rowIndex];
|
|
if (!row.Teams || row.Teams.length <= 0) {
|
|
continue;
|
|
}
|
|
|
|
for (var teamIndex = 0; teamIndex < row.Teams.length; teamIndex++) {
|
|
var team = row.Teams[teamIndex];
|
|
var rawTeam = scenarioDetailsService.getTeamInScenario(getScenarioId(), row.ExpCatId, team.Id);
|
|
|
|
// update rest value in the collection that contains already assigned resources
|
|
if (team.Resources && team.Resources.length > 0) {
|
|
for (var resourceIndex = 0; resourceIndex < team.Resources.length; resourceIndex++) {
|
|
var resource = team.Resources[resourceIndex];
|
|
if (resource.Id === resourceId) {
|
|
resource.RestQuantityValues[colIndex] = restValue || 0;
|
|
var rawResource = rawTeam.Resources[resource.Id];
|
|
if (rawResource) {
|
|
rawResource.RestQuantityValues[weekEnding] = resource.RestQuantityValues[colIndex] / $scope.getUomMultiplier(rawResource.ExpenditureCategoryId);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// update rest value in the collection that contains resources available for assign
|
|
if (rawTeam.AllResources) {
|
|
var rawResource = rawTeam.AllResources[resourceId];
|
|
if (rawResource) {
|
|
rawResource.RestQuantityValues[weekEnding] = (restValue || 0) / $scope.getUomMultiplier(rawResource.ExpenditureCategoryId);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
function updateTeamRestQuantityValue(teamId, expenditureCategoryId, colIndex, deltaValue) {
|
|
if (!teamId || !expenditureCategoryId || colIndex < 0) {
|
|
return;
|
|
}
|
|
|
|
if (!$scope.data.Calendar.Rows || $scope.data.Calendar.Rows.length <= 1) {
|
|
return;
|
|
}
|
|
|
|
var weekEnding = $scope.data.Calendar.WeekHeaders[colIndex].Milliseconds;
|
|
for (var rowIndex = 1; rowIndex < $scope.data.Calendar.Rows.length; rowIndex++) {
|
|
var row = $scope.data.Calendar.Rows[rowIndex];
|
|
if (row.ExpCatId !== expenditureCategoryId) {
|
|
continue;
|
|
}
|
|
|
|
if (!row.Teams || row.Teams.length <= 0) {
|
|
continue;
|
|
}
|
|
|
|
for (var teamIndex = 0; teamIndex < row.Teams.length; teamIndex++) {
|
|
var team = row.Teams[teamIndex];
|
|
if (team.Id === teamId) {
|
|
var rawTeam = scenarioDetailsService.getTeamInScenario(getScenarioId(), row.ExpCatId, team.Id);
|
|
|
|
team.RestQuantityValues[colIndex] = ((team.RestQuantityValues[colIndex] || 0) - (deltaValue || 0));
|
|
rawTeam.RestQuantityValues[weekEnding] = team.RestQuantityValues[colIndex] / $scope.getUomMultiplier(row.ExpCatId);
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
function NoteIndex(n) {
|
|
for (var i = 0; i < $scope.NotesToAdd.length; i++) {
|
|
if ($scope.NotesToAdd[i].Id == n.Id) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1
|
|
};
|
|
}]); |