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.
This commit is contained in:
parent
b3ae436679
commit
1a10e8968b
5 changed files with 103 additions and 32 deletions
|
@ -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": {
|
||||
|
|
|
@ -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"<tr><td>{category}</td>"
|
||||
has_notes = result["notes"].get(category)
|
||||
html += f"<tr><td>{category}{'*' if has_notes else ''}</td>"
|
||||
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"<tr><td>{category}</td>"
|
||||
has_notes = result["notes"].get(category)
|
||||
html += f"<tr><td>{category}{'*' if has_notes else ''}</td>"
|
||||
for currency in currencies:
|
||||
value = transactions.get(currency, "")
|
||||
if value != "":
|
||||
|
@ -198,4 +213,11 @@ def generate_transparency_table(result, currencies=None):
|
|||
</table>
|
||||
"""
|
||||
|
||||
if result["notes"]:
|
||||
html += "<p><b>Notes:</b></p>"
|
||||
html += "<ul>"
|
||||
for category, footnote in result["notes"].items():
|
||||
html += f"<li>{category}: {footnote}</li>"
|
||||
html += "</ul>"
|
||||
|
||||
return html
|
||||
|
|
22
main.py
22
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:
|
||||
|
|
|
@ -85,6 +85,11 @@
|
|||
donations are being used.
|
||||
</p>
|
||||
<div class="table-responsive">{{ finances|safe }}</div>
|
||||
<p class="card-text">
|
||||
Want to know how we got here? Check out all of our
|
||||
<a href="/transparency.html">transparency reports</a> for more
|
||||
information.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
25
templates/transparency.html
Normal file
25
templates/transparency.html
Normal file
|
@ -0,0 +1,25 @@
|
|||
{% extends "base.html" %} {% block title %}Membership / Donations{% endblock %}
|
||||
{% block content %}
|
||||
<div class="container my-5">
|
||||
<div class="text-center mb-5">
|
||||
<h1 class="special-header fancy-text-primary">Transparency</h1>
|
||||
<p class="lead">
|
||||
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.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{% for year, year_data in finances.items() %}
|
||||
{% for month, month_data in year_data.items() %}
|
||||
<div class="card shadow-sm mt-4">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Transparency Report for {{ month }}/{{ year }}</h5>
|
||||
<div class="table-responsive">{{ month_data|safe }}</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
Loading…
Reference in a new issue