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
{% block content %}
2024-04-04 18:48:22 +11:00
< h1 class = "text-3xl text-gray-900 mb-4" >
{{ '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" >
< input type = "date" class = "block w-full border-0 py-1 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-inset focus:ring-emerald-600" name = "dt" value = "{{ transaction.dt.strftime('%Y-%m-%d') if transaction else '' }}" >
< / td >
< td class = "pt-2 pb-1 px-1" colspan = "2" >
< input type = "text" class = "block w-full border-0 py-1 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-inset focus:ring-emerald-600" name = "description" value = "{{ transaction.description if transaction else '' }}" >
< / 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" >
< div class = "absolute inset-y-0 left-0 flex items-center" >
< 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 >
< input type = "text" class = "block w-full border-0 py-1 pl-16 text-gray-900 ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-emerald-600" name = "account" value = "{{ posting.account }}" >
< / 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 >
< input type = "number" class = "block w-full border-0 py-1 pl-7 text-gray-900 ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-inset focus:ring-emerald-600" 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 >
< input type = "number" class = "block w-full border-0 py-1 pl-7 text-gray-900 ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-inset focus:ring-emerald-600" 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 %}
< 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" >
< div class = "absolute inset-y-0 left-0 flex items-center" >
< 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 >
< input type = "text" class = "block w-full border-0 py-1 pl-16 text-gray-900 ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-emerald-600" name = "account" >
< / 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 >
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 >
< input type = "number" class = "block w-full border-0 py-1 pl-7 text-gray-900 ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-inset focus:ring-emerald-600" name = "amount" step = "0.01" 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
< / tr >
< 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" >
< div class = "absolute inset-y-0 left-0 flex items-center" >
< 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 >
< input type = "text" class = "block w-full border-0 py-1 pl-16 text-gray-900 ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-emerald-600" name = "account" >
< / 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 >
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 >
< input type = "number" class = "block w-full border-0 py-1 pl-7 text-gray-900 ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-inset focus:ring-emerald-600" name = "amount" step = "0.01" oninput = "changeAmount(this)" >
2022-12-24 20:41:33 +11:00
< / div >
< / td >
< / tr >
{% 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 18:48:22 +11:00
< button type = "submit" name = "action" value = "delete" class = "px-3 py-1 text-red-600 ring-1 ring-inset ring-red-500 hover:bg-gray-50" 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 18:48:22 +11:00
< button type = "submit" class = "bg-emerald-600 px-3 py-1 text-white shadow-sm hover:bg-emerald-700" > 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 >
{% endblock %}
2022-12-24 20:13:11 +11:00
{% block scripts %}
2024-04-04 18:48:22 +11:00
{# TODO: This would be much easier with React or similar... #}
2022-12-24 20:13:11 +11:00
< 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
2024-04-04 18:48:22 +11:00
let inputAmount =
'< 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 = "block w-full border-0 py-1 pl-7 text-gray-900 ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-inset focus:ring-emerald-600" name = "amount" step = "0.01" oninput = "changeAmount(this)" > ' +
'< / div > ';
2022-12-24 20:13:11 +11:00
// Add new posting row
let trNew = document.createElement('tr');
2024-04-04 18:48:22 +11:00
trNew.innerHTML =
'< tr > ' +
'< 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" > ' +
'< 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)" > ' +
(sign === 'dr' ? '< option value = "dr" selected > Dr< / option > < option value = "cr" > Cr< / option > ' : '< option value = "dr" > Dr< / option > < option value = "cr" selected > Cr< / option > ') +
'< / select > ' +
'< / div > ' +
'< input type = "text" class = "block w-full border-0 py-1 pl-16 text-gray-900 ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-emerald-600" name = "account" > ' +
'< / 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 > ' +
(sign === 'dr' ? ('< td class = "amount-dr has-amount py-1 px-1" > ' + inputAmount + '< / td > ') : '< td class = "amount-dr py-1 px-1" > < / td > ') +
(sign === 'cr' ? ('< td class = "amount-cr has-amount py-1 pl-1" > ' + inputAmount + '< / td > ') : '< td class = "amount-cr py-1 pl-1" > < / td > ') +
'< / tr > ';
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 >
{% endblock %}