society-self-service/sstreasury/jinja2/sstreasury/budget_edit.html

300 lines
12 KiB
HTML

{% extends 'sstreasury/base.html' %}
{#
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 <https://www.gnu.org/licenses/>.
#}
{% block title %}{% if request.resolver_match.url_name == 'budget_new' %}New{% else %}Edit{% endif %} budget{% endblock %}
{% block maincontent %}
<h1>{% if request.resolver_match.url_name == 'budget_new' %}New{% else %}Edit{% endif %} budget</h1>
<form class="ui form" method="POST">
<div class="ui disabled inline grid field">
<label class="three wide column">ID</label>
<input class="eleven wide column" type="text" name="id" value="{{ revision.budget.id if revision.budget.id != None else '' }}">
</div>
<div class="ui required inline grid field">
<label class="three wide column">Name</label>
<input class="eleven wide column" type="text" name="name" value="{{ revision.name }}">
</div>
<div class="ui required inline grid field">
<label class="three wide column">Due date</label>
<div class="eleven wide column">
<div class="ui calendar" id="cal_date">
<div class="ui input left icon grid">
<i class="calendar icon" style="z-index: 999;"></i>
<input class="twelve wide column" type="text" name="date" value="{{ revision.date or '' }}">
</div>
</div>
</div>
</div>
<div class="ui required inline grid field">
<label class="three wide column">Contributors</label>
<textarea class="eleven wide column" rows="2" name="contributors" style="font-family: Lato,'Helvetica Neue',Arial,Helvetica,sans-serif;">{{ contributors }}</textarea>
</div>
<div class="ui divider"></div>
<div class="ui inline grid field">
<label class="three wide column">Comments</label>
<textarea class="eleven wide column" rows="2" name="comments">{{ revision.comments }}</textarea>
</div>
<div class="ui divider"></div>
<div class="ui inline grid field">
<label class="three wide column">Revenue</label>
<div id="revenue_grid"></div>
<input type="hidden" name="revenue" id="revenue_input">
</div>
<div class="ui accordion">
<div class="{% if revision.revenue_comments %}active {% endif %}title">
<i class="dropdown icon"></i>
Revenue comments
</div>
<div class="content">
<div class="ui inline grid field">
<label class="three wide column">Comments</label>
<textarea class="eleven wide column" rows="2" name="revenue_comments">{{ revision.revenue_comments }}</textarea>
</div>
</div>
</div>
<div class="ui divider"></div>
<div class="ui inline grid field">
<label class="three wide column">Expenses</label>
<div id="expense_grid"></div>
<input type="hidden" name="expense" id="expense_input">
</div>
<div class="ui accordion">
<div class="{% if revision.expense_comments %}active {% endif %}title">
<i class="dropdown icon"></i>
Expense comments
</div>
<div class="{% if revision.expense_comments %}active {% endif %}content">
<div class="ui inline grid field">
<label class="three wide column">Comments</label>
<textarea class="eleven wide column" rows="2" name="expense_comments">{{ revision.expense_comments }}</textarea>
</div>
</div>
</div>
<div class="ui divider"></div>
<div class="ui error message"></div>
<input type="hidden" name="csrfmiddlewaretoken" value="{{ csrf_token }}">
<input class="ui primary button" type="submit" name='submit' value="Save">
<input class="ui button" type="submit" name='submit' value="Save and continue editing">
</form>
{% endblock %}
{% block head %}
{{ super() }}
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/semantic-ui-calendar@0.0.8/dist/calendar.min.css" integrity="sha256-KCHiPtYk/vfF5/6lDXpz5r5FuIYchVdai0fepwGft80=" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/jsgrid@1.5.3/dist/jsgrid.min.css" integrity="sha256-a/jNbtm7jpeKiXCShJ8YC+eNL9Abh7CBiYXHgaofUVs=" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/jsgrid@1.5.3/dist/jsgrid-theme.min.css" integrity="sha256-0rD7ZUV4NLK6VtGhEim14ZUZGC45Kcikjdcr4N03ddA=" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/dragula@3.7.2/dist/dragula.min.css" integrity="sha256-iVhQxXOykHeL03K08zkxBGxDCLCuzRGGiTYf2FL6mLY=" crossorigin="anonymous">
{% endblock %}
{% block script %}
{{ super() }}
<script src="https://cdn.jsdelivr.net/npm/semantic-ui-calendar@0.0.8/dist/calendar.min.js" integrity="sha256-Pnz4CK94A8tUiYWCfg/Ko25YZrHqOKeMS4JDXVTcVA0=" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/jsgrid@1.5.3/dist/jsgrid.min.js" integrity="sha256-lzjMTpg04xOdI+MJdjBst98bVI6qHToLyVodu3EywFU=" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/dragula@3.7.2/dist/dragula.min.js" integrity="sha256-ug4bHfqHFAj2B5MESRxbLd3R3wdVMQzug2KHZqFEmFI=" crossorigin="anonymous"></script>
<script>
function leftpad(n) {
if (n < 10)
return '0' + n;
return '' + n;
}
$('#cal_date').calendar({
type: 'date',
formatter: {
date: function(date, settings) {
return date.getFullYear() + '-' + leftpad(date.getMonth() + 1) + '-' + leftpad(date.getDate());
}
}
});
$('.ui.accordion').accordion();
$('.ui.form').form({
on: 'blur',
keyboardShortcuts: false,
fields: {
name: 'empty',
contributors: 'empty'
},
onSuccess: function(event, fields) {
var revenue_data = [];
$('#revenue_grid .jsgrid-grid-body tr:not(.totalrow):not(.jsgrid-nodata-row)').each(function(i, el) {
var row = $(el).data('JSGridItem');
revenue_data.push({
'Description': row['Description'],
'Unit price': row['Unit price'],
'Units': row['Units'],
'IWT': row['IWT'],
});
});
$('#revenue_input').val(JSON.stringify(revenue_data));
var expense_data = [];
$('#expense_grid .jsgrid-grid-body tr:not(.totalrow):not(.jsgrid-nodata-row)').each(function(i, el) {
var row = $(el).data('JSGridItem');
expense_data.push({
'Description': row['Description'],
'Unit price': row['Unit price'],
'Units': row['Units'],
});
});
$('#expense_input').val(JSON.stringify(expense_data));
}
});
// Interferes with jsGrid
$('.ui.form').on('keyup keypress', function(e) {
var keyCode = e.keyCode || e.which;
if (keyCode === 13) {
e.preventDefault();
return false;
}
});
function recalcRevTotal(args) {
//console.log(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 = $('<tr class="jsgrid-row totalrow" style="font-style: italic;"></tr>');
totalrow.append($('<td class="jsgrid-cell">Less IWT fees:</td>').prop('colspan', args.grid.fields.length - 2));
totalrow.append($('<td class="jsgrid-cell jsgrid-align-right"></td>').text('($' + totalIWT.toFixed(2) + ')'));
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 - 2));
totalrow.append($('<td class="jsgrid-cell jsgrid-align-right"></td>').text('$' + (total - totalIWT).toFixed(2)));
totalrow.append($('<td class="jsgrid-cell"></td>'));
$(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 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 - 2));
totalrow.append($('<td class="jsgrid-cell jsgrid-align-right"></td>').text('$' + (total * 0.05).toFixed(2)));
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 - 2));
totalrow.append($('<td class="jsgrid-cell jsgrid-align-right"></td>').text('$' + (total * 1.05).toFixed(2)));
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;
var revenue_data = JSON.parse({{ import('json').dumps(import('json').dumps(revision.revenue))|safe }});
$('#revenue_grid').jsGrid({
width: '100%',
height: '20em',
inserting: true,
editing: true,
noDataContent: 'No entries. Click the green plus icon at the top right to add a new row.',
data: revenue_data,
fields: [
{ 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); } },
{ type: 'control', width: '10%', modeSwitchButton: false },
],
onItemUpdated: recalcRevTotal,
onRefreshed: recalcRevTotal,
});
var expense_data = JSON.parse({{ import('json').dumps(import('json').dumps(revision.expense))|safe }});
$('#expense_grid').jsGrid({
width: '100%',
height: '20em',
inserting: true,
editing: true,
noDataContent: 'No entries. Click the green plus icon at the top right to add a new row.',
data: expense_data,
fields: [
{ 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); } },
{ type: 'control', width: '10%', modeSwitchButton: false },
],
onItemUpdated: recalcExpTotal,
onRefreshed: recalcExpTotal,
});
dragula([document.querySelector('#revenue_grid tbody')], {
accepts: function (el, target, source, sibling) {
return sibling !== null && !sibling.classList.contains('totalrow');
},
invalid: function (el, handle) {
return el.classList.contains('totalrow');
}
});
dragula([document.querySelector('#expense_grid tbody')], {
accepts: function (el, target, source, sibling) {
return sibling !== null && !sibling.classList.contains('totalrow');
},
invalid: function (el, handle) {
return el.classList.contains('totalrow');
}
});
//$('.jsgrid-insert-mode-button').click();
</script>
{% endblock %}