home · contact · privacy
To ledger view, add movement of Bookings up and down.
authorChristian Heller <c.heller@plomlompom.de>
Wed, 29 Jan 2025 00:50:36 +0000 (01:50 +0100)
committerChristian Heller <c.heller@plomlompom.de>
Wed, 29 Jan 2025 00:50:36 +0000 (01:50 +0100)
ledger.py
templates/_macros.tmpl
templates/edit_structured.tmpl
templates/ledger_raw.tmpl
templates/ledger_structured.tmpl

index 098c6f6a82c6c5ae4d05536ae38b6b4d5591c441..0c6ed5eccd78541a3808b74c113a489a8b15fe35 100755 (executable)
--- a/ledger.py
+++ b/ledger.py
@@ -259,6 +259,13 @@ class Handler(PlomHttpHandler):
             self.server.load()
         elif 'save_file' in self.postvars.as_dict:
             self.server.save()
+        elif self.pagename.startswith('ledger_'):
+            for key in self.postvars.keys_prefixed('move_'):
+                toks = key.split('_')
+                id_ = int(toks[1])
+                self.server.move_booking(id_, up=toks[2] == 'up')
+                self.redirect(Path(self.pagename).joinpath(f'#{id_}'))
+                return
         elif self.pagename == 'edit_structured':
             if self.postvars.first('apply'):
                 line_keys = self.postvars.keys_prefixed('line_')
@@ -305,6 +312,8 @@ class Handler(PlomHttpHandler):
             id_ = int(self.path_toks[2])
             if self.pagename.startswith('edit_'):
                 ctx['id'] = id_
+        elif self.pagename.startswith('ledger_'):
+            ctx['max_id'] = self.server.bookings[-1].id_
         if self.pagename == 'balance':
             id_ = int(self.params.first('up_incl') or '-1')
             valid, balance_roots = self.server.balance_roots(id_)
@@ -380,15 +389,41 @@ class Server(PlomHttpServer):
             '\n'.join([line.raw for line in self.dat_lines]), encoding='utf8')
         self.load()
 
-    def rewrite_booking(self, id_: int, new_dat_lines: list[DatLine]) -> None:
-        """Rewrite .dat_lines for Booking of .id_ with new_dat_lines."""
-        old_booking = self.bookings[id_]
-        start_idx = self.dat_lines.index(old_booking.dat_lines[0])
-        end_idx = self.dat_lines.index(old_booking.dat_lines[-1])
+    def _margin_indices(self, booking: Booking) -> tuple[int, int]:
+        start_idx = self.dat_lines.index(booking.dat_lines[0])
+        end_idx = self.dat_lines.index(booking.dat_lines[-1])
+        return start_idx, end_idx
+
+    def _replace_from_to(self,
+                         start_idx: int,
+                         end_idx: int,
+                         new_lines: list[DatLine]
+                         ) -> None:
         self.dat_lines = (self.dat_lines[:start_idx]
-                          + new_dat_lines + self.dat_lines[end_idx+1:])
+                          + new_lines
+                          + self.dat_lines[end_idx+1:])
         self.load_bookings()
 
+    def move_booking(self, id_: int, up: bool) -> None:
+        """Move Booking of id_ one step up or downwards"""
+        id_other = id_ + (-1 if up else 1)
+        agent = self.bookings[id_]
+        other = self.bookings[id_other]
+        start_agent, end_agent = self._margin_indices(agent)
+        start_other, end_other = self._margin_indices(other)
+        gap_lines = self.dat_lines[(end_other if up else end_agent) + 1:
+                                   start_agent if up else start_other]
+        self._replace_from_to(start_other if up else start_agent,
+                              end_agent if up else end_other,
+                              (agent.dat_lines if up else other.dat_lines)
+                              + gap_lines
+                              + (other.dat_lines if up else agent.dat_lines))
+
+    def rewrite_booking(self, id_: int, new_dat_lines: list[DatLine]) -> None:
+        """Rewrite .dat_lines for Booking of .id_ with new_dat_lines."""
+        self._replace_from_to(*self._margin_indices(self.bookings[id_]),
+                              new_dat_lines)
+
     @property
     def dat_lines_sans_empty(self) -> list[DatLine]:
         """Return only those .data_lines with .code or .comment."""
index c88bce683b2f4e0d0a9dc60b446e3e5c99daa697..6c2e57c58f8c7942767738d3cf3402434b877bdc 100644 (file)
@@ -11,7 +11,8 @@ td.invalid, tr.warning td.invalid { background-color: #ff0000; }
 table.ledger tr > td:first-child { background-color: white; }
 {% endmacro %}
 
-{% macro table_dat_lines(dat_lines, raw) %}
+{% macro table_dat_lines(dat_lines, max_id, raw) %}
+<form action="/ledger_{% if raw %}raw{% else %}structured{% endif %}" method="POST">
 <table class="ledger">
 {% for dat_line in dat_lines %}
   {% if (not raw) and dat_line.is_intro and loop.index > 1 %}
@@ -19,9 +20,9 @@ table.ledger tr > td:first-child { background-color: white; }
   {% endif %}
   <tr class="alternating{% if dat_line.is_questionable %} warning{% endif %}">
   {% if dat_line.is_intro %}
-    <td id="{{dat_line.booking_id}}"><a href="#{{dat_line.booking_id}}">[#]</a></td>
+    <td id="{{dat_line.booking_id}}"><a href="#{{dat_line.booking_id}}">[#]</a><input type="submit" name="move_{{dat_line.booking_id}}_up" value="^"{% if dat_line.booking_id == 0 %} disabled{% endif %}/></td>
   {% elif dat_line.booking_line.idx == 1 %}
-    <td><a href="/balance?up_incl={{dat_line.booking_id}}">[b]</a></td>
+    <td><a href="/balance?up_incl={{dat_line.booking_id}}">[b]</a><input type="submit" name="move_{{dat_line.booking_id}}_down" value="v"{% if dat_line.booking_id == max_id %} disabled{% endif %}/></td>
   {% else %}
     <td></td>
   {% endif %}
@@ -56,6 +57,7 @@ table.ledger tr > td:first-child { background-color: white; }
   {% endif %}
 {% endfor %}
 </table>
+</form>
 {% endmacro %}
 
 {% macro taint_js() %}
index f85a59db57b65370e3b18754fe5fa3f700d32169..efa474ee5f3e72e40e17693a489008b48c61f180 100644 (file)
@@ -62,10 +62,10 @@ function update_form() {
       add_td_input('account', dat_line.booking_line.account, 30);
       // not using input[type=number] cuz no minimal step size, therefore regex test instead
       const amt_input = add_td_input('amount', dat_line.booking_line.amount == 'None' ? '' : dat_line.booking_line.amount, 12);
-      amt_input.pattern = '^[0-9]+(\.[0-9]+)?$';
+      amt_input.pattern = '^-?[0-9]+(\.[0-9]+)?$';
       amt_input.classList.add("number_input");
       // ensure integer amounts at least line up with double-digit decimals
-      if (amt_input.value.match(/^[0-9]+$/)) { amt_input.value += '.00'; }
+      if (amt_input.value.match(/^-?[0-9]+$/)) { amt_input.value += '.00'; }
       // imply that POST handler will set '€' currency if unset, but amount set
       const curr_input = add_td_input('currency', dat_line.booking_line.currency, 3);
       curr_input.placeholder = '€';
index d11e1927d7bc5bdb67f52d0326cb41daa0bf6158..d1e13f0f929a0255522f752e8b66188b85d7eb42 100644 (file)
@@ -5,9 +5,10 @@
 table { font-family: monospace; }
 {{ macros.css_errors() }}
 {{ macros.css_ledger_index_col() }}
+table.ledger > tbody > tr > td:first-child input[type=submit] { font-size: 0.5em; }
 {% endblock %}
 
 {% block content %}
-{{ macros.table_dat_lines(dat_lines, raw=true) }}
+{{ macros.table_dat_lines(dat_lines, max_id, raw=true) }}
 {% endblock %}
 
index 654cf80a27e8bd542f7c6437b89c33c71ef4f626..8d9b88cadf024a7ed0247363ff9fea42689dd75c 100644 (file)
@@ -7,8 +7,9 @@
 {{ 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:first-child { white-space: nowrap; }
 {% endblock %}
 
 {% block content %}
-{{ macros.table_dat_lines(dat_lines, raw=false) }}
+{{ macros.table_dat_lines(dat_lines, max_id, raw=false) }}
 {% endblock %}