From d84f6f66fd0331967847ba6e5c258bbf0515d7a2 Mon Sep 17 00:00:00 2001
From: Christian Heller <c.heller@plomlompom.de>
Date: Wed, 29 Jan 2025 08:47:45 +0100
Subject: [PATCH] Disallow movement into different date.

---
 ledger.py                        | 47 ++++++++++++++++++++++++--------
 templates/_macros.tmpl           |  6 ++--
 templates/balance.tmpl           |  2 +-
 templates/ledger_raw.tmpl        |  2 +-
 templates/ledger_structured.tmpl |  2 +-
 5 files changed, 41 insertions(+), 18 deletions(-)

diff --git a/ledger.py b/ledger.py
index b9577dc..f179efe 100755
--- a/ledger.py
+++ b/ledger.py
@@ -104,7 +104,12 @@ class DatLine:
     @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
+        return self.booking.id_ if self.booking else -1
+
+    @property
+    def booking(self) -> Optional['Booking']:
+        """If .booking_line, matching Booking, else None."""
+        return self.booking_line.booking if self.booking_line else None
 
     @property
     def error(self) -> str:
@@ -203,9 +208,13 @@ class Booking:
     """Represents lines of individual booking."""
     # pylint: disable=too-few-public-methods
 
-    def __init__(self, id_: int, dat_lines: list[DatLine]) -> None:
-        self.intro_line = IntroLine(self, dat_lines[0].code)
-        dat_lines[0].booking_line = self.intro_line
+    def __init__(self,
+                 id_: int,
+                 dat_lines: list[DatLine],
+                 prev_booking: Optional[Self]
+                 ) -> None:
+        self.intro = IntroLine(self, dat_lines[0].code)
+        dat_lines[0].booking_line = self.intro
         self._transfer_lines = []
         for i, dat_line in enumerate(dat_lines[1:]):
             dat_line.booking_line = TransferLine(self, dat_line.code, i + 1)
@@ -231,11 +240,25 @@ class Booking:
             self._transfer_lines[-1].errors += ['needed sink missing']
         self.id_ = id_
         self.dat_lines = dat_lines
+        self.prev = prev_booking
+        if self.prev:
+            self.prev.next = self
+        self.next: Optional[Self] = None
+
+    def can_move(self, up: bool) -> bool:
+        """Whether movement rules would allow self to move up or down."""
+        if (up and ((not self.prev)
+                    or self.prev.intro.date != self.intro.date)):
+            return False
+        if ((not up) and ((not self.next)
+                          or self.next.intro.date != self.intro.date)):
+            return False
+        return True
 
     @property
     def is_questionable(self) -> bool:
         """Whether lines count any errors."""
-        for _ in [bl for bl in [self.intro_line] + self._transfer_lines
+        for _ in [bl for bl in [self.intro] + self._transfer_lines
                   if bl.errors]:
             return True
         return False
@@ -313,8 +336,6 @@ 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_)
@@ -375,11 +396,13 @@ class Server(PlomHttpServer):
             if dat_line.code:
                 booking_lines += [dat_line]
             elif booking_lines:
-                booking = Booking(len(self.bookings), booking_lines)
-                if last_date > booking.intro_line.date:
-                    booking.intro_line.errors += ['date < previous valid date']
+                booking = Booking(
+                        len(self.bookings), booking_lines,
+                        self.bookings[-1] if self.bookings else None)
+                if last_date > booking.intro.date:
+                    booking.intro.errors += ['date < previous valid date']
                 else:
-                    last_date = booking.intro_line.date
+                    last_date = booking.intro.date
                 self.bookings += [booking]
                 booking_lines = []
 
@@ -431,7 +454,7 @@ class Server(PlomHttpServer):
         if to_end or copied is self.bookings[-1]:
             intro = DatLine(
                 f'{dt_date.today().isoformat()} '
-                f'{copied.intro_line.target} ; {copied.dat_lines[0].comment}')
+                f'{copied.intro.target} ; {copied.dat_lines[0].comment}')
             self.dat_lines += [empty_line, intro] + copied.dat_lines[1:]
             prev_id = self.bookings[-1].id_
         else:
diff --git a/templates/_macros.tmpl b/templates/_macros.tmpl
index f8f9325..6cbeb01 100644
--- a/templates/_macros.tmpl
+++ b/templates/_macros.tmpl
@@ -11,7 +11,7 @@ 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, max_id, raw) %}
+{% macro table_dat_lines(dat_lines, raw) %}
 <form action="/ledger_{% if raw %}raw{% else %}structured{% endif %}" method="POST">
 <table class="ledger">
 {% for dat_line in dat_lines %}
@@ -20,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><input type="submit" name="move_{{dat_line.booking_id}}_up" value="^"{% if dat_line.booking_id == 0 %} disabled{% endif %}/></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 not dat_line.booking.can_move(1) %} disabled{% endif %}/></td>
   {% elif dat_line.booking_line.idx == 1 %}
-    <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>
+    <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 not dat_line.booking.can_move(0) %} disabled{% endif %}/></td>
   {% elif dat_line.booking_line.idx == 2 %}
     <td><input type="submit" name="copy_{{dat_line.booking_id}}_here" value="c" /><input type="submit" name="copy_{{dat_line.booking_id}}_to_end" value="C" /></td>
   {% else %}
diff --git a/templates/balance.tmpl b/templates/balance.tmpl
index a8a3b7f..acf81fc 100644
--- a/templates/balance.tmpl
+++ b/templates/balance.tmpl
@@ -62,7 +62,7 @@ span.indent { letter-spacing: 3em; }
 {% endblock css %}
 
 {% block content %}
-<p>balance after <a href="/bookings/{{booking.id_}}">booking {{booking.id_}} ({{booking.intro_line.date}}: {{booking.intro_line.target}})</a></p>
+<p>balance after <a href="/bookings/{{booking.id_}}">booking {{booking.id_}} ({{booking.intro.date}}: {{booking.intro.target}})</a></p>
 <table{% if not valid %} class="warning"{% endif %}>
 {% for root in roots %}
 {{ account_with_children(root, indent=0) }}
diff --git a/templates/ledger_raw.tmpl b/templates/ledger_raw.tmpl
index d1e13f0..7f803ab 100644
--- a/templates/ledger_raw.tmpl
+++ b/templates/ledger_raw.tmpl
@@ -9,6 +9,6 @@ table.ledger > tbody > tr > td:first-child input[type=submit] { font-size: 0.5em
 {% endblock %}
 
 {% block content %}
-{{ macros.table_dat_lines(dat_lines, max_id, raw=true) }}
+{{ macros.table_dat_lines(dat_lines, raw=true) }}
 {% endblock %}
 
diff --git a/templates/ledger_structured.tmpl b/templates/ledger_structured.tmpl
index 8d9b88c..da1f46f 100644
--- a/templates/ledger_structured.tmpl
+++ b/templates/ledger_structured.tmpl
@@ -11,5 +11,5 @@ table.ledger > tbody > tr > td:first-child { white-space: nowrap; }
 {% endblock %}
 
 {% block content %}
-{{ macros.table_dat_lines(dat_lines, max_id, raw=false) }}
+{{ macros.table_dat_lines(dat_lines, raw=false) }}
 {% endblock %}
-- 
2.30.2