society-self-service/sstreasury/static/sstreasury/budget.js

212 lines
7.1 KiB
JavaScript

/*
Society Self-Service
Copyright © 2018-2023 Yingtong Li (RunasSudo)
Copyright © 2023 MUMUS Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
$('.ui.accordion').accordion();
var revTotal = 0;
var revTotalIWT = 0;
var expTotal = 0;
var emergency_fund_mult = 0.05;
function recalcRevTotal(args) {
revTotal = 0;
revTotalIWT = 0;
for (var row of args.grid.data) {
revTotal += row['Unit price'] * row['Units'];
if (row['Unit price'] > 0 && row['IWT']) {
revTotalIWT += (row['Unit price'] * ticketingFeeProportion + ticketingFeeFixed) * row['Units'];
}
}
$(args.grid._body).find('.totalrow').remove();
if (revTotalIWT > 0) {
var totalrow = $('<tr class="jsgrid-row totalrow" style="font-style: italic;"></tr>');
totalrow.append($('<td class="jsgrid-cell">Less ticketing fees:</td>').prop('colspan', args.grid.fields.length - (editing ? 2 : 1)));
totalrow.append($('<td class="jsgrid-cell jsgrid-align-right"></td>').text('($' + revTotalIWT.toFixed(2) + ')'));
if (editing) {
totalrow.append($('<td class="jsgrid-cell"></td>'));
}
$(args.grid._body).find('tr:last').after(totalrow);
}
var totalrow = $('<tr class="jsgrid-row totalrow" style="font-weight: bold;"></tr>');
totalrow.append($('<td class="jsgrid-cell">Total:</td>').prop('colspan', args.grid.fields.length - (editing ? 2 : 1)));
totalrow.append($('<td class="jsgrid-cell jsgrid-align-right"></td>').text('$' + (revTotal - revTotalIWT).toFixed(2)));
if (editing) {
totalrow.append($('<td class="jsgrid-cell"></td>'));
}
$(args.grid._body).find('tr:last').after(totalrow);
}
function recalcExpTotal(args) {
expTotal = 0;
for (var row of args.grid.data) {
expTotal += row['Unit price'] * row['Units'];
}
$(args.grid._body).find('.totalrow').remove();
emergency_fund_mult = 0.05;
if ($('#expense_no_emergency_fund').length > 0 && $('#expense_no_emergency_fund').prop('checked')) {
emergency_fund_mult = 0;
}
var totalrow = $('<tr class="jsgrid-row totalrow" style="font-style: italic;"></tr>');
totalrow.append($('<td class="jsgrid-cell">Plus emergency fund:</td>').prop('colspan', args.grid.fields.length - (editing ? 2 : 1)));
totalrow.append($('<td class="jsgrid-cell jsgrid-align-right"></td>').text('$' + (expTotal * emergency_fund_mult).toFixed(2)));
if (editing) {
totalrow.append($('<td class="jsgrid-cell"></td>'));
}
$(args.grid._body).find('tr:last').after(totalrow);
var totalrow = $('<tr class="jsgrid-row totalrow" style="font-weight: bold;"></tr>');
totalrow.append($('<td class="jsgrid-cell">Total:</td>').prop('colspan', args.grid.fields.length - (editing ? 2 : 1)));
totalrow.append($('<td class="jsgrid-cell jsgrid-align-right"></td>').text('$' + (expTotal * (1 + emergency_fund_mult)).toFixed(2)));
if (editing) {
totalrow.append($('<td class="jsgrid-cell"></td>'));
}
$(args.grid._body).find('tr:last').after(totalrow);
}
// Allow floats
function FloatNumberField(config) {
jsGrid.NumberField.call(this, config);
}
FloatNumberField.prototype = new jsGrid.NumberField({
filterValue: function() {
return parseFloat(this.filterControl.val());
},
insertValue: function() {
return parseFloat(this.insertControl.val());
},
editValue: function() {
return parseFloat(this.editControl.val());
}
});
jsGrid.fields.float = FloatNumberField;
function makeGrid() {
f = [
{ name: 'Description', type: 'text', width: '50%', validate: 'required' },
{ name: 'Unit price', type: 'float', width: '12.5%', validate: 'required', itemTemplate: function(value, item) { return '$' + value.toFixed(2); } },
{ name: 'Units', type: 'float', width: '12.5%', validate: 'required' },
{ name: 'IWT', title: ticketingFeeName, type: 'checkbox', width: '5%', insertTemplate: function() { var result = jsGrid.fields.checkbox.prototype.insertTemplate.call(this); result.prop('checked', true); return result; } },
{ name: 'Total', align: 'right', width: '10%', itemTemplate: function(value, item) { return '$' + (item['Unit price'] * item['Units']).toFixed(2); } },
];
if (editing) {
f.push({ type: 'control', width: '10%', modeSwitchButton: false });
}
$('#revenue_grid').jsGrid({
width: '100%',
height: editing ? '20em' : 'auto',
inserting: editing,
editing: editing,
noDataContent: editing ? 'No entries. Click the green plus icon at the top right to add a new row.' : 'No entries',
data: revenue_data,
fields: f,
onItemUpdated: recalcRevTotal,
onRefreshed: recalcRevTotal,
});
f = [
{ name: 'Description', type: 'text', width: '50%', validate: 'required' },
{ name: 'Unit price', type: 'float', width: '12.5%', validate: 'required', itemTemplate: function(value, item) { return '$' + value.toFixed(2); } },
{ name: 'Units', type: 'float', width: '12.5%', validate: 'required' },
{ name: 'Total', align: 'right', width: '10%', itemTemplate: function(value, item) { return '$' + (item['Unit price'] * item['Units']).toFixed(2); } },
]
if (editing) {
f.push({ type: 'control', width: '10%', modeSwitchButton: false });
}
$('#expense_grid').jsGrid({
width: '100%',
height: editing ? '20em' : 'auto',
inserting: editing,
editing: editing,
noDataContent: editing ? 'No entries. Enter details above then click the green plus icon.' : 'No entries',
data: expense_data,
fields: f,
onItemUpdated: recalcExpTotal,
onRefreshed: recalcExpTotal,
});
if (!editing) {
$('.jsgrid-filter-row, .jsgrid-insert-row').attr('style', 'display: none !important;'); /* Override Semantic UI */
}
}
function makeCharts() {
// Display expense, revenue charts on budget view page
if (document.getElementById('chartExpenses')) {
new Chart(document.getElementById('chartExpenses'), {
type: 'pie',
data: {
labels: expense_data.map(e => e['Description']),
datasets: [{
label: 'Expenses',
data: expense_data.map(e => e['Unit price'] * e['Units'])
}]
},
options: {
plugins: {
tooltip: {
callbacks: {
label: i => '$' + i.parsed.toFixed(2)
}
}
}
},
});
}
if (document.getElementById('chartRevExp')) {
new Chart(document.getElementById('chartRevExp'), {
type: 'bar',
data: {
labels: ['Revenue', 'Expenses'],
datasets: [{
label: 'Budget',
data: [revTotal - revTotalIWT, (expTotal * (1 + emergency_fund_mult))],
backgroundColor: ['#36a2eb', '#ff6384']
}]
},
options: {
scales: {
y: {
beginAtZero: true
}
},
plugins: {
legend: {
display: false
},
tooltip: {
callbacks: {
label: i => '$' + i.parsed.y.toFixed(2)
}
}
}
},
});
}
}