feat: Exclude current month from Pay buttons and balance
Hide Pay/Pay All buttons for months still in progress, exclude current month debt from balance column, and show in-progress month debt in a muted red color. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
102
app.py
102
app.py
@@ -183,7 +183,8 @@ def adults_view():
|
|||||||
|
|
||||||
month_labels = get_month_labels(sorted_months, ADULT_MERGED_MONTHS)
|
month_labels = get_month_labels(sorted_months, ADULT_MERGED_MONTHS)
|
||||||
adult_names = sorted([name for name, tier, _ in members if tier == "A"])
|
adult_names = sorted([name for name, tier, _ in members if tier == "A"])
|
||||||
|
current_month = datetime.now().strftime("%Y-%m")
|
||||||
|
|
||||||
monthly_totals = {m: {"expected": 0, "paid": 0} for m in sorted_months}
|
monthly_totals = {m: {"expected": 0, "paid": 0} for m in sorted_months}
|
||||||
formatted_results = []
|
formatted_results = []
|
||||||
for name in adult_names:
|
for name in adult_names:
|
||||||
@@ -191,6 +192,7 @@ def adults_view():
|
|||||||
row = {"name": name, "months": [], "balance": data["total_balance"], "unpaid_periods": "", "raw_unpaid_periods": ""}
|
row = {"name": name, "months": [], "balance": data["total_balance"], "unpaid_periods": "", "raw_unpaid_periods": ""}
|
||||||
unpaid_months = []
|
unpaid_months = []
|
||||||
raw_unpaid_months = []
|
raw_unpaid_months = []
|
||||||
|
payable_amount = 0
|
||||||
for m in sorted_months:
|
for m in sorted_months:
|
||||||
mdata = data["months"].get(m, {"expected": 0, "original_expected": 0, "attendance_count": 0, "paid": 0, "exception": None})
|
mdata = data["months"].get(m, {"expected": 0, "original_expected": 0, "attendance_count": 0, "paid": 0, "exception": None})
|
||||||
expected = mdata.get("expected", 0)
|
expected = mdata.get("expected", 0)
|
||||||
@@ -223,13 +225,17 @@ def adults_view():
|
|||||||
elif paid > 0:
|
elif paid > 0:
|
||||||
status = "partial"
|
status = "partial"
|
||||||
cell_text = f"{paid}/{fee_display}"
|
cell_text = f"{paid}/{fee_display}"
|
||||||
unpaid_months.append(month_labels[m])
|
if m < current_month:
|
||||||
raw_unpaid_months.append(datetime.strptime(m, "%Y-%m").strftime("%m/%Y"))
|
unpaid_months.append(month_labels[m])
|
||||||
|
raw_unpaid_months.append(datetime.strptime(m, "%Y-%m").strftime("%m/%Y"))
|
||||||
|
payable_amount += amount_to_pay
|
||||||
else:
|
else:
|
||||||
status = "unpaid"
|
status = "unpaid"
|
||||||
cell_text = f"0/{fee_display}"
|
cell_text = f"0/{fee_display}"
|
||||||
unpaid_months.append(month_labels[m])
|
if m < current_month:
|
||||||
raw_unpaid_months.append(datetime.strptime(m, "%Y-%m").strftime("%m/%Y"))
|
unpaid_months.append(month_labels[m])
|
||||||
|
raw_unpaid_months.append(datetime.strptime(m, "%Y-%m").strftime("%m/%Y"))
|
||||||
|
payable_amount += amount_to_pay
|
||||||
elif paid > 0:
|
elif paid > 0:
|
||||||
status = "surplus"
|
status = "surplus"
|
||||||
cell_text = f"PAID {paid}"
|
cell_text = f"PAID {paid}"
|
||||||
@@ -252,11 +258,22 @@ def adults_view():
|
|||||||
"tooltip": tooltip
|
"tooltip": tooltip
|
||||||
})
|
})
|
||||||
|
|
||||||
row["unpaid_periods"] = ", ".join(unpaid_months) if unpaid_months else ("Older debt" if data["total_balance"] < 0 else "")
|
# Compute balance excluding current/future months
|
||||||
|
current_month_debt = 0
|
||||||
|
for m in sorted_months:
|
||||||
|
if m >= current_month:
|
||||||
|
mdata = data["months"].get(m, {"expected": 0, "paid": 0})
|
||||||
|
exp = mdata.get("expected", 0)
|
||||||
|
pd = int(mdata.get("paid", 0))
|
||||||
|
current_month_debt += max(0, exp - pd)
|
||||||
|
settled_balance = data["total_balance"] + current_month_debt
|
||||||
|
|
||||||
|
row["unpaid_periods"] = ", ".join(unpaid_months) if unpaid_months else ("Older debt" if settled_balance < 0 and payable_amount == 0 else "")
|
||||||
row["raw_unpaid_periods"] = "+".join(raw_unpaid_months)
|
row["raw_unpaid_periods"] = "+".join(raw_unpaid_months)
|
||||||
row["balance"] = data["total_balance"]
|
row["balance"] = settled_balance
|
||||||
|
row["payable_amount"] = payable_amount
|
||||||
formatted_results.append(row)
|
formatted_results.append(row)
|
||||||
|
|
||||||
formatted_totals = []
|
formatted_totals = []
|
||||||
for m in sorted_months:
|
for m in sorted_months:
|
||||||
t = monthly_totals[m]
|
t = monthly_totals[m]
|
||||||
@@ -268,14 +285,19 @@ def adults_view():
|
|||||||
status = "unpaid"
|
status = "unpaid"
|
||||||
else:
|
else:
|
||||||
status = "surplus"
|
status = "surplus"
|
||||||
|
|
||||||
formatted_totals.append({
|
formatted_totals.append({
|
||||||
"text": f"{t['paid']} / {t['expected']} CZK",
|
"text": f"{t['paid']} / {t['expected']} CZK",
|
||||||
"status": status
|
"status": status
|
||||||
})
|
})
|
||||||
|
|
||||||
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"])
|
def settled_balance(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"])
|
data = result["members"][name]
|
||||||
|
debt = sum(max(0, data["months"].get(m, {"expected": 0, "paid": 0}).get("expected", 0) - int(data["months"].get(m, {"expected": 0, "paid": 0}).get("paid", 0))) for m in sorted_months if m >= current_month)
|
||||||
|
return data["total_balance"] + debt
|
||||||
|
|
||||||
|
credits = sorted([{"name": n, "amount": settled_balance(n)} for n in adult_names if settled_balance(n) > 0], key=lambda x: x["name"])
|
||||||
|
debts = sorted([{"name": n, "amount": abs(settled_balance(n))} for n in adult_names if settled_balance(n) < 0], key=lambda x: x["name"])
|
||||||
unmatched = result["unmatched"]
|
unmatched = result["unmatched"]
|
||||||
import json
|
import json
|
||||||
|
|
||||||
@@ -294,7 +316,8 @@ def adults_view():
|
|||||||
unmatched=unmatched,
|
unmatched=unmatched,
|
||||||
attendance_url=attendance_url,
|
attendance_url=attendance_url,
|
||||||
payments_url=payments_url,
|
payments_url=payments_url,
|
||||||
bank_account=BANK_ACCOUNT
|
bank_account=BANK_ACCOUNT,
|
||||||
|
current_month=current_month
|
||||||
)
|
)
|
||||||
|
|
||||||
@app.route("/juniors")
|
@app.route("/juniors")
|
||||||
@@ -341,7 +364,8 @@ def juniors_view():
|
|||||||
month_labels = get_month_labels(sorted_months, JUNIOR_MERGED_MONTHS)
|
month_labels = get_month_labels(sorted_months, JUNIOR_MERGED_MONTHS)
|
||||||
junior_names = sorted([name for name, tier, _ in adapted_members])
|
junior_names = sorted([name for name, tier, _ in adapted_members])
|
||||||
junior_members_dict = {name: fees_dict for name, _, fees_dict in junior_members}
|
junior_members_dict = {name: fees_dict for name, _, fees_dict in junior_members}
|
||||||
|
current_month = datetime.now().strftime("%Y-%m")
|
||||||
|
|
||||||
monthly_totals = {m: {"expected": 0, "paid": 0} for m in sorted_months}
|
monthly_totals = {m: {"expected": 0, "paid": 0} for m in sorted_months}
|
||||||
formatted_results = []
|
formatted_results = []
|
||||||
for name in junior_names:
|
for name in junior_names:
|
||||||
@@ -349,6 +373,7 @@ def juniors_view():
|
|||||||
row = {"name": name, "months": [], "balance": data["total_balance"], "unpaid_periods": "", "raw_unpaid_periods": ""}
|
row = {"name": name, "months": [], "balance": data["total_balance"], "unpaid_periods": "", "raw_unpaid_periods": ""}
|
||||||
unpaid_months = []
|
unpaid_months = []
|
||||||
raw_unpaid_months = []
|
raw_unpaid_months = []
|
||||||
|
payable_amount = 0
|
||||||
for m in sorted_months:
|
for m in sorted_months:
|
||||||
mdata = data["months"].get(m, {"expected": 0, "original_expected": 0, "attendance_count": 0, "paid": 0, "exception": None})
|
mdata = data["months"].get(m, {"expected": 0, "original_expected": 0, "attendance_count": 0, "paid": 0, "exception": None})
|
||||||
expected = mdata.get("expected", 0)
|
expected = mdata.get("expected", 0)
|
||||||
@@ -401,14 +426,18 @@ def juniors_view():
|
|||||||
status = "partial"
|
status = "partial"
|
||||||
cell_text = f"{paid}/{fee_display}"
|
cell_text = f"{paid}/{fee_display}"
|
||||||
amount_to_pay = expected - paid
|
amount_to_pay = expected - paid
|
||||||
unpaid_months.append(month_labels[m])
|
if m < current_month:
|
||||||
raw_unpaid_months.append(datetime.strptime(m, "%Y-%m").strftime("%m/%Y"))
|
unpaid_months.append(month_labels[m])
|
||||||
|
raw_unpaid_months.append(datetime.strptime(m, "%Y-%m").strftime("%m/%Y"))
|
||||||
|
payable_amount += amount_to_pay
|
||||||
else:
|
else:
|
||||||
status = "unpaid"
|
status = "unpaid"
|
||||||
cell_text = f"0/{fee_display}"
|
cell_text = f"0/{fee_display}"
|
||||||
amount_to_pay = expected
|
amount_to_pay = expected
|
||||||
unpaid_months.append(month_labels[m])
|
if m < current_month:
|
||||||
raw_unpaid_months.append(datetime.strptime(m, "%Y-%m").strftime("%m/%Y"))
|
unpaid_months.append(month_labels[m])
|
||||||
|
raw_unpaid_months.append(datetime.strptime(m, "%Y-%m").strftime("%m/%Y"))
|
||||||
|
payable_amount += amount_to_pay
|
||||||
elif paid > 0:
|
elif paid > 0:
|
||||||
status = "surplus"
|
status = "surplus"
|
||||||
cell_text = f"PAID {paid}"
|
cell_text = f"PAID {paid}"
|
||||||
@@ -428,11 +457,23 @@ def juniors_view():
|
|||||||
"tooltip": tooltip
|
"tooltip": tooltip
|
||||||
})
|
})
|
||||||
|
|
||||||
row["unpaid_periods"] = ", ".join(unpaid_months) if unpaid_months else ("Older debt" if data["total_balance"] < 0 else "")
|
# Compute balance excluding current/future months
|
||||||
|
current_month_debt = 0
|
||||||
|
for m in sorted_months:
|
||||||
|
if m >= current_month:
|
||||||
|
mdata = data["months"].get(m, {"expected": 0, "paid": 0})
|
||||||
|
exp = mdata.get("expected", 0)
|
||||||
|
if isinstance(exp, int):
|
||||||
|
pd = int(mdata.get("paid", 0))
|
||||||
|
current_month_debt += max(0, exp - pd)
|
||||||
|
settled_balance = data["total_balance"] + current_month_debt
|
||||||
|
|
||||||
|
row["unpaid_periods"] = ", ".join(unpaid_months) if unpaid_months else ("Older debt" if settled_balance < 0 and payable_amount == 0 else "")
|
||||||
row["raw_unpaid_periods"] = "+".join(raw_unpaid_months)
|
row["raw_unpaid_periods"] = "+".join(raw_unpaid_months)
|
||||||
row["balance"] = data["total_balance"]
|
row["balance"] = settled_balance
|
||||||
|
row["payable_amount"] = payable_amount
|
||||||
formatted_results.append(row)
|
formatted_results.append(row)
|
||||||
|
|
||||||
formatted_totals = []
|
formatted_totals = []
|
||||||
for m in sorted_months:
|
for m in sorted_months:
|
||||||
t = monthly_totals[m]
|
t = monthly_totals[m]
|
||||||
@@ -444,15 +485,27 @@ def juniors_view():
|
|||||||
status = "unpaid"
|
status = "unpaid"
|
||||||
else:
|
else:
|
||||||
status = "surplus"
|
status = "surplus"
|
||||||
|
|
||||||
formatted_totals.append({
|
formatted_totals.append({
|
||||||
"text": f"{t['paid']} / {t['expected']} CZK",
|
"text": f"{t['paid']} / {t['expected']} CZK",
|
||||||
"status": status
|
"status": status
|
||||||
})
|
})
|
||||||
|
|
||||||
# Format credits and debts
|
# 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"])
|
def junior_settled_balance(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"])
|
data = result["members"][name]
|
||||||
|
debt = 0
|
||||||
|
for m in sorted_months:
|
||||||
|
if m >= current_month:
|
||||||
|
mdata = data["months"].get(m, {"expected": 0, "paid": 0})
|
||||||
|
exp = mdata.get("expected", 0)
|
||||||
|
if isinstance(exp, int):
|
||||||
|
debt += max(0, exp - int(mdata.get("paid", 0)))
|
||||||
|
return data["total_balance"] + debt
|
||||||
|
|
||||||
|
junior_all_names = [name for name, _, _ in adapted_members]
|
||||||
|
credits = sorted([{"name": n, "amount": junior_settled_balance(n)} for n in junior_all_names if junior_settled_balance(n) > 0], key=lambda x: x["name"])
|
||||||
|
debts = sorted([{"name": n, "amount": abs(junior_settled_balance(n))} for n in junior_all_names if junior_settled_balance(n) < 0], key=lambda x: x["name"])
|
||||||
unmatched = result["unmatched"]
|
unmatched = result["unmatched"]
|
||||||
import json
|
import json
|
||||||
|
|
||||||
@@ -471,7 +524,8 @@ def juniors_view():
|
|||||||
unmatched=unmatched,
|
unmatched=unmatched,
|
||||||
attendance_url=attendance_url,
|
attendance_url=attendance_url,
|
||||||
payments_url=payments_url,
|
payments_url=payments_url,
|
||||||
bank_account=BANK_ACCOUNT
|
bank_account=BANK_ACCOUNT,
|
||||||
|
current_month=current_month
|
||||||
)
|
)
|
||||||
|
|
||||||
@app.route("/payments")
|
@app.route("/payments")
|
||||||
|
|||||||
@@ -167,6 +167,12 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cell-unpaid-current {
|
||||||
|
color: #994444;
|
||||||
|
background-color: rgba(153, 68, 68, 0.05);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
.cell-overridden {
|
.cell-overridden {
|
||||||
color: #ffa500 !important;
|
color: #ffa500 !important;
|
||||||
}
|
}
|
||||||
@@ -532,9 +538,9 @@
|
|||||||
</td>
|
</td>
|
||||||
{% for cell in row.months %}
|
{% for cell in row.months %}
|
||||||
<td data-month-idx="{{ loop.index0 }}" title="{{ cell.tooltip }}"
|
<td data-month-idx="{{ loop.index0 }}" title="{{ cell.tooltip }}"
|
||||||
class="{% if cell.status == 'empty' %}cell-empty{% elif cell.status == 'unpaid' or cell.status == 'partial' %}cell-unpaid{% elif cell.status == 'ok' %}cell-ok{% endif %}{% if cell.overridden %} cell-overridden{% endif %}">
|
class="{% if cell.status == 'empty' %}cell-empty{% elif (cell.status == 'unpaid' or cell.status == 'partial') and cell.raw_month >= current_month %}cell-unpaid-current{% elif cell.status == 'unpaid' or cell.status == 'partial' %}cell-unpaid{% elif cell.status == 'ok' %}cell-ok{% endif %}{% if cell.overridden %} cell-overridden{% endif %}">
|
||||||
{{ cell.text }}
|
{{ cell.text }}
|
||||||
{% if cell.status == 'unpaid' or cell.status == 'partial' %}
|
{% if (cell.status == 'unpaid' or cell.status == 'partial') and cell.raw_month < current_month %}
|
||||||
<button class="pay-btn"
|
<button class="pay-btn"
|
||||||
onclick="showPayQR('{{ row.name|e }}', {{ cell.amount }}, '{{ cell.month|e }}', '{{ cell.raw_month }}')">Pay</button>
|
onclick="showPayQR('{{ row.name|e }}', {{ cell.amount }}, '{{ cell.month|e }}', '{{ cell.raw_month }}')">Pay</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -542,9 +548,9 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
<td class="{% if row.balance > 0 %}balance-pos{% elif row.balance < 0 %}balance-neg{% endif %}" style="position: relative;">
|
<td class="{% if row.balance > 0 %}balance-pos{% elif row.balance < 0 %}balance-neg{% endif %}" style="position: relative;">
|
||||||
{{ "%+d"|format(row.balance) if row.balance != 0 else "0" }}
|
{{ "%+d"|format(row.balance) if row.balance != 0 else "0" }}
|
||||||
{% if row.balance < 0 %}
|
{% if row.payable_amount > 0 %}
|
||||||
<button class="pay-btn"
|
<button class="pay-btn"
|
||||||
onclick="showPayQR('{{ row.name|e }}', {{ -row.balance }}, '{{ row.unpaid_periods|e }}', '{{ row.raw_unpaid_periods|e }}')">Pay All</button>
|
onclick="showPayQR('{{ row.name|e }}', {{ row.payable_amount }}, '{{ row.unpaid_periods|e }}', '{{ row.raw_unpaid_periods|e }}')">Pay All</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -167,6 +167,12 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cell-unpaid-current {
|
||||||
|
color: #994444;
|
||||||
|
background-color: rgba(153, 68, 68, 0.05);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
.cell-overridden {
|
.cell-overridden {
|
||||||
color: #ffa500 !important;
|
color: #ffa500 !important;
|
||||||
}
|
}
|
||||||
@@ -532,9 +538,9 @@
|
|||||||
</td>
|
</td>
|
||||||
{% for cell in row.months %}
|
{% for cell in row.months %}
|
||||||
<td data-month-idx="{{ loop.index0 }}" title="{{ cell.tooltip }}"
|
<td data-month-idx="{{ loop.index0 }}" title="{{ cell.tooltip }}"
|
||||||
class="{% if cell.status == 'empty' %}cell-empty{% elif cell.status == 'unpaid' or cell.status == 'partial' %}cell-unpaid{% elif cell.status == 'ok' %}cell-ok{% endif %}{% if cell.overridden %} cell-overridden{% endif %}">
|
class="{% if cell.status == 'empty' %}cell-empty{% elif (cell.status == 'unpaid' or cell.status == 'partial') and cell.raw_month >= current_month %}cell-unpaid-current{% elif cell.status == 'unpaid' or cell.status == 'partial' %}cell-unpaid{% elif cell.status == 'ok' %}cell-ok{% endif %}{% if cell.overridden %} cell-overridden{% endif %}">
|
||||||
{{ cell.text }}
|
{{ cell.text }}
|
||||||
{% if cell.status == 'unpaid' or cell.status == 'partial' %}
|
{% if (cell.status == 'unpaid' or cell.status == 'partial') and cell.raw_month < current_month %}
|
||||||
<button class="pay-btn"
|
<button class="pay-btn"
|
||||||
onclick="showPayQR('{{ row.name|e }}', {{ cell.amount }}, '{{ cell.month|e }}', '{{ cell.raw_month }}')">Pay</button>
|
onclick="showPayQR('{{ row.name|e }}', {{ cell.amount }}, '{{ cell.month|e }}', '{{ cell.raw_month }}')">Pay</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -542,9 +548,9 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
<td class="{% if row.balance > 0 %}balance-pos{% elif row.balance < 0 %}balance-neg{% endif %}" style="position: relative;">
|
<td class="{% if row.balance > 0 %}balance-pos{% elif row.balance < 0 %}balance-neg{% endif %}" style="position: relative;">
|
||||||
{{ "%+d"|format(row.balance) if row.balance != 0 else "0" }}
|
{{ "%+d"|format(row.balance) if row.balance != 0 else "0" }}
|
||||||
{% if row.balance < 0 %}
|
{% if row.payable_amount > 0 %}
|
||||||
<button class="pay-btn"
|
<button class="pay-btn"
|
||||||
onclick="showPayQR('{{ row.name|e }}', {{ -row.balance }}, '{{ row.unpaid_periods|e }}', '{{ row.raw_unpaid_periods|e }}')">Pay All</button>
|
onclick="showPayQR('{{ row.name|e }}', {{ row.payable_amount }}, '{{ row.unpaid_periods|e }}', '{{ row.raw_unpaid_periods|e }}')">Pay All</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
Reference in New Issue
Block a user