privatecoffee-website/helpers/finances.py
Kumi 1acc142dba
feat: add production debug config and enhance data handling
Introduced a new debug configuration for production mode in VSCode, facilitating easier debugging in different environments. Additionally, updated the `get_transparency_data` function to handle year and month as strings, ensuring compatibility with subsequent operations. Added a necessary parameter to a function call in `main.py` to align with the updated function signature.
2024-07-01 07:04:47 +02:00

250 lines
8.4 KiB
Python

from decimal import Decimal
from datetime import datetime
def get_latest_month(data, allow_current=False):
years = sorted(data.keys())
latest_year = years[-1]
months = sorted(data[latest_year].keys())
latest_month = months[-1]
if (
not allow_current
and latest_year == str(datetime.now().year)
and latest_month == str(datetime.now().month)
):
try:
latest_month = months[-2]
except IndexError:
latest_year = years[-2]
latest_month = months[-1]
return int(latest_month), int(latest_year)
def get_transparency_data(data, year=None, month=None, allow_current=False):
if year is None:
year = max(data.keys())
if month is None:
month = max(data[year].keys())
year = str(year)
month = str(month)
if (
not allow_current
and year == str(datetime.now().year)
and month == str(datetime.now().month)
):
try:
month = max([m for m in data[year].keys() if m != str(datetime.now().month)])
except ValueError:
year = str(int(year) - 1)
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 = {}
notes = {}
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() if k != "Notes"
}
for category in data[y][m]:
for currency, amount in data[y][m][category].items():
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))
# 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() 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 {
"start_balance": start_balance,
"end_balance": end_balance,
"incomes": incomes,
"expenses": expenses,
"accumulated_incomes": accumulated_incomes,
"accumulated_expenses": accumulated_expenses,
"notes": notes,
}
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", "Notes"}
)
)
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():
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 != "":
html += f"<td>{format_value(value, currency)}</td>"
else:
html += "<td></td>"
html += "</tr>"
# Add expense rows
for category, transactions in result["expenses"].items():
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 != "":
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>
"""
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