diff --git a/main.py b/main.py index a7b33bf..74fd255 100644 --- a/main.py +++ b/main.py @@ -4,6 +4,7 @@ import datetime as dt from pfbudget.graph import average, discrete, monthly from pfbudget.transactions import load_transactions, save_transactions +import pfbudget.report as report import pfbudget.tools as tools @@ -143,10 +144,31 @@ def status(state, args): def graph(state, args): + start, end = None, None + 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) + if args.option == "monthly": - monthly(state, args.start, args.end) + monthly(state, start, end) elif args.option == "discrete": - discrete(state, args.start, args.end) + discrete(state, start, end) + + +def f_report(state, args): + report.net(state) if __name__ == "__main__": @@ -205,8 +227,13 @@ if __name__ == "__main__": default="monthly", help="graph option help", ) - p_graph.add_argument("start", type=str, nargs="?", help="graph start date") - p_graph.add_argument("end", type=str, nargs="?", help="graph end date") + 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_init.set_defaults(func=init) p_restart.set_defaults(func=restart) @@ -215,6 +242,7 @@ if __name__ == "__main__": p_vacation.set_defaults(func=vacation) p_status.set_defaults(func=status) p_graph.set_defaults(func=graph) + p_report.set_defaults(func=f_report) state = tools.pfstate(p) state.filename = p diff --git a/pfbudget/graph.py b/pfbudget/graph.py index 601c804..ce2443d 100644 --- a/pfbudget/graph.py +++ b/pfbudget/graph.py @@ -148,6 +148,7 @@ def discrete(state, start, end): labels=["Fixed", "Required", "Health", *get_discretionary_expenses()], ) plt.legend(loc="upper left") + plt.grid() plt.show() diff --git a/pfbudget/report.py b/pfbudget/report.py new file mode 100644 index 0000000..ef6ed2c --- /dev/null +++ b/pfbudget/report.py @@ -0,0 +1,85 @@ +from .categories import ( + get_income_categories, + get_fixed_expenses, + get_required_expenses, + get_health_expenses, + get_discretionary_expenses, +) +from .transactions import ( + load_transactions, + daterange, + by_category, + by_month, + by_month_and_category, + by_year_and_category, +) + + +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 + + 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 + ) + ) + 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 + ) + ) + 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], + ) + ) diff --git a/pfbudget/transactions.py b/pfbudget/transactions.py index cd8c1da..668194c 100644 --- a/pfbudget/transactions.py +++ b/pfbudget/transactions.py @@ -153,15 +153,27 @@ def by_month(transactions, start=None, end=None) -> dict: def by_category(transactions) -> dict: transactions_by_category = dict.fromkeys(get_categories(), None) - for transaction in transactions: - try: - transactions_by_category[transaction.category].append(transaction) - except AttributeError: - transactions_by_category[transaction.category] = [transaction] + + if transactions: + for transaction in transactions: + try: + transactions_by_category[transaction.category].append(transaction) + except AttributeError: + transactions_by_category[transaction.category] = [transaction] return transactions_by_category +def by_year_and_category(transactions, start, end) -> dict: + yearly_transactions_by_category = {} + + yearly_transactions = by_year(transactions, start, end) + for year, transactions in yearly_transactions.items(): + yearly_transactions_by_category[year] = by_category(transactions) + + return yearly_transactions_by_category + + def by_month_and_category(transactions, start, end) -> dict: monthly_transactions_by_categories = {}