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

View File

@@ -17,7 +17,12 @@ JUNIOR_FEE_DEFAULT = 500 # CZK for 2+ practices
JUNIOR_MONTHLY_RATE = {
"2025-09": 250
}
MERGED_MONTHS = {
ADULT_MERGED_MONTHS = {
#"2025-12": "2026-01", # keys are merged into values
#"2025-09": "2025-10"
}
JUNIOR_MERGED_MONTHS = {
"2025-12": "2026-01", # keys are merged into values
"2025-09": "2025-10"
}
@@ -65,13 +70,13 @@ def parse_dates(header_row: list[str]) -> list[tuple[int, datetime]]:
return dates
def group_by_month(dates: list[tuple[int, datetime]]) -> dict[str, list[int]]:
def group_by_month(dates: list[tuple[int, datetime]], merged_months: dict[str, str]) -> dict[str, list[int]]:
"""Group column indices by YYYY-MM, handling merged months."""
months: dict[str, list[int]] = {}
for col, dt in dates:
key = dt.strftime("%Y-%m")
# Apply merged month mapping if configured
target_key = MERGED_MONTHS.get(key, key)
target_key = merged_months.get(key, key)
months.setdefault(target_key, []).append(col)
return months
@@ -172,7 +177,7 @@ def get_members_with_fees() -> tuple[list[tuple[str, str, dict[str, int]]], list
if not dates:
return [], []
months = group_by_month(dates)
months = group_by_month(dates, ADULT_MERGED_MONTHS)
sorted_months = sorted(months.keys())
members_raw = get_members(rows)
@@ -211,8 +216,8 @@ def get_junior_members_with_fees() -> tuple[list[tuple[str, str, dict[str, tuple
main_dates = parse_dates(main_rows[0])
junior_dates = parse_dates(junior_rows[0])
main_months = group_by_month(main_dates)
junior_months = group_by_month(junior_dates)
main_months = group_by_month(main_dates, JUNIOR_MERGED_MONTHS)
junior_months = group_by_month(junior_dates, JUNIOR_MERGED_MONTHS)
# Collect all unique sorted months
all_months = set(main_months.keys()).union(set(junior_months.keys()))

View File

@@ -290,9 +290,11 @@ def reconcile(
# Initialize ledger
ledger: dict[str, dict[str, dict]] = {}
other_ledger: dict[str, list] = {}
exceptions = exceptions or {}
for name in member_names:
ledger[name] = {}
other_ledger[name] = []
for m in sorted_months:
# Robust normalization for lookup
norm_name = normalize(name)
@@ -328,12 +330,13 @@ def reconcile(
# Strip markers like [?]
person_str = re.sub(r"\[\?\]\s*", "", person_str)
is_other = purpose_str.lower().startswith("other:")
if person_str and purpose_str:
# We have pre-matched data (either from script or manual)
# Support multiple people/months in the comma-separated string
matched_members = [(p.strip(), "auto") for p in person_str.split(",") if p.strip()]
matched_months = [m.strip() for m in purpose_str.split(",") if m.strip()]
matched_months = [purpose_str] if is_other else [m.strip() for m in purpose_str.split(",") if m.strip()]
# Use Inferred Amount if available, otherwise bank Amount
amount = tx.get("inferred_amount")
@@ -359,6 +362,21 @@ def reconcile(
continue
# Allocate payment across matched members and months
if is_other:
num_allocations = len(matched_members)
per_allocation = amount / num_allocations if num_allocations > 0 else 0
for member_name, confidence in matched_members:
if member_name in other_ledger:
other_ledger[member_name].append({
"amount": per_allocation,
"date": tx["date"],
"sender": tx["sender"],
"message": tx["message"],
"purpose": purpose_str,
"confidence": confidence,
})
continue
num_allocations = len(matched_members) * len(matched_months)
per_allocation = amount / num_allocations if num_allocations > 0 else 0
@@ -399,6 +417,7 @@ def reconcile(
name: {
"tier": member_tiers[name],
"months": ledger[name],
"other_transactions": other_ledger[name],
"total_balance": final_balances[name]
}
for name in member_names