EnVisageOnline/Main-RMO/Source/EnVisage/Scripts/Plugins/CapacityPlanning.js

637 lines
22 KiB
JavaScript

/* ===========================================================
* CapacityPlanning.js v0.0.1
* ===========================================================
* Copyright 2015 Prevu
* ========================================================== */
(function ($) {
"use strict"; // jshint ;_;
/* CapacityPlanning CLASS DEFINITION
* ====================== */
var CapacityPlanning = function (element, options) {
this.init(element, options);
};
CapacityPlanning.prototype = {
constructor: CapacityPlanning,
init: function (element, options) {
var plugin = this;
this.options = options;
this.$container = $(element);
this.$dataset = {};
this.$dataset.data = [];
this.$dataLoaded = false;
this.$controls = {};
this.$maxDate = this.getUtcFromText("1/1/2100");
this.$allCatgoriesIndex = null;
if (!this.options.teamId) {
throw "Team Id must be set";
}
// Init filter elements: datepickers and select2
this.$container.find('[data-capacity-planning="filterDates"]').datepicker(this.options.datePickerOptions);
this.$controls.categoriesFilter = this.$container.find('[data-capacity-planning="filterCategories"]').select2();
// Attach event receivers to controls
this.$controls.isPermanentFilter = this.$container.find('[data-capacity-planning="filterIsPermanent"]');
this.$controls.isPermanentFilter.on("click", function () {
plugin.switchPermanent(this);
});
this.$container.find('[data-capacity-planning="filterIncreaseCapacity"]').on("click", function () {
plugin.increaseCapacity();
});
this.$container.find('[data-capacity-planning="filterDecreaseCapacity"]').on("click", function () {
plugin.decreaseCapacity();
});
this.$container.find('[data-capacity-planning="filterResetCapacity"]').on("click", function () {
plugin.resetCapacity();
});
this.$container.find('[data-capacity-planning="filterResetEntireCapacity"]').on("click", function () {
plugin.resetEntireCapacity(this);
});
this.$container.find('[data-capacity-planning="formDecreaseSubmitBtn"]').on("click", function () {
plugin.fillDecreaseSubmit(this);
});
// Caching other controls
this.$controls.dataTable = this.$container.find('[data-capacity-planning="dataTable"]');
this.$controls.endDateFilter = this.$container.find('[data-capacity-planning="filterEndDate"]');
this.$controls.startDateFilter = this.$container.find('[data-capacity-planning="filterStartDate"]');
this.$controls.amountFilter = this.$container.find('[data-capacity-planning="filterAmount"]');
// Public initial ecpenditure categories list
this.initCategoriesFilter();
this.createCategoriesIndex();
if (this.options.data) {
this.setData(this.options.data);
}
if (this.options.initCallback && (typeof this.options.initCallback === 'function')) {
this.options.initCallback(this.$dataset);
}
},
initCategoriesFilter: function () {
var expCatDropDown = this.$controls.categoriesFilter;
expCatDropDown.empty();
var selInPlanHtml = $('<optgroup />').prop('label', "Categories in Plan").appendTo(expCatDropDown);
var selAddHtml = $('<optgroup />').prop('label', "Add Category").appendTo(expCatDropDown);
if (!this.options.expenditureCategories || (this.options.expenditureCategories.length < 1))
return;
$.each(this.options.expenditureCategories, function () {
$("<option />").val(this.Id).text(this.Name).appendTo(this.InPlan == true ? selInPlanHtml : selAddHtml);
});
expCatDropDown.select2({
allowClear: true
});
},
switchPermanent: function (control) {
var isChecked = this.$controls.isPermanentFilter.prop('checked');
if (isChecked) {
this.$controls.endDateFilter.css('visibility', 'hidden');
this.$container.find('.enddatespan').hide();
} else {
this.$controls.endDateFilter.css('visibility', 'visible');
this.$container.find('.enddatespan').show();
}
},
createCategoriesIndex: function () {
this.$allCatgoriesIndex = [];
var currentCat;
for (var index = 0; index < this.options.expenditureCategories.length; index++) {
currentCat = this.options.expenditureCategories[index];
this.$allCatgoriesIndex[currentCat.Id] = currentCat;
}
},
getCategoryById: function (id) {
return this.$allCatgoriesIndex[id];
},
categoryExists: function (catId) {
var catItem = this.getCategoryById(catId);
return (catItem !== undefined);
},
increaseCapacity: function () {
if (!this.isFormValid())
return;
var startDateAsText = this.$controls.startDateFilter.val();
var endDateAsText = this.$controls.endDateFilter.val();
var expCat = this.$controls.categoriesFilter.val();
var amount = this.$controls.amountFilter.val();
var isPermanent = this.$controls.isPermanentFilter.prop('checked');
var startDate = this.getUtcFromText(startDateAsText);
var endDate = this.getUtcFromText(endDateAsText);
if (isPermanent)
endDate = null;
this.changeCapacity(startDate, endDate, expCat, amount);
this.renderTable();
},
decreaseCapacity: function () {
if (!this.isFormValid())
return;
var startDateAsText = this.$controls.startDateFilter.val();
var endDateAsText = this.$controls.endDateFilter.val();
var expCat = this.$controls.categoriesFilter.val();
var amount = this.$controls.amountFilter.val();
var isPermanent = this.$controls.isPermanentFilter.prop('checked');
var startDate = this.getUtcFromText(startDateAsText);
var endDate = this.getUtcFromText(endDateAsText);
if (isPermanent)
endDate = null;
this.changeCapacity(startDate, endDate, expCat, -amount);
this.renderTable();
},
changeCapacity: function (startDate, endDate, expCat, amount) {
var positions = null;
var startDateUnix = this.getUnixDateFromUtc(startDate);
var endDateUnix = this.getUnixDateFromUtc(endDate);
if (this.$dataset.data && (this.$dataset.data.length > 0)) {
for (var catIndex = 0; catIndex < this.$dataset.data.length; catIndex++) {
if (this.$dataset.data[catIndex].Id == expCat) {
positions = this.$dataset.data[catIndex].Positions;
break;
}
}
}
if (!positions) {
// Category not found
var expCatItem = this.getCategoryById(expCat);
if (expCatItem) {
var newCatItem = {
Id: expCatItem.Id,
Name: expCatItem.Name,
InPlan: expCatItem.InPlan,
Positions: []
};
this.$dataset.data.push(newCatItem);
positions = newCatItem.Positions;
} else
throw "Expenditure category was not found in initial list of categories";
}
if (amount > 0) {
// Perform increase capacity
for (var index = 0; index < amount; index++) {
var newItem = {
Need: 1,
StartDate: startDateUnix,
EndDate: endDateUnix
}
positions.push(newItem);
}
} else {
// Perform decrease capacity
// try to find the exact suitable rows
var remainingAmount = amount;
var endDateExact = endDate ? endDate : this.$maxDate;
for (var index = 0; index < Math.abs(amount) ; index++) {
for (var posIndex = positions.length - 1; posIndex >= 0; posIndex--) {
if ((positions[posIndex].StartDateEx == startDate) &&
(positions[posIndex].EndDateEx == endDateExact) &&
(positions[posIndex].Need == 1)) {
positions.splice(posIndex, 1);
remainingAmount++; // is negative value
break;
}
}
}
if (Math.abs(remainingAmount) > 0) {
// looking for partial rows to decrease
var removablePositions = [];
for (var index = 0; index < Math.abs(remainingAmount) ; index++) {
var posItem = {
StartDate: startDateUnix,
EndDate: endDateUnix,
StartDateEx: startDate,
EndDateEx: endDateExact
};
removablePositions.push(posItem);
}
var remIndex = 0;
while (remIndex < removablePositions.length) {
var currRemovablePos = removablePositions[remIndex];
for (var posIndex = positions.length - 1; posIndex >= 0; posIndex--) {
if ((currRemovablePos.StartDateEx <= positions[posIndex].StartDateEx) &&
(currRemovablePos.EndDateEx > positions[posIndex].StartDateEx) &&
(currRemovablePos.EndDateEx < positions[posIndex].EndDateEx)) {
var tmpStartDate = positions[posIndex].StartDate;
var tmpStartDateEx = positions[posIndex].StartDateEx;
positions[posIndex].StartDate = currRemovablePos.EndDate;
positions[posIndex].StartDateEx = currRemovablePos.EndDateEx;
currRemovablePos.EndDate = tmpStartDate;
currRemovablePos.EndDateEx = tmpStartDateEx;
if (currRemovablePos.StartDateEx == currRemovablePos.EndDateEx) {
removablePositions.splice(remIndex, 1);
remIndex = -1;
break;
}
} else {
if ((currRemovablePos.StartDateEx > positions[posIndex].StartDateEx) &&
(currRemovablePos.StartDateEx < positions[posIndex].EndDateEx) &&
(currRemovablePos.EndDateEx >= positions[posIndex].EndDateEx)) {
var tmpEndDate = positions[posIndex].EndDate;
var tmpEndDateEx = positions[posIndex].EndDateEx;
positions[posIndex].EndDate = currRemovablePos.StartDate;
positions[posIndex].EndDateEx = currRemovablePos.StartDateEx;
currRemovablePos.StartDate = tmpEndDate;
currRemovablePos.StartDateEx = tmpEndDateEx;
if (currRemovablePos.StartDateEx == currRemovablePos.EndDateEx) {
removablePositions.splice(remIndex, 1);
remIndex = -1;
break;
}
} else {
if ((currRemovablePos.StartDateEx <= positions[posIndex].StartDateEx) &&
(currRemovablePos.EndDateEx >= positions[posIndex].EndDateEx)) {
if (positions[posIndex].EndDateEx != currRemovablePos.EndDateEx) {
var newPosItem = {
StartDate: positions[posIndex].EndDate,
EndDate: currRemovablePos.EndDate,
StartDateEx: positions[posIndex].EndDateEx,
EndDateEx: currRemovablePos.EndDateEx
};
removablePositions.push(newPosItem);
}
currRemovablePos.EndDate = positions[posIndex].StartDate;
currRemovablePos.EndDateEx = positions[posIndex].StartDateEx;
positions.splice(posIndex, 1);
if (currRemovablePos.StartDateEx == currRemovablePos.EndDateEx) {
removablePositions.splice(remIndex, 1);
remIndex = -1;
break;
}
} else {
if ((currRemovablePos.StartDateEx > positions[posIndex].StartDateEx) &&
(currRemovablePos.EndDateEx < positions[posIndex].EndDateEx)) {
var tmpEndDate = positions[posIndex].EndDate;
var tmpEndDateEx = positions[posIndex].EndDateEx;
positions[posIndex].EndDate = currRemovablePos.StartDate;
positions[posIndex].EndDateEx = currRemovablePos.StartDateEx;
var newPos = {
Need: positions[posIndex].Need,
StartDate: currRemovablePos.EndDate,
EndDate: tmpEndDate,
StartDateEx: currRemovablePos.EndDateEx,
EndDateEx: tmpEndDateEx
}
positions.push(newPos);
removablePositions.splice(remIndex, 1);
remIndex = -1;
break;
}
}
}
}
}
remIndex++;
}
}
}
},
deleteRecord: function (element) {
var itemId = $(element).attr('data-capacity-planning');
for (var catIndex = this.$dataset.data.length - 1; catIndex >= 0; catIndex--) {
var positions = this.$dataset.data[catIndex].Positions;
if (positions != null) {
for (var posIndex = positions.length - 1; posIndex >= 0; posIndex--) {
if (positions[posIndex].Id == itemId) {
positions.splice(posIndex, 1);
break;
}
}
}
}
this.renderTable();
},
resetCapacity: function () {
var startDateAsText = this.$controls.startDateFilter.val();
var endDateAsText = this.$controls.endDateFilter.val();
var expCat = this.$controls.categoriesFilter.val();
var isPermanent = this.$controls.isPermanentFilter.prop('checked');
var startDate = this.getUtcFromText(startDateAsText);
var endDate = this.getUtcFromText(endDateAsText);
if (isPermanent)
endDate = null;
var decreaseBoundValue = this.getPositionsCount() + 1;
this.changeCapacity(startDate, endDate, expCat, -decreaseBoundValue);
this.renderTable();
},
resetEntireCapacity: function (control) {
var plugin = this;
bootbox.dialog({
title: "Reset Planned Capacity",
message: '<div class="row"> ' +
'<div class="col-md-12"> ' +
'<p> Are you sure you want to reset Planned Capacity and make it equal to Actual Capacity? </p> ' +
'</div>' +
'<div class="col-md-10 col-md-offset-1"> ' +
'<div class="radio"> <label for="resetAllDates-0"> <input type="radio" name="resetAllDates" id="resetAllDates-0" value="0" checked="checked"> Use currently selected date range </label> </div>' +
'<div class="radio"> <label for="resetAllDates-1"> <input type="radio" name="resetAllDates" id="resetAllDates-1" value="1"> Reset entire capacity </label> </div> ' +
'</div>' +
'</div>',
buttons: {
cancel: {
label: "Cancel",
className: "btn-default",
callback: function () {
}
},
main: {
label: "Reset",
className: "btn-primary",
callback: function () {
var answer = $("input[name='resetAllDates']:checked").val() == "1";
if (answer == true) {
// Reset entire capacity for all categories and full period
for (var catIndex = 0; catIndex < plugin.$dataset.data.length; catIndex++) {
plugin.$dataset.data[catIndex].Positions = null;
}
}
else {
// Reset capacity for the given time period
var startDateAsText = plugin.$controls.startDateFilter.val();
var endDateAsText = plugin.$controls.endDateFilter.val();
var isPermanent = plugin.$controls.isPermanentFilter.prop('checked');
var startDate = plugin.getUtcFromText(startDateAsText);
var endDate = plugin.getUtcFromText(endDateAsText);
if (isPermanent)
endDate = null;
var decreaseBoundValue = plugin.getPositionsCount() + 1;
for (var catIndex = 0; catIndex < plugin.$dataset.data.length; catIndex++) {
var expCat = plugin.$dataset.data[catIndex].Id;
plugin.changeCapacity(startDate, endDate, expCat, -decreaseBoundValue);
}
}
plugin.renderTable();
}
}
}
});
},
fillDecreaseSubmit: function (control) {
// Not implemented in current version of widget
},
getRecordIdentifier: function (categoryId, counter) {
var id = this.options.teamId + "_" + categoryId + "_" + counter;
return id;
},
getPositionsCount: function () {
var count = 0;
for (var catIndex = 0; catIndex < this.$dataset.data.length; catIndex++) {
if (this.$dataset.data[catIndex].Positions)
count += this.$dataset.data[catIndex].Positions.length;
}
return count;
},
renderTable: function () {
if (this.options.dataTableRenderingCallback && (typeof this.options.dataTableRenderingCallback === 'function')) {
this.options.dataTableRenderingCallback(this.$dataset);
}
var plugin = this;
var noPositions = true;
var positionsExist = false;
var counter = 1;
var tbl = this.$controls.dataTable.empty();
var data = this.$dataset;
tbl.append($("<thead><tr><th class=\"autowidth\">Expenditure Category</th><th class=\"autowidth\">Start Date</th><th class=\"autowidth\">End Date</th><th class=\"smallCol\"></th></tr></thead>"));
if (data && data["data"]) {
$.each(data["data"], function () {
if (this.Positions != null) {
for (var ctr = 0; ctr < this.Positions.length; ctr++) {
var pos = this.Positions[ctr];
var categoryId = this.Id;
var rowId = plugin.getRecordIdentifier(categoryId, counter);
var startDatePc = plugin.formatPCDate(pos.StartDate);
var endDatePc = plugin.formatPCDate(pos.EndDate);
var row = $('<tr></tr>');
row.append($("<td></td>").text(this.Name));
row.append($("<td></td>").text(startDatePc));
row.append($("<td></td>").text(endDatePc));
var btnsTD = "";
if (pos.Need > 0) {
btnsTD += "<a class=\"btn btn-xs btn-danger\" title=\"Remove Position\" data-capacity-planning=\"" +
rowId + "\"><i class=\"fa fa-times\"></i></a>";
} else {
btnsTD += "<a class=\"btn btn-xs btn-danger\" title=\"Remove Position\" data-capacity-planning=\"" +
rowId + "\"><i class=\"fa fa-times\"></i></a>";
}
row.append($("<td>" + btnsTD + "</td>"));
row.find('a.btn').on("click", function () {
plugin.deleteRecord(this);
})
tbl.append(row);
noPositions = false;
// Cache values for speed calculations
pos.Id = rowId;
pos.StartDateEx = plugin.getUtcFromText(startDatePc);
if (!endDatePc || (endDatePc.length < 1)) {
pos.EndDateEx = plugin.$maxDate;
}
else {
pos.EndDateEx = plugin.getUtcFromText(endDatePc);
}
counter++;
}
}
});
}
if (noPositions == true) {
tbl.append($("<tr><td colspan=\"4\">No open positions</td></tr>"));
}
if (this.options.dataTableRenderedCallback && (typeof this.options.dataTableRenderedCallback === 'function')) {
this.options.dataTableRenderedCallback(this.$dataset);
}
},
getData: function () {
var exportData = [];
for (var catIndex = 0; catIndex < this.$dataset.data.length; catIndex++) {
var catItem = {
Id: this.$dataset.data[catIndex].Id,
Name: this.$dataset.data[catIndex].Name,
InPlan: this.$dataset.data[catIndex].InPlan,
Positions: null
}
if (this.$dataset.data[catIndex].Positions && (this.$dataset.data[catIndex].Positions.length > 0)) {
catItem.Positions = [];
for (var posIndex = 0; posIndex < this.$dataset.data[catIndex].Positions.length; posIndex++) {
var posItem = {
StartDate: this.$dataset.data[catIndex].Positions[posIndex].StartDate,
EndDate: this.$dataset.data[catIndex].Positions[posIndex].EndDate,
Need: this.$dataset.data[catIndex].Positions[posIndex].Need
}
catItem.Positions.push(posItem);
}
exportData.push(catItem);
}
}
return exportData;
},
setData: function (incomingData) {
this.$dataset.data = [];
if (!incomingData || !incomingData.data || (incomingData.data.length < 1)) {
return;
}
for (var catIndex = 0; catIndex < incomingData.data.length; catIndex++) {
var currentCat = incomingData.data[catIndex];
if (currentCat.Positions && (currentCat.Positions.length > 0) && this.categoryExists(currentCat.Id)) {
var newCatItem = {
Id: currentCat.Id,
Name: currentCat.Name,
InPlan: currentCat.InPlan,
Positions: []
};
for (var posIndex = 0; posIndex < currentCat.Positions.length; posIndex++) {
var currentPos = currentCat.Positions[posIndex];
if (currentPos.Need && (currentPos.Need > 0))
{
var posItem = {
StartDate: currentPos.StartDate,
EndDate: currentPos.EndDate,
Need: currentPos.Need,
};
newCatItem.Positions.push(posItem);
}
}
this.$dataset.data.push(newCatItem);
}
}
this.renderTable();
},
formatPCDate: function (jsonDate) {
if (jsonDate == null || jsonDate == "")
return "";
var dt = new Date(parseInt(jsonDate.replace("/Date(", "").replace(")/", ""), 10));
return (dt.getMonth() + 1) + "/" + dt.getDate() + "/" + dt.getFullYear();
},
getUnixDateFromText: function (dateAsText) {
var utcDateMs = dateAsText ? (new Date(dateAsText)) : null;
utcDateMs = utcDateMs ? Date.UTC(utcDateMs.getFullYear(), utcDateMs.getMonth(), utcDateMs.getDate()) : null;
var unixDate = utcDateMs ? "/Date(" + utcDateMs + ")/" : null;
return unixDate;
},
getUnixDateFromUtc: function (dateAsUtc) {
var unixDate = dateAsUtc ? "/Date(" + dateAsUtc + ")/" : null;
return unixDate;
},
getUtcFromText: function (dateAsText) {
var utcDateMs = dateAsText ? (new Date(dateAsText)) : null;
utcDateMs = utcDateMs ? Date.UTC(utcDateMs.getFullYear(), utcDateMs.getMonth(), utcDateMs.getDate()) : null;
return utcDateMs;
},
getUtcFromDate: function (dateAsDate) {
var utcDateMs = dateAsDate ? Date.UTC(dateAsDate.getFullYear(), dateAsDate.getMonth(), dateAsDate.getDate()) : null;
return utcDateMs;
},
destroy: function () {
var e = $.Event('destroy');
this.$container.trigger(e);
if (e.isDefaultPrevented()) return;
this.$container
// .off('.pageState')
.removeData('capacityPlanner');
},
isFormValid: function () {
return this.$controls.startDateFilter.valid() && this.$controls.endDateFilter.valid() &&
this.$controls.categoriesFilter.valid() && this.$controls.amountFilter.valid();
}
};
/* CAPACITY PLANNING PLUGIN DEFINITION
* ======================= */
$.fn.capacityPlanner = function (option, args) {
return this.each(function () {
var $this = $(this),
data = $this.data('capacityPlanner'),
options = $.extend({}, $.fn.capacityPlanner.defaults, $this.data(), typeof option == 'object' && option);
if (!data) $this.data('capacityPlanner', (data = new CapacityPlanning(this, options)));
if (typeof option == 'string')
data[option].apply(data, [].concat(args));
});
};
$.fn.capacityPlanner.defaults = {
teamId: null,
datePickerOptions: {
format: 'm/d/yyyy'
},
expenditureCategories: null,
data: null,
initCallback: null,
dataTableRenderingCallback: null,
dataTableRenderedCallback: null,
};
$.fn.capacityPlanner.Constructor = CapacityPlanning;
}(jQuery));