chore: Remove archived pages (fees, reconcile) from web UI

Deleted /fees, /fees-juniors, /reconcile, /reconcile-juniors routes and
their templates. Payment Ledger (/payments) is retained. Nav updated
across all remaining templates.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-09 12:48:15 +02:00
parent 109ef983f0
commit 1ac5df7be5
10 changed files with 0 additions and 2696 deletions

387
app.py
View File

@@ -157,151 +157,6 @@ def sync_bank():
def version():
return BUILD_META
@app.route("/fees")
def fees():
attendance_url = f"https://docs.google.com/spreadsheets/d/{ATTENDANCE_SHEET_ID}/edit"
payments_url = f"https://docs.google.com/spreadsheets/d/{PAYMENTS_SHEET_ID}/edit"
members_data = get_cached_data("attendance_regular", ATTENDANCE_SHEET_ID, get_members_with_fees)
record_step("fetch_members")
if not members_data:
return "No data."
members, sorted_months = members_data
# Filter to adults only for display
results = [(name, fees) for name, tier, fees in members if tier == "A"]
# Format month labels
month_labels = get_month_labels(sorted_months, ADULT_MERGED_MONTHS)
monthly_totals = {m: 0 for m in sorted_months}
# Get exceptions for formatting
credentials_path = CREDENTIALS_PATH
exceptions = get_cached_data(
"exceptions_dict", PAYMENTS_SHEET_ID, fetch_exceptions,
PAYMENTS_SHEET_ID, credentials_path,
serialize=lambda d: [[list(k), v] for k, v in d.items()],
deserialize=lambda c: {tuple(k): v for k, v in c},
)
record_step("fetch_exceptions")
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))
# Check for exception
norm_period = normalize(m)
ex_data = exceptions.get((norm_name, norm_period))
override_amount = ex_data["amount"] if ex_data else None
if override_amount is not None and override_amount != fee:
cell = f"{override_amount} ({fee}) CZK ({count})" if count > 0 else f"{override_amount} ({fee}) CZK"
is_overridden = True
else:
if isinstance(fee, int):
monthly_totals[m] += fee
cell = f"{fee} CZK ({count})" if count > 0 else "-"
is_overridden = False
row["months"].append({"cell": cell, "overridden": is_overridden})
formatted_results.append(row)
record_step("process_data")
return render_template(
"fees.html",
months=[month_labels[m] for m in sorted_months],
results=formatted_results,
totals=[f"{monthly_totals[m]} CZK" for m in sorted_months],
attendance_url=attendance_url,
payments_url=payments_url
)
@app.route("/fees-juniors")
def fees_juniors():
attendance_url = f"https://docs.google.com/spreadsheets/d/{ATTENDANCE_SHEET_ID}/edit#gid={JUNIOR_SHEET_GID}"
payments_url = f"https://docs.google.com/spreadsheets/d/{PAYMENTS_SHEET_ID}/edit"
members_data = get_cached_data("attendance_juniors", ATTENDANCE_SHEET_ID, get_junior_members_with_fees)
record_step("fetch_junior_members")
if not members_data:
return "No data."
members, sorted_months = members_data
# Sort members by name
results = sorted([(name, fees) for name, tier, fees in members], key=lambda x: x[0])
# Format month labels
month_labels = get_month_labels(sorted_months, JUNIOR_MERGED_MONTHS)
monthly_totals = {m: 0 for m in sorted_months}
# Get exceptions for formatting (reusing payments sheet)
credentials_path = CREDENTIALS_PATH
exceptions = get_cached_data(
"exceptions_dict", PAYMENTS_SHEET_ID, fetch_exceptions,
PAYMENTS_SHEET_ID, credentials_path,
serialize=lambda d: [[list(k), v] for k, v in d.items()],
deserialize=lambda c: {tuple(k): v for k, v in c},
)
record_step("fetch_exceptions")
formatted_results = []
for name, month_fees in results:
row = {"name": name, "months": []}
norm_name = normalize(name)
for m in sorted_months:
fee_data = month_fees.get(m, (0, 0, 0, 0))
if len(fee_data) == 4:
fee, total_count, adult_count, junior_count = fee_data
else:
fee, total_count = fee_data
adult_count, junior_count = 0, 0
# Check for exception
norm_period = normalize(m)
ex_data = exceptions.get((norm_name, norm_period))
override_amount = ex_data["amount"] if ex_data else None
if ex_data is None and isinstance(fee, int):
monthly_totals[m] += fee
# Formulate the count string display
if adult_count > 0 and junior_count > 0:
count_str = f"{total_count} ({adult_count}A+{junior_count}J)"
elif adult_count > 0:
count_str = f"{total_count} (A)"
elif junior_count > 0:
count_str = f"{total_count} (J)"
else:
count_str = f"{total_count}"
if override_amount is not None and override_amount != fee:
cell = f"{override_amount} ({fee}) CZK / {count_str}" if total_count > 0 else f"{override_amount} ({fee}) CZK"
is_overridden = True
else:
if fee == "?":
cell = f"? / {count_str}" if total_count > 0 else "-"
else:
cell = f"{fee} CZK / {count_str}" if total_count > 0 else "-"
is_overridden = False
row["months"].append({"cell": cell, "overridden": is_overridden})
formatted_results.append(row)
record_step("process_data")
return render_template(
"fees-juniors.html",
months=[month_labels[m] for m in sorted_months],
results=formatted_results,
totals=[f"{t} CZK" if isinstance(t, int) else t for t in monthly_totals.values()],
attendance_url=attendance_url,
payments_url=payments_url
)
@app.route("/adults")
def adults_view():
attendance_url = f"https://docs.google.com/spreadsheets/d/{ATTENDANCE_SHEET_ID}/edit"
@@ -442,110 +297,6 @@ def adults_view():
bank_account=BANK_ACCOUNT
)
@app.route("/reconcile")
def reconcile_view():
attendance_url = f"https://docs.google.com/spreadsheets/d/{ATTENDANCE_SHEET_ID}/edit"
payments_url = f"https://docs.google.com/spreadsheets/d/{PAYMENTS_SHEET_ID}/edit"
# Use hardcoded credentials path for now, consistent with other scripts
credentials_path = CREDENTIALS_PATH
members_data = get_cached_data("attendance_regular", ATTENDANCE_SHEET_ID, get_members_with_fees)
record_step("fetch_members")
if not members_data:
return "No data."
members, sorted_months = members_data
transactions = get_cached_data("payments_transactions", PAYMENTS_SHEET_ID, fetch_sheet_data, PAYMENTS_SHEET_ID, credentials_path)
record_step("fetch_payments")
exceptions = get_cached_data(
"exceptions_dict", PAYMENTS_SHEET_ID, fetch_exceptions,
PAYMENTS_SHEET_ID, credentials_path,
serialize=lambda d: [[list(k), v] for k, v in d.items()],
deserialize=lambda c: {tuple(k): v for k, v in c},
)
record_step("fetch_exceptions")
result = reconcile(members, sorted_months, transactions, exceptions)
record_step("reconcile")
# Format month labels
month_labels = get_month_labels(sorted_months, ADULT_MERGED_MONTHS)
# Filter to adults for the main table
adult_names = sorted([name for name, tier, _ in members if tier == "A"])
formatted_results = []
for name in adult_names:
data = result["members"][name]
row = {"name": name, "months": [], "balance": data["total_balance"], "unpaid_periods": "", "raw_unpaid_periods": ""}
unpaid_months = []
raw_unpaid_months = []
for m in sorted_months:
mdata = data["months"].get(m, {"expected": 0, "original_expected": 0, "paid": 0})
expected = mdata["expected"]
paid = int(mdata["paid"])
status = "empty"
cell_text = "-"
amount_to_pay = 0
if expected > 0:
if paid >= expected:
status = "ok"
cell_text = "OK"
elif paid > 0:
status = "partial"
cell_text = f"{paid}/{expected}"
amount_to_pay = expected - paid
unpaid_months.append(month_labels[m])
raw_unpaid_months.append(datetime.strptime(m, "%Y-%m").strftime("%m/%Y"))
else:
status = "unpaid"
cell_text = f"UNPAID {expected}"
amount_to_pay = expected
unpaid_months.append(month_labels[m])
raw_unpaid_months.append(datetime.strptime(m, "%Y-%m").strftime("%m/%Y"))
elif paid > 0:
status = "surplus"
cell_text = f"PAID {paid}"
row["months"].append({
"text": cell_text,
"status": status,
"amount": amount_to_pay,
"month": month_labels[m],
"raw_month": m
})
row["unpaid_periods"] = ", ".join(unpaid_months) if unpaid_months else ("Older debt" if data["total_balance"] < 0 else "")
row["raw_unpaid_periods"] = "+".join(raw_unpaid_months)
row["balance"] = data["total_balance"] # Updated to use total_balance
formatted_results.append(row)
# Format credits and debts
credits = sorted([{"name": n, "amount": a["total_balance"]} for n, a in result["members"].items() if a["total_balance"] > 0 and n in adult_names], 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 and n in adult_names], key=lambda x: x["name"])
# Format unmatched
unmatched = result["unmatched"]
import json
record_step("process_data")
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"]),
month_labels_json=json.dumps(month_labels),
credits=credits,
debts=debts,
unmatched=unmatched,
attendance_url=attendance_url,
payments_url=payments_url,
bank_account=BANK_ACCOUNT
)
@app.route("/juniors")
def juniors_view():
attendance_url = f"https://docs.google.com/spreadsheets/d/{ATTENDANCE_SHEET_ID}/edit#gid={JUNIOR_SHEET_GID}"
@@ -723,144 +474,6 @@ def juniors_view():
bank_account=BANK_ACCOUNT
)
@app.route("/reconcile-juniors")
def reconcile_juniors_view():
attendance_url = f"https://docs.google.com/spreadsheets/d/{ATTENDANCE_SHEET_ID}/edit#gid={JUNIOR_SHEET_GID}"
payments_url = f"https://docs.google.com/spreadsheets/d/{PAYMENTS_SHEET_ID}/edit"
credentials_path = CREDENTIALS_PATH
junior_members_data = get_cached_data("attendance_juniors", ATTENDANCE_SHEET_ID, get_junior_members_with_fees)
record_step("fetch_junior_members")
if not junior_members_data:
return "No data."
junior_members, sorted_months = junior_members_data
transactions = get_cached_data("payments_transactions", PAYMENTS_SHEET_ID, fetch_sheet_data, PAYMENTS_SHEET_ID, credentials_path)
record_step("fetch_payments")
exceptions = get_cached_data(
"exceptions_dict", PAYMENTS_SHEET_ID, fetch_exceptions,
PAYMENTS_SHEET_ID, credentials_path,
serialize=lambda d: [[list(k), v] for k, v in d.items()],
deserialize=lambda c: {tuple(k): v for k, v in c},
)
record_step("fetch_exceptions")
# Adapt junior tuple format (name, tier, {month: (fee, total_count, adult_count, junior_count)})
# to what match_payments expects: (name, tier, {month: (expected_fee, attendance_count)})
adapted_members = []
for name, tier, fees_dict in junior_members:
adapted_fees = {}
for m, fee_data in fees_dict.items():
if len(fee_data) == 4:
fee, total_count, _, _ = fee_data
adapted_fees[m] = (fee, total_count)
else:
fee, count = fee_data
adapted_fees[m] = (fee, count)
adapted_members.append((name, tier, adapted_fees))
result = reconcile(adapted_members, sorted_months, transactions, exceptions)
record_step("reconcile")
# Format month labels
month_labels = get_month_labels(sorted_months, JUNIOR_MERGED_MONTHS)
# Filter to juniors for the main table
junior_names = sorted([name for name, tier, _ in adapted_members])
junior_members_dict_rc = {name: fees_dict for name, _, fees_dict in junior_members}
formatted_results = []
for name in junior_names:
data = result["members"][name]
row = {"name": name, "months": [], "balance": data["total_balance"], "unpaid_periods": "", "raw_unpaid_periods": ""}
unpaid_months = []
raw_unpaid_months = []
for m in sorted_months:
mdata = data["months"].get(m, {"expected": 0, "original_expected": 0, "paid": 0})
expected = mdata["expected"]
paid = int(mdata["paid"])
orig_fee_data = junior_members_dict_rc.get(name, {}).get(m)
adult_count = 0
junior_count = 0
att_count = 0
if orig_fee_data and len(orig_fee_data) == 4:
_, att_count, adult_count, junior_count = orig_fee_data
breakdown = ""
if adult_count > 0 and junior_count > 0:
breakdown = f":{junior_count}J,{adult_count}A"
elif junior_count > 0:
breakdown = f":{junior_count}J"
elif adult_count > 0:
breakdown = f":{adult_count}A"
count_str = f" ({att_count}{breakdown})" if att_count > 0 else ""
status = "empty"
cell_text = "-"
amount_to_pay = 0
if expected == "?" or (isinstance(expected, int) and expected > 0):
if expected == "?":
status = "empty"
cell_text = f"?{count_str}"
elif paid >= expected:
status = "ok"
cell_text = "OK"
elif paid > 0:
status = "partial"
cell_text = f"{paid}/{expected}"
amount_to_pay = expected - paid
unpaid_months.append(month_labels[m])
raw_unpaid_months.append(datetime.strptime(m, "%Y-%m").strftime("%m/%Y"))
else:
status = "unpaid"
cell_text = f"UNPAID {expected}"
amount_to_pay = expected
unpaid_months.append(month_labels[m])
raw_unpaid_months.append(datetime.strptime(m, "%Y-%m").strftime("%m/%Y"))
elif paid > 0:
status = "surplus"
cell_text = f"PAID {paid}"
row["months"].append({
"text": cell_text,
"status": status,
"amount": amount_to_pay,
"month": month_labels[m],
"raw_month": m
})
row["unpaid_periods"] = ", ".join(unpaid_months) if unpaid_months else ("Older debt" if data["total_balance"] < 0 else "")
row["raw_unpaid_periods"] = "+".join(raw_unpaid_months)
row["balance"] = data["total_balance"]
formatted_results.append(row)
# 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"])
import json
record_step("process_data")
return render_template(
"reconcile-juniors.html",
months=[month_labels[m] for m in sorted_months],
raw_months=sorted_months,
results=formatted_results,
member_data=json.dumps(result["members"]),
month_labels_json=json.dumps(month_labels),
credits=credits,
debts=debts,
unmatched=[],
attendance_url=attendance_url,
payments_url=payments_url,
bank_account=BANK_ACCOUNT
)
@app.route("/payments")
def payments():
attendance_url = f"https://docs.google.com/spreadsheets/d/{ATTENDANCE_SHEET_ID}/edit"