home · contact · privacy
Remove "copy here", refactor and bugfix Booking addition/rewriting. master
authorChristian Heller <c.heller@plomlompom.de>
Thu, 13 Mar 2025 12:11:45 +0000 (13:11 +0100)
committerChristian Heller <c.heller@plomlompom.de>
Thu, 13 Mar 2025 12:11:45 +0000 (13:11 +0100)
src/run.py
src/templates/_macros.tmpl
src/templates/ledger_structured.tmpl

index 911a5e3648d1bfded229bd94f3d4d28f7457c0ca..68aa534c738893a68549379717de4ebb53aa9c5f 100755 (executable)
@@ -455,17 +455,17 @@ class Handler(PlomHttpHandler):
         return Path('/bookings').joinpath(f'{new_id}')
 
     def post_ledger_action(self) -> Path:
         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)
         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)
             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_}')
                 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:
         return Path(EDIT_STRUCT).joinpath(f'{id_}')
 
     def do_GET(self) -> None:
@@ -569,7 +569,6 @@ class Server(PlomHttpServer):
         booked_lines: list[DatLine] = []
         gap_lines: list[DatLine] = []
         booking: Optional[Booking] = 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:
         for dat_line in self.dat_lines + [DatLine('')]:
             if dat_line.code:
                 if gap_lines:
@@ -582,23 +581,30 @@ class Server(PlomHttpServer):
             else:
                 if booked_lines:
                     booking = Booking(len(self.bookings), booked_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
                     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()
 
         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(
     def save(self) -> None:
         """Save current state to ._path_dat."""
         self._path_dat.write_text(
@@ -656,48 +662,54 @@ class Server(PlomHttpServer):
         self._recalc_dat_lines()
         return new_id
 
         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):
     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 [])
             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
             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]
         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:
             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:
                     new_idx = i_booking.id_
                     break
                 if i_booking.next.date > new_date:
@@ -709,50 +721,44 @@ class Server(PlomHttpServer):
             # 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
             # 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)
             self._move_booking(old_id, new_idx)
-        if new_booking.id_ == 0:
+        if updated.id_ == 0:
             self.initial_gap_lines += before_gap
         else:
             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()
         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.bookings += [booking]
         booking.recalc_prev_next(self.bookings)
         self._recalc_dat_lines()
+        self._check_date_order()
         return booking.id_
 
         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__":
 
 
 if __name__ == "__main__":
index fe783f7c953b4beef14f7a8484513a49261e7ce2..97eddaf5e0f7e72e5cab8a09b6934c1bff8f3ff3 100644 (file)
@@ -19,13 +19,13 @@ td.invalid, tr.warning td.invalid { background-color: #ff0000; }
 
 
 {% macro css_ledger_index_col() %}
 
 
 {% 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 %}
 
 
 
 {% 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) %}
 {% endmacro %}
 
 {% macro table_dat_lines(dat_lines, raw) %}
@@ -42,13 +42,12 @@ table.ledger tr > td:first-child { background-color: white; }
     <td{% if dat_line.is_intro %} id="{{dat_line.booking_id}}"{% endif %}>
     {% if dat_line.is_intro %}
       <a href="#{{dat_line.booking_id}}">[#]</a>
     <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>
     {% 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 %}
     {% 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>
 
     {% endif %}
     </td>
 
index da1f46fcc38001d1a705f0d04239548a111341c6..da853e56fa1c5d50c0b20a2d8540bfe252f0f0ec 100644 (file)
@@ -5,8 +5,9 @@
 {{ macros.css_td_money() }}
 {{ macros.css_errors() }}
 {{ macros.css_ledger_index_col() }}
 {{ macros.css_td_money() }}
 {{ macros.css_errors() }}
 {{ macros.css_ledger_index_col() }}
-table.ledger > tbody > tr > td.date, table.ledger > tbody > tr > td:first-child { font-family: monospace; font-size: 1.3em; text-align: center; }
 table.ledger > tbody > tr > td { vertical-align: middle; }
 table.ledger > tbody > tr > td { vertical-align: middle; }
+table.ledger > tbody > tr > td.date, table.ledger > tbody > tr > td:first-child { font-family: monospace; font-size: 1.3em; }
+table.ledger > tbody > tr > td.date { text-align: center; }
 table.ledger > tbody > tr > td:first-child { white-space: nowrap; }
 {% endblock %}
 
 table.ledger > tbody > tr > td:first-child { white-space: nowrap; }
 {% endblock %}