Files
boker/ledger_repositories.py

213 lines
7.3 KiB
Python

#!/usr/bin/env python3
from __future__ import annotations
from db import db
if db is None: # pragma: no cover - imported only when DB dependencies exist.
raise RuntimeError("Database dependencies are not installed.")
from db_models import ( # noqa: E402
CANONICAL_EVENT_TYPES,
LedgerEvent,
Player,
PokerSession,
User,
canonical_event_type,
)
from storage import EventRow # noqa: E402
def session_event_ref(session: PokerSession) -> str:
return f"{session.session_date.isoformat()}-{session.sequence_on_date:02d}"
def append_ledger_event(
league_id: str,
session_id: str,
event_type: str,
amount_cents: int,
created_by_user_id: str,
player_id: str | None = None,
note: str | None = None,
legacy_event_id: str | None = None,
legacy_player_name: str | None = None,
) -> LedgerEvent:
canonical_type = canonical_event_type(event_type)
if canonical_type not in CANONICAL_EVENT_TYPES:
raise ValueError(f"Unsupported event type: {event_type}")
if canonical_type in {"note", "session_open", "session_close"}:
amount_cents = 0
if player_id is None and canonical_type not in {"note", "session_open", "session_close"}:
raise ValueError(f"Event type {canonical_type} requires a player.")
event = LedgerEvent(
league_id=league_id,
session_id=session_id,
player_id=player_id,
event_type=canonical_type,
amount_cents=amount_cents,
note=note.strip() if note else None,
created_by_user_id=created_by_user_id,
legacy_event_id=legacy_event_id,
legacy_player_name=legacy_player_name,
)
db.session.add(event)
return event
def list_ledger_events_for_league(league_id: str) -> list[LedgerEvent]:
return (
LedgerEvent.query.filter_by(league_id=league_id, voided_at=None)
.order_by(LedgerEvent.created_at.asc(), LedgerEvent.id.asc())
.all()
)
def list_ledger_events_for_session(league_id: str, session_id: str) -> list[LedgerEvent]:
return (
LedgerEvent.query.filter_by(
league_id=league_id,
session_id=session_id,
voided_at=None,
)
.order_by(LedgerEvent.created_at.asc(), LedgerEvent.id.asc())
.all()
)
def _event_row(event: LedgerEvent, session: PokerSession, player: Player | None, user: User | None) -> EventRow:
return EventRow(
id=event.id,
created_at=event.created_at.isoformat() if event.created_at else "",
session_id=session_event_ref(session),
session_date=session.session_date.isoformat(),
player_name=(player.display_name if player is not None else event.legacy_player_name or ""),
event_type=event.event_type,
amount_cents=event.amount_cents,
note=event.note or "",
actor=user.email if user is not None else "",
voided_at=event.voided_at.isoformat() if event.voided_at else "",
void_reason=event.void_reason or "",
)
def list_all_event_rows_for_league(league_id: str) -> list[EventRow]:
"""All events including voided — for display feeds only, not accounting."""
rows = (
db.session.query(LedgerEvent, PokerSession, Player, User)
.join(
PokerSession,
(PokerSession.league_id == LedgerEvent.league_id)
& (PokerSession.id == LedgerEvent.session_id),
)
.outerjoin(
Player,
(Player.league_id == LedgerEvent.league_id)
& (Player.id == LedgerEvent.player_id),
)
.outerjoin(User, User.id == LedgerEvent.created_by_user_id)
.filter(LedgerEvent.league_id == league_id)
.order_by(
PokerSession.session_date.asc(),
PokerSession.sequence_on_date.asc(),
LedgerEvent.created_at.asc(),
LedgerEvent.id.asc(),
)
.all()
)
return [_event_row(event, session, player, user) for event, session, player, user in rows]
def void_ledger_event(event_id: str, league_id: str, user_id: str, reason: str) -> LedgerEvent:
from datetime import datetime, timezone
event = LedgerEvent.query.filter_by(id=event_id, league_id=league_id).first()
if event is None:
raise ValueError("Event not found.")
if event.voided_at is not None:
raise ValueError("Event is already voided.")
event.voided_at = datetime.now(timezone.utc)
event.voided_by_user_id = user_id
event.void_reason = reason.strip() or "Voided by owner."
return event
def list_event_rows_for_league(league_id: str) -> list[EventRow]:
rows = (
db.session.query(LedgerEvent, PokerSession, Player, User)
.join(
PokerSession,
(PokerSession.league_id == LedgerEvent.league_id)
& (PokerSession.id == LedgerEvent.session_id),
)
.outerjoin(
Player,
(Player.league_id == LedgerEvent.league_id)
& (Player.id == LedgerEvent.player_id),
)
.outerjoin(User, User.id == LedgerEvent.created_by_user_id)
.filter(
LedgerEvent.league_id == league_id,
LedgerEvent.voided_at.is_(None),
)
.order_by(
PokerSession.session_date.asc(),
PokerSession.sequence_on_date.asc(),
LedgerEvent.created_at.asc(),
LedgerEvent.id.asc(),
)
.all()
)
return [_event_row(event, session, player, user) for event, session, player, user in rows]
def list_all_event_rows_for_session(league_id: str, session_id: str) -> list[EventRow]:
"""All events for a session including voided — for display only, not accounting."""
rows = (
db.session.query(LedgerEvent, PokerSession, Player, User)
.join(
PokerSession,
(PokerSession.league_id == LedgerEvent.league_id)
& (PokerSession.id == LedgerEvent.session_id),
)
.outerjoin(
Player,
(Player.league_id == LedgerEvent.league_id)
& (Player.id == LedgerEvent.player_id),
)
.outerjoin(User, User.id == LedgerEvent.created_by_user_id)
.filter(
LedgerEvent.league_id == league_id,
LedgerEvent.session_id == session_id,
)
.order_by(LedgerEvent.created_at.asc(), LedgerEvent.id.asc())
.all()
)
return [_event_row(event, session, player, user) for event, session, player, user in rows]
def list_event_rows_for_session(league_id: str, session_id: str) -> list[EventRow]:
rows = (
db.session.query(LedgerEvent, PokerSession, Player, User)
.join(
PokerSession,
(PokerSession.league_id == LedgerEvent.league_id)
& (PokerSession.id == LedgerEvent.session_id),
)
.outerjoin(
Player,
(Player.league_id == LedgerEvent.league_id)
& (Player.id == LedgerEvent.player_id),
)
.outerjoin(User, User.id == LedgerEvent.created_by_user_id)
.filter(
LedgerEvent.league_id == league_id,
LedgerEvent.session_id == session_id,
LedgerEvent.voided_at.is_(None),
)
.order_by(LedgerEvent.created_at.asc(), LedgerEvent.id.asc())
.all()
)
return [_event_row(event, session, player, user) for event, session, player, user in rows]