Feat: separate merged months configs and add 'other' payments to member popups
All checks were successful
Deploy to K8s / deploy (push) Successful in 10s
Build and Push / build (push) Successful in 8s

This commit is contained in:
Jan Novak
2026-03-09 23:07:22 +01:00
parent 75a36eb49b
commit 1257f0d644
5 changed files with 123 additions and 21 deletions

30
app.py
View File

@@ -12,15 +12,15 @@ from flask import Flask, render_template, g, send_file, request
scripts_dir = Path(__file__).parent / "scripts"
sys.path.append(str(scripts_dir))
from attendance import get_members_with_fees, get_junior_members_with_fees, SHEET_ID as ATTENDANCE_SHEET_ID, JUNIOR_SHEET_GID, MERGED_MONTHS
from attendance import get_members_with_fees, get_junior_members_with_fees, SHEET_ID as ATTENDANCE_SHEET_ID, JUNIOR_SHEET_GID, ADULT_MERGED_MONTHS, JUNIOR_MERGED_MONTHS
from match_payments import reconcile, fetch_sheet_data, fetch_exceptions, normalize, DEFAULT_SPREADSHEET_ID as PAYMENTS_SHEET_ID
def get_month_labels(sorted_months):
def get_month_labels(sorted_months, merged_months):
labels = {}
for m in sorted_months:
dt = datetime.strptime(m, "%Y-%m")
# Find which months were merged into m (e.g. 2026-01 is merged into 2026-02)
merged_in = sorted([k for k, v in MERGED_MONTHS.items() if v == m])
merged_in = sorted([k for k, v in merged_months.items() if v == m])
if merged_in:
all_dts = [datetime.strptime(x, "%Y-%m") for x in sorted(merged_in + [m])]
years = {d.year for d in all_dts}
@@ -87,7 +87,7 @@ def fees():
results = [(name, fees) for name, tier, fees in members if tier == "A"]
# Format month labels
month_labels = get_month_labels(sorted_months)
month_labels = get_month_labels(sorted_months, ADULT_MERGED_MONTHS)
monthly_totals = {m: 0 for m in sorted_months}
@@ -144,7 +144,7 @@ def fees_juniors():
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)
month_labels = get_month_labels(sorted_months, JUNIOR_MERGED_MONTHS)
monthly_totals = {m: 0 for m in sorted_months}
@@ -227,7 +227,7 @@ def reconcile_view():
record_step("reconcile")
# Format month labels
month_labels = get_month_labels(sorted_months)
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"])
@@ -235,7 +235,8 @@ def reconcile_view():
formatted_results = []
for name in adult_names:
data = result["members"][name]
row = {"name": name, "months": [], "balance": data["total_balance"]}
row = {"name": name, "months": [], "balance": data["total_balance"], "unpaid_periods": ""}
unpaid_months = []
for m in sorted_months:
mdata = data["months"].get(m, {"expected": 0, "original_expected": 0, "paid": 0})
expected = mdata["expected"]
@@ -253,10 +254,12 @@ def reconcile_view():
status = "partial"
cell_text = f"{paid}/{expected}"
amount_to_pay = expected - paid
unpaid_months.append(month_labels[m])
else:
status = "unpaid"
cell_text = f"UNPAID {expected}"
amount_to_pay = expected
unpaid_months.append(month_labels[m])
elif paid > 0:
status = "surplus"
cell_text = f"PAID {paid}"
@@ -268,12 +271,13 @@ def reconcile_view():
"month": month_labels[m]
})
row["unpaid_periods"] = ", ".join(unpaid_months) if unpaid_months else ("Older debt" if data["total_balance"] < 0 else "")
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], 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"])
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
@@ -330,7 +334,7 @@ def reconcile_juniors_view():
record_step("reconcile")
# Format month labels
month_labels = get_month_labels(sorted_months)
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])
@@ -338,7 +342,8 @@ def reconcile_juniors_view():
formatted_results = []
for name in junior_names:
data = result["members"][name]
row = {"name": name, "months": [], "balance": data["total_balance"]}
row = {"name": name, "months": [], "balance": data["total_balance"], "unpaid_periods": ""}
unpaid_months = []
for m in sorted_months:
mdata = data["months"].get(m, {"expected": 0, "original_expected": 0, "paid": 0})
expected = mdata["expected"]
@@ -359,10 +364,12 @@ def reconcile_juniors_view():
status = "partial"
cell_text = f"{paid}/{expected}"
amount_to_pay = expected - paid
unpaid_months.append(month_labels[m])
else:
status = "unpaid"
cell_text = f"UNPAID {expected}"
amount_to_pay = expected
unpaid_months.append(month_labels[m])
elif paid > 0:
status = "surplus"
cell_text = f"PAID {paid}"
@@ -374,6 +381,7 @@ def reconcile_juniors_view():
"month": month_labels[m]
})
row["unpaid_periods"] = ", ".join(unpaid_months) if unpaid_months else ("Older debt" if data["total_balance"] < 0 else "")
row["balance"] = data["total_balance"]
formatted_results.append(row)