+def add_taxes(lines):
+ import decimal
+ bookings, _ = parse_lines(lines.copy())
+ _, account_sums = bookings_to_account_tree(bookings)
+ expenses_so_far = -1 * account_sums['Assets']['€']
+ needed_income_before_krankenkasse = expenses_so_far
+ ESt_this_month = 0
+ left_over = needed_income_before_krankenkasse - ESt_this_month
+ too_low = 0
+ too_high = 2 * needed_income_before_krankenkasse
+ E0 = 10908
+ E1 = 15999
+ E2 = 62809
+ E3 = 277825
+ while True:
+ zvE = 12 * needed_income_before_krankenkasse
+ 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 / 12
+ left_over = needed_income_before_krankenkasse - ESt_this_month
+ if abs(left_over - expenses_so_far) < 0.1:
+ break
+ elif left_over < expenses_so_far:
+ too_low = needed_income_before_krankenkasse
+ elif left_over > expenses_so_far:
+ too_high = needed_income_before_krankenkasse
+ needed_income_before_krankenkasse = too_low + (too_high - too_low)/2
+ line_income_tax = f' Reserves:Einkommenssteuer {ESt_this_month:.2f}€ ; expenses so far: {expenses_so_far:.2f}€; zvE: {zvE:.2f}€; ESt total: {ESt:.2f}€; needed before Krankenkasse: {needed_income_before_krankenkasse:.2f}€'
+ kk_minimum_income = 1096.67
+ kk_factor = decimal.Decimal(0.189)
+ kk_minimum_tax = decimal.Decimal(207.27)
+ # kk_minimum_income = 1131.67
+ # kk_factor = decimal.Decimal(0.191)
+ # kk_minimum_tax = decimal.Decimal(216.15)
+ # kk_factor = decimal.Decimal(0.197)
+ # kk_minimum_tax = decimal.Decimal(222.94)
+ kk_add = max(0, kk_factor * needed_income_before_krankenkasse - kk_minimum_tax)
+ line_kk_minimum = f' Reserves:Month:Krankenkassendefaultbeitrag {kk_minimum_tax:.2f}€ ; assed minimum income {kk_minimum_income:.2f}€ * {kk_factor:.3f}'
+ line_kk_add = f' Reserves:Month:Krankenkassenbeitragswachstum {kk_add:.2f}€ ; max(0, {kk_factor:.3f} * {needed_income_before_krankenkasse:.2f}€ - {kk_minimum_tax:.2f}€)'
+ final_minus = expenses_so_far + ESt_this_month + kk_minimum_tax + kk_add
+ line_finish = f' Assets -{final_minus:.2f}€'
+ return [line_income_tax, line_kk_minimum, line_kk_add, line_finish]
+
+
+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
+
+