self.booking_line: Optional[BookingLine] = None
@property
- def type(self) -> str:
- """Provide categorization of .code part."""
- return self.booking_line.type if self.booking_line else 'no_data'
+ def is_intro(self) -> bool:
+ """Return if intro line of a Booking."""
+ return self.booking_line.is_intro if self.booking_line else False
@property
def booking_id(self) -> int:
"""If .booking_line, its .booking_id, else -1."""
return self.booking_line.booking_id if self.booking_line else -1
+ @property
+ def error(self) -> str:
+ """Return error if registered on attempt to parse into BookingLine."""
+ return self.booking_line.error if self.booking_line else ''
+
@property
def is_empty(self) -> bool:
"""Return if both .code and .comment are empty."""
class BookingLine:
"""Parsed code part of a DatLine belonging to a Booking."""
- def __init__(self, booking_id: int, code: str) -> None:
+ def __init__(self, booking_id: int, code: str, as_intro: bool = False
+ ) -> None:
+ self.error = ''
self.booking_id = booking_id
- self.type = 'invalid'
+ self.is_intro = as_intro
+ if self.is_intro:
+ if code[0].isspace():
+ self.error = 'intro line indented'
+ return
self.acc, self.amt, self.curr = '', '', ''
- if code[0].isspace():
- toks = code.lstrip().split()
- self.acc = toks[0]
- if 1 == len(toks):
- self.type = 'value'
- elif 3 == len(toks):
- amt_dec = Decimal(toks[1])
- exp = amt_dec.as_tuple().exponent
- assert isinstance(exp, int)
- self.amt = (f'{amt_dec:.1f}…' if exp < -2
- else f'{amt_dec:.2f}')
- self.curr = toks[2]
- self.type = 'value'
- else:
- self.type = 'intro'
+ if not code[0].isspace():
+ self.error = 'non-intro line not indented'
+ return
+ toks = code.lstrip().split()
+ self.acc = toks[0]
+ if len(toks) not in {1, 3}:
+ self.error = 'illegal number of tokens'
+ return
+ if 3 == len(toks):
+ amt_dec = Decimal(toks[1])
+ exp = amt_dec.as_tuple().exponent
+ assert isinstance(exp, int)
+ self.amt = (f'{amt_dec:.1f}…' if exp < -2
+ else f'{amt_dec:.2f}')
+ self.curr = toks[2]
class Booking:
def __init__(self, id_: int, dat_lines: list[DatLine]) -> None:
self.id_ = id_
self.dat_lines = dat_lines
- for dat_line in self.dat_lines:
+ self.dat_lines[0].booking_line = BookingLine(
+ self.id_, self.dat_lines[0].code, as_intro=True)
+ for dat_line in self.dat_lines[1:]:
dat_line.booking_line = BookingLine(self.id_, dat_line.code)
{% macro table_dat_lines(dat_lines, single, raw) %}
<table>
{% for dat_line in dat_lines %}
- {% if (not (raw or single)) and dat_line.type == "intro" and loop.index > 1 %}
+ {% if (not (raw or single)) and dat_line.is_intro and loop.index > 1 %}
<tr><td colspan=5> </td></tr>
{% endif %}
- <tr class="{{dat_line.type}}">
+ <tr>
{% if not single %}
- {% if dat_line.type == "intro" %}
+ {% if dat_line.is_intro %}
<td id="{{dat_line.booking_id}}"><a href="#{{dat_line.booking_id}}">#</a></td>
{% else %}
<td></td>
{% endif %}
{% endif %}
{% if raw %}
- {% if dat_line.type == "intro" %}
+ {% if dat_line.is_intro %}
<td><a href="/booking/{{dat_line.booking_id}}"/>{{dat_line.raw_nbsp}}</a></td>
{% else %}
<td>{{dat_line.raw_nbsp}}</td>
{% endif %}
{% else %}
- {% if dat_line.type == "intro" %}
- <td class="code" colspan=3><a href="/booking/{{dat_line.booking_id}}">{{dat_line.code}}</a></td>
- {% elif dat_line.type == "value" %}
+ {% if dat_line.is_intro %}
+ <td class="code{% if dat_line.error %} invalid{% endif %}" colspan=3><a href="/booking/{{dat_line.booking_id}}">{{dat_line.code}}</a></td>
+ {% elif not dat_line.error %}
<td class="amt">{{dat_line.booking_line.amt}}</td>
<td class="curr">{{dat_line.booking_line.curr|truncate(4,true,"…")}}</td>
<td>{{dat_line.booking_line.acc}}</td>
{% else %}
- <td colspan=3>{{dat_line.code}}</td>
+ <td class="invalid" colspan=3>{{dat_line.code}}</td>
{% endif %}
<td>{{dat_line.comment}}</td>
{% endif %}
</tr>
+ {% if dat_line.error and not raw %}
+ <tr><td class="invalid" colspan={% if single %}4{% else %}5{% endif %}>{{dat_line.error}}</td></tr>
+ {% endif %}
{% endfor %}
</table>
{% endmacro %}