home · contact · privacy
Improve accounting scripts.
authorChristian Heller <c.heller@plomlompom.de>
Fri, 5 Jan 2024 00:59:45 +0000 (01:59 +0100)
committerChristian Heller <c.heller@plomlompom.de>
Fri, 5 Jan 2024 00:59:45 +0000 (01:59 +0100)
ledger.py
plomlib.py
todo.py
todo_templates/day.html
todo_templates/task.html
todo_templates/todo.html

index 36cca98c1c0d5e28fefb986c8d07907da7cfb553..0ef3d441a8714c36e910b92f1c8e0fcfcd2ea7ce 100755 (executable)
--- a/ledger.py
+++ b/ledger.py
@@ -6,7 +6,7 @@ from urllib.parse import parse_qs, urlparse
 from plomlib import PlomDB, PlomException, run_server, PlomHandler 
 
 server_port = 8082
-db_path = '/home/plom/org/ledger2023.dat'
+db_path = '/home/plom/org/ledger2024.dat'
 
 html_head = """
 <style>
@@ -56,10 +56,8 @@ add_form_footer = """
 </form>
 """
 add_free_html = """<br />
-<textarea name="booking" rows=10 cols=80>
-{% for line in lines %}{{ line }}
-{% endfor %}
-</textarea>
+<textarea name="booking" rows=10 cols=80>{% for line in lines %}{{ line }}{% if not loop.last %}
+{% endif %}{% endfor %}</textarea>
 """
 add_structured_html = """
 <input type="submit" name="add_taxes" value="add taxes" tabindex="5" />
@@ -371,26 +369,30 @@ class LedgerDB(PlomDB):
                     elif b.date_string > date:
                         start_at = b.start_line 
                         break
+                lines += ['']  # DEBUG is new
         return self.write_lines_in_total_lines_at(self.real_lines, start_at, lines)
 
     def update(self, start, end, lines, date):
-        total_lines = self.real_lines[:start] + self.real_lines[end:]
+        remaining_lines = self.real_lines[:start] + self.real_lines[end:]
         n_original_lines = end - start
-        start_at = len(total_lines)
+        start_at = len(remaining_lines)
         for b in self.bookings:
             if b.date_string == date:
-                if start_at == len(total_lines) or b.start_line == start:
+                if start_at == len(remaining_lines) or b.start_line == start:
                     start_at = b.start_line 
                     if b.start_line > start:
                         start_at -= n_original_lines
             elif b.date_string > date:
                 break
-        if start_at == len(total_lines):
+        # print("DEBUG update start_at", start_at, "len(remaining_lines)", len(remaining_lines), "len(self.real_lines)", len(self.real_lines), "end", end)
+        if start_at != 0 and end != len(self.real_lines) and start_at == len(remaining_lines):
+            # Add empty predecessor line if appending.
             lines = [''] + lines
-        return self.write_lines_in_total_lines_at(total_lines, start_at, lines)
+        return self.write_lines_in_total_lines_at(remaining_lines, start_at, lines)
 
     def write_lines_in_total_lines_at(self, total_lines, start_at, lines):
-        total_lines = total_lines[:start_at] + lines + [''] + total_lines[start_at:]
+        # total_lines = total_lines[:start_at] + lines + [''] + total_lines[start_at:]
+        total_lines = total_lines[:start_at] + lines + total_lines[start_at:]
         _, _ = parse_lines(lines)
         text = '\n'.join(total_lines)
         self.write_db(text)
@@ -581,7 +583,7 @@ class LedgerDB(PlomDB):
             else:
                 line += f"\t\t"
             for currency, amount in account_sums[path + node].items():
-                if currency != '€' and amount > 0:
+                if currency != '€' and amount != 0:
                     line += f"{amount:5.2f} {currency}\t"
             lines += [line]
             indent += "  "
@@ -672,13 +674,23 @@ class LedgerDB(PlomDB):
             if b.start_line > start:
                 next_booking = b
                 break
-        start_at = next_booking.start_line + len(next_booking.lines) - (end - start) + 1 
-        self.make_move(start, end, start_at-1)
+        # start_at = next_booking.start_line + len(next_booking.lines) - (end - start) + 1 
+        # self.make_move(start, end, start_at-1)
+        start_at = next_booking.start_line + len(next_booking.lines) - (end - start)
+        print("DEBUG", start, end, start_at)
+        self.make_move(start, end, start_at)
         return redir_nth
 
     def make_move(self, start, end, start_at):
+        # FIXME currently broken due to changed self.write_lines_in_total_lines_at, easy fix would be lines += [""] maybe?
         lines = self.get_lines(start, end)
-        total_lines = self.real_lines[:start-1] + self.real_lines[end:]  # +1 because we reduce the original position's two empty border lines to in-between line
+        if start == 0:
+            total_lines = self.real_lines[end+1:]
+            lines = [''] + lines
+            start_at += 1
+        else: 
+            total_lines = self.real_lines[:start-1] + self.real_lines[end:]  # -1 because we reduce the original position's two empty limit lines to one in-between line
+            lines += ['']
         self.write_lines_in_total_lines_at(total_lines, start_at, lines)
 
     def booking_lines_from_postvars(self, postvars):
@@ -722,7 +734,8 @@ class LedgerDB(PlomDB):
                 temp_bookings, _ = parse_lines(temp_lines)
                 for currency in temp_bookings[0].sink:
                     amount = temp_bookings[0].sink[currency]
-                    lines += [f'Assets  {amount:.2f} {currency}']
+                    # lines += [f'Assets  {amount:.2f} {currency}']
+                    lines += [f'Assets  {amount} {currency}']
             except PlomException:
                 pass
         elif 'add_taxes' in postvars.keys():
@@ -756,6 +769,7 @@ class LedgerHandler(PlomHandler):
         postvars = parse_qs(self.rfile.read(length).decode(), keep_blank_values=1)
         start = int(postvars['start'][0])
         end = int(postvars['end'][0])
+        print("DEBUG start, end", start, end)
         db = LedgerDB(prefix)
         add_empty_line = None
         lines = []
@@ -785,6 +799,7 @@ class LedgerHandler(PlomHandler):
                 nth = db.get_nth_for_booking_of_start_line(start) 
             else:
                 new_start = db.update(start, end, lines, target_date)
+                print("DEBUG save", new_start, start, end, lines)
                 nth = db.get_nth_for_booking_of_start_line(new_start)
                 if new_start > start: 
                     nth -= 1 
index a94eb54af2646ed1079c5498af0cb0bb0aa59775..1d832957c13aee703f32ce5956becb27eb684047 100644 (file)
@@ -33,7 +33,7 @@ class PlomDB:
             return
 
         # collect modification times of numbered .bak files
-        print('DEBUG BACKUP')
+        print('DEBUG BACKUP')
         bak_prefix = f'{self.db_file}.bak.'
         # backup_dates = []
         mtimes_to_paths = {}
@@ -41,44 +41,47 @@ class PlomDB:
                      if path.startswith(os.path.basename(bak_prefix))]:
             path = os.path.dirname(bak_prefix) + f'/{path}'
             mod_time = os.path.getmtime(path)
-            print(f'DEBUG pre-exists: {path} {mod_time}')
+            print(f'DEBUG pre-exists: {path} {mod_time}')
             mtimes_to_paths[str(datetime.fromtimestamp(mod_time))] = path
-            # backup_dates += [str(datetime.fromtimestamp(mod_time))]
 
-        for mtime in sorted(mtimes_to_paths.keys()):
-            print(f'DEBUG mtimes_to_paths: {mtime}:{mtimes_to_paths[mtime]}')
+        for mtime in sorted(mtimes_to_paths.keys()):
+            print(f'DEBUG mtimes_to_paths: {mtime}:{mtimes_to_paths[mtime]}')
 
         # collect what numbered .bak files to save: the older, the fewer; for each
         # timedelta, keep the newest file that's older
         ages_to_keep = [timedelta(minutes=4**i) for i in range(0, 8)]
-        print(f'DEBUG ages_to_keep: {ages_to_keep}')
+        print(f'DEBUG ages_to_keep: {ages_to_keep}')
         now = datetime.now()
         to_save = {}
         for age in ages_to_keep:
             limit = now - age
             for mtime in reversed(sorted(mtimes_to_paths.keys())):
-                print(f'DEBUG checking if {mtime} < {limit} ({now} - {age})')
-                if datetime.strptime(mtime, '%Y-%m-%d %H:%M:%S.%f') < limit:
-                    print('DEBUG it is, adding!')
+                # print(f'DEBUG checking if {mtime} < {limit} ({now} - {age})')
+                if len(mtime) < 20:
+                    mtime_test = mtime + '.000000'
+                else:
+                    mtime_test = mtime
+                if datetime.strptime(mtime_test, '%Y-%m-%d %H:%M:%S.%f') < limit:
+                    # print('DEBUG it is, adding!')
                     to_save[mtime] = mtimes_to_paths[mtime]
                     break
 
         for path in [path for path in mtimes_to_paths.values()
                      if path not in to_save.values()]:
-            print(f'DEBUG removing {path} cause not in to_save')
+            print(f'DEBUG removing {path} cause not in to_save')
             os.remove(path)
 
         i = 0
         for mtime in sorted(to_save.keys()):
             source = to_save[mtime]
             target = f'{bak_prefix}{i}'
-            print(f'DEBUG to_save {source} -> {target}')
+            print(f'DEBUG to_save {source} -> {target}')
             if source != target:
                 shutil.move(source, target)
             i += 1
 
         # put copy of current state at end of bak list
-        print(f'DEBUG saving current state to {bak_prefix}{i}')
+        print(f'DEBUG saving current state to {bak_prefix}{i}')
         shutil.copy(self.db_file, f'{bak_prefix}{i}')
 
     def write_text_to_db(self, text, mode='w'):
diff --git a/todo.py b/todo.py
index 478a41e106e23c525eb7af7d172d4e518165cf63..7b8b83e5ddfe34aa42cb9e6b6e3021b3ceadde71 100644 (file)
--- a/todo.py
+++ b/todo.py
@@ -309,7 +309,7 @@ class TodoDB(PlomDB):
             task_rows.sort(key=lambda r: False if not r['todo'] else True, reverse=True)
         elif task_sort == 'comment':
             task_rows.sort(key=lambda r: '' if not r['todo'] else r['todo'].comment, reverse=True)
-        return j2env.get_template('day.html').render(db=self, action=self.prefix+'/day', prev_date=prev_date_str, next_date=next_date_str, task_rows=task_rows)
+        return j2env.get_template('day.html').render(db=self, action=self.prefix+'/day', prev_date=prev_date_str, next_date=next_date_str, task_rows=task_rows, sort=task_sort)
 
     def show_calendar(self, start_date_str, end_date_str):
         self.t_filter_and = ['calendar']
@@ -333,14 +333,18 @@ class TodoDB(PlomDB):
             days_to_show[current_date].weekday = datetime.strptime(current_date, DATE_FORMAT).strftime('%A')[:2]
         return j2env.get_template('calendar.html').render(db=self, days=days_to_show, action=self.prefix+'/calendar', today=str(datetime.now())[:10], start_date=start_date_str, end_date=end_date_str)
 
-    def show_todo(self, task_uuid, selected_date):
+    def show_todo(self, task_uuid, selected_date, referer):
+        if selected_date not in self.days.keys():
+           self.days[selected_date] = self.add_day() 
         if task_uuid in self.days[selected_date].todos:
             todo = self.days[selected_date].todos[task_uuid]
         else:
             todo = self.days[selected_date].add_todo(task_uuid)
-        return j2env.get_template('todo.html').render(db=self, todo=todo, action=self.prefix+'/todo')
+        return j2env.get_template('todo.html').render(db=self, todo=todo, action=self.prefix+'/todo', referer=referer)
 
     def update_todo_mini(self, task_uuid, date, day_effort, done, importance):
+        if date not in self.days.keys():
+           self.days[date] = self.add_day() 
         if task_uuid in self.days[date].todos.keys():
             todo = self.days[date].todos[task_uuid]
         else:
@@ -363,9 +367,9 @@ class TodoDB(PlomDB):
         todo.comment = comment
         todo.day_tags = self.collect_tags(day_tags_joined, day_tags_checked) 
 
-    def show_task(self, id_):
+    def show_task(self, id_, referer=''):
         task = self.tasks[id_] if id_ else self.add_task()
-        return j2env.get_template('task.html').render(db=self, task=task, action=self.prefix+'/task')
+        return j2env.get_template('task.html').render(db=self, task=task, action=self.prefix+'/task', referer=referer)
 
     def update_task(self, id_, title, default_effort, tags_joined, tags_checked, links):
         task = self.tasks[id_] if id_ in self.tasks.keys() else self.add_task(id_)
@@ -455,16 +459,20 @@ class TodoHandler(PlomHandler):
                              del db.selected_day.todos[uuid]
                      if 'choose' in postvars.keys():
                          for i, uuid in enumerate(postvars['t_uuid']):
-                             uuids = postvars['choose'] + postvars['done'] if 'done' in postvars.keys() else []
-                             if uuid in uuids or postvars['day_effort'][i] != '' or postvars['importance'][i] != '1.0':
+                             uuids = postvars['choose']
+                             uuids += postvars['done'] if 'done' in postvars.keys() else []
+                             if uuid in uuids or postvars['day_effort'][i] != '' or (postvars['importance'][i] not in {'1.0', ''}):
                                  done = 'done' in postvars and uuid in postvars['done']
                                  db.update_todo_mini(uuid, db.selected_date, postvars['day_effort'][i], done, postvars['importance'][i])
                  if 'day_comment' in postvars.keys():
                      db.selected_day.comment = postvars['day_comment'][0]
                  params_to_encode += [('selected_date', db.selected_date)]
 
-        encoded_params = urlencode(params_to_encode)
-        homepage = f'{parsed_url.path}?{encoded_params}'
+        if 'referer' in postvars.keys() and len(postvars['referer'][0]) > 0:
+            homepage = postvars['referer'][0]
+        else:
+            encoded_params = urlencode(params_to_encode)
+            homepage = f'{parsed_url.path}?{encoded_params}'
         db.write()
         self.redirect(homepage)
 
@@ -501,6 +509,7 @@ class TodoHandler(PlomHandler):
 
         selected_date = t_filter_and = t_filter_not = None
         hide_unchosen = hide_done = False
+        referer = params.get('referer', [''])[0]
         if parsed_url.path in {app_config['prefix'] + '/day', app_config['prefix'] + '/tasks'}:
             selected_date = get_param('selected_date')
         if parsed_url.path in {app_config['prefix'] + '/day', app_config['prefix'] + '/tasks', app_config['prefix'] + '/task'}:
@@ -516,10 +525,10 @@ class TodoHandler(PlomHandler):
         elif parsed_url.path == app_config['prefix'] + '/todo':
             todo_date = params.get('date', [None])[0]
             task_uuid = params.get('task', [None])[0]
-            page = db.show_todo(task_uuid, todo_date)
+            page = db.show_todo(task_uuid, todo_date, referer)
         elif parsed_url.path == app_config['prefix'] + '/task':
             id_ = params.get('id', [None])[0]
-            page = db.show_task(id_)
+            page = db.show_task(id_, referer)
         elif parsed_url.path == app_config['prefix'] + '/tasks':
             page = db.show_tasks()
         elif parsed_url.path == app_config['prefix'] + '/add_task':
index 7dd8f66fa99c28e55f8a892cc1a05543e8e02305..4f5f1fcec74db21ce5aab21cf9912e65dfa88c70 100644 (file)
@@ -6,6 +6,7 @@ table.alternating tr:nth-child(even) {
 table.alternating tr:nth-child(odd) {
     background-color: #ffffff;
 }
+th.desc { background: linear-gradient(to bottom, white, grey); }
 td.checkbox, td.number { height: 0.1em; padding: 0em; text-align: center; }
 td.checkbox { width: 0.1em }
 td button { height: 1.5em; padding: 0em; margin: 0em }
@@ -31,17 +32,24 @@ comment: <input name="day_comment" value="{{ db.selected_day.comment|e }}">
 <input type="hidden" name="selected_date" value="{{ db.selected_date }}" />
 </p>
 <table class="alternating">
-<tr><th><a href="?sort=title">task</a></th><th><a href="?sort=chosen">choose?</a></th><th><a href="?sort=done">done?</a></th><th><a href="?sort=default_effort">effort</a></th><th><a href="?sort=importance">importance</a></th><th>edit?</th><th>day tags</th><th><a href="?sort=comment">comment</a></th></tr>
+<tr>
+<th {% if sort=='title' %}class="desc"{% endif %}><a href="?sort=title">task</a></th>
+<th {% if sort=='chosen' %}class="desc"{% endif %}><a href="?sort=chosen">todo</a></th>
+<th {% if sort=='done' %}class="desc"{% endif %}><a href="?sort=done">done</a></th>
+<th {% if sort=='default_effort' %}class="desc"{% endif %}><a href="?sort=default_effort">effort</a></th>
+<th {% if sort=='importance' %}class="desc"{% endif %}><a href="?sort=importance">importance</a></th>
+<th>edit?</th><th>day tags</th>
+<th {% if sort=='comment' %}class="desc"{% endif %}><a href="?sort=comment">comment</a></th></tr>
 {% for row in task_rows %}
 <tr>
 <input name="t_uuid" value="{{ row.uuid }}" type="hidden" >
-<td><details><summary>] <a href="{{db.prefix}}/task?id={{ row.uuid }}" />{{ row.task.current_title|e }}</a></summary>tags: {% for tag in row.task.tags | sort %}<a href="{{db.prefix}}/day?t_and={{tag|e}}">{{ tag }}</a> {% endfor %}</details></td>
+<td><details><summary>] <a href="{{db.prefix}}/task?id={{ row.uuid }}&referer=day" />{{ row.task.current_title|e }}</a></summary>tags: {% for tag in row.task.tags | sort %}<a href="{{db.prefix}}/day?t_and={{tag|e}}">{{ tag }}</a> {% endfor %}</details></td>
 {% if row.todo %}
 <td class="checkbox"><input name="choose" type="checkbox" value="{{ row.uuid }}" checked></td>
 <td class="checkbox"><input name="done" type="checkbox" value="{{ row.uuid }}" {% if row.todo.done %}checked{% endif %}></td>
 <td class="number"><input class="day_effort_input" name="day_effort" type="number" step=0.1 size=8 value="{% if row.todo.day_effort is not none %}{{ row.todo.day_effort }}{% endif %}" placeholder={{ row.task.current_default_effort }} ></td>
 <td class="number"><input name="importance" type="number" step=0.1 size=8 value="{{row.todo.importance}}" ></td>
-<td><a href="{{db.prefix}}/todo?task={{row.uuid}}&date={{db.selected_date}}">edit</a></td>
+<td><a href="{{db.prefix}}/todo?task={{row.uuid}}&date={{db.selected_date}}&referer=day">edit</a></td>
 <td>{% for tag in row.todo.day_tags | sort %}<a href="{{db.prefix}}/tags?t_and={{tag|e}}">{{ tag }}</a> {% endfor %}</td>
 <td>{{ row.todo.comment|e }}</td>
 {% else %}
@@ -49,7 +57,7 @@ comment: <input name="day_comment" value="{{ db.selected_day.comment|e }}">
 <td class="checkbox"><input name="done" type="checkbox" value="{{ row.uuid }}"></td>
 <td class="number"><input class="day_effort_input" name="day_effort" type="number" step=0.1 size=8 placeholder={{ row.task.current_default_effort }} ></td>
 <td class="number"><input name="importance" type="number" step=0.1 size=8 value="1.0" ></td>
-<td><a href="{{db.prefix}}/todo?task={{row.uuid}}&date={{db.selected_date}}">edit</a></td>
+<td><a href="{{db.prefix}}/todo?task={{row.uuid}}&date={{db.selected_date}}&referer=day">edit</a></td>
 <td></td>
 <td></td>
 <td></td>
index ef2402daec9847013e77e71adec306b5f00d5bc8..b0ed3e8b115f9f195ef1eb1fb5fff7e3e8c7f553 100644 (file)
@@ -11,6 +11,7 @@ textarea { width: 100% };
 <form id="form_to_watch" action="{{action|e}}" method="POST">
 <h3>edit task</h3>
 <input type="hidden" name="id" value="{{ task.id_ }}" />
+<input type="hidden" name="referer" value="{{ referer }}" />
 <table>
 <tr><th>title</th><td class="input"><input name="title" type="text" value="{{ task.title|e }}" /><details><summary>history</summary><ul>{% for k,v in task.title_history.items() | sort(attribute='0', reverse=True) %}<li>{{ k }}: {{ v|e }}{% endfor %}</ul></details></td></tr>
 <tr><th>default effort</th><td class="input"><input type="number" name="default_effort" value="{{ task.default_effort }}" step=0.1 size=8 required /><details><summary>history</summary><ul>{% for k,v in task.default_effort_history.items() | sort(attribute='0', reverse=True) %}<li>{{ k }}: {{ v|e }}{% endfor %}</ul></details></td></tr>
index 4bf83e3cc783d7c70e738972206f65b77372704d..af48a478186816d3857538ff19ff3f5a0c674e83 100644 (file)
@@ -12,6 +12,7 @@ textarea { width: 100% };
 <h3>edit todo</h3>
 <input type="hidden" name="task_uuid" value="{{ todo.task.id_ }}" />
 <input type="hidden" name="date" value="{{ todo.day.date }}" />
+<input type="hidden" name="referer" value="{{ referer }}" />
 <table>
 <tr><th>task</th><td><a href="{{db.prefix}}/task?id={{ todo.task.id_ }}">{{ todo.task.title|e }}</a></td></tr>
 <tr><th>default effort</th><td>{{ todo.default_effort }}</td></tr>