Move from a direct access to DB by the parsers/categorizers to a middle layer, which will bring an easier way to have two input alternatives. This patch starts by instantiating the manager on the cli runnable and using it for the parser function. This patch also moves the queries to a different file, so that introducing new functions on the DB client becomes more manageable and clearer. Finally, the new manager will need converters to move from the code type Transaction to the DB types. This will eventually simplify the code data model by removing more of its method and leaving it a simple dataclass. Issue #14
139 lines
4.7 KiB
Python
139 lines
4.7 KiB
Python
from __future__ import annotations
|
|
from dateutil.rrule import rrule, YEARLY
|
|
from typing import TYPE_CHECKING
|
|
import datetime as dt
|
|
|
|
import pfbudget.core.categories
|
|
|
|
if TYPE_CHECKING:
|
|
from pfbudget.db.client import DatabaseClient
|
|
|
|
|
|
def net(db: DatabaseClient, start: dt.date = dt.date.min, end: dt.date = dt.date.max):
|
|
transactions = db.get_daterange(start, end)
|
|
start, end = transactions[0].date, transactions[-1].date
|
|
|
|
yearly_transactions = tuple(
|
|
(
|
|
year,
|
|
{
|
|
group: sum(
|
|
transaction.value
|
|
for transaction in transactions
|
|
if transaction.category in categories
|
|
and year <= transaction.date <= year.replace(month=12, day=31)
|
|
)
|
|
for group, categories in pfbudget.core.categories.groups.items()
|
|
},
|
|
)
|
|
for year in [
|
|
year.date()
|
|
for year in rrule(
|
|
YEARLY, dtstart=start.replace(day=1), until=end.replace(day=1)
|
|
)
|
|
]
|
|
)
|
|
|
|
for year, groups in yearly_transactions:
|
|
print(f"\n{year.year}\n")
|
|
|
|
income = groups.pop("income-fixed") + groups.pop("income-extra")
|
|
print(f"Income: {income:.2f} €\n")
|
|
|
|
investments = -groups.pop("investment")
|
|
|
|
expenses = 0
|
|
for group, value in groups.items():
|
|
expenses -= value
|
|
if income != 0:
|
|
print(
|
|
f"{group.capitalize()} expenses: {-value:.2f} € ({-value/income*100:.1f}%)"
|
|
)
|
|
else:
|
|
print(f"{group.capitalize()} expenses: {-value:.2f} €")
|
|
|
|
print(f"\nNet total: {income-expenses:.2f} €")
|
|
if income != 0:
|
|
print(
|
|
f"Total expenses are {expenses:.2f} € ({expenses/income*100:.1f}% of income)\n"
|
|
)
|
|
else:
|
|
print(f"Total expenses are {expenses:.2f} €. No income this year!\n")
|
|
|
|
print(f"Invested: {investments:.2f}€\n")
|
|
|
|
|
|
def detailed(db: DatabaseClient, start: dt.date = dt.date.min, end: dt.date = dt.date.max):
|
|
transactions = db.get_daterange(start, end)
|
|
start, end = transactions[0].date, transactions[-1].date
|
|
|
|
yearly_transactions: tuple[int, dict[str, float]] = tuple(
|
|
(
|
|
year.year,
|
|
{
|
|
category: sum(
|
|
transaction.value
|
|
for transaction in transactions
|
|
if transaction.category == category
|
|
and year <= transaction.date <= year.replace(month=12, day=31)
|
|
)
|
|
for category in pfbudget.core.categories.categories
|
|
},
|
|
)
|
|
for year in [
|
|
year.date()
|
|
for year in rrule(
|
|
YEARLY, dtstart=start.replace(day=1), until=end.replace(day=1)
|
|
)
|
|
]
|
|
)
|
|
|
|
for year, categories in yearly_transactions:
|
|
print(f"\n{year}\n")
|
|
|
|
income = sum(
|
|
sum
|
|
for category, sum in categories.items()
|
|
if category in pfbudget.core.categories.groups["income-fixed"]
|
|
or category in pfbudget.core.categories.groups["income-extra"]
|
|
)
|
|
print(f"Income: {income:.2f}€\n")
|
|
|
|
investments = -sum(
|
|
sum
|
|
for category, sum in categories.items()
|
|
if category in pfbudget.core.categories.groups["investment"]
|
|
)
|
|
|
|
expenses = 0
|
|
for category, value in categories.items():
|
|
if (
|
|
category not in pfbudget.core.categories.groups["income-fixed"]
|
|
and category not in pfbudget.core.categories.groups["income-extra"]
|
|
and category not in pfbudget.core.categories.groups["investment"]
|
|
):
|
|
if category == "Null":
|
|
if value != 0:
|
|
print(f"Null: {value} != 0€")
|
|
continue
|
|
expenses -= value
|
|
|
|
if income != 0:
|
|
print(
|
|
f"{category.capitalize()} expenses: {-value:.2f} € ({-value/income*100:.1f}%)"
|
|
)
|
|
else:
|
|
print(f"{category.capitalize()} expenses: {-value:.2f} €")
|
|
if income != 0:
|
|
print(
|
|
f"\nNet total: {income-expenses:.2f} € ({(income-expenses)/income*100:.1f}% of income)"
|
|
)
|
|
print(
|
|
f"Total expenses are {expenses:.2f} € ({expenses/income*100:.1f}% of income)\n"
|
|
)
|
|
else:
|
|
print(f"\nNet total: {income-expenses:.2f} €")
|
|
print(f"Total expenses are {expenses:.2f} €. No income this year!\n")
|
|
|
|
print(f"Invested: {investments:.2f}€\n")
|