return Path('/bookings').joinpath(f'{new_id}')
def post_ledger_action(self) -> Path:
- """Based on trigger postvar call .server.(move|copy)_booking."""
+ """Parse postvar to call .server.(move|copy|add_empty_new)_booking."""
if 'add_booking' in self.postvars.as_dict:
id_ = self.server.add_empty_booking()
else:
keys_prefixed = self.postvars.keys_prefixed(PREFIX_LEDGER)
- action, id_str, dir_ = keys_prefixed[0].split('_', maxsplit=3)[1:]
+ action, id_str = keys_prefixed[0].split('_', maxsplit=2)[1:]
id_ = int(id_str)
- if action == 'move':
- id_ = self.server.move_booking(id_, dir_ == 'up')
+ if action.startswith('move'):
+ id_ = self.server.move_booking(id_, action == 'moveup')
return Path(self.path).joinpath(f'#{id_}')
- id_ = self.server.copy_booking(id_, dir_ == 'to_end')
+ id_ = self.server.copy_booking(id_)
return Path(EDIT_STRUCT).joinpath(f'{id_}')
def do_GET(self) -> None:
booked_lines: list[DatLine] = []
gap_lines: list[DatLine] = []
booking: Optional[Booking] = None
- last_date = ''
for dat_line in self.dat_lines + [DatLine('')]:
if dat_line.code:
if gap_lines:
else:
if booked_lines:
booking = Booking(len(self.bookings), booked_lines[:])
- if last_date > booking.date:
- booking.intro_line.errors += [
- 'date < previous valid date']
- else:
- last_date = booking.date
booking.apply_to_account_dict(self.accounts)
self.bookings += [booking]
booked_lines.clear()
for acc_name, desc in dat_line.comment_instructions.items():
Account.ensure_in_dict(acc_name, self.accounts)
self.accounts[acc_name].desc = desc
+ self._check_date_order()
for booking in self.bookings:
booking.recalc_prev_next(self.bookings)
if booking:
booking.gap_lines = gap_lines[:-1]
self._recalc_prev_line_empty()
+ def _check_date_order(self) -> None:
+ last_date = ''
+ err_msg = 'date < previous valid date'
+ for booking in self.bookings:
+ if err_msg in booking.intro_line.errors:
+ booking.intro_line.errors.remove(err_msg)
+ if last_date > booking.date:
+ booking.intro_line.errors += [err_msg]
+ else:
+ last_date = booking.date
+
def save(self) -> None:
"""Save current state to ._path_dat."""
self._path_dat.write_text(
self._recalc_dat_lines()
return new_id
+ def _remove_booking(
+ self,
+ to_remove: Booking,
+ summed_gap: list[DatLine]
+ ) -> None:
+ del self.bookings[to_remove.id_]
+ for booking in self.bookings[to_remove.id_:]:
+ booking.id_ -= 1
+ if to_remove.id_ == 0:
+ self.initial_gap_lines += summed_gap
+ else:
+ assert to_remove.prev is not None
+ to_remove.prev.gap_lines += summed_gap
+ for neighbour in to_remove.prev, to_remove.next:
+ if neighbour:
+ neighbour.recalc_prev_next(self.bookings)
+ self._recalc_dat_lines()
+
def rewrite_booking(self, old_id: int, new_lines: list[DatLine]) -> int:
"""Rewrite Booking with new_lines, move if changed date."""
old_booking = self.bookings[old_id]
booked_start, booked_end, gap_start_found = -1, 0, False
for i, line in enumerate(new_lines):
- if booked_start < 0 and line.code.strip():
- booked_start = i
- elif booked_start >= 0 and not line.code.strip():
- gap_start_found = True
- if not gap_start_found:
- booked_end += 1
+ if booked_start < 0 and line.code.strip(): # ignore any initial
+ booked_start = i # empty lines
+ elif booked_start >= 0 and not line.code.strip(): # past start,
+ gap_start_found = True # yet empty? gap
+ if not gap_start_found: # end index is always after current line,
+ booked_end += 1 # provided we're not yet in the gap
elif line.code.strip():
new_lines[i] = DatLine(f'; {line.code}')
before_gap = new_lines[:booked_start]
new_booked_lines = (new_lines[booked_start:booked_end]
if booked_start > -1 else [])
- after_gap = old_booking.gap_lines_copied + new_lines[booked_end:]
- if not new_booked_lines:
- del self.bookings[old_id]
- for booking in self.bookings[old_id:]:
- booking.id_ -= 1
- summed_gap = before_gap + after_gap
- if old_booking.id_ == 0:
- self.initial_gap_lines += summed_gap
- else:
- assert old_booking.prev is not None
- old_booking.prev.gap_lines += summed_gap
- for neighbour in old_booking.prev, old_booking.next:
- if neighbour:
- neighbour.recalc_prev_next(self.bookings)
- self._recalc_dat_lines()
+ after_gap = old_booking.gap_lines_copied # new gap be old gap _plus_
+ after_gap += new_lines[booked_end:] # any new gap lines
+ if not new_booked_lines: # interpret empty posting as deletion request
+ self._remove_booking(old_booking, before_gap + after_gap)
return old_id if old_id < len(self.bookings) else 0
+ updated = Booking(old_id, new_booked_lines, after_gap)
+ self.bookings[old_id] = updated
+ updated.recalc_prev_next(self.bookings)
new_date = new_booked_lines[0].code.lstrip().split(maxsplit=1)[0]
- if new_date == old_booking.date:
- new_booking = Booking(old_id, new_booked_lines, after_gap)
- self.bookings[old_id] = new_booking
- new_booking.recalc_prev_next(self.bookings)
- else:
+ if new_date != old_booking.date: # if changed date, move to there
i_booking = self.bookings[0]
new_idx = None
while i_booking.next:
- if not i_booking.prev and i_booking.date > new_date:
+ if (not i_booking.prev) and i_booking.date > new_date:
new_idx = i_booking.id_
break
if i_booking.next.date > new_date:
# land on the edge closest to our last position
if i_booking.date == new_date and old_id < i_booking.id_:
new_idx = [b for b in self.bookings
- if b.date == new_date][0].id_
- new_booking = Booking(new_idx, new_booked_lines, after_gap)
- self.bookings[old_id] = new_booking
+ if b != updated and b.date == new_date][0].id_
self._move_booking(old_id, new_idx)
- if new_booking.id_ == 0:
+ if updated.id_ == 0:
self.initial_gap_lines += before_gap
else:
- assert new_booking.prev is not None
- new_booking.prev.gap_lines += before_gap
+ assert updated.prev is not None
+ updated.prev.gap_lines += before_gap
self._recalc_dat_lines()
- return new_booking.id_
-
- def add_empty_booking(self) -> int:
- """Add new Booking to end of ledger."""
- booking = Booking(len(self.bookings),
- [DatLine(f'{dt_date.today().isoformat()} ?')])
+ self._check_date_order()
+ return updated.id_
+
+ def _add_new_booking(
+ self,
+ target: str,
+ dat_lines_transaction: list[DatLine],
+ intro_comment: str = ''
+ ) -> int:
+ booking = Booking(
+ len(self.bookings),
+ [DatLine(f'{dt_date.today().isoformat()} {target}'
+ + ' ; '.join([''] + [s for s in [intro_comment] if s]))
+ ] + dat_lines_transaction)
self.bookings += [booking]
booking.recalc_prev_next(self.bookings)
self._recalc_dat_lines()
+ self._check_date_order()
return booking.id_
- def copy_booking(self, id_: int, to_end: bool) -> int:
- """Add copy of Booking of id_ to_end of ledger, or after copied."""
- copied = self.bookings[id_]
- new_id = len(self.bookings) if to_end else copied.id_ + 1
- if to_end:
- intro_comment = copied.booked_lines[0].comment
- intro = DatLine(
- f'{dt_date.today().isoformat()} {copied.target}'
- + (f' ; {intro_comment}' if intro_comment else ''))
- new_booking = Booking(new_id,
- [intro] + copied.booked_lines_copied[1:],
- copied.gap_lines_copied)
- self.bookings += [new_booking]
- else:
- new_booking = Booking(new_id,
- copied.booked_lines_copied,
- copied.gap_lines_copied)
- self.bookings[new_id:new_id] = [new_booking]
- for b in self.bookings[new_id + 1:]:
- b.id_ += 1
- new_booking.recalc_prev_next(self.bookings)
- self._recalc_dat_lines()
- return new_id
+ def add_empty_booking(self) -> int:
+ """Add new Booking to end of ledger."""
+ return self._add_new_booking('?', [])
+
+ def copy_booking(self, copied_id: int) -> int:
+ """Add copy of Booking of copied_id to_end of ledger."""
+ copied = self.bookings[copied_id]
+ return self._add_new_booking(copied.target,
+ copied.booked_lines_copied[1:],
+ copied.booked_lines[0].comment)
if __name__ == "__main__":
{% macro css_ledger_index_col() %}
-table.ledger tr > td:first-child { background-color: white; }
+table.ledger tr > td:first-child { background-color: white; text-align: right; }
{% endmacro %}
-{% macro table_dat_lines_action_button(dat_line, verb, direction, label, enabled=true) %}
-<input type="submit" name="ledger_{{verb}}_{{dat_line.booking_id}}_{{direction}}" value="{{label}}"{% if not enabled %} disabled{% endif %} />
+{% macro table_dat_lines_action_button(dat_line, action, label, enabled=true) %}
+<input type="submit" name="ledger_{{action}}_{{dat_line.booking_id}}" value="{{label}}"{% if not enabled %} disabled{% endif %} />
{% endmacro %}
{% macro table_dat_lines(dat_lines, raw) %}
<td{% if dat_line.is_intro %} id="{{dat_line.booking_id}}"{% endif %}>
{% if dat_line.is_intro %}
<a href="#{{dat_line.booking_id}}">[#]</a>
- {{ table_dat_lines_action_button(dat_line, "move", "up", "^", dat_line.booking.can_move(1)) }}
+ {{ table_dat_lines_action_button(dat_line, "moveup", "^", dat_line.booking.can_move(1)) }}
{% elif dat_line.booking_line.idx == 1 %}
<a href="/balance?up_incl={{dat_line.booking_id}}">[b]</a>
- {{ table_dat_lines_action_button(dat_line, "move", "down", "v", dat_line.booking.can_move(0)) }}
+ {{ table_dat_lines_action_button(dat_line, "movedown", "v", dat_line.booking.can_move(0)) }}
{% elif dat_line.booking_line.idx == 2 %}
- {{ table_dat_lines_action_button(dat_line, "copy", "here", "c") }}
- {{ table_dat_lines_action_button(dat_line, "copy", "to_end", "C") }}
+ {{ table_dat_lines_action_button(dat_line, "copy", "C") }}
{% endif %}
</td>