"""Kontostände (in Kontowährung) und Umrechnung in die Hauptwährung. Saldo = Anfangssaldo + Einnahmen - Ausgaben - Transfers-raus + Transfers-rein, gezählt ab dem Stichtag des Kontos (siehe ``counts_after_opening``). Transfers werden in der jeweiligen Kontowährung verrechnet (bei währungsüber- greifenden Transfers am Zielkonto der gutgeschriebene Betrag ``to_amount_cents``). """ from datetime import date from sqlalchemy import func from sqlalchemy.orm import Session from ..models import Account, Transaction, TxnType from . import currency def _sum(query) -> int: return int(query.scalar() or 0) def counts_after_opening(account: Account): """Bedingung, die Buchungen bis zum Stichtag ausschliesst. Der Anfangssaldo beschreibt den Stand *am Ende* des Stichtags – so steht er auf jedem Auszug und im E-Banking. Buchungen bis einschliesslich dieses Tages stecken also schon darin und dürfen nicht noch einmal zählen. Sie bleiben in der Liste und in den Auswertungen, nur der Kontostand lässt sie aus. Ohne Stichtag zählt alles – dann ist der Anfangssaldo der Stand von Beginn an. """ if account.opening_date is None: return None return Transaction.date > account.opening_date def account_balance_cents(db: Session, account: Account) -> int: ab = counts_after_opening(account) def summe(spalte, *bedingungen) -> int: query = db.query(func.sum(spalte)).filter(*bedingungen) if ab is not None: query = query.filter(ab) return _sum(query) income = summe( Transaction.amount_cents, Transaction.account_id == account.id, Transaction.type == TxnType.income, ) expense = summe( Transaction.amount_cents, Transaction.account_id == account.id, Transaction.type == TxnType.expense, ) transfer_out = summe( Transaction.amount_cents, Transaction.account_id == account.id, Transaction.type == TxnType.transfer, ) transfer_in = summe( func.coalesce(Transaction.to_amount_cents, Transaction.amount_cents), Transaction.to_account_id == account.id, Transaction.type == TxnType.transfer, ) return account.opening_balance_cents + income - expense - transfer_out + transfer_in def account_balance_base_cents( db: Session, account: Account, base_currency: str, on: date ) -> int: """Saldo in die Hauptwährung umgerechnet (aktueller Kurs). Fällt auf den nativen Wert zurück, wenn kein Kurs verfügbar ist.""" native = account_balance_cents(db, account) if account.currency == base_currency: return native conv = currency.convert(db, native, account.currency, base_currency, on) return conv if conv is not None else native def net_worth_cents(db: Session, accounts: list[Account], base_currency: str, on: date) -> int: return sum( account_balance_base_cents(db, a, base_currency, on) for a in accounts if not a.is_archived )