From: Christian Heller Date: Fri, 2 Feb 2024 04:28:21 +0000 (+0100) Subject: Improve accounting scripts. X-Git-Url: https://plomlompom.com/repos/condition_descriptions?a=commitdiff_plain;h=38b9ce0ba2b5af072554999bc5836ffe455f06db;p=misc Improve accounting scripts. --- diff --git a/ledger.py b/ledger.py index f29f73e..656a26e 100755 --- a/ledger.py +++ b/ledger.py @@ -29,95 +29,6 @@ class LedgerTextLine: self.comment = split_by_comment[1].lstrip() - -# html_head = """ -# -# -# ledger -# ledger2 -# balance -# balance2 -# add free -# add structured -# add -#
-# """ -# booking_html = """ -#

{{date}} {{desc}} {{head_comment|e}}
-# [edit: structured -# / free -# | copy:structured -# / free -# | move {% if move_up %}up{% else %}up{% endif %}/{% if move_down %}down{% else %}down{% endif %} -# | balance after -# ] -# -# {% for l in booking_lines %} -# {% if l.acc %} -# -# {% else %} -# -# {% endif %} -# {% endfor %} -#
{{l.acc|e}}{{l.money|e}}{{l.comment|e}}
{{l.comment|e}}

-# """ -# add_form_header = """
-# -# -# """ -# add_form_footer = """ -# -# -# -#
-# """ -# add_free_html = """
-# -# """ -# add_structured_html = """ -# -# -# -# -# -# -# -#
-# -# -# -# -#
-# {% for line in booking_lines %} -# -# -# -# -# -# -# -#
-# {% endfor %} -# {% for name, items in datalist_sets.items() %} -# -# {% for item in items %} -# -# {% endfor %} -# -# {% endfor %} -# """ - - class Wealth: def __init__(self): @@ -176,59 +87,6 @@ class Account: return full_moneys -# def apply_booking_to_account_balances(account_sums, account, currency, amount): -# if not account in account_sums: -# account_sums[account] = {currency: amount} -# elif not currency in account_sums[account].keys(): -# account_sums[account][currency] = amount -# else: -# account_sums[account][currency] += amount -# -# -# def bookings_to_account_tree(bookings): -# account_sums = {} -# for booking in bookings: -# for account, changes in booking.account_changes.items(): -# for currency, amount in changes.items(): -# apply_booking_to_account_balances(account_sums, account, currency, amount) -# account_tree = {} -# def collect_branches(account_name, path): -# node = account_tree -# path_copy = path[:] -# while len(path_copy) > 0: -# step = path_copy.pop(0) -# node = node[step] -# toks = account_name.split(":", maxsplit=1) -# parent = toks[0] -# if parent in node.keys(): -# child = node[parent] -# else: -# child = {} -# node[parent] = child -# if len(toks) == 2: -# k, v = collect_branches(toks[1], path + [parent]) -# if k not in child.keys(): -# child[k] = v -# else: -# child[k].update(v) -# return parent, child -# for account_name in sorted(account_sums.keys()): -# k, v = collect_branches(account_name, []) -# if k not in account_tree.keys(): -# account_tree[k] = v -# else: -# account_tree[k].update(v) -# def collect_totals(parent_path, tree_node): -# for k, v in tree_node.items(): -# child_path = parent_path + ":" + k -# for currency, amount in collect_totals(child_path, v).items(): -# apply_booking_to_account_balances(account_sums, parent_path, currency, amount) -# return account_sums[parent_path] -# for account_name in account_tree.keys(): -# account_sums[account_name] = collect_totals(account_name, account_tree[account_name]) -# return account_tree, account_sums - - def parse_lines_to_bookings(lines, ignore_editable_exceptions=False): lines = [LedgerTextLine(line) for line in lines] lines += [LedgerTextLine('')] # to simulate ending of last booking @@ -258,129 +116,6 @@ def parse_lines_to_bookings(lines, ignore_editable_exceptions=False): return bookings -# def parse_lines(lines, validate_bookings=True): -# inside_booking = False -# date_string, description = None, None -# booking_lines = [] -# start_line = 0 -# bookings = [] -# comments = [] -# lines = lines.copy() + [''] # to ensure a booking-ending last line -# last_date = '' -# for i, line in enumerate(lines): -# prefix = f"line {i}" -# # we start with the case of an utterly empty line -# comments += [""] -# stripped_line = line.rstrip() -# if stripped_line == '': -# if inside_booking: -# # assume we finished a booking, finalize, and commit to DB -# if len(booking_lines) < 2: -# raise PlomException(f"{prefix} booking ends to early") -# booking = Booking(date_string, description, booking_lines, start_line, validate_bookings) -# bookings += [booking] -# # expect new booking to follow so re-zeroall booking data -# inside_booking = False -# date_string, description = None, None -# booking_lines = [] -# continue -# # if non-empty line, first get comment if any, and commit to DB -# split_by_comment = stripped_line.split(sep=";", maxsplit=1) -# if len(split_by_comment) == 2: -# comments[i] = split_by_comment[1].lstrip() -# # if pre-comment empty: if inside_booking, this must be a comment-only line so we keep it for later ledger-output to capture those comments; otherwise, no more to process for this line -# non_comment = split_by_comment[0].rstrip() -# if non_comment.rstrip() == '': -# if inside_booking: -# booking_lines += [''] -# continue -# # if we're starting a booking, parse by first-line pattern -# if not inside_booking: -# start_line = i -# toks = non_comment.split(maxsplit=1) -# date_string = toks[0] -# try: -# datetime.strptime(date_string, '%Y-%m-%d') -# except ValueError: -# raise PlomException(f"{prefix} bad date string: {date_string}") -# # if last_date > date_string: -# # raise PlomException(f"{prefix} out-of-order-date") -# last_date = date_string -# try: -# description = toks[1] -# except IndexError: -# raise PlomException(f"{prefix} bad description: {description}") -# inside_booking = True -# booking_lines += [non_comment] -# continue -# # otherwise, read as transfer data -# toks = non_comment.split() # ignore specification's allowance of single spaces in names -# if len(toks) > 3: -# raise PlomException(f"{prefix} too many booking line tokens: {toks}") -# amount, currency = None, None -# account_name = toks[0] -# if account_name[0] == '[' and account_name[-1] == ']': -# # ignore specification's differentiation of "virtual" accounts -# account_name = account_name[1:-1] -# decimal_chars = ".-0123456789" -# if len(toks) == 3: -# i_currency = 1 -# try: -# amount = decimal.Decimal(toks[1]) -# i_currency = 2 -# except decimal.InvalidOperation: -# try: -# amount = decimal.Decimal(toks[2]) -# except decimal.InvalidOperation: -# raise PlomException(f"{prefix} no decimal number in: {toks[1:]}") -# currency = toks[i_currency] -# if currency[0] in decimal_chars: -# raise PlomException(f"{prefix} currency starts with int, dot, or minus: {currency}") -# elif len(toks) == 2: -# value = toks[1] -# inside_amount = False -# inside_currency = False -# amount_string = "" -# currency = "" -# dots_counted = 0 -# for i, c in enumerate(value): -# if i == 0: -# if c in decimal_chars: -# inside_amount = True -# else: -# inside_currency = True -# if inside_currency: -# if c in decimal_chars and len(amount_string) == 0: -# inside_currency = False -# inside_amount = True -# else: -# currency += c -# continue -# if inside_amount: -# if c not in decimal_chars: -# if len(currency) > 0: -# raise PlomException(f"{prefix} amount has non-decimal chars: {value}") -# inside_currency = True -# inside_amount = False -# currency += c -# continue -# if c == '-' and len(amount_string) > 1: -# raise PlomException(f"{prefix} amount has non-start '-': {value}") -# if c == '.': -# if dots_counted > 1: -# raise PlomException(f"{prefix} amount has multiple dots: {value}") -# dots_counted += 1 -# amount_string += c -# if len(currency) == 0: -# raise PlomException(f"{prefix} currency missing: {value}") -# if len(amount_string) > 0: -# amount = decimal.Decimal(amount_string) -# booking_lines += [(account_name, amount, currency)] -# if inside_booking: -# raise PlomException(f"{prefix} last booking unfinished") -# return bookings, comments - - class TransferLine: def __init__(self, line=None, account='', amount=None, currency='', comment='', validate=True): @@ -578,144 +313,89 @@ class Booking: self.clean() self.parse_lines() - def add_taxes(self): + def add_taxes(self, db): acc_kk_add = 'Reserves:KrankenkassenBeitragsWachstum' - acc_kk_minimum = 'Reserves:Month:KrankenkassenDefaultBeitrag' + acc_kk_minimum = 'Reserves:Monthly:KrankenkassenDefaultBeitrag' acc_kk = 'Expenses:KrankenKasse' - acc_est = 'Reserves:EinkommensSteuer' + acc_ESt = 'Reserves:EinkommensSteuer' acc_assets = 'Assets' - acc_buffer = 'Reserves:NeuAnfangsPuffer:Ausgaben' - buffer_expenses = 0 # FIXME: hardcoded for now - kk_expenses = 0 # FIXME: hardcoded for now - est_expenses = 0 # FIXME: hardcoded for now - months_passed = 0 # FIXME: hardcoded for now - last_monthbreak_assets = 0 # FIXME: hardcoded for now - last_monthbreak_est = 0 # FIXME: hardcoded for now - last_monthbreak_kk_minimum = 0 # FIXME: hardcoded for now - last_monthbreak_kk_add = 0 # FIXME: hardcoded for now - expenses_so_far = 0 # FIXME: hardcoded for now - needed_netto = self.account_changes['Assets'].money_dict['€'] + acc_neuanfangspuffer_expenses = 'Reserves:NeuAnfangsPuffer:Ausgaben' + months_passed = datetime.strptime(self.date, '%Y-%m-%d').month - 1 + past_kk_expenses = 0 + past_kk_add = 0 + past_neuanfangspuffer_expenses = 0 + for b in db.bookings: + if b.date == self.date: + break + if acc_neuanfangspuffer_expenses in b.account_changes.keys(): + past_neuanfangspuffer_expenses -= b.account_changes[acc_neuanfangspuffer_expenses].money_dict['€'] + if acc_kk_add in b.account_changes.keys(): + past_kk_add += b.account_changes[acc_kk_add].money_dict['€'] + if acc_kk_minimum in b.account_changes.keys(): + past_kk_expenses += b.account_changes[acc_kk_minimum].money_dict['€'] + + needed_netto = -self.account_changes['Assets'].money_dict['€'] + past_taxed_needs_before_kk = past_neuanfangspuffer_expenses - past_kk_expenses ESt_this_month = 0 - left_over = needed_netto - ESt_this_month + E0 = decimal.Decimal(11604) + E1 = decimal.Decimal(17006) + E2 = decimal.Decimal(66761) + E3 = decimal.Decimal(277826) + taxed_income_before_kk = needed_netto too_low = 0 too_high = 2 * needed_netto - E0 = decimal.Decimal(10908) - E1 = decimal.Decimal(15999) - E2 = decimal.Decimal(62809) - E3 = decimal.Decimal(277825) while True: - zvE = buffer_expenses - kk_expenses + (12 - months_passed) * needed_netto - # if finish: - # zvE += last_monthbreak_assets + last_monthbreak_kk_add + last_monthbreak_kk_minimum + estimate_for_remaining_year = (12 - months_passed) * taxed_income_before_kk + zvE = past_taxed_needs_before_kk + estimate_for_remaining_year if zvE < E0: - ESt = decimal.Decimal(0) + ESt_year = decimal.Decimal(0) elif zvE < E1: y = (zvE - E0)/10000 - ESt = (decimal.Decimal(979.18) * y + 1400) * y + ESt_year = (decimal.Decimal(922.98) * y + 1400) * y elif zvE < E2: y = (zvE - E1)/10000 - ESt = (decimal.Decimal(192.59) * y + 2397) * y + decimal.Decimal(966.53) + ESt_year = (decimal.Decimal(181.19) * y + 2397) * y + decimal.Decimal(1025.38) elif zvE < E3: - ESt = decimal.Decimal(0.42) * (zvE - decimal.Decimal(62809)) + decimal.Decimal(16405.54) + ESt_year = decimal.Decimal(0.42) * zvE - 10602.13 else: - ESt = decimal.Decimal(0.45) * (zvE - decimal.Decimal(277825)) + decimal.Decimal(106713.52) - ESt_this_month = (ESt + last_monthbreak_est - est_expenses) / (12 - months_passed) - left_over = needed_netto - ESt_this_month - if abs(left_over - expenses_so_far) < 0.001: + ESt_year = decimal.Decimal(0.45) * zvE - 18936.88 + ESt_this_month = ESt_year / 12 + taxed_income_minus_ESt = taxed_income_before_kk - ESt_this_month + if abs(taxed_income_minus_ESt - needed_netto) < 0.001: break - elif left_over < expenses_so_far: - too_low = needed_netto - elif left_over > expenses_so_far: - too_high = needed_netto - needed_netto = too_low + (too_high - too_low)/2 + elif taxed_income_minus_ESt < needed_netto: + too_low = taxed_income_before_kk + elif taxed_income_minus_ESt > needed_netto: + too_high = taxed_income_before_kk + taxed_income_before_kk = too_low + (too_high - too_low)/2 ESt_this_month = ESt_this_month.quantize(decimal.Decimal('0.00')) - self.transfer_lines += [TransferLine(None, acc_est, ESt_this_month, '€')] - kk_minimum_income = 1131.67 - kk_factor = decimal.Decimal(0.197) - kk_minimum_tax = decimal.Decimal(222.94).quantize(decimal.Decimal('0.00')) - kk_add_so_far = account_sums[acc_kk_add]['€'] if acc_kk_add in account_sums.keys() else 0 - kk_add = needed_netto / (1 - kk_factor) - needed_netto - kk_minimum_tax - hit_kk_minimum_income_limit = False - if kk_add_so_far + kk_add < 0: - hit_kk_minimum_income_limit = True - kk_add_uncorrect = kk_add - kk_add = -(kk_add + kk_add_so_far) + comment = f'estimated zvE: {past_taxed_needs_before_kk}€ + {estimate_for_remaining_year:.2f}€ = {zvE:.2f}€ → year ESt: {ESt_year:.2f} → needed taxed income before Krankenkasse: {taxed_income_before_kk:.2f}€' + self.transfer_lines += [TransferLine(None, acc_ESt, ESt_this_month, '€', comment)] + + kk_factor = decimal.Decimal(1.197) + kk_minimum_income = 1178.33 + kk_minimum_tax = decimal.Decimal(232.13).quantize(decimal.Decimal('0.00')) + if self.date < '2024-02-01': + kk_minimum_income = 1131.67 + kk_minimum_tax = decimal.Decimal(222.94).quantize(decimal.Decimal('0.00')) + comment = f'assumed minimum income of {kk_minimum_income:.2f}€ * {kk_factor:.3f}' + self.transfer_lines += [TransferLine(None, acc_kk_minimum, kk_minimum_tax, '€', comment)] + kk_add = taxed_income_before_kk * kk_factor - taxed_income_before_kk - kk_minimum_tax kk_add = decimal.Decimal(kk_add).quantize(decimal.Decimal('0.00')) - self.transfer_lines += [TransferLine(None, acc_kk_minimum, kk_minimum_tx, '€')] - self.transfer_lines += [TransferLine(None, acc_kk_add, kk_add, '€')] - diff = - last_monthbreak_est + ESt_this_month - last_monthbreak_kk_add + kk_add - if not finish: - diff += kk_minimum_tax - final_minus = expenses_so_far - old_needed_income_before_anything + diff - diff += kk_minimum_tax - final_minus = expenses_so_far - old_needed_income_before_anything + diff - self.transfer_lines += [TransferLine(None, acc_assets, -diff, '€')] - self.transfer_lines += [TransferLine(None, acc_assets, final_minus, '€')] - self.transfer_lines += [TransferLine(None, acc_buffer, -final_minus, '€')] - - -# class Booking: -# -# def __init__(self, date_string, description, booking_lines, start_line, process=True): -# self.date_string = date_string -# self.description = description -# self.lines = booking_lines -# self.start_line = start_line -# if process: -# self.validate_booking_lines() -# self.sink = {} -# self.account_changes = self.parse_booking_lines_to_account_changes() -# -# def validate_booking_lines(self): -# prefix = f"booking at line {self.start_line}" -# sums = {} -# empty_values = 0 -# for line in self.lines[1:]: -# if line == '': -# continue -# _, amount, currency = line -# if amount is None: -# if empty_values > 0: -# raise PlomException(f"{prefix} relates more than one empty value of same currency {currency}") -# empty_values += 1 -# continue -# if currency not in sums: -# sums[currency] = 0 -# sums[currency] += amount -# if empty_values == 0: -# for k, v in sums.items(): -# if v != 0: -# raise PlomException(f"{prefix} does not add up to zero / {k} {v}") -# else: -# sinkable = False -# for k, v in sums.items(): -# if v != 0: -# sinkable = True -# if not sinkable: -# raise PlomException(f"{prefix} has empty value that cannot be filled") -# -# def parse_booking_lines_to_account_changes(self): -# account_changes = {} -# debt = {} -# sink_account = None -# for line in self.lines[1:]: -# if line == '': -# continue -# account, amount, currency = line -# if amount is None: -# sink_account = account -# continue -# apply_booking_to_account_balances(account_changes, account, currency, amount) -# if currency not in debt: -# debt[currency] = amount -# else: -# debt[currency] += amount -# if sink_account: -# for currency, amount in debt.items(): -# apply_booking_to_account_balances(account_changes, sink_account, currency, -amount) -# self.sink[currency] = -amount -# return account_changes + if past_kk_add + kk_add < 0: # *if* kk_add would actually kill all earlier kk_add … + kk_add = - past_kk_add # … shrink it so it won't push the kk_add total below 0 + comment = f'local negative as large as possible without moving {acc_kk_add} below zero' + else: + comment = f'({taxed_income_before_kk:.2f}€ * {kk_factor:.3f}) - {taxed_income_before_kk:.2f} - {kk_minimum_tax}' + self.transfer_lines += [TransferLine(None, acc_kk_add, kk_add, '€', comment)] + diff_through_taxes_and_kk = ESt_this_month + kk_minimum_tax + kk_add + comment = f'{ESt_this_month} + {kk_minimum_tax} + {kk_add}' + self.transfer_lines += [TransferLine(None, acc_assets, -diff_through_taxes_and_kk, '€', comment)] + final_loss = diff_through_taxes_and_kk + needed_netto + comment = f'{needed_netto} + {diff_through_taxes_and_kk}' + self.transfer_lines += [TransferLine(None, acc_assets, final_loss, '€', comment)] + self.transfer_lines += [TransferLine(None, acc_neuanfangspuffer_expenses, -final_loss, '€')] class LedgerDB(PlomDB): @@ -730,65 +410,6 @@ class LedgerDB(PlomDB): def read_db_file(self, f): self.text_lines += f.readlines() - # self.text_lines += [l.rstrip() for l in f.readlines()] # TODO is this necessary? (parser already removes lines?) - - # def get_lines(self, start, end): - # return self.text_lines[start:end] - - # def write_db(self, text, mode='w'): - # if text[-1] != '\n': - # text += '\n' - # self.write_text_to_db(text) - - # def insert_at_date(self, lines, date): - # start_at = 0 - # if len(self.bookings) > 0: - # if date >= self.bookings[-1].date_string: - # start_at = len(self.text_lines) - # lines = [''] + lines - # else: - # for b in self.bookings: - # if b.date_string == date: - # start_at = b.start_line - # elif b.date_string > date: - # start_at = b.start_line - # break - # lines += [''] # DEBUG is new - # return self.write_lines_in_total_lines_at(self.text_lines, start_at, lines) - - # def update(self, start, end, lines, date): - # remaining_lines = self.text_lines[:start] + self.text_lines[end:] - # n_original_lines = end - start - # start_at = len(remaining_lines) - # for b in self.bookings: - # if b.date_string == date: - # 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 - # # print("DEBUG update start_at", start_at, "len(remaining_lines)", len(remaining_lines), "len(self.text_lines)", len(self.text_lines), "end", end) - # if start_at != 0 and end != len(self.text_lines) and start_at == len(remaining_lines): - # # Add empty predecessor line if appending. - # lines = [''] + 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:] - # _, _ = parse_lines(lines) - # text = '\n'.join(total_lines) - # self.write_db(text) - # return start_at - - # def get_nth_for_booking_of_start_line(self, start_line): - # nth = 0 - # for b in self.bookings: - # if b.start_line >= start_line: - # break - # nth += 1 - # return nth def insert_booking_at_date(self, booking): place_at = 0 @@ -803,175 +424,12 @@ class LedgerDB(PlomDB): place_at = i + 1 self.bookings.insert(place_at, booking) - def add_taxes(self, lines, finish=False): - ret = [] - bookings, _ = parse_lines(lines) - date = bookings[0].date_string - acc_kk_add = 'Reserves:KrankenkassenBeitragsWachstum' - acc_kk_minimum = 'Reserves:Month:KrankenkassenDefaultBeitrag' - acc_kk = 'Expenses:KrankenKasse' - acc_est = 'Reserves:Einkommenssteuer' - acc_assets = 'Assets' - acc_buffer = 'Reserves:NeuAnfangsPuffer:Ausgaben' - last_monthbreak_assets = 0 - last_monthbreak_est = 0 - last_monthbreak_kk_minimum = 0 - last_monthbreak_kk_add = 0 - buffer_expenses = 0 - kk_expenses = 0 - est_expenses = 0 - months_passed = -int(finish) - for b in self.bookings: - if date == b.date_string: - break - acc_keys = b.account_changes.keys() - if acc_buffer in acc_keys: - buffer_expenses -= b.account_changes[acc_buffer]['€'] - if acc_kk_add in acc_keys: - kk_expenses += b.account_changes[acc_kk_add]['€'] - if acc_kk in acc_keys: - kk_expenses += b.account_changes[acc_kk]['€'] - if acc_est in acc_keys: - est_expenses += b.account_changes[acc_est]['€'] - if acc_kk_add in acc_keys and acc_kk_minimum in acc_keys: - months_passed += 1 - if finish: - last_monthbreak_kk_add = b.account_changes[acc_kk_add]['€'] - last_monthbreak_est = b.account_changes[acc_est]['€'] - last_monthbreak_kk_minimum = b.account_changes[acc_kk_minimum]['€'] - last_monthbreak_assets = b.account_changes[acc_buffer]['€'] - old_needed_income_before_anything = - last_monthbreak_assets - last_monthbreak_kk_add - last_monthbreak_kk_minimum - last_monthbreak_est - if finish: - ret += [f' {acc_est} {-last_monthbreak_est}€ ; for old assumption of needed income: {old_needed_income_before_anything}€'] - _, account_sums = bookings_to_account_tree(bookings) - expenses_so_far = -1 * account_sums[acc_assets]['€'] + old_needed_income_before_anything - needed_income_before_kk = expenses_so_far - ESt_this_month = 0 - left_over = needed_income_before_kk - ESt_this_month - too_low = 0 - too_high = 2 * needed_income_before_kk - E0 = decimal.Decimal(10908) - E1 = decimal.Decimal(15999) - E2 = decimal.Decimal(62809) - E3 = decimal.Decimal(277825) - while True: - zvE = buffer_expenses - kk_expenses + (12 - months_passed) * needed_income_before_kk - if finish: - zvE += last_monthbreak_assets + last_monthbreak_kk_add + last_monthbreak_kk_minimum - if zvE < E0: - ESt = decimal.Decimal(0) - elif zvE < E1: - y = (zvE - E0)/10000 - ESt = (decimal.Decimal(979.18) * y + 1400) * y - elif zvE < E2: - y = (zvE - E1)/10000 - ESt = (decimal.Decimal(192.59) * y + 2397) * y + decimal.Decimal(966.53) - elif zvE < E3: - ESt = decimal.Decimal(0.42) * (zvE - decimal.Decimal(62809)) + decimal.Decimal(16405.54) - else: - ESt = decimal.Decimal(0.45) * (zvE - decimal.Decimal(277825)) + decimal.Decimal(106713.52) - ESt_this_month = (ESt + last_monthbreak_est - est_expenses) / (12 - months_passed) - left_over = needed_income_before_kk - ESt_this_month - if abs(left_over - expenses_so_far) < 0.001: - break - elif left_over < expenses_so_far: - too_low = needed_income_before_kk - elif left_over > expenses_so_far: - too_high = needed_income_before_kk - needed_income_before_kk = too_low + (too_high - too_low)/2 - ESt_this_month = ESt_this_month.quantize(decimal.Decimal('0.00')) - ret += [f' {acc_est} {ESt_this_month}€ ; expenses so far: {expenses_so_far:.2f}€; zvE: {zvE:.2f}€; ESt total: {ESt:.2f}€; needed before Krankenkasse: {needed_income_before_kk:.2f}€'] - kk_minimum_income = 1131.67 - if date < '2023-02-01': - kk_minimum_income = decimal.Decimal(1096.67) - kk_factor = decimal.Decimal(0.189) - kk_minimum_tax = decimal.Decimal(207.27).quantize(decimal.Decimal('0.00')) - elif date < '2023-08-01': - kk_factor = decimal.Decimal(0.191) - kk_minimum_tax = decimal.Decimal(216.15).quantize(decimal.Decimal('0.00')) - else: - kk_factor = decimal.Decimal(0.197) - kk_minimum_tax = decimal.Decimal(222.94).quantize(decimal.Decimal('0.00')) - kk_add_so_far = account_sums[acc_kk_add]['€'] if acc_kk_add in account_sums.keys() else 0 - kk_add = needed_income_before_kk / (1 - kk_factor) - needed_income_before_kk - kk_minimum_tax - hit_kk_minimum_income_limit = False - if kk_add_so_far + kk_add < 0: - hit_kk_minimum_income_limit = True - kk_add_uncorrect = kk_add - kk_add = -(kk_add + kk_add_so_far) - kk_add = decimal.Decimal(kk_add).quantize(decimal.Decimal('0.00')) - if finish: - ret += [f' {acc_kk_add} {-last_monthbreak_kk_add}€ ; for old assumption of needed income'] - else: - ret += [f' {acc_kk_minimum} {kk_minimum_tax}€ ; assumed minimum income {kk_minimum_income:.2f}€ * {kk_factor:.3f}'] - if hit_kk_minimum_income_limit: - ret += [f' {acc_kk_add} {kk_add}€ ; {needed_income_before_kk:.2f}€ / (1 - {kk_factor:.3f}) - {needed_income_before_kk:.2f}€ - {kk_minimum_tax}€ = {kk_add_uncorrect:.2f}€ would reduce current {acc_kk_dd} ({kk_add_so_far:.2f}€) below 0'] - else: - ret += [f' {acc_kk_add} {kk_add}€ ; {needed_income_before_kk:.2f}€ / (1 - {kk_factor:.3f}) - {needed_income_before_kk:.2f}€ - {kk_minimum_tax}€'] - diff = - last_monthbreak_est + ESt_this_month - last_monthbreak_kk_add + kk_add - if not finish: - diff += kk_minimum_tax - final_minus = expenses_so_far - old_needed_income_before_anything + diff - ret += [f' {acc_assets} {-diff} €'] - ret += [f' {acc_assets} {final_minus} €'] - year_needed = buffer_expenses + final_minus + (12 - months_passed - 1) * final_minus - if finish: - ret += [f' {acc_buffer} {-final_minus} €'] - else: - ret += [f' {acc_buffer} {-final_minus} € ; assume as to earn in year: {acc_buffer} + {12 - months_passed - 1} * this = {year_needed}'] - return ret - - # def add_mirror(self, lines): - # ret = [] - # bookings, _ = parse_lines(lines) - # booking = bookings[0] - # for line in booking.lines[1:]: - # ret += [f'? {-line[1]} {line[2]}'] - # return ret - def ledger_as_html(self): for index, booking in enumerate(self.bookings): booking.can_up = index > 0 and self.bookings[index - 1].date == booking.date booking.can_down = index < len(self.bookings) - 1 and self.bookings[index + 1].date == booking.date return j2env.get_template('ledger.html').render(bookings=self.bookings) - # def ledger_as_html(self): - # booking_tmpl = jinja2.Template(booking_html) - # single_c_tmpl = jinja2.Template('{{c|e}}
') ## - # elements_to_write = [] - # last_i = i = 0 ## - # for nth, booking in enumerate(self.bookings): - # move_up = nth > 0 and self.bookings[nth - 1].date_string == booking.date_string - # move_down = nth < len(self.bookings) - 1 and self.bookings[nth + 1].date_string == booking.date_string - # booking_end = last_i = booking.start_line + len(booking.lines) - # booking_lines = [] - # i = booking.start_line ## - # elements_to_write += [single_c_tmpl.render(c=c) for c in self.comments[last_i:i] if c != ''] ## - # for booking_line in booking.lines[1:]: - # i += 1 ## - # comment = self.comments[i] ## - # if booking_line == '': - # booking_lines += [{'acc': None, 'money': None, 'comment': comment}] ## - # continue - # account = booking_line[0] - # money = '' - # if booking_line[1] is not None: - # money = f'{booking_line[1]} {booking_line[2]}' - # booking_lines += [{'acc': booking_line[0], 'money':money, 'comment':comment}] ## - # elements_to_write += [booking_tmpl.render( - # prefix=self.prefix, - # nth=nth, - # start=booking.start_line, - # end=booking_end, - # date=booking.date_string, - # desc=booking.description, - # head_comment=self.comments[booking.start_line], - # move_up=move_up, - # move_down=move_down, - # booking_lines = booking_lines)] - # elements_to_write += [single_c_tmpl.render(c=c) for c in self.comments[last_i:] if c != ''] # - # return '\n'.join(elements_to_write) - def balance_as_html(self, until_after=None): bookings = self.bookings[:(until_after if until_after is None else int(until_after)+1)] account_trunk = Account('', None) @@ -1001,31 +459,6 @@ class LedgerDB(PlomDB): walk_tree(nodes, 0, acc) return j2env.get_template('balance.html').render(nodes=nodes) - # def balance_as_html(self, until=None): - # bookings = self.bookings[:until if until is None else int(until)] - # lines = [] - # account_tree, account_sums = bookings_to_account_tree(bookings) - # def print_subtree(lines, indent, node, subtree, path): - # line = f"{indent}{node}" - # n_tabs = 5 - (len(line) // 8) - # line += n_tabs * "\t" - # if "€" in account_sums[path + node].keys(): - # amount = account_sums[path + node]["€"] - # line += f"{amount:9.2f} €\t" - # else: - # line += f"\t\t" - # for currency, amount in account_sums[path + node].items(): - # if currency != '€' and amount != 0: - # line += f"{amount:5.2f} {currency}\t" - # lines += [line] - # indent += " " - # for k, v in sorted(subtree.items()): - # print_subtree(lines, indent, k, v, path + node + ":") - # for k, v in sorted(account_tree.items()): - # print_subtree(lines, "", k, v, "") - # content = "\n".join(lines) - # return f"
{content}
" - def edit(self, index, sent=None, error_msg=None, edit_mode='table', copy=False): accounts = set() if sent or -1 == index: @@ -1045,70 +478,6 @@ class LedgerDB(PlomDB): accounts.add(transfer_line.account) return j2env.get_template('edit.html').render(content=content, index=index, error_msg=error_msg, edit_mode=edit_mode, accounts=accounts, adding=(copy or -1 == index)) - # def add_free(self, start=0, end=0, copy=False): - # tmpl = jinja2.Template(add_form_header + add_free_html + add_form_footer) - # lines = self.get_lines(start, end) - # if copy: - # start = end = 0 - # return tmpl.render(action=self.prefix + '/add_free', start=start, end=end, lines=lines) - - # def add_structured(self, start=0, end=0, copy=False, temp_lines=[], add_empty_line=None): - # tmpl = jinja2.Template(add_form_header + add_structured_html + add_form_footer) - # lines = temp_lines if len(''.join(temp_lines)) > 0 else self.get_lines(start, end) - # bookings, comments = parse_lines(lines, validate_bookings=False) - # if len(bookings) > 1: - # raise PlomException('can only structurally edit single Booking') - # if add_empty_line is not None: - # comments = comments[:add_empty_line+1] + [''] + comments[add_empty_line+1:] - # booking = bookings[0] - # booking.lines = booking.lines[:add_empty_line+1] + [''] + booking.lines[add_empty_line+1:] - # action = self.prefix + '/add_structured' - # datalist_sets = {'descriptions': set(), 'accounts': set(), 'currencies': set()} - # for b in self.bookings: - # datalist_sets['descriptions'].add(b.description) - # for account, moneys in b.account_changes.items(): - # datalist_sets['accounts'].add(account) - # for currency in moneys.keys(): - # datalist_sets['currencies'].add(currency) - # content = '' - # today = str(datetime.now())[:10] - # booking_lines = [] - # if copy: - # start = end = 0 - # desc = head_comment = '' - # if len(bookings) == 0: - # date=today - # else: - # booking = bookings[0] - # desc = booking.description - # date = today if copy else booking.date_string - # head_comment=comments[0] - # for i in range(1, len(comments)): - # account = amount = currency = '' - # if i < len(booking.lines) and booking.lines[i] != '': - # account = booking.lines[i][0] - # amount = booking.lines[i][1] - # currency = booking.lines[i][2] - # booking_lines += [{ - # 'i': i, - # 'acc': account, - # 'amt': amount, - # 'curr': currency if currency else '€', - # 'comment': comments[i], - # 'comm_cols': len(comments[i])}] - # for i in range(len(comments), len(comments) + 8): - # booking_lines += [{'i': i, 'acc': '', 'amt': '', 'curr': '€', 'comment': ''}] - # content += tmpl.render( - # action=action, - # date=date, - # desc=desc, - # head_comment=head_comment, - # booking_lines=booking_lines, - # datalist_sets=datalist_sets, - # start=start, - # end=end) - # return content - def move_up(self, index): return self.move(index, -1) @@ -1131,97 +500,6 @@ class LedgerDB(PlomDB): lines += booking.for_writing self.write_text_to_db('\n'.join(lines) + '\n') - # def move_up(self, start, end): - # prev_booking = None - # for redir_nth, b in enumerate(self.bookings): - # if b.start_line >= start: - # break - # prev_booking = b - # start_at = prev_booking.start_line - # self.make_move(start, end, start_at) - # return redir_nth - 1 - - # def move_down(self, start, end): - # next_booking = None - # for redir_nth, b in enumerate(self.bookings): - # 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) - # 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) - # if start == 0: - # total_lines = self.text_lines[end+1:] - # lines = [''] + lines - # start_at += 1 - # else: - # total_lines = self.text_lines[:start-1] + self.text_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): - # add_empty_line = None - # date = postvars['date'][0] - # description = postvars['description'][0] - # start_comment = postvars['line_0_comment'][0] - # start_line = f'{date} {description}' - # if start_comment.rstrip() != '': - # start_line += f' ; {start_comment}' - # lines = [start_line] - # if 'line_0_add' in postvars.keys(): - # add_empty_line = 0 - # i = j = 1 - # while f'line_{i}_comment' in postvars.keys(): - # if f'line_{i}_delete' in postvars.keys(): - # i += 1 - # continue - # elif f'line_{i}_delete_after' in postvars.keys(): - # break - # elif f'line_{i}_add' in postvars.keys(): - # add_empty_line = j - # account = postvars[f'line_{i}_account'][0] - # amount = postvars[f'line_{i}_amount'][0] - # currency = postvars[f'line_{i}_currency'][0] - # comment = postvars[f'line_{i}_comment'][0] - # i += 1 - # new_main = f' {account} {amount}' - # if '' == new_main.rstrip() == comment.rstrip(): # don't write empty lines, ignore currency if nothing else set - # continue - # if len(amount.rstrip()) > 0: - # new_main += f' {currency}' - # j += 1 - # new_line = new_main - # if comment.rstrip() != '': - # new_line += f' ; {comment}' - # lines += [new_line] - # if 'add_sink' in postvars.keys(): - # temp_lines = lines.copy() + ['_'] - # try: - # 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} {currency}'] - # except PlomException: - # pass - # elif 'add_taxes' in postvars.keys(): - # lines += self.add_taxes(lines, finish=False) - # elif 'add_taxes2' in postvars.keys(): - # lines += self.add_taxes(lines, finish=True) - # elif 'replace' in postvars.keys(): - # for i, line in enumerate(lines): - # lines[i] = line.replace(postvars['replace_from'][0], postvars['replace_to'][0]) - # elif 'add_mirror' in postvars.keys(): - # lines += self.add_mirror(lines) - # return lines, add_empty_line - - class LedgerHandler(PlomHandler): @@ -1275,7 +553,10 @@ class LedgerHandler(PlomHandler): if 'check' == submit_button: error_msg = 'All looks fine!' elif submit_button in {'mirror', 'fill_sink', 'add_taxes'}: - getattr(booking, submit_button)() + if 'add_taxes' == submit_button: + booking.add_taxes(db) + else: + getattr(booking, submit_button)() elif 'replace' == submit_button: booking.replace(postvars['replace_from'][0], postvars['replace_to'][0]) elif submit_button in {'textarea', 'table'}: @@ -1287,57 +568,6 @@ class LedgerHandler(PlomHandler): index = index if index >= 0 else len(db.bookings) - 1 self.redirect(prefix + f'/ledger#{index}') - # def forward_posts(self): - # prefix = self.apps['ledger'] if hasattr(self, 'apps') else '' - # parsed_url = urlparse(self.path) - # length = int(self.headers['content-length']) - # 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 = [] - # # get inputs - # if prefix + '/add_structured' == parsed_url.path and not 'revert' in postvars.keys(): - # lines, add_empty_line = db.booking_lines_from_postvars(postvars) - # elif prefix + '/add_free' == parsed_url.path and not 'revert' in postvars.keys(): - # lines = postvars['booking'][0].splitlines() - # # validate where appropriate - # if ('save' in postvars.keys()) or ('check' in postvars.keys()): - # _, _ = parse_lines(lines) - # # if saving, process where to and where to redirect after - # if 'save' in postvars.keys(): - # last_date = str(datetime.now())[:10] - # if len(db.bookings) > 0: - # last_date = db.bookings[-1].date_string - # target_date = last_date[:] - # first_line_tokens = lines[0].split() if len(lines) > 0 else '' - # first_token = first_line_tokens[0] if len(first_line_tokens) > 0 else '' - # try: - # datetime.strptime(first_token, '%Y-%m-%d') - # target_date = first_token - # except ValueError: - # pass - # if start == end == 0: - # start = db.insert_at_date(lines, target_date) - # 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 - # self.redirect(prefix + f'/#{nth}') - # # otherwise just re-build editing form - # else: - # if prefix + '/add_structured' == parsed_url.path: - # edit_content = db.add_structured(start, end, temp_lines=lines, add_empty_line=add_empty_line) - # else: - # edit_content = db.add_free(start, end) - # header = jinja2.Template(html_head).render(prefix=prefix) - # self.send_HTML(header + edit_content) - def do_GET(self): self.try_do(self.forward_gets) diff --git a/plomlib.py b/plomlib.py index 1d83295..2d92977 100644 --- a/plomlib.py +++ b/plomlib.py @@ -98,7 +98,7 @@ class PlomHandler(BaseHTTPRequestHandler): html_foot = '\n' def fail_400(self, e): - self.send_HTML(f'ERROR: {e}', 400) + self.send_HTML(f'ERROR BAR: {e}', 400) def send_HTML(self, html, code=200): self.send_code_and_headers(code, [('Content-type', 'text/html')]) diff --git a/todo.py b/todo.py index 3ea09b0..11b0149 100644 --- a/todo.py +++ b/todo.py @@ -87,6 +87,7 @@ class Task: self.comment = comment self.visible = True + self.parents = [] @classmethod def from_dict(cls, db, d, id_): @@ -113,6 +114,8 @@ class Task: def subtasks(self): subtasks = [] for id_ in self.subtask_ids.now: + if len(id_) == 0: + continue subtasks += [self.db.tasks[id_]] return subtasks @@ -159,22 +162,16 @@ class Day: self.date = date self.db = db self.comment = comment - self.archived = True self.linked_todos_as_list = [] - self.todos = {} @classmethod def from_dict(cls, db, d, date=None): comment = d['comment'] if 'comment' in d.keys() else '' day = cls(db, date, comment) - for id_ in d['linked_todos']: - day.linked_todos_as_list += [db.todos[id_]] return day def to_dict(self): - d = {'comment': self.comment, 'linked_todos': []} - for todo_id in self.linked_todos.keys(): - d['linked_todos'] += [todo_id] + d = {'comment': self.comment} return d @property @@ -256,6 +253,10 @@ class Todo: def title(self): return self.task.title.at(self.earliest_date) + @property + def dated_title(self): + return f'{self.earliest_date}:{self.title}' + @property def children(self): return [self.db.todos[id_] for id_ in self.child_ids] @@ -294,9 +295,9 @@ class Todo: if not date in efforts.keys(): efforts[date] = None if effort is not None: - to_add = effort + to_add = effort / len(child.parents) elif child.done: - to_add = child.task.default_effort.at(date) + to_add = child.task.default_effort.at(date) / len(child.parents) if to_add is not None: if efforts[date] is not None: efforts[date] += to_add @@ -334,13 +335,6 @@ class Todo: return False return True - @property - def path(self): - path = '' - if self.parent: - path = f'{self.parent.path}{self.parent.title}:' - return path - @property def tags(self): return self.day_tags | self.task.tags.now @@ -371,16 +365,14 @@ class Todo: class TodoDB(PlomDB): def __init__(self, - prefix, selected_date=None, - t_filter_and = None, - t_filter_not = None, + tag_filter_and = None, + tag_filter_not = None, hide_unchosen=False, hide_done=False): - self.prefix = prefix self.selected_date = selected_date if selected_date else today_date() - self.t_filter_and = t_filter_and if t_filter_and else [] - self.t_filter_not = t_filter_not if t_filter_not else [] + self.tag_filter_and = tag_filter_and if tag_filter_and else [] + self.tag_filter_not = tag_filter_not if tag_filter_not else [] self.hide_unchosen = hide_unchosen self.hide_done = hide_done @@ -394,25 +386,33 @@ class TodoDB(PlomDB): def read_db_file(self, f): d = json.load(f) + for id_, t_dict in d['tasks'].items(): t = self.add_task(id_=id_, dict_source=t_dict) for tag in t.tags.now: self.t_tags.add(tag) + for task in self.tasks.values(): + for child in task.subtasks: + child.parents += [task] + for id_, todo_dict in d['todos'].items(): - todo = self.add_todo(todo_dict, id_) - self.todos[id_] = todo + todo = self.add_todo(todo_dict, id_, link_into_day=False) for tag in todo.day_tags: self.t_tags.add(tag) - for date, day_dict in d['days'].items(): - self.add_day(dict_source=day_dict, date=date) for todo in self.todos.values(): for child in todo.children: child.parents += [todo] + + for date, day_dict in d['days'].items(): + self.add_day(dict_source=day_dict, date=date) + for todo in self.todos.values(): for date in todo.efforts.keys(): if not date in self.days.keys(): self.add_day(date) - if not todo in self.days[date].linked_todos_as_list: - self.days[date].linked_todos_as_list += [todo] + # if not todo in self.days[date].linked_todos_as_list: + # self.days[date].linked_todos_as_list += [todo] + self.days[date].linked_todos_as_list += [todo] + self.set_visibilities() def to_dict(self): @@ -421,8 +421,6 @@ class TodoDB(PlomDB): d['tasks'][uuid] = t.to_dict() for date, day in self.days.items(): d['days'][date] = day.to_dict() - for todo in day.todos.values(): - d['todos'][todo.id_] = todo.to_dict() for id_, todo in self.todos.items(): d['todos'][id_] = todo.to_dict() return d @@ -464,8 +462,9 @@ class TodoDB(PlomDB): task.tags.set(tags) task.subtask_ids.set(subtask_ids) task.comment = comment + return task - def add_todo(self, todo_dict=None, id_=None, task=None, efforts=None): + def add_todo(self, todo_dict=None, id_=None, task=None, efforts=None, link_into_day=True): id_ = id_ if id_ else str(uuid4()) if todo_dict: todo = Todo.from_dict(self, todo_dict, id_) @@ -473,9 +472,22 @@ class TodoDB(PlomDB): todo = Todo(self, id_, task, efforts=efforts) children = [] for child_task in task.subtasks: - children += [self.add_todo(task=child_task, efforts=efforts)] + # if Todo expects chuldren, adopt any compatibles found in DB.selected_date + # before creating new child Todos + child_todo = None + if list(efforts.keys())[0] == self.selected_date: + adoptable_todos = [t for t in self.selected_day.linked_todos_as_list + if t.task.id_ == child_task.id_] + if len(adoptable_todos) > 0: + child_todo = adoptable_todos[0] + if not child_todo: + child_todo = self.add_todo(task=child_task, + efforts=efforts, link_into_day=link_into_day) + children += [child_todo] todo.child_ids = [child.id_ for child in children] self.todos[id_] = todo + if link_into_day: + self.selected_day.linked_todos_as_list += [todo] return todo def _update_todo_shared(self, id_, done, comment, importance): @@ -495,7 +507,7 @@ class TodoDB(PlomDB): raise PlomException('todo must have at least one effort!') todo.children = children todo.efforts = efforts - for date in todo.efforts: + for date in todo.efforts.keys(): if not date in self.days.keys(): self.add_day(date=date) if not self in self.days[date].linked_todos_as_list: @@ -513,6 +525,9 @@ class TodoDB(PlomDB): parent.child_ids.remove(todo.id_) del self.todos[id_] + def delete_task(self, id_): + del self.tasks[id_] + def delete_effort(self, todo, date, force=False): if (not force) and len(todo.efforts) == 1: raise PlomException('todo must retain at least one effort!') @@ -521,9 +536,12 @@ class TodoDB(PlomDB): # views + def show_message(self, message): + return j2env.get_template('message.html').render(message=message) + def show_calendar(self, start_date_str, end_date_str): - self.t_filter_and = ['calendar'] - self.t_filter_not = ['deleted'] + self.tag_filter_and = ['calendar'] + self.tag_filter_not = ['deleted'] self.set_visibilities() start_date_obj, end_date_obj = self.get_limit_days_from_date_strings(start_date_str, end_date_str) days_to_show = {} @@ -559,16 +577,22 @@ class TodoDB(PlomDB): return j2env.get_template('do_todos.html').render( day=self.selected_day, tags=self.t_tags, - filter_and=self.t_filter_and, - filter_not=self.t_filter_not, + filter_and=self.tag_filter_and, + filter_not=self.tag_filter_not, prev_date=prev_date_str, next_date=next_date_str, todos=todos, sort=sort_order, hide_done=self.hide_done) - def show_todo(self, id_, return_to, search, start_date, end_date): + def show_todo(self, id_, search, start_date, end_date): todo = self.todos[id_] + filtered_tasks = [t for t in self.tasks.values() + if t != todo.task] + filtered_todos = [t for t in self.todos.values() + if t != todo] + + # if going into the past, only allow 3-day ranges start_day, end_day = self.get_limit_days_from_date_strings(start_date, end_date) if start_day < datetime.now() - timedelta(days=2): end_day = start_day + timedelta(days=3) @@ -577,10 +601,6 @@ class TodoDB(PlomDB): for n in range(int((end_day - start_day).days) + 1): day = start_day + timedelta(n) legal_dates += [day.strftime(DATE_FORMAT)] - filtered_todos = [t for t in self.todos.values() - if t.visible - and t != todo - and (len(search) == 0 or t.matches(search))] date_filtered_todos = [] for date in legal_dates: for filtered_todo in filtered_todos: @@ -588,33 +608,28 @@ class TodoDB(PlomDB): continue if date in filtered_todo.efforts.keys(): date_filtered_todos += [filtered_todo] + return j2env.get_template('todo.html').render( - filter_and=self.t_filter_and, - filter_not=self.t_filter_not, tags=self.t_tags, todo=todo, filtered_todos=date_filtered_todos, + filtered_tasks=filtered_tasks, child_todos=todo.children, - return_to=return_to, start_date=start_date, end_date=end_date) - def show_task(self, id_, return_to='', search=''): + def show_task(self, id_): task = self.tasks[id_] if id_ else self.add_task() - selected = task.id_ in self.selected_day.todos.keys() + if not id_: + task.default_effort.set(1.0) filtered_tasks = [t for t in self.tasks.values() - if t.visible - and t != task - and (t not in task.subtasks) - and (len(search) == 0 or t.matches(search))] + if t != task + and (t not in task.subtasks)] return j2env.get_template('task.html').render( - search=search, + selected_date=self.selected_date, tags=self.t_tags, - filter_and=self.t_filter_and, - filter_not=self.t_filter_not, filtered_tasks=filtered_tasks, - task=task, - return_to=return_to) + task=task) def show_tasks(self, expand_uuid): expanded_tasks = {} @@ -623,8 +638,8 @@ class TodoDB(PlomDB): expanded_tasks[uuid] = self.tasks[uuid] return j2env.get_template('tasks.html').render( db=self, - filter_and=self.t_filter_and, - filter_not=self.t_filter_not, + filter_and=self.tag_filter_and, + filter_not=self.tag_filter_not, expand_uuid=expand_uuid, expanded_tasks=expanded_tasks) @@ -642,36 +657,37 @@ class TodoDB(PlomDB): continue relevant_todos += [todo] - tasks = [] + filtered_tasks = [] chosen_tasks = [todo.task for todo in self.selected_day.linked_todos_as_list] for uuid, task in self.tasks.items(): if (not task.visible)\ or (not task.matches(search))\ or (hide_chosen_tasks and task in chosen_tasks): continue - tasks += [task] + filtered_tasks += [task] if sort_order == 'title': chosen_todos.sort(key=lambda t: t.title) - relevant_todos.sort(key=lambda t: t.title) + relevant_todos.sort(key=lambda t: t.dated_title) tasks.sort(key=lambda t: t.title.then) elif sort_order == 'effort': chosen_todos.sort(key=lambda t: t.day_effort if t.day_effort else (t.default_effort if t.done else 0), reverse=True) relevant_todos.sort(key=lambda t: t.all_days_effort, reverse=True) - tasks.sort(key=lambda t: t.default_effort.then, reverse=True) + filtered_tasks.sort(key=lambda t: t.default_effort.then, reverse=True) return j2env.get_template('pick_tasks.html').render( sort=sort_order, tags=self.t_tags, chosen_todos=chosen_todos, - filter_and=self.t_filter_and, - filter_not=self.t_filter_not, + filter_and=self.tag_filter_and, + filter_not=self.tag_filter_not, day=self.selected_day, prev_date=prev_date_str, next_date=next_date_str, - tasks=tasks, + all_tasks=[t for t in self.tasks.values()], + filtered_tasks=filtered_tasks, hide_chosen_tasks=hide_chosen_tasks, relevant_todos=relevant_todos, search=search) @@ -680,12 +696,12 @@ class TodoDB(PlomDB): def set_visibilities(self): for uuid, t in self.tasks.items(): - t.visible = len([tag for tag in self.t_filter_and if not tag in t.tags.now]) == 0\ - and len([tag for tag in self.t_filter_not if tag in t.tags.now]) == 0\ + t.visible = len([tag for tag in self.tag_filter_and if not tag in t.tags.now]) == 0\ + and len([tag for tag in self.tag_filter_not if tag in t.tags.now]) == 0\ and ((not self.hide_unchosen) or uuid in self.selected_day.todos.keys()) for todo in self.todos.values(): - todo.visible = len([tag for tag in self.t_filter_and if not tag in todo.day_tags | todo.task.tags.now ]) == 0\ - and len([tag for tag in self.t_filter_not if tag in todo.day_tags | todo.task.tags.now ]) == 0\ + todo.visible = len([tag for tag in self.tag_filter_and if not tag in todo.day_tags | todo.task.tags.now ]) == 0\ + and len([tag for tag in self.tag_filter_not if tag in todo.day_tags | todo.task.tags.now ]) == 0\ and ((not self.hide_done) or (not todo.done)) def get_limit_days_from_date_strings(self, start_date_str, end_date_str): @@ -726,28 +742,33 @@ class ParamsParser: param = param != '0' return param - def get_cookied(self, key, default=None): + def cookie_key_from_params_key(self, prefix, key): + return f'{prefix}:{key}' if prefix else key + + def get_cookied(self, key, default=None, prefix=None): + cookie_key = self.cookie_key_from_params_key(prefix, key) param = self.get(key, default) if param == '-': param = None - if key in self.cookie_db.keys(): - del self.cookie_db[key] - if param is None and key in self.cookie_db.keys(): - param = self.cookie_db[key] + if cookie_key in self.cookie_db.keys(): + del self.cookie_db[cookie_key] + if param is None and cookie_key in self.cookie_db.keys(): + param = self.cookie_db[cookie_key] if param is not None: - self.cookie_db[key] = param + self.cookie_db[cookie_key] = param return param - def get_cookied_chain(self, key, default=None): + def get_cookied_chain(self, key, default=None, prefix=None): + cookie_key = self.cookie_key_from_params_key(prefix, key) params = self.params.get(key, default) if params == ['-']: params = None - if key in self.cookie_db.keys(): - del self.cookie_db[key] - if params is None and key in self.cookie_db.keys(): - params = self.cookie_db[key] + if cookie_key in self.cookie_db.keys(): + del self.cookie_db[cookie_key] + if params is None and cookie_key in self.cookie_db.keys(): + params = self.cookie_db[cookie_key] if params is not None: - self.cookie_db[key] = params + self.cookie_db[cookie_key] = params return params @@ -759,39 +780,24 @@ class PostvarsParser: def has(self, key): return key in self.postvars.keys() - def get(self, key, on_empty=None): - return self.get_at_index(key, 0, on_empty) + def get(self, key, on_empty=None, float_if_possible=False): + return self.get_at_index(key, 0, on_empty, float_if_possible) - def get_at_index(self, key, i, on_empty=None): + def get_at_index(self, key, i, on_empty=None, float_if_possible=False): if self.has(key) and len(self.postvars[key][i]) > 0: - return self.postvars[key][i] - return on_empty - - def get_float_if_possible(self, key): - s = self.get(key) - if s is not None: - return float(s) + val = self.postvars[key][i] else: - return None + val = on_empty + if float_if_possible and val is not None: + return float(val) + else: + return val def get_all(self, key, on_empty=None): if self.has(key) and len(self.postvars[key]) > 0: - return self.postvars[key] + return [v for v in self.postvars[key] if len(v) > 0] return on_empty - def get_defined_tags(self, joined_key, key_prefix): - tags = set() - tags_checked = [] - for k in self.postvars.keys(): - if k.startswith(key_prefix): - tags_checked += [k[len(key_prefix):]] - tags_joined = self.get(joined_key, '') - for tag in [tag.strip() for tag in tags_joined.split(';') if tag.strip() != '']: - tags.add(tag) - for tag in tags_checked: - tags.add(tag) - return tags - def set(self, key, value): self.postvars[key] = [value] @@ -801,7 +807,6 @@ class TodoHandler(PlomHandler): def config_init(self): return { 'cookie_name': 'todo_cookie', - 'prefix': '', 'cookie_path': '/' } @@ -809,7 +814,7 @@ class TodoHandler(PlomHandler): default_path = '/todo' handler.add_route('GET', default_path, self.show_db) handler.add_route('POST', default_path, self.write_db) - return 'todo', {'cookie_name': 'todo_cookie', 'prefix': default_path, 'cookie_path': default_path} + return 'todo', {'cookie_name': 'todo_cookie', 'cookie_path': default_path} def do_POST(self): self.try_do(self.write_db) @@ -822,10 +827,10 @@ class TodoHandler(PlomHandler): length = int(self.headers['content-length']) postvars = PostvarsParser(parse_qs(self.rfile.read(length).decode(), keep_blank_values=1)) - db = TodoDB(prefix=config['prefix']) + db = TodoDB() redir_params = [] # if we do encounter a filter post, we repost it (and if empty, the emptying command '-') - for param_name, filter_db_name in {('t_and', 't_filter_and'), ('t_not', 't_filter_not')}: + for param_name, filter_db_name in {('and_tag', 'tag_filter_and'), ('not_tag', 'tag_filter_not')}: filter_db = getattr(db, filter_db_name) if postvars.has(param_name): for target in postvars.get_all(param_name, []): @@ -837,7 +842,7 @@ class TodoHandler(PlomHandler): if site in {'calendar', 'todo'}: redir_params += [('end', postvars.get('end', '-'))] redir_params += [('start', postvars.get('start', '-'))] - if site in {'todo', 'task', 'pick_tasks'}: + if site in {'todo', 'pick_tasks'}: redir_params += [('search', postvars.get('search', ''))] redir_params += [('search', postvars.get('search', ''))] redir_params += [('search', postvars.get('search', ''))] @@ -849,6 +854,7 @@ class TodoHandler(PlomHandler): redir_params += [('id', todo_id)] old_todo = db.todos[todo_id] if todo_id in db.todos.keys() else None efforts = {} + latest_date = db.selected_date for i, date in enumerate(postvars.get_all('effort_date', [])): if '' == date: continue @@ -856,43 +862,62 @@ class TodoHandler(PlomHandler): datetime.strptime(date, DATE_FORMAT) except ValueError: raise PlomException(f'bad date string') + latest_date = date efforts[date] = None if not (old_todo and old_todo.children): - efforts[date] = postvars.get_at_index('effort', i, None) + efforts[date] = postvars.get_at_index('effort', i, on_empty=None, + float_if_possible=True) if postvars.has('delete'): has_day_effort = len([e for e in efforts.values() if e is not None]) > 0 if postvars.has('done')\ or postvars.get('comment')\ - or len(postvars.get_defined_tags('joined_day_tags', 'day_tag_')) > 0\ + or postvars.get_all('tag', [])\ or has_day_effort: raise PlomException('will not remove todo of preserve-worthy values') db.delete_todo(todo_id) - if not postvars.get('return_to'): - postvars.set('return_to', 'calendar') + postvars.set('return_to', 'calendar') elif postvars.has('update'): if postvars.has('delete_effort'): for date in postvars.get_all('delete_effort'): db.delete_effort(old_todo, date) del efforts[date] - children = [db.todos[id_] for id_ in postvars.get_all('adopt_child', [])] + children = [db.todos[id_] for id_ in postvars.get_all('adopt_child', []) + if id_ in db.todos.keys()] + for child in children: + if not todo_id in [t.id_ for t in child.parents]: + child.parents += [db.todos[todo_id]] + tasks_to_birth = [db.tasks[id_] for id_ in postvars.get_all('birth_child', [])] + for task in tasks_to_birth: + children += [db.add_todo(task=task, efforts={latest_date: None})] db.update_todo(id_=todo_id, efforts=efforts, done=postvars.has('done'), comment=postvars.get('comment', ''), - tags=postvars.get_defined_tags('joined_day_tags', 'day_tag_'), + tags=postvars.get_all('tag', []), importance=float(postvars.get('importance')), children=children) elif 'task' == site: task_id = postvars.get('task_id') - if postvars.has('update'): - db.update_task( + if postvars.has('delete') and task_id in db.tasks.keys(): + if [t for t in db.todos.values() if task_id == t.task.id_]: + raise PlomException('will not remove Task describing existing Todos') + if postvars.get('title', '')\ + or postvars.get_all('tag', [])\ + or postvars.get_all('subtask', [])\ + or postvars.get('comment', ''): + raise PlomException('will not remove Task of preserve-worthy values') + db.delete_task(task_id) + elif postvars.has('update'): + task = db.update_task( id_=task_id, title=postvars.get('title', ''), - default_effort=postvars.get_float_if_possible('default_effort'), - tags=postvars.get_defined_tags('joined_tags', 'tag_'), + default_effort=postvars.get('default_effort', float_if_possible=True), + tags=postvars.get_all('tag', []), subtask_ids=postvars.get_all('subtask', []), comment=postvars.get('comment', '')) + if postvars.has('add_as_todo'): + db.add_todo(task=task, efforts={postvars.get('selected_date'): None}) redir_params += [('id', task_id)] elif 'pick_tasks' == site: @@ -926,12 +951,18 @@ class TodoHandler(PlomHandler): db.selected_date = postvars.get('date') redir_params += [('date', db.selected_date)] db.selected_day.comment = postvars.get('day_comment', '') - for i, todo_id in enumerate(postvars.get_all('todo_id')): - old_todo = None if not todo_id in db.todos.keys() else db.todos[todo_id] + for i, todo_id in enumerate(postvars.get_all('todo_id', [])): + old_todo = db.todos[todo_id] done = todo_id in postvars.get_all('done', []) day_effort_input = postvars.get_at_index('effort', i, '') day_effort = float(day_effort_input) if len(day_effort_input) > 0 else None comment = postvars.get_at_index('effort_comment', i, '') + if (day_effort is not None) and (not done) and day_effort < 0 and 0 == len(comment): + if len(old_todo.efforts) > 1: + db.delete_effort(old_todo, db.selected_date) + else: + db.delete_todo(todo_id) + continue importance = float(postvars.get_at_index('importance', i)) if old_todo\ and old_todo.done == done\ @@ -964,50 +995,53 @@ class TodoHandler(PlomHandler): cookie_db = self.get_cookie_db(config['cookie_name']) params = ParamsParser(parsed_url.query, cookie_db) - selected_date = t_filter_and = t_filter_not = None + selected_date = tag_filter_and = tag_filter_not = None hide_unchosen = hide_done = False return_to = params.get('return_to', '') - if site in {'do_todos', 'pick_tasks', 'calendar'}: + if site in {'do_todos', 'pick_tasks', 'calendar', 'task'}: selected_date = params.get_cookied('date') - if site in {'do_todos', 'pick_tasks', 'task', 'todo'}: - t_filter_and = params.get_cookied_chain('t_and') - t_filter_not = params.get_cookied_chain('t_not') + if site in {'do_todos', 'pick_tasks'}: + tag_filter_and = params.get_cookied_chain('and_tag', prefix=site) + tag_filter_not = params.get_cookied_chain('not_tag', prefix=site) if 'do_todos' == site: hide_done = params.get('hide_done', False) - db = TodoDB(config['prefix'], selected_date, t_filter_and, t_filter_not, hide_unchosen, hide_done) + db = TodoDB(selected_date, tag_filter_and, tag_filter_not, hide_unchosen, hide_done) if site in {'todo', 'task'}: id_ = params.get('id') - if site in {'todo', 'task', 'pick_tasks'}: + if site in {'todo', 'pick_tasks'}: search = params.get('search', '') if site in {'do_todos', 'pick_tasks'}: - sort_order = params.get_cookied('sort') - if site in {'calendar', 'todo'}: - start_date = params.get_cookied('start') - end_date = params.get_cookied('end') - if 'do_todos' == site: + sort_order = params.get_cookied('sort', prefix=site) + if site in {'', 'calendar', 'todo'}: + start_date = params.get_cookied('start', prefix=site) + end_date = params.get_cookied('end', prefix=site) + if 'reset_cookie' == site: + cookie_db = { # sensible defaults + params.cookie_key_from_params_key('pick_tasks', 'and_tag'): ['default'], + params.cookie_key_from_params_key('pick_tasks', 'not_tag'): ['ignore'], + params.cookie_key_from_params_key('todo', 'and_tag'): ['default'], + params.cookie_key_from_params_key('todo', 'not_tag'): ['ignore'], + params.cookie_key_from_params_key('todo', 'start'): 'yesterday', + params.cookie_key_from_params_key('todo', 'end'): 'today'} + page = db.show_message('cookie unset!') + elif 'do_todos' == site: page = db.show_do_todos(sort_order) elif 'pick_tasks' == site: hide_chosen_tasks = params.get('hide_chosen_tasks', False) page = db.show_pick_tasks(search, hide_chosen_tasks, sort_order) elif site == 'todo': - page = db.show_todo(id_, return_to, search, start_date, end_date) + page = db.show_todo(id_, search, start_date, end_date) elif 'task' == site: - page = db.show_task(id_, return_to, search) + page = db.show_task(id_) elif 'tasks' == site: expand_uuid = params.get('expand_uuid') page = db.show_tasks(expand_uuid) elif 'add_task' == site: page = db.show_task(None) - elif 'unset_cookie' == site: - page = 'no cookie to unset.' - if len(cookie_db) > 0: - self.unset_cookie(config['cookie_name'], config['cookie_path']) - page = 'cookie unset!' - else: + else: # 'calendar' == site page = db.show_calendar(start_date, end_date) - if 'unset_cookie' != site: - self.set_cookie(config['cookie_name'], config['cookie_path'], cookie_db) + self.set_cookie(config['cookie_name'], config['cookie_path'], cookie_db) self.send_HTML(page) diff --git a/todo_templates/base.html b/todo_templates/base.html index b010333..1084e4f 100644 --- a/todo_templates/base.html +++ b/todo_templates/base.html @@ -1,3 +1,4 @@ +{% import 'macros.html' as macros %} @@ -22,7 +25,7 @@ tasks: list add | day: pick tasks do todos | calendar -| unset cookie +| reset cookie
{% block content %} {% endblock %} diff --git a/todo_templates/day.html b/todo_templates/day.html deleted file mode 100644 index 05b5fd7..0000000 --- a/todo_templates/day.html +++ /dev/null @@ -1,101 +0,0 @@ -{% extends 'base.html' %} -{% block css %} -table.alternating tr:nth-child(even) { - background-color: #cccccc; -} -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 } -td details { display: inline } -input[type="number"] { text-align: right; } -{% endblock %} -{% block content %} -
-{% include 'tagfilters.html' %} -

- - hide unchosen hide done -

-
- -

edit day

-
-

-| prev | {{db.selected_date}} | next | -comment: - - -

-
-done: {{ db.selected_day.todos_sum|round(2) }} ({{ db.selected_day.todos_sum2|round(2)}}) - -{% for todo in done_tasks %} - - - - - -{% endfor %} -
{{todo.task.current_title|e}}{{todo.effort}}{{todo.comment}}
-
- - - - - - - - - -{% for row in task_rows %} - - - -{% if row.todo %} - - - - - - - -{% else %} - - - - - - - -{% endif %} - -{% endfor %} -
tasktododoneeffortimportanceedit?day tagscomment
] {{ row.task.current_title|e }}tags: {% for tag in row.task.tags | sort %}{{ tag }} {% endfor %}
edit{% for tag in row.todo.day_tags | sort %}{{ tag }} {% endfor %}{{ row.todo.comment|e }} -edit
- -
-{% include 'watch_form.html' %} - -{% endblock %} diff --git a/todo_templates/do_todos.html b/todo_templates/do_todos.html index 129018f..fd6feb2 100644 --- a/todo_templates/do_todos.html +++ b/todo_templates/do_todos.html @@ -14,7 +14,7 @@ th.centered { text-align: center; } {% include 'tagfilters.html' %}
hide done - +

| prev | {{day.date}} | next | comment: @@ -52,7 +52,7 @@ comment: -{{todo.path}}{{todo.title}} +{% if todo.parents|length > 0 %}…:{% endif %}{{todo.title}} @@ -61,7 +61,7 @@ comment: {% endfor %} - + {% include 'watch_form.html' %} diff --git a/todo_templates/pick_tasks.html b/todo_templates/pick_tasks.html index 3ffbf79..28919df 100644 --- a/todo_templates/pick_tasks.html +++ b/todo_templates/pick_tasks.html @@ -8,11 +8,6 @@

pick todos for day

-{% include 'tagfilters.html' %} -
- hide chosen tasks
-search: -

prev | {{day.date}} | next | {{day.comment}} @@ -21,7 +16,17 @@ search: - +
+ + + + + +{% for task in all_tasks %} + +{% endfor %} + + -{% for task in tasks %} +{% for task in filtered_tasks %}
task quick-add:
do @@ -67,7 +72,7 @@ search: {{todo.all_days_effort}}/{{todo.default_effort}} -{{todo.day.date}} {{todo.task.title.then|e}} +{{todo.dated_title|e}} {{todo.comment|e}} @@ -79,7 +84,7 @@ search: tasks
@@ -97,7 +102,13 @@ search: {% endfor %}
- + +
+{% include 'tagfilters.html' %} +
+ hide chosen tasks
+search: +

{% include 'watch_form.html' %} diff --git a/todo_templates/tagfilters.html b/todo_templates/tagfilters.html index 21333f6..6b2a9f1 100644 --- a/todo_templates/tagfilters.html +++ b/todo_templates/tagfilters.html @@ -1,32 +1,6 @@ -need: -{% for and_filter in filter_and %} - -{% endfor %} - +need tags: +{{ macros.tagselection(submit_name='and_tag', selected_tags=filter_and, all_tags=tags) }}
-hide: -{% for not_filter in filter_not %} - -{% endfor %} - +hide tags: +{{ macros.tagselection(submit_name='not_tag', selected_tags=filter_not, all_tags=tags) }} diff --git a/todo_templates/task.html b/todo_templates/task.html index 7a8f981..2666d0d 100644 --- a/todo_templates/task.html +++ b/todo_templates/task.html @@ -1,18 +1,25 @@ {% extends 'base.html' %} {% block css %} -th { padding-right: 1em; } +tr.toplevel th { padding-right: 1em; } +tr.toplevel td, tr.toplevel th { padding-top: 1em; } +tr.lowlevel td, tr.lowlevel th { padding-top: 0em; } {% endblock %} {% block content %}

edit task

-
+ - - + + + + + + + - + - + - + - +{% if task.parents %} + + + + +{% endif %} + + + +{% for subtask in filtered_tasks %} + +{% endfor %} +
add as todo to {{ selected_date }}
title @@ -27,14 +34,14 @@ th { padding-right: 1em; }
comment - +
default effort {% if task.subtasks %} @@ -53,15 +60,13 @@ th { padding-right: 1em; }
tags -{% for tag in tags | sort %} - {{ tag }} -
-{% endfor %} -add: -
+ +add: {{ macros.tagselection(submit_name='tag', selected_tags=task.tags.now, all_tags=tags) }}
history
    @@ -73,13 +78,26 @@ add:
parents + +
children +{% if task.subtasks %} - {% for subtask in task.subtasks %} - + @@ -88,37 +106,22 @@ add: {% endfor %} - - - - - -{% for subtask in filtered_tasks %} - - - - -{% endfor %} -
---
- - -{{subtask.title.now}} -
+{% endif %} +
+add:
- -
-{% include 'tagfilters.html' %} -
-search: - + +
+ +
- -{% include 'watch_form.html' %} - {% endblock %} diff --git a/todo_templates/tasks.html b/todo_templates/tasks.html index ed01d30..a125985 100644 --- a/todo_templates/tasks.html +++ b/todo_templates/tasks.html @@ -23,20 +23,20 @@ tr.expanded { color: #888888; } {{ t.default_effort.now }} {% if uuid == expand_uuid %} -[-] +[-] {% elif t.subtasks|count > 0 %} -[+] +[+] {% endif %} -{{ t.title.now|e }} -{% for tag in t.tags.now | sort %}{{ tag }} {% endfor %} +{{ t.title.now|e }} +{% for tag in t.tags.now | sort %}{{ tag }} {% endfor %} {% if uuid == expand_uuid %} {% for uuid, t in expanded_tasks.items() %} {{ t.default_effort }} -  [+] {{ t.title.now|e }} -{% for tag in t.tags.now | sort %}{{ tag }} {% endfor %} +  [+] {{ t.title.now|e }} +{% for tag in t.tags.now | sort %}{{ tag }} {% endfor %} {% endfor %} {% endif %} diff --git a/todo_templates/todo.html b/todo_templates/todo.html index 404ac1c..45ed8a8 100644 --- a/todo_templates/todo.html +++ b/todo_templates/todo.html @@ -1,39 +1,54 @@ {% extends 'base.html' %} {% block css %} -th.toplevel { padding-right: 1em; } -input[type="submit"].delete { background-color: #ff7777; } -div.delete { float: right; } +tr.toplevel th { padding-right: 1em; } td.center { text-align: center; } +tr.toplevel td, tr.toplevel th { padding-top: 1em; } +tr.lowlevel td, tr.lowlevel th { padding-top: 0em; } {% endblock %} {% block content %}

edit todo

-
+ - + - - - - - - - - + + + - - + + +
task{{ todo.task.title.then|e }}
default effort{{ todo.default_effort }}
task +{{ todo.task.title.then|e }} +{% if todo.task.comment %} +
{{ todo.task.comment|e }}
+{% else %} +
  +{% endif %} +
efforts
work +{% if todo.children %} +[{% if todo.done %}✓{% else %} {% endif %}] +{% else %} + +{% endif %} +done
+(sum: {{todo.all_days_effort}}) +
- + + + + + {% for date, effort in todo.efforts.items() %} - + {% endfor %} - + @@ -55,50 +70,27 @@ td.center { text-align: center; } - - - - - - - - - - - - - + + - - - - - - - - + + - - - + + +{% endif %} - - -
dateeffortdelete
dateeffortdelete
- +{{date}} @@ -43,7 +58,7 @@ td.center { text-align: center; }
total effort{{todo.all_days_effort}}
importance - -
comment - +
comment +
done -{% if todo.children %}✓{% else %}{% endif %} -
day tags -{% for tag in tags | sort %} -{% if tag in todo.task.tags.now %} - âœ“ -{% else %} - -{% endif %} {{ tag }} +
tags +new: +{% for tag in todo.task.tags.now | sort %}{% endfor %} +{{ macros.tagselection(submit_name='tag', selected_tags=todo.day_tags, all_tags=tags) }}
-{% endfor %} -add:
parents +{% if todo.parents %} +
parents
children - - - + + +
adoptefforttitlecomments
children +{% if todo.children %} + + + {% for todo in child_todos %} - + - -{% endfor %} - - - -{% for todo in filtered_todos %} - - - - + {% endfor %}
adoptedefforttitlecomments
{{ '{:2.1f}'.format(todo.all_days_effort) }} {% if todo.done %}✓{% else %} {% endif %} {{todo.day.date}} {{todo.title}}
--------
{{ '{:2.1f}'.format(todo.all_days_effort) }} {% if todo.done %}✓{% else %} {% endif %}{{todo.day.date}} {{todo.title}}{{todo.comment}}
+{% endif %} +
+make from task:
+
+adopt (from to ):
+ + +{% for todo in filtered_todos %} +{% endfor %} + + +{% for task in filtered_tasks %} +{% endfor %} +
- +
- -
-{% include 'tagfilters.html' %} -
-from: -to: -search: - - -{% include 'watch_form.html' %} - {% endblock %} diff --git a/todo_templates/watch_form.html b/todo_templates/watch_form.html index 62fee48..75b5223 100644 --- a/todo_templates/watch_form.html +++ b/todo_templates/watch_form.html @@ -1,5 +1,6 @@