refactor: code quality improvements across the backend
- Remove insecure SSL verification bypass in attendance.py - Add gunicorn as production WSGI server (Dockerfile + entrypoint) - Fix silent data loss in reconciliation (log + surface unmatched members) - Add required column validation in payment sheet parsing - Add input validation on /qr route (account format, amount bounds, SPD injection) - Centralize configuration into scripts/config.py - Extract credentials path to env-configurable constant - Hide unmatched transactions from reconcile-juniors page - Fix test mocks to bypass cache layer (all 8 tests now pass reliably) - Add pytest + pytest-cov dev dependencies - Fix typo "Inffering" in infer_payments.py - Update CLAUDE.md to reflect current project state Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -3,12 +3,15 @@
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import urllib.request
|
||||
from datetime import datetime, timedelta
|
||||
from html.parser import HTMLParser
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
from attendance import get_members_with_fees
|
||||
from czech_utils import normalize, parse_month_references
|
||||
from sync_fio_to_sheets import get_sheets_service, DEFAULT_SPREADSHEET_ID
|
||||
@@ -203,7 +206,7 @@ def fetch_sheet_data(spreadsheet_id: str, credentials_path: str) -> list[dict]:
|
||||
return -1
|
||||
|
||||
idx_date = get_col_index("Date")
|
||||
idx_amount = get_col_index("Amount")
|
||||
idx_amount = get_col_index("Amount")
|
||||
idx_manual = get_col_index("manual fix")
|
||||
idx_person = get_col_index("Person")
|
||||
idx_purpose = get_col_index("Purpose")
|
||||
@@ -212,6 +215,11 @@ def fetch_sheet_data(spreadsheet_id: str, credentials_path: str) -> list[dict]:
|
||||
idx_message = get_col_index("Message")
|
||||
idx_bank_id = get_col_index("Bank ID")
|
||||
|
||||
required = {"Date": idx_date, "Amount": idx_amount, "Person": idx_person, "Purpose": idx_purpose}
|
||||
missing = [name for name, idx in required.items() if idx == -1]
|
||||
if missing:
|
||||
raise ValueError(f"Required columns missing from payments sheet: {', '.join(missing)}. Found headers: {header}")
|
||||
|
||||
transactions = []
|
||||
for row in rows[1:]:
|
||||
def get_val(idx):
|
||||
@@ -381,12 +389,13 @@ def reconcile(
|
||||
per_allocation = amount / num_allocations if num_allocations > 0 else 0
|
||||
|
||||
for member_name, confidence in matched_members:
|
||||
# If we matched via sheet 'Person' column, name might be partial or have markers
|
||||
# but usually it's the exact member name from get_members_with_fees.
|
||||
# Let's ensure it exists in our ledger.
|
||||
if member_name not in ledger:
|
||||
# Try matching by base name if it was Jan Novak (Kačerr) etc.
|
||||
pass
|
||||
logger.warning(
|
||||
"Payment matched to unknown member %r (tx: %s, %s) — adding to unmatched",
|
||||
member_name, tx.get("date", "?"), tx.get("message", "?"),
|
||||
)
|
||||
unmatched.append(tx)
|
||||
continue
|
||||
|
||||
for month_key in matched_months:
|
||||
entry = {
|
||||
@@ -396,7 +405,7 @@ def reconcile(
|
||||
"message": tx["message"],
|
||||
"confidence": confidence,
|
||||
}
|
||||
if month_key in ledger.get(member_name, {}):
|
||||
if month_key in ledger[member_name]:
|
||||
ledger[member_name][month_key]["paid"] += per_allocation
|
||||
ledger[member_name][month_key]["transactions"].append(entry)
|
||||
else:
|
||||
|
||||
Reference in New Issue
Block a user