diff --git a/.gitignore b/.gitignore index c4b22e6..05f62fd 100644 --- a/.gitignore +++ b/.gitignore @@ -8,5 +8,4 @@ settings.py **/migrations/** !**/migrations/__init__.py -static promo_uploads diff --git a/selfserv/urls.py b/selfserv/urls.py index b993489..de36050 100644 --- a/selfserv/urls.py +++ b/selfserv/urls.py @@ -32,3 +32,4 @@ if 'ssmembership' in settings.INSTALLED_APPS: urlpatterns.append(path('', include('ssmain.urls'))) urlpatterns.extend(static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)) +urlpatterns.extend(static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)) diff --git a/sstreasury/jinja2/sstreasury/budget_edit.html b/sstreasury/jinja2/sstreasury/budget_edit.html index 7b6c9d5..e694ec5 100644 --- a/sstreasury/jinja2/sstreasury/budget_edit.html +++ b/sstreasury/jinja2/sstreasury/budget_edit.html @@ -55,13 +55,14 @@
-
- +
+
+
- Revenue comments + Revenue comments (click to show/hide)
@@ -73,16 +74,17 @@
-
- +
-
-
+
+ +
+
- Expense comments + Expense comments (click to show/hide)
-
-
+
+
@@ -112,13 +114,14 @@ + + + + {% endblock %} diff --git a/sstreasury/models.py b/sstreasury/models.py index 67dfe66..61d803e 100644 --- a/sstreasury/models.py +++ b/sstreasury/models.py @@ -63,6 +63,7 @@ class BudgetRevision(models.Model): revenue_comments = models.TextField() expense = JSONField(default=[]) + expense_no_emergency_fund = models.BooleanField() expense_comments = models.TextField() class Meta: diff --git a/sstreasury/static/sstreasury/budget.js b/sstreasury/static/sstreasury/budget.js new file mode 100644 index 0000000..d7baef0 --- /dev/null +++ b/sstreasury/static/sstreasury/budget.js @@ -0,0 +1,144 @@ +/* + Society Self-Service + Copyright © 2018 Yingtong Li (RunasSudo) + + 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 . +*/ + +$('.ui.accordion').accordion(); + +function recalcRevTotal(args) { + var total = 0; + var totalIWT = 0; + for (var row of args.grid.data) { + total += row['Unit price'] * row['Units']; + if (row['Unit price'] > 0 && row['IWT']) { + totalIWT += (row['Unit price'] - (row['Unit price'] - 0.8) / 1.019) * row['Units']; + } + } + + $(args.grid._body).find('.totalrow').remove(); + + if (totalIWT > 0) { + var totalrow = $(''); + totalrow.append($('Less IWT fees:').prop('colspan', args.grid.fields.length - (editing ? 2 : 1))); + totalrow.append($('').text('($' + totalIWT.toFixed(2) + ')')); + if (editing) { + totalrow.append($('')); + } + $(args.grid._body).find('tr:last').after(totalrow); + } + + var totalrow = $(''); + totalrow.append($('Total:').prop('colspan', args.grid.fields.length - (editing ? 2 : 1))); + totalrow.append($('').text('$' + (total - totalIWT).toFixed(2))); + if (editing) { + totalrow.append($('')); + } + $(args.grid._body).find('tr:last').after(totalrow); +} + +function recalcExpTotal(args) { + var total = 0; + for (var row of args.grid.data) { + total += row['Unit price'] * row['Units']; + } + + $(args.grid._body).find('.totalrow').remove(); + + var emergency_fund_mult = 0.05; + if ($('#expense_no_emergency_fund').length > 0 && $('#expense_no_emergency_fund').prop('checked')) { + emergency_fund_mult = 0; + } + + var totalrow = $(''); + totalrow.append($('Plus emergency fund:').prop('colspan', args.grid.fields.length - (editing ? 2 : 1))); + totalrow.append($('').text('$' + (total * emergency_fund_mult).toFixed(2))); + if (editing) { + totalrow.append($('')); + } + $(args.grid._body).find('tr:last').after(totalrow); + + var totalrow = $(''); + totalrow.append($('Total:').prop('colspan', args.grid.fields.length - (editing ? 2 : 1))); + totalrow.append($('').text('$' + (total * (1 + emergency_fund_mult)).toFixed(2))); + if (editing) { + totalrow.append($('')); + } + $(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', 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. Click the green plus icon at the top right to add a new row.' : 'No entries', + data: expense_data, + fields: f, + onItemUpdated: recalcExpTotal, + onRefreshed: recalcExpTotal, + }); +} diff --git a/sstreasury/views.py b/sstreasury/views.py index 0c0e06b..29dd30f 100644 --- a/sstreasury/views.py +++ b/sstreasury/views.py @@ -86,6 +86,7 @@ def revision_from_form(budget, revision, form): revision.revenue_comments = form['revenue_comments'] revision.expense = json.loads(form['expense']) revision.expense_comments = form['expense_comments'] + revision.expense_no_emergency_fund = True if form.get('expense_no_emergency_fund', False) else False revision.save()