diff --git a/pfbudget/report.py b/pfbudget/report.py index bc4f903..594cda6 100644 --- a/pfbudget/report.py +++ b/pfbudget/report.py @@ -1,78 +1,42 @@ -from .categories import ( - get_income_categories, - get_fixed_expenses, - get_required_expenses, - get_health_expenses, - get_discretionary_expenses, -) -from .transactions import load_transactions, by_year_and_category +from __future__ import annotations +from dateutil.rrule import rrule, YEARLY +from typing import TYPE_CHECKING +import datetime as dt + +import pfbudget.categories as categories + +if TYPE_CHECKING: + from pfbudget.database import DBManager -def net(state, start=None, end=None): - transactions = load_transactions(state.data_dir) - if not start: - start = transactions[0].date - if not end: - end = transactions[-1].date +def net(db: DBManager, 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 - income, fixed, required, health, discretionary = [], [], [], [], [] - yearly_transactions_by_categories = by_year_and_category(transactions, start, end) - for _, transactions_by_category in yearly_transactions_by_categories.items(): - income.append( - sum( - float(t.value) - for category, transactions in transactions_by_category.items() - if transactions and category in get_income_categories() - for t in transactions - ) + 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 categories.groups.items() + }, ) - fixed.append( - sum( - -float(t.value) - for category, transactions in transactions_by_category.items() - if transactions and category in get_fixed_expenses() - for t in transactions + for year in [ + year.date() + for year in rrule( + YEARLY, dtstart=start.replace(day=1), until=end.replace(day=1) ) - ) - required.append( - sum( - -float(t.value) - for category, transactions in transactions_by_category.items() - if transactions and category in get_required_expenses() - for t in transactions - ) - ) - health.append( - sum( - -float(t.value) - for category, transactions in transactions_by_category.items() - if transactions and category in get_health_expenses() - for t in transactions - ) - ) - discretionary.append( - sum( - -float(t.value) - for category, transactions in transactions_by_category.items() - if transactions and category in get_discretionary_expenses() - for t in transactions - ) - ) + ] + ) - for i, year in enumerate(yearly_transactions_by_categories.keys()): - print(year) - print( - "Income: {:.2f}, Expenses: {:.2f}, Net: {:.2f}\n" - "Fixed Expenses: {:.2f}\n" - "Required Expenses: {:.2f}\n" - "Health Expenses: {:.2f}\n" - "Discretionary Expenses: {:.2f}\n".format( - income[i], - fixed[i] + required[i] + health[i] + discretionary[i], - income[i] - (fixed[i] + required[i] + health[i] + discretionary[i]), - fixed[i], - required[i], - health[i], - discretionary[i], - ) - ) + for year, groups in yearly_transactions: + print(year.year) + print(f"Income: {groups.pop('income'):.2f}€") + for group, value in groups.items(): + print(f"{group.capitalize()} expenses: {value:.2f}€") + print() diff --git a/pfbudget/runnable.py b/pfbudget/runnable.py index 9ce7256..64079ab 100644 --- a/pfbudget/runnable.py +++ b/pfbudget/runnable.py @@ -1,12 +1,12 @@ from pathlib import Path import argparse -import datetime as dt from .categories import categorize_data from .database import DBManager -from .graph import discrete, monthly from .parsers import parse_data -from . import report +import pfbudget.graph +import pfbudget.report +import pfbudget.utils DEFAULT_DB = "data.db" @@ -67,10 +67,18 @@ def argparser() -> argparse.ArgumentParser: func=lambda args: categorize_data(DBManager(args.database)) ) + period = argparse.ArgumentParser(add_help=False).add_mutually_exclusive_group() + period.add_argument( + "--interval", type=str, nargs=2, help="graph interval", metavar=("START", "END") + ) + period.add_argument("--start", type=str, nargs=1, help="graph start date") + period.add_argument("--end", type=str, nargs=1, help="graph end date") + period.add_argument("--year", type=str, nargs=1, help="graph year") + """ Graph """ - p_graph = subparsers.add_parser("graph", parents=[help]) + p_graph = subparsers.add_parser("graph", parents=[help, period]) p_graph.add_argument( "option", type=str, @@ -79,20 +87,17 @@ def argparser() -> argparse.ArgumentParser: default="monthly", help="graph option help", ) - p_graph_interval = p_graph.add_mutually_exclusive_group() - p_graph_interval.add_argument( - "--interval", type=str, nargs=2, help="graph interval", metavar=("START", "END") - ) - p_graph_interval.add_argument("--start", type=str, nargs=1, help="graph start date") - p_graph_interval.add_argument("--end", type=str, nargs=1, help="graph end date") - p_graph_interval.add_argument("--year", type=str, nargs=1, help="graph year") p_graph.set_defaults(func=graph) - p_report = subparsers.add_parser("report", help="report help") + """ + Report + """ + p_report = subparsers.add_parser("report", parents=[help, period]) + p_report.set_defaults(func=report) + p_status = subparsers.add_parser("status", help="status help") p_status.set_defaults(func=status) - p_report.set_defaults(func=f_report) return parser @@ -133,39 +138,22 @@ def graph(args): state (PFState): Internal state of the program args (dict): argparse variables """ - start, end = dt.date.min, dt.date.max - if args.start or args.interval: - start = dt.datetime.strptime(args.start[0], "%Y/%m/%d").date() - - if args.end or args.interval: - end = dt.datetime.strptime(args.end[0], "%Y/%m/%d").date() - - if args.interval: - start = dt.datetime.strptime(args.interval[0], "%Y/%m/%d").date() - end = dt.datetime.strptime(args.interval[1], "%Y/%m/%d").date() - - if args.year: - start = dt.datetime.strptime(args.year[0], "%Y").date() - end = dt.datetime.strptime( - str(int(args.year[0]) + 1), "%Y" - ).date() - dt.timedelta(days=1) - + start, end = pfbudget.utils.parse_args_period(args) if args.option == "monthly": - monthly(DBManager(args.database), start, end) + pfbudget.graph.monthly(DBManager(args.database), start, end) elif args.option == "discrete": - discrete(DBManager(args.database), start, end) + pfbudget.graph.discrete(DBManager(args.database), start, end) -def f_report(state, args): - """Report - - Prints a detailed report of the transactions over a period of time. +def report(args): + """Prints a detailed report of the transactions over a period of time. Args: state (PFState): Internal state of the program args (dict): argparse variables """ - report.net(state) + start, end = pfbudget.utils.parse_args_period(args) + pfbudget.report.net(DBManager(args.database), start, end) def run(): diff --git a/pfbudget/utils.py b/pfbudget/utils.py index 0df2f1c..7061439 100644 --- a/pfbudget/utils.py +++ b/pfbudget/utils.py @@ -1,3 +1,4 @@ +from datetime import date, datetime, timedelta from decimal import Decimal from pathlib import Path @@ -52,3 +53,24 @@ def find_credit_institution(fn, banks, creditcards): raise CreditCardNotAvailableError return bank, cc + + +def parse_args_period(args): + start, end = date.min, date.max + if args.start or args.interval: + start = datetime.strptime(args.start[0], "%Y/%m/%d").date() + + if args.end or args.interval: + end = datetime.strptime(args.end[0], "%Y/%m/%d").date() + + if args.interval: + start = datetime.strptime(args.interval[0], "%Y/%m/%d").date() + end = datetime.strptime(args.interval[1], "%Y/%m/%d").date() + + if args.year: + start = datetime.strptime(args.year[0], "%Y").date() + end = datetime.strptime(str(int(args.year[0]) + 1), "%Y").date() - timedelta( + days=1 + ) + + return start, end