diff --git a/khata/web/helpers.py b/khata/web/helpers.py index cb42dd1..578609b 100644 --- a/khata/web/helpers.py +++ b/khata/web/helpers.py @@ -79,7 +79,11 @@ def shift_day(d: date, delta: int) -> date: return d + timedelta(days=delta) -# Indian weekly expiry days: NIFTY (NFO) weekly = Thursday (3 in Python weekday). -# BSE/Sensex weekly = Tuesday (1). We mark both. -def is_expiry_day(d: date) -> bool: - return d.weekday() in (1, 3) +# Retained for compatibility — the web UI now derives expiry days from the +# user's own trade history (see queries.expiry_days_in_range). This helper +# defaults to False so templates without an `expiry_days` set don't light up +# days that aren't actually expiries. +def is_expiry_day(d: date, expiry_days: frozenset[date] | None = None) -> bool: + if expiry_days is None: + return False + return d in expiry_days diff --git a/khata/web/main.py b/khata/web/main.py index 598ca6d..84fe495 100644 --- a/khata/web/main.py +++ b/khata/web/main.py @@ -26,7 +26,6 @@ TEMPLATES.env.globals.update( fmt_rupees=fmt_rupees, paise_to_rupees=paise_to_rupees, - is_expiry_day=H.is_expiry_day, month_name=H.month_name, fmt_time_ist=H.fmt_time_ist, today_iso=lambda: H.today_ist().isoformat(), @@ -70,6 +69,9 @@ def calendar_view( grid = H.month_grid(year, month) prev_y, prev_m = H.prev_month(year, month) next_y, next_m = H.next_month(year, month) + expiry_days = Q.expiry_days_in_range( + conn, user_id, date(year, month, 1), date(next_y, next_m, 1) + ) # Month totals total_net = sum((d.get("net_paise") or 0) for d in summary.values()) @@ -84,6 +86,7 @@ def calendar_view( "month": month, "grid": grid, "summary": summary, + "expiry_days": expiry_days, "prev_y": prev_y, "prev_m": prev_m, "next_y": next_y, @@ -109,6 +112,8 @@ def day_view( trades = Q.trades_on_day(conn, user_id, d) totals = Q.day_totals(trades) note = Q.get_daily_note(conn, user_id, d) + # `d` counts as an expiry day if any trade in the user's book expires on it. + expiry_days = Q.expiry_days_in_range(conn, user_id, d, H.shift_day(d, 1)) return TEMPLATES.TemplateResponse( request, "day.html", @@ -120,7 +125,7 @@ def day_view( "totals": totals, "note": note, "endpoint": f"/notes/day/{d.isoformat()}", - "is_expiry": H.is_expiry_day(d), + "is_expiry": d in expiry_days, }, ) diff --git a/khata/web/queries.py b/khata/web/queries.py index 2a09a94..f1464d5 100644 --- a/khata/web/queries.py +++ b/khata/web/queries.py @@ -8,6 +8,35 @@ # ── calendar ─────────────────────────────────────────────────────────── +def expiry_days_in_range( + conn: sqlite3.Connection, user_id: int, start: date, end_excl: date +) -> frozenset[date]: + """Distinct `trades.expiry` values falling inside [start, end_excl). + + Used to mark calendar days the user actually had an expiring contract on, + rather than hardcoding Tue/Thu. Empty set if the user has no derivative + trades or no data yet. + """ + rows = conn.execute( + """ + SELECT DISTINCT expiry + FROM trades + WHERE user_id = ? + AND expiry IS NOT NULL + AND expiry >= ? + AND expiry < ? + """, + (user_id, start.isoformat(), end_excl.isoformat()), + ).fetchall() + out: set[date] = set() + for r in rows: + try: + out.add(date.fromisoformat(r["expiry"])) + except (TypeError, ValueError): + continue + return frozenset(out) + + def month_summary_by_day( conn: sqlite3.Connection, user_id: int, year: int, month: int ) -> dict[str, dict[str, Any]]: diff --git a/khata/web/templates/calendar.html b/khata/web/templates/calendar.html index a00f62e..9bf475f 100644 --- a/khata/web/templates/calendar.html +++ b/khata/web/templates/calendar.html @@ -35,10 +35,10 @@