feat: add member details popup with attendance and fee exceptions
All checks were successful
Deploy to K8s / deploy (push) Successful in 12s
Build and Push / build (push) Successful in 8s

This commit is contained in:
Jan Novak
2026-03-02 21:41:36 +01:00
parent 99b23199b1
commit 815b962dd7
7 changed files with 512 additions and 16 deletions

31
app.py
View File

@@ -9,7 +9,7 @@ scripts_dir = Path(__file__).parent / "scripts"
sys.path.append(str(scripts_dir))
from attendance import get_members_with_fees, SHEET_ID as ATTENDANCE_SHEET_ID
from match_payments import reconcile, fetch_sheet_data, DEFAULT_SPREADSHEET_ID as PAYMENTS_SHEET_ID
from match_payments import reconcile, fetch_sheet_data, fetch_exceptions, normalize, DEFAULT_SPREADSHEET_ID as PAYMENTS_SHEET_ID
app = Flask(__name__)
@@ -37,14 +37,29 @@ def fees():
monthly_totals = {m: 0 for m in sorted_months}
# Get exceptions for formatting
credentials_path = ".secret/fuj-management-bot-credentials.json"
exceptions = fetch_exceptions(PAYMENTS_SHEET_ID, credentials_path)
formatted_results = []
for name, month_fees in results:
row = {"name": name, "months": []}
norm_name = normalize(name)
for m in sorted_months:
fee, count = month_fees.get(m, (0, 0))
monthly_totals[m] += fee
cell = f"{fee} CZK ({count})" if count > 0 else "-"
row["months"].append(cell)
# Check for exception
norm_period = normalize(m)
override = exceptions.get((norm_name, norm_period))
if override is not None and override != fee:
cell = f"{override} ({fee}) CZK ({count})" if count > 0 else f"{override} ({fee}) CZK"
is_overridden = True
else:
cell = f"{fee} CZK ({count})" if count > 0 else "-"
is_overridden = False
row["months"].append({"cell": cell, "overridden": is_overridden})
formatted_results.append(row)
return render_template(
@@ -69,7 +84,8 @@ def reconcile_view():
return "No data."
transactions = fetch_sheet_data(PAYMENTS_SHEET_ID, credentials_path)
result = reconcile(members, sorted_months, transactions)
exceptions = fetch_exceptions(PAYMENTS_SHEET_ID, credentials_path)
result = reconcile(members, sorted_months, transactions, exceptions)
# Format month labels
month_labels = {
@@ -84,8 +100,9 @@ def reconcile_view():
data = result["members"][name]
row = {"name": name, "months": [], "balance": data["total_balance"]}
for m in sorted_months:
mdata = data["months"].get(m, {"expected": 0, "paid": 0})
mdata = data["months"].get(m, {"expected": 0, "original_expected": 0, "paid": 0})
expected = mdata["expected"]
original = mdata["original_expected"]
paid = int(mdata["paid"])
cell_status = ""
@@ -106,14 +123,16 @@ def reconcile_view():
# Format credits and debts
credits = sorted([{"name": n, "amount": a["total_balance"]} for n, a in result["members"].items() if a["total_balance"] > 0], key=lambda x: x["name"])
debts = sorted([{"name": n, "amount": abs(a["total_balance"])} for n, a in result["members"].items() if a["total_balance"] < 0], key=lambda x: x["name"])
# Format unmatched
unmatched = result["unmatched"]
import json
return render_template(
"reconcile.html",
months=[month_labels[m] for m in sorted_months],
raw_months=sorted_months,
results=formatted_results,
member_data=json.dumps(result["members"]),
credits=credits,
debts=debts,
unmatched=unmatched,