+ if not transfer_line.account in self.account_changes.keys():
+ self.account_changes[transfer_line.account] = Wealth()
+ money = {transfer_line.currency: transfer_line.amount}
+ self.account_changes[transfer_line.account] += money
+ money_changes += money
+ if sink_account is None and (not money_changes.sink_empty) and self.validate:
+ raise PlomException(f'{intro}: does not balance (undeclared non-empty sink)')
+ if sink_account is not None:
+ if not sink_account in self.account_changes.keys():
+ self.account_changes[sink_account] = Wealth()
+ self.account_changes[sink_account] += money_changes.as_sink
+
+ @property
+ def for_writing(self):
+ lines = [f'{self.date} {self.description}']
+ if self.top_comment is not None and self.top_comment.rstrip() != '':
+ lines[0] += f' ; {self.top_comment}'
+ for line in self.transfer_lines:
+ lines += [line.for_writing]
+ return lines
+
+ @property
+ def comment_cols(self):
+ return max(20, len(self.top_comment))
+
+ def validate_head(self):
+ if not self.validate:
+ return
+ if len(self.date) == 0:
+ raise PlomException(f'{self.intro}: missing date')
+ if len(self.description) == 0:
+ raise PlomException(f'{self.intro}: missing description')
+ try:
+ datetime.strptime(self.date, '%Y-%m-%d')
+ except ValueError:
+ raise PlomException(f'{self.intro}: bad headline date format: {self.date}')
+
+ def fill_sink(self):
+ replacement_lines = []
+ for i, line in enumerate(self.transfer_lines):
+ if line.amount is None:
+ for currency, amount in self.account_changes[line.account].money_dict.items():
+ replacement_lines += [TransferLine(None, f'{line.account}', amount, currency).for_writing]
+ break
+ lines = self.lines[:i+1] + [LedgerTextLine(l) for l in replacement_lines] + self.lines[i+2:]
+ self.clean()
+ self.lines = lines
+ self.parse_lines()
+
+ def mirror(self):
+ new_transfer_lines = []
+ for transfer_line in self.transfer_lines:
+ uncommented_source = LedgerTextLine(transfer_line.for_writing).non_comment
+ comment = f're: {uncommented_source.lstrip()}'
+ new_account = '?'
+ new_transfer_lines += [TransferLine(None, new_account, -transfer_line.amount, transfer_line.currency, comment)]
+ for transfer_line in new_transfer_lines:
+ self.lines += [LedgerTextLine(transfer_line.for_writing)]
+ self.clean()
+ self.parse_lines()
+
+ def replace(self, replace_from, replace_to):
+ lines = []
+ for l in self.for_writing:
+ lines += [l.replace(replace_from, replace_to)]
+ self.lines = [LedgerTextLine(l) for l in lines]
+ self.clean()
+ self.parse_lines()
+
+ def add_taxes(self):
+ 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'
+ 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['€']
+ ESt_this_month = 0
+ left_over = needed_netto - ESt_this_month
+ 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
+ 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_netto - ESt_this_month
+ if abs(left_over - expenses_so_far) < 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
+ 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)
+ 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