From 1a10e8968bb8bb95b654310d27a1f7bfe08d5396 Mon Sep 17 00:00:00 2001 From: Kumi Date: Mon, 3 Jun 2024 16:12:21 +0200 Subject: [PATCH] feat: Enhance financial transparency Introduced detailed financial reporting on a per-category basis, including notes for specific expenses and administrative costs in the data model. Enhanced the financial transparency feature by integrating a monthly financial report generator in the backend, enabling dynamic generation and display of detailed financial reports on the website. This commit also includes a new transparency report page, enriching user engagement with detailed insights into financial allocations and expenditures. - Refactored finance-related data structures to include notes about specific financial activities. - Extended the financial reporting capabilities to generate and display detailed notes and categorizations of expenses and incomes, improving transparency. - Updated the UI to display these detailed reports, ensuring that users have access to comprehensive financial information. This change aligns with our commitment to transparency and accountability by providing clear, detailed financial information to our members and donors. --- data/finances.json | 17 ++++------ helpers/finances.py | 66 ++++++++++++++++++++++++------------- main.py | 22 +++++++++++++ templates/membership.html | 5 +++ templates/transparency.html | 25 ++++++++++++++ 5 files changed, 103 insertions(+), 32 deletions(-) create mode 100644 templates/transparency.html diff --git a/data/finances.json b/data/finances.json index 0e66932..2b2b7d5 100644 --- a/data/finances.json +++ b/data/finances.json @@ -4,15 +4,14 @@ "Membership Fees": { "EUR": 365 }, - "Donations": {}, "Server Costs": { "EUR": -216.57 }, "Domain Names": {}, - "Operating Expenses": { - "EUR": -36.10 - }, - "Conversions": {} + "Administrative Expenses": { + "EUR": -36.10, + "Notes": "Administrative fee for the formation of the association" + } }, "5": { "Membership Fees": { @@ -23,11 +22,9 @@ "XMR": 0.447661805527 }, "Server Costs": { - "EUR": -430.04 - }, - "Domain Names": {}, - "Operating Expenses": {}, - "Conversions": {} + "EUR": -430.04, + "Notes": "Includes setup costs and two monthly payments for new server" + } }, "6": { "Server Costs": { diff --git a/helpers/finances.py b/helpers/finances.py index e4a0b32..b55111f 100644 --- a/helpers/finances.py +++ b/helpers/finances.py @@ -9,6 +9,7 @@ def get_latest_month(data): return int(latest_month), int(latest_year) + def get_transparency_data(data, year=None, month=None): if year is None: year = max(data.keys()) @@ -23,6 +24,7 @@ def get_transparency_data(data, year=None, month=None): balances = {} incomes = {} expenses = {} + notes = {} start_balance = {} end_balance = {} @@ -37,41 +39,51 @@ def get_transparency_data(data, year=None, month=None): # If the month is the one we are interested in, capture the start balance if int(y) == int(year) and int(m) == int(month): - start_balance = {k: Decimal(v) for k, v in balances.items()} + start_balance = { + k: Decimal(v) for k, v in balances.items() if k != "Notes" + } for category in data[y][m]: for currency, amount in data[y][m][category].items(): - if currency not in balances: - balances[currency] = Decimal(0) - balances[currency] += Decimal(str(amount)) + if currency == "Notes": + if int(y) == int(year) and int(m) == int(month): + notes[category] = amount + else: + if currency not in balances: + balances[currency] = Decimal(0) + balances[currency] += Decimal(str(amount)) - # Track incomes and expenses - if int(y) == int(year) and int(m) == int(month): - if Decimal(str(amount)) > 0: - if category not in incomes: - incomes[category] = {} - if currency not in incomes[category]: - incomes[category][currency] = Decimal(0) - incomes[category][currency] += Decimal(str(amount)) - else: - if category not in expenses: - expenses[category] = {} - if currency not in expenses[category]: - expenses[category][currency] = Decimal(0) - expenses[category][currency] += Decimal(str(amount)) + # Track incomes and expenses + if int(y) == int(year) and int(m) == int(month): + if Decimal(str(amount)) > 0: + if category not in incomes: + incomes[category] = {} + if currency not in incomes[category]: + incomes[category][currency] = Decimal(0) + incomes[category][currency] += Decimal(str(amount)) + else: + if category not in expenses: + expenses[category] = {} + if currency not in expenses[category]: + expenses[category][currency] = Decimal(0) + expenses[category][currency] += Decimal(str(amount)) # If the month is the one we are interested in, capture the end balance if int(y) == int(year) and int(m) == int(month): - end_balance = {k: Decimal(v) for k, v in balances.items()} + end_balance = { + k: Decimal(v) for k, v in balances.items() if k != "Notes" + } # Calculate accumulated sums of incomes and expenses accumulated_incomes = { currency: sum(incomes[cat].get(currency, Decimal(0)) for cat in incomes) for currency in balances + if currency != "Notes" } accumulated_expenses = { currency: sum(expenses[cat].get(currency, Decimal(0)) for cat in expenses) for currency in balances + if currency != "Notes" } return { @@ -81,6 +93,7 @@ def get_transparency_data(data, year=None, month=None): "expenses": expenses, "accumulated_incomes": accumulated_incomes, "accumulated_expenses": accumulated_expenses, + "notes": notes, } @@ -94,7 +107,7 @@ def generate_transparency_table(result, currencies=None): + list(data["accumulated_incomes"].keys()) + list(data["accumulated_expenses"].keys()) ) - - {"EUR"} + - {"EUR", "Notes"} ) ) @@ -152,7 +165,8 @@ def generate_transparency_table(result, currencies=None): # Add income rows for category, transactions in result["incomes"].items(): - html += f"{category}" + has_notes = result["notes"].get(category) + html += f"{category}{'*' if has_notes else ''}" for currency in currencies: value = transactions.get(currency, "") if value != "": @@ -163,7 +177,8 @@ def generate_transparency_table(result, currencies=None): # Add expense rows for category, transactions in result["expenses"].items(): - html += f"{category}" + has_notes = result["notes"].get(category) + html += f"{category}{'*' if has_notes else ''}" for currency in currencies: value = transactions.get(currency, "") if value != "": @@ -198,4 +213,11 @@ def generate_transparency_table(result, currencies=None): """ + if result["notes"]: + html += "

Notes:

" + html += "" + return html diff --git a/main.py b/main.py index 6c9a279..ae42d8f 100644 --- a/main.py +++ b/main.py @@ -69,6 +69,28 @@ def catch_all(path): } ) + if path == "transparency": + finances = json.loads( + (pathlib.Path(__file__).parent / "data" / "finances.json").read_text() + ) + + finance_data = {} + + for year in sorted(finances.keys(), reverse=True): + for month in sorted(finances[year].keys(), reverse=True): + if year not in finance_data: + finance_data[year] = {} + print(get_transparency_data(finances, year, month)) + finance_data[year][month] = generate_transparency_table( + get_transparency_data(finances, year, month) + ) + + kwargs.update( + { + "finances": finance_data, + } + ) + return render_template(f"{path}.html", **kwargs) except TemplateNotFound: diff --git a/templates/membership.html b/templates/membership.html index dc56848..a5fd904 100644 --- a/templates/membership.html +++ b/templates/membership.html @@ -85,6 +85,11 @@ donations are being used.

{{ finances|safe }}
+

+ Want to know how we got here? Check out all of our + transparency reports for more + information. +

diff --git a/templates/transparency.html b/templates/transparency.html new file mode 100644 index 0000000..59626c2 --- /dev/null +++ b/templates/transparency.html @@ -0,0 +1,25 @@ +{% extends "base.html" %} {% block title %}Membership / Donations{% endblock %} +{% block content %} +
+
+

Transparency

+

+ Private.coffee is funded by its members and donations. We believe in + transparency and accountability. Below you can find financial reports for + each month since our inception. +

+
+ + {% for year, year_data in finances.items() %} + {% for month, month_data in year_data.items() %} +
+
+
Transparency Report for {{ month }}/{{ year }}
+
{{ month_data|safe }}
+
+
+ {% endfor %} + {% endfor %} + +
+{% endblock %}