New Membership Page and other changes #4

Merged
kumi merged 29 commits from membership-page into main 2024-05-30 10:15:10 +00:00
4 changed files with 219 additions and 87 deletions
Showing only changes of commit 6033a47b6a - Show all commits

View file

@ -86,8 +86,9 @@ h5 {
}
.currency-col {
width: 175px;
width: 200px;
white-space: nowrap;
text-align: right;
}
.table-transparency td:not(:first-child) {

193
helpers/finances.py Normal file
View file

@ -0,0 +1,193 @@
from decimal import Decimal
def get_transparency_data(data, year=None, month=None):
if year is None:
year = max(data.keys())
if month is None:
month = max(data[year].keys())
assert year in data, f"Year {year} not found in data"
assert month in data[year], f"Month {month}-{year} not found in data"
# Initialize balances
balances = {}
incomes = {}
expenses = {}
start_balance = {}
end_balance = {}
for y in sorted(data.keys()):
if int(y) > int(year):
break
for m in sorted(data[y].keys()):
if int(y) == int(year) and int(m) > int(month):
break
# 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()}
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))
# 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()}
# 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
}
accumulated_expenses = {
currency: sum(expenses[cat].get(currency, Decimal(0)) for cat in expenses)
for currency in balances
}
return {
"start_balance": start_balance,
"end_balance": end_balance,
"incomes": incomes,
"expenses": expenses,
"accumulated_incomes": accumulated_incomes,
"accumulated_expenses": accumulated_expenses,
}
def generate_transparency_table(result, currencies=None):
def extract_currencies(data):
return ["EUR"] + (
list(
set(
list(data["start_balance"].keys())
+ list(data["end_balance"].keys())
+ list(data["accumulated_incomes"].keys())
+ list(data["accumulated_expenses"].keys())
)
- {"EUR"}
)
)
def format_currency(value, currency):
if currency == "EUR":
return f"{value:,.2f}"
elif currency in ["BTC", "ETH", "XMR"]:
return f"{value:,.9f} {currency}"
else:
return f"{value} {currency}"
def format_value(value, currency):
if value == 0:
return f"{format_currency(value, currency)}"
elif value > 0:
return f"+ {format_currency(value, currency)}"
else:
return f"- {format_currency(abs(value), currency)}"
html = """
<table class="table table-bordered table-transparency">
<thead class="table-light">
<tr>
<th scope="col">Category</th>
"""
if currencies is None:
currencies = extract_currencies(result)
# Add currency headers
for currency in currencies:
if currency == "EUR":
html += '<th class="currency-col" scope="col">Euros (€)</th>'
elif currency == "BTC":
html += '<th class="currency-col" scope="col">Bitcoin (BTC)</th>'
elif currency == "ETH":
html += '<th class="currency-col" scope="col">Ethereum (ETH)</th>'
elif currency == "XMR":
html += '<th class="currency-col" scope="col">Monero (XMR)</th>'
else:
html += f'<th class="currency-col" scope="col">{currency}</th>'
html += """
</tr>
</thead>
<tbody>
"""
# Add start balance row
html += "<tr><td>Account Balance (start of month)</td>"
for currency in currencies:
value = result["start_balance"].get(currency, Decimal(0))
html += f"<td>{format_value(value, currency)}</td>"
html += "</tr>"
# Add income rows
for category, transactions in result["incomes"].items():
html += f"<tr><td>{category}</td>"
for currency in currencies:
value = transactions.get(currency, "")
if value != "":
html += f"<td>{format_value(value, currency)}</td>"
else:
html += "<td></td>"
html += "</tr>"
# Add expense rows
for category, transactions in result["expenses"].items():
html += f"<tr><td>{category}</td>"
for currency in currencies:
value = transactions.get(currency, "")
if value != "":
html += f"<td>{format_value(value, currency)}</td>"
else:
html += "<td></td>"
html += "</tr>"
# Add total income row
html += '<tr class="table-secondary"><td><b>Total Income</b></td>'
for currency in currencies:
value = result["accumulated_incomes"].get(currency, Decimal(0))
html += f"<td><b>{format_value(value, currency)}</b></td>"
html += "</tr>"
# Add total expenses row
html += '<tr class="table-secondary"><td><b>Total Expenses</b></td>'
for currency in currencies:
value = result["accumulated_expenses"].get(currency, Decimal(0))
html += f"<td><b>{format_value(value, currency)}</b></td>"
html += "</tr>"
# Add end balance row
html += '<tr class="table-secondary"><td><b>Account Balance (end of month)</b></td>'
for currency in currencies:
value = result["end_balance"].get(currency, Decimal(0))
html += f"<td><b>{format_value(value, currency)}</b></td>"
html += "</tr>"
html += """
</tbody>
</table>
"""
return html

26
main.py
View file

@ -7,6 +7,8 @@ import os
from argparse import ArgumentParser
from helpers.finances import generate_transparency_table, get_transparency_data
app = Flask(__name__)
@ -28,9 +30,27 @@ def catch_all(path):
if app.development_mode:
warning = render_template("prod-warning.html")
return render_template(
f"{path}.html", services=services, warning=warning
)
kwargs = {
"services": services,
"warning": warning,
}
if path == "membership":
finances = json.loads(
(pathlib.Path(__file__).parent / "finances.json").read_text()
)
finances_table = generate_transparency_table(
get_transparency_data(finances)
)
kwargs.update(
{
"finances": finances_table,
}
)
return render_template(f"{path}.html", **kwargs)
except TemplateNotFound:
return "404 Not Found", 404

View file

@ -84,89 +84,7 @@
income and expenses for the last month.
</p>
<div class="table-responsive">
<table class="table table-bordered table-transparency">
<thead class="table-light">
<tr>
<th scope="col">Category</th>
<th class="currency-col" scope="col">Euros (€)</th>
<th class="currency-col" scope="col">Bitcoin (BTC)</th>
<th class="currency-col" scope="col">Ethereum (ETH)</th>
<th class="currency-col" scope="col">Monero (XMR)</th>
</tr>
</thead>
<tbody>
<tr>
<td>Account Balance (start of month)</td>
<td>+ €112.33</td>
<td>0 BTC</td>
<td>0 ETH</td>
<td>0 XMR</td>
</tr>
<tr>
<td>Membership Fees</td>
<td>+ €390.00</td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Donations</td>
<td></td>
<td>+ 0.00043400 BTC</td>
<td></td>
<td>+ 0.447661805527 XMR</td>
</tr>
<tr>
<td>Server Costs</td>
<td>- €430.04</td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Domain Names</td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Operating Expenses</td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Conversions</td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr class="table-secondary">
<td><b>Total Income</b></td>
<td><b>+ €390.00</b></td>
<td><b>+ 0.00043400 BTC</b></td>
<td><b>0 ETH</b></td>
<td><b>+ 0.447661805527 XMR</b></td>
</tr>
<tr class="table-secondary">
<td><b>Total Expenses</b></td>
<td><b>- €430.04</b></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr class="table-secondary">
<td><b>Account Balance (end of month)</b></td>
<td><b>+ €72.29</b></td>
<td><b>+ 0.00043400 BTC</b></td>
<td><b>0 ETH</b></td>
<td><b>+ 0.447661805527 XMR</b></td>
</tr>
</tbody>
</table>
{{ finances|safe }}
</div>
</div>
</div>