2022-12-24 18:20:42 +11:00
{# DrCr: Web-based double-entry bookkeeping framework
2024-04-04 18:48:22 +11:00
Copyright (C) 2022–2024 Lee Yingtong Li (RunasSudo)
2022-12-24 18:20:42 +11:00
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 / > .
#}
2024-04-04 19:38:59 +11:00
{% extends 'base.html' %}
2023-01-29 19:12:28 +11:00
{% block title %}{{ 'Edit' if transaction and transaction.id else 'New' }} transaction{% endblock %}
2022-12-24 18:20:42 +11:00
2024-04-06 02:00:30 +11:00
{# Macros for template posting rows as these are reused #}
{% macro template_dr() %}
< tr >
< td > < / td >
{#< td > < / td > #}
< td class = "py-1 px-1" colspan = "2" >
< div class = "relative flex" >
< div class = "relative flex flex-grow items-stretch shadow-sm" >
< div class = "absolute inset-y-0 left-0 flex items-center z-10" >
< select class = "h-full border-0 bg-transparent py-0 pl-2 pr-8 text-gray-900 focus:ring-2 focus:ring-inset focus:ring-indigo-600" name = "sign" onchange = "changeDrCr(this)" >
< option value = "dr" selected > Dr< / option >
< option value = "cr" > Cr< / option >
< / select >
< / div >
< div class = "relative combobox w-full" >
< input type = "text" class = "bordered-field pl-16 peer" name = "account" autocomplete = "off" >
{% include 'components/accounts_combobox_inner.html' %}
< / div >
< / div >
< a class = "relative -ml-px px-2 py-2 text-gray-500 hover:text-gray-700" href = "#" onclick = "addPosting(this);return false;" >
< svg xmlns = "http://www.w3.org/2000/svg" fill = "none" viewBox = "0 0 24 24" stroke-width = "1.5" stroke = "currentColor" class = "w-4 h-4" >
< path stroke-linecap = "round" stroke-linejoin = "round" d = "M12 4.5v15m7.5-7.5h-15" / >
< / svg >
< / a >
< / div >
< / td >
< td class = "amount-dr has-amount py-1 px-1" >
< div class = "relative shadow-sm" >
< div class = "pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3" >
< span class = "text-gray-500" > $< / span >
< / div >
< input type = "number" class = "bordered-field pl-7" name = "amount" step = "0.01" oninput = "changeAmount(this)" >
< / div >
< / td >
< td class = "amount-cr py-1 pl-1" > < / td >
< / tr >
{% endmacro %}
{% macro template_cr() %}
< tr >
< td > < / td >
{#< td > < / td > #}
< td class = "py-1 px-1" colspan = "2" >
< div class = "relative flex" >
< div class = "relative flex flex-grow items-stretch shadow-sm" >
< div class = "absolute inset-y-0 left-0 flex items-center z-10" >
< select class = "h-full border-0 bg-transparent py-0 pl-2 pr-8 text-gray-900 focus:ring-2 focus:ring-inset focus:ring-indigo-600" name = "sign" onchange = "changeDrCr(this)" >
< option value = "dr" > Dr< / option >
< option value = "cr" selected > Cr< / option >
< / select >
< / div >
< div class = "relative combobox w-full" >
< input type = "text" class = "bordered-field pl-16 peer" name = "account" autocomplete = "off" >
{% include 'components/accounts_combobox_inner.html' %}
< / div >
< / div >
< a class = "relative -ml-px px-2 py-2 text-gray-500 hover:text-gray-700" href = "#" onclick = "addPosting(this);return false;" >
< svg xmlns = "http://www.w3.org/2000/svg" fill = "none" viewBox = "0 0 24 24" stroke-width = "1.5" stroke = "currentColor" class = "w-4 h-4" >
< path stroke-linecap = "round" stroke-linejoin = "round" d = "M12 4.5v15m7.5-7.5h-15" / >
< / svg >
< / a >
< / div >
< / td >
< td class = "amount-dr py-1 px-1" > < / td >
< td class = "amount-cr has-amount py-1 pl-1" >
< div class = "relative shadow-sm" >
< div class = "pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3" >
< span class = "text-gray-500" > $< / span >
< / div >
< input type = "number" class = "bordered-field pl-7" name = "amount" step = "0.01" oninput = "changeAmount(this)" >
< / div >
< / td >
< / tr >
{% endmacro %}
2022-12-24 18:20:42 +11:00
{% block content %}
2024-04-04 21:02:29 +11:00
< h1 class = "page-heading mb-4" >
2024-04-04 18:48:22 +11:00
{{ 'Edit' if transaction and transaction.id else 'New' }} transaction
< / h1 >
2022-12-24 18:20:42 +11:00
< form method = "POST" >
2024-04-04 18:48:22 +11:00
< table class = "min-w-full" >
2022-12-24 18:20:42 +11:00
< thead >
2024-04-04 18:48:22 +11:00
< tr class = "border-b border-gray-300" >
< th class = "pt-0.5 pb-1 pr-1 text-gray-900 font-semibold text-start" > Date< / th >
< th class = "pt-0.5 pb-1 px-1 text-gray-900 font-semibold text-start" colspan = "2" > Description< / th >
< th class = "pt-0.5 pb-1 px-1 text-gray-900 font-semibold text-start" > Dr< / th >
< th class = "pt-0.5 pb-1 pl-1 text-gray-900 font-semibold text-start" > Cr< / th >
2022-12-24 18:20:42 +11:00
< / tr >
< / thead >
< tbody >
< tr >
2024-04-04 18:48:22 +11:00
< td class = "pt-2 pb-1 pr-1" >
2024-04-04 21:02:29 +11:00
< input type = "date" class = "bordered-field" name = "dt" value = "{{ transaction.dt.strftime('%Y-%m-%d') if transaction else '' }}" >
2024-04-04 18:48:22 +11:00
< / td >
< td class = "pt-2 pb-1 px-1" colspan = "2" >
2024-04-04 21:02:29 +11:00
< input type = "text" class = "bordered-field" name = "description" value = "{{ transaction.description if transaction else '' }}" >
2024-04-04 18:48:22 +11:00
< / td >
2022-12-24 18:20:42 +11:00
< td > < / td >
< td > < / td >
< / tr >
2022-12-24 20:41:33 +11:00
{% if transaction %}
{% for posting in transaction.postings %}
< tr >
< td > < / td >
2024-04-04 18:48:22 +11:00
{#< td > < / td > #}
< td class = "py-1 px-1" colspan = "2" >
< div class = "relative flex" >
< div class = "relative flex flex-grow items-stretch shadow-sm" >
2024-04-06 02:00:30 +11:00
< div class = "absolute inset-y-0 left-0 flex items-center z-10" >
2024-04-04 18:48:22 +11:00
< select class = "h-full border-0 bg-transparent py-0 pl-2 pr-8 text-gray-900 focus:ring-2 focus:ring-inset focus:ring-indigo-600" name = "sign" onchange = "changeDrCr(this)" >
< option value = "dr" { % if posting . quantity > = 0 %} selected{% endif %}>Dr< / option >
< option value = "cr" { % if posting . quantity < 0 % } selected { % endif % } > Cr< / option >
< / select >
< / div >
2024-04-06 02:00:30 +11:00
< div class = "relative combobox w-full" >
< input type = "text" class = "bordered-field pl-16 peer" name = "account" value = "{{ posting.account }}" autocomplete = "off" >
{% include 'components/accounts_combobox_inner.html' %}
< / div >
2024-04-04 18:48:22 +11:00
< / div >
< a class = "relative -ml-px px-2 py-2 text-gray-500 hover:text-gray-700" href = "#" onclick = "addPosting(this);return false;" >
< svg xmlns = "http://www.w3.org/2000/svg" fill = "none" viewBox = "0 0 24 24" stroke-width = "1.5" stroke = "currentColor" class = "w-4 h-4" >
< path stroke-linecap = "round" stroke-linejoin = "round" d = "M12 4.5v15m7.5-7.5h-15" / >
< / svg >
< / a >
2022-12-24 20:41:33 +11:00
< / div >
< / td >
{% if posting.quantity >= 0 %}
2024-04-04 18:48:22 +11:00
< td class = "amount-dr has-amount py-1 px-1" >
< div class = "relative shadow-sm" >
< div class = "pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3" >
< span class = "text-gray-500" > $< / span >
< / div >
2024-04-04 21:02:29 +11:00
< input type = "number" class = "bordered-field pl-7" name = "amount" step = "0.01" value = "{{ posting.amount().quantity_string() }}" oninput = "changeAmount(this)" >
2022-12-24 20:41:33 +11:00
< / div >
< / td >
2024-04-04 18:48:22 +11:00
< td class = "amount-cr py-1 pl-1" > < / td >
2022-12-24 20:41:33 +11:00
{% else %}
2024-04-04 18:48:22 +11:00
< td class = "amount-dr py-1 px-1" > < / td >
< td class = "amount-cr has-amount py-1 pl-1" >
< div class = "relative shadow-sm" >
< div class = "pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3" >
< span class = "text-gray-500" > $< / span >
< / div >
2024-04-04 21:02:29 +11:00
< input type = "number" class = "bordered-field pl-7" name = "amount" step = "0.01" value = "{{ (posting.amount()|abs).quantity_string() }}" oninput = "changeAmount(this)" >
2022-12-24 20:41:33 +11:00
< / div >
< / td >
{% endif %}
< / tr >
{% endfor %}
{% else %}
2024-04-06 02:00:30 +11:00
{{ template_dr() }}
{{ template_cr() }}
2022-12-24 20:41:33 +11:00
{% endif %}
2022-12-24 18:20:42 +11:00
< / tbody >
< / table >
2024-04-04 18:48:22 +11:00
< div class = "flex justify-end mt-4 space-x-2" >
2023-01-29 19:12:28 +11:00
{% if transaction and transaction.id %}
2024-04-04 21:02:29 +11:00
< button type = "submit" name = "action" value = "delete" class = "btn-secondary text-red-600 ring-red-500" onclick = "confirm('Are you sure you want to delete this transaction? This operation is irreversible.')" > Delete< / button >
2023-01-29 19:12:28 +11:00
{% endif %}
2024-04-04 21:02:29 +11:00
< button type = "submit" class = "btn-primary" > Save< / button >
2022-12-24 18:20:42 +11:00
< / div >
2023-05-28 13:02:12 +10:00
< input type = "hidden" name = "referrer" value = "{{ request.referrer or '' }}" >
2022-12-24 18:20:42 +11:00
< / form >
2024-04-06 02:00:30 +11:00
{# Save HTML for template posting rows so we can access this from JS when required #}
< table style = "display:none" id = "template-dr" > {{ template_dr() }}< / table >
< table style = "display:none" id = "template-cr" > {{ template_cr() }}< / table >
2022-12-24 18:20:42 +11:00
{% endblock %}
2022-12-24 20:13:11 +11:00
{% block scripts %}
< script >
function changeDrCr(el) {
2024-04-04 18:48:22 +11:00
let trPosting = el.parentNode.parentNode.parentNode.parentNode.parentNode;
2022-12-24 20:13:11 +11:00
let amountContent = trPosting.querySelector('.has-amount').innerHTML;
let amountValue = trPosting.querySelector('.has-amount input').value;
// Remove input boxes
for (let td of trPosting.querySelectorAll('.amount-dr, .amount-cr')) {
td.innerHTML = '';
td.classList.remove('has-amount');
}
// Add correct input box
let td = trPosting.querySelector(el.value === 'dr' ? '.amount-dr' : '.amount-cr');
td.innerHTML = amountContent;
td.classList.add('has-amount');
td.querySelector('input').value = amountValue;
}
function addPosting(el) {
let trPosting = el.parentNode.parentNode.parentNode;
let sign = trPosting.querySelector('select').value; // Use same sign as row clicked
// Add new posting row
let trNew = document.createElement('tr');
2024-04-06 02:00:30 +11:00
trNew.innerHTML = document.getElementById(sign === 'dr' ? 'template-dr' : 'template-cr').querySelector('tr').innerHTML;
2022-12-24 20:13:11 +11:00
trPosting.after(trNew);
}
2022-12-24 21:02:49 +11:00
function changeAmount(el) {
let amountInputs = document.querySelectorAll('input[name="amount"]');
if (amountInputs.length === 2) {
// Get other input
let otherInput;
for (inp of amountInputs) {
if (inp !== el) {
otherInput = inp;
break;
}
}
// Update other input with amount
otherInput.value = el.value;
}
}
2022-12-24 20:13:11 +11:00
< / script >
2024-04-06 02:00:30 +11:00
< script src = "{{ url_for('static', filename='js/combobox.js') }}" > < / script >
2022-12-24 20:13:11 +11:00
{% endblock %}