From 37c97453a93a560cb4c2e9bfa94d93b83b54e1ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Murta?= Date: Mon, 5 Jul 2021 22:53:03 +0100 Subject: [PATCH] Refactored report module Bring report up-to-date with sqlite3 database and yaml configuration files. Updates report command in runnable.py. Moves date arg parsing to utils module Report and graph now share an ArgumentParser with shared date options. --- pfbudget/report.py | 108 +++++++++++++++---------------------------- pfbudget/runnable.py | 62 ++++++++++--------------- pfbudget/utils.py | 22 +++++++++ 3 files changed, 83 insertions(+), 109 deletions(-) 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