diff --git a/.gitignore b/.gitignore index bfd854c..f08a12f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ venv/ -.DS_Store \ No newline at end of file +env/ +.DS_Store +**/__pycache__ \ No newline at end of file diff --git a/commands/balance-sheet b/commands/balance-sheet index 7410ab3..7a31fcf 100755 --- a/commands/balance-sheet +++ b/commands/balance-sheet @@ -1,2 +1,4 @@ #!/usr/bin/env bash -echo "TO DO" \ No newline at end of file +now="$(date +'%Y%m%d')" +mkdir -p reports/balsheet/$now +bean-report ledger/main.beancount balsheet > reports/balsheet/$now/balsheet.html \ No newline at end of file diff --git a/commands/budget b/commands/budget new file mode 100755 index 0000000..4a23991 --- /dev/null +++ b/commands/budget @@ -0,0 +1,100 @@ +#!/usr/bin/env python3 +from beancount import loader +from beancount.query import query +from beancount.core.data import Custom +from beancount.core.amount import Amount, add, sub +from beancount.parser import printer +import argparse +from datetime import date +from dateutil.relativedelta import relativedelta +from tabulate import tabulate +from decimal import Decimal +from functools import reduce + +class bcolors: + HEADER = '\033[95m' + OKBLUE = '\033[94m' + OKCYAN = '\033[96m' + OKGREEN = '\033[92m' + WARNING = '\033[93m' + FAIL = '\033[91m' + ENDC = '\033[0m' + BOLD = '\033[1m' + UNDERLINE = '\033[4m' + +def get_budget_entries(entries, period, start_date): + budgets = [] + for entry in entries: + if isinstance(entry, Custom) and entry.values[1].value == period and entry.date <= date.fromisoformat(start_date): + budgets.append({ "date": entry.date, "account": entry.values[0].value, "period": entry.values[1].value, "budget": entry.values[2].value }) + return budgets + +def get_expenses(entries, options, period, start_date): + period_delta = relativedelta(months=1) if period == "monthly" else relativedelta(years=1) + end_date = date.fromisoformat(start_date) + period_delta + expenses_query = f"SELECT account, sum(position) FROM OPEN ON {start_date} CLOSE ON {end_date.isoformat()} WHERE account ~ \"Expenses\"" + rtypes, rrows = query.run_query( + entries, options, expenses_query) + expenses = {} + for row in rrows: + expenses[row.account] = row.sum_position + return expenses + +def build_budget(budget_entries, expenses): + result = [] + for entry in budget_entries: + expense = Amount(Decimal(0), entry["budget"].currency) + expense_perc = 0 + remaining = entry["budget"] + if entry["account"] in expenses: + expense = expenses[entry["account"]].get_only_position() + expense_perc = (expense.units.number / entry["budget"].number) * 100 + remaining = sub(remaining, expense.units) + result.append({ + "Account": entry["account"], + "Budget": entry["budget"].to_string(), + "Expense": expense, + "Expense (%)": "{}{:,.2f}%{}".format(bcolors.FAIL if expense_perc >= 100 else '', expense_perc, bcolors.ENDC), + "Remaining": remaining + }) + return result + +def print_report(budget_report, period, start_date, budget_sum, expenses_sum): + print(f"Budget Report (period={period}, start_date={start_date})") + print(f"Budget: {budget_sum}") + print(f"{bcolors.FAIL if expenses_sum >= budget_sum else ''}Expenses: {expenses_sum}{bcolors.ENDC}") + headings = ['Account', 'Budget', 'Expense', '(%)', 'Remaining',] + print(tabulate(budget_report, headers="keys", numalign="right", floatfmt=".2f")) + +def main(): + parser = argparse.ArgumentParser(description='Generate budget report') + parser.add_argument('start_date', metavar='start_date', type=str, nargs=1, + help='Start date (end date will be one month after if monthly report or one year after if yearly report)') + parser.add_argument('-p', metavar='period', type=str, choices=["monthly", "yearly"], default="monthly", required=False, + help='Period (monthly or yearly)') + + args = parser.parse_args() + start_date = args.start_date[0] + period = args.p + + filename = "ledger/main.beancount" + entries, errors, options = loader.load_file(filename) + + if errors: + printer.print_errors(errors) + + budget_entries = get_budget_entries(entries, period, start_date) + # TODO: Multiple currencies + budget_sum = reduce(lambda a, b: add(a, b["budget"]), budget_entries, Amount(Decimal(0), budget_entries[0]["budget"].currency)) + expenses = get_expenses(entries, options, period, start_date) + filtered_expenses = {} + for entry in budget_entries: + if entry["account"] in expenses: + filtered_expenses[entry["account"]] = expenses[entry["account"]] + expenses_sum = reduce(lambda a, b: add(a, b.get_only_position().units), + filtered_expenses.values(), + Amount(Decimal(0), budget_entries[0]["budget"].currency)) + budget_report = build_budget(budget_entries, expenses) + print_report(budget_report, period, start_date, budget_sum, expenses_sum) + +main() \ No newline at end of file diff --git a/commands/income-statement b/commands/income-statement index 7410ab3..085782c 100755 --- a/commands/income-statement +++ b/commands/income-statement @@ -1,2 +1,4 @@ #!/usr/bin/env bash -echo "TO DO" \ No newline at end of file +now="$(date +'%Y%m%d')" +mkdir -p reports/income/$now +bean-report ledger/main.beancount income > reports/income/$now/income.html \ No newline at end of file diff --git a/commands/net-worth b/commands/net-worth new file mode 100755 index 0000000..176987a --- /dev/null +++ b/commands/net-worth @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +bean-report ledger/main.beancount networth \ No newline at end of file diff --git a/commands/objectives b/commands/objectives new file mode 100644 index 0000000..e69de29 diff --git a/ledger/budget.md b/ledger/budget.md new file mode 100644 index 0000000..dc7fdc1 --- /dev/null +++ b/ledger/budget.md @@ -0,0 +1,9 @@ +# Budget + +## Menjar fora +4.5 x 5.45 +4.5 x 16.5 +4.5 x 50 += 323.78 +- 209 (Targ. Restaurant) += 114.78 \ No newline at end of file diff --git a/ledger/plugins/__pycache__/amortize_over.cpython-311.pyc b/ledger/plugins/__pycache__/amortize_over.cpython-311.pyc deleted file mode 100644 index 79aff24..0000000 Binary files a/ledger/plugins/__pycache__/amortize_over.cpython-311.pyc and /dev/null differ diff --git a/reports/balsheet/20231217/balsheet.html b/reports/balsheet/20231217/balsheet.html new file mode 100644 index 0000000..5960612 --- /dev/null +++ b/reports/balsheet/20231217/balsheet.html @@ -0,0 +1,259 @@ + + + + + + + + + + + +
+ +
+

Assets

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AccountEUROther
Assets
Assets:Benefits
Assets:Benefits:Edenred
Assets:Benefits:Edenred:TarjetaTransport40
Assets:Benefits:Edenred:TicketsRestaurant209
Assets:Debt
Assets:Debt:DeutesPerCobrar
Assets:Invest
Assets:Invest:R4
Assets:Invest:R4:Amundi
Assets:Invest:R4:Amundi:MSCIWRLD20436.50810
Assets:Invest:R4:Amundi:SUSTINC709.77780
Assets:Invest:R4:BNP
Assets:Invest:R4:BNP:DISTECH788.20245 USD
Assets:Invest:R4:Fidelity
Assets:Invest:R4:Fidelity:GLTECH14143.9382
Assets:Invest:R4:MSFT1367.2 USD
Assets:Invest:R4:PLTR160.250 USD
Assets:Invest:R4:Vanguard
Assets:Invest:R4:Vanguard:EMMK2664.81727
Assets:Liquid
Assets:Liquid:Caixabank
Assets:Liquid:Caixabank:Corrent18903.80
Assets:Liquid:Caixabank:Estalvi12666.49
Assets:Liquid:R4
Assets:Liquid:R4:EUR44.04
Assets:PersonalProperty
Assets:PersonalProperty:AltresPropietats
Assets:PersonalProperty:Cotxe10000
Assets:PersonalProperty:JoiesArtCollecionables1250
Assets:PersonalProperty:MetallsPreciosos
Assets:PersonalProperty:VivendaPrincipal
81068.371372315.65245 USD
+ +
+ +
+
+ +
+

Liabilities

+ + + + + + + + + + + + + + + + + + +
AccountEUROther
Liabilities
+ +
+
+
+
+

Equity

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AccountEUROther
Equity
Equity:Opening-Balances-80819.37
Equity:Opening-Balances:USD-2315.652 USD
-80819.37-2315.652 USD
+ +
+ +
+ + + + diff --git a/reports/income/2023/12.html b/reports/income/20231217/income.html similarity index 100% rename from reports/income/2023/12.html rename to reports/income/20231217/income.html diff --git a/requirements.txt b/requirements.txt index 750fb45..973d349 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,6 +3,7 @@ beancount==2.3.6 beautifulsoup4==4.12.2 blinker==1.7.0 bottle==0.12.25 +budget-report==0.4 cachetools==5.3.2 certifi==2023.11.17 chardet==5.2.0 @@ -45,6 +46,7 @@ rsa==4.9 simplejson==3.19.2 six==1.16.0 soupsieve==2.5 +tabulate==0.9.0 uritemplate==4.1.1 urllib3==2.1.0 Werkzeug==3.0.1