self.code = halves[0]
self.booking_line: Optional[BookingLine] = None
+ @property
+ def as_dict(self) -> dict:
+ """Return as JSON-ready dict."""
+ assert isinstance(self.booking_line, (IntroLine, TransferLine))
+ return {'comment': self.comment, 'code': self.code,
+ 'is_intro': self.is_intro, 'error': self.error,
+ 'booking_line': self.booking_line.as_dict}
+
@property
def is_intro(self) -> bool:
"""Return if intro line of a Booking."""
else:
self.date = toks[0]
+ @property
+ def as_dict(self) -> dict:
+ """Return as JSON-ready dict."""
+ return {'date': self.date, 'target': self.target}
+
class TransferLine(BookingLine):
"""Non-first Booking line, expected to carry value movement."""
def __init__(self, booking: 'Booking', code: str) -> None:
super().__init__(booking)
+ self.currency = ''
if not code[0].isspace():
self.errors += ['transfer line not indented']
toks = code.lstrip().split()
self.account = toks[0]
self.amount: Optional[Decimal] = None
- if 1 == len(toks):
- self.currency = ''
- elif 3 <= len(toks):
+ if 3 <= len(toks):
self.currency = toks[2]
try:
self.amount = Decimal(toks[1])
return f'{self.amount:.1f}…' if exp < -2 else f'{self.amount:.2f}'
return ''
+ @property
+ def as_dict(self) -> dict:
+ """Return as JSON-ready dict."""
+ return {'account': self.account, 'currency': self.currency,
+ 'amount': str(self.amount)}
+
class Booking:
"""Represents lines of individual booking."""
ctx = {'tainted': self.server.tainted}
if self.pagename == 'booking' or self.pagename.startswith('edit_'):
ctx['id'] = int(self.path_toks[2])
- ctx['dat_lines'] = self.server.bookings[ctx['id']].dat_lines
if self.pagename == 'balance':
valid, balance_roots = self.server.balance_roots(
int(self.params.first('cutoff') or '0'))
self.send_rendered(Path('balance.tmpl'),
ctx | {'roots': balance_roots, 'valid': valid})
elif self.pagename in {'booking', 'edit_structured'}:
+ ctx['dat_lines'] = [dl.as_dict for dl
+ in self.server.bookings[ctx['id']].dat_lines]
self.send_rendered(Path('edit_structured.tmpl'), ctx)
elif self.pagename == 'edit_raw':
+ ctx['dat_lines'] = self.server.bookings[ctx['id']].dat_lines
self.send_rendered(Path('edit_raw.tmpl'), ctx)
elif self.pagename == 'ledger_raw':
self.send_rendered(Path('ledger_raw.tmpl'),
{{ macros.css_errors() }}
{% endblock %}
+
+{% block script %}
+var dat_lines = {{dat_lines|tojson|safe}};
+
+function update_form() {
+ const table = document.getElementById("dat_lines");
+ table.innerHTML = "";
+ function add_button(td, text, disable, f) {
+ const btn = document.createElement("button");
+ td.appendChild(btn);
+ btn.textContent = text;
+ btn.type = "button"; // otherwise will act as form submit
+ btn.disabled = disable;
+ btn.onclick = function() {f(); update_form();};
+ }
+ function add_td(tr, colspan=1) {
+ const td = document.createElement("td");
+ tr.appendChild(td);
+ td.colSpan = colspan;
+ return td;
+ }
+ for (let i = 0; i < dat_lines.length; i++) {
+ const dat_line = dat_lines[i];
+ const tr = document.createElement("tr");
+ table.appendChild(tr);
+ function add_input(name, value, colspan=1) {
+ const td = add_td(tr, colspan);
+ const input = document.createElement("input");
+ td.appendChild(input);
+ input.name = `line_${i}_${name}`
+ input.value = value.trim();
+ if (dat_line.error) {
+ td.classList.add("invalid");
+ }
+ }
+ if (dat_line.is_intro) {
+ add_input('date', dat_line.booking_line.date)
+ add_input('target', dat_line.booking_line.target, 2)
+ } else if (!dat_line.error) {
+ add_input('account', dat_line.booking_line.account);
+ add_input('amount', dat_line.booking_line.amount == 'None' ? '' : dat_line.booking_line.amount);
+ add_input('currency', dat_line.booking_line.currency);
+ } else {
+ add_input('error', dat_line.code, 3)
+ }
+ add_input('comment', dat_line.comment);
+ const td_btns = add_td(tr);
+ add_button(td_btns, 'delete', false, function() {
+ dat_lines.splice(i, 1);
+ });
+ add_button(td_btns, 'move up', i > 1 ? false : true, function() {
+ const prev_line = dat_lines[i-1];
+ dat_lines.splice(i-1, 1);
+ dat_lines.splice(i, 0, prev_line);
+ });
+ add_button(td_btns, 'move down', i+1 < dat_lines.length ? false : true, function() {
+ const next_line = dat_lines[i];
+ dat_lines.splice(i, 1);
+ dat_lines.splice(i+1, 0, next_line);
+ });
+ if (dat_line.error) {
+ const tr = document.createElement("tr");
+ table.appendChild(tr);
+ const td = add_td(tr, 3);
+ tr.appendChild(document.createElement("td"));
+ td.textContent = dat_line.error;
+ td.classList.add("invalid");
+ tr.classList.add("warning");
+ }
+ }
+ const tr = document.createElement("tr");
+ table.appendChild(tr);
+ const td = add_td(tr, 5);
+ add_button(td, 'add line', false, function() {
+ new_line = {error: '', comment: '', booking_line: {account: '', amount: '', currency: ''}};
+ dat_lines.push(new_line);
+ });
+}
+
+window.onload = update_form;
+{% endblock %}
+
+
{% block content %}
<a href="/edit_raw/{{id}}">edit raw</a>
<hr />
<form action="/edit_structured/{{id}}" method="POST">
-<table>
-{% for dat_line in dat_lines %}
- <tr>
- {% if dat_line.is_intro %}
- <td{% if dat_line.error %} class="invalid"{% endif %}><input name="line_{{loop.index0}}_date" value="{{dat_line.booking_line.date}}" /></td>
- <td{% if dat_line.error %} class="invalid"{% endif %} colspan=2><input name="line_{{loop.index0}}_target" value="{{dat_line.booking_line.target}}" /></td>
- {% elif not dat_line.error %}
- <td><input name="line_{{loop.index0}}_account" value="{{dat_line.booking_line.account}}" /></td>
- <td><input name="line_{{loop.index0}}_amount" value="{{dat_line.booking_line.amount or ''}}" /></td>
- <td><input name="line_{{loop.index0}}_currency" value="{{dat_line.booking_line.currency or ''}}" /></td>
- {% else %}
- <td class="invalid" colspan=3><input name="line_{{loop.index0}}_error" value="{{dat_line.code|trim}}" /></td>
- {% endif %}
- <td><input name="line_{{loop.index0}}_comment" value="{{dat_line.comment|trim}}" /></td>
- </tr>
- {% if dat_line.error %}
- <tr class="warning">
- <td class="invalid" colspan=3>{{dat_line.error}}</td>
- <td></td>
- </tr>
- {% endif %}
-{% endfor %}
+<table id="dat_lines">
</table>
<input type="submit" value="update" />
</form>