Files
bacchus/apps/backend/app/api/ledger.py
2025-09-28 19:13:01 +02:00

127 lines
4.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from typing import List, Optional, Literal, Set
from fastapi import APIRouter, Depends, Query
from pydantic import BaseModel
from sqlalchemy.orm import Session
from fastapi import Body
from app.core.database import get_db
from app.core.auth import get_current_user
from app.models.user import User
from app.models.topup import Topup
from app.models.booking import Booking
router = APIRouter(prefix="/ledger", tags=["ledger"])
class LedgerItem(BaseModel):
id: str
type: Literal["topup", "booking"]
amount_cents: int # topup > 0, booking < 0
created_at: str # ISO-String
status: Optional[str] = None # nur für topup: pending | confirmed | rejected
note: Optional[str] = None # nur für topup/admin: Notiz / 5-stelliger Code
product_id: Optional[int] = None
product_name: Optional[str] = None
@router.get("/me", response_model=List[LedgerItem])
def list_ledger_me(
limit: int = Query(100, ge=1, le=1000),
offset: int = Query(0, ge=0),
types: str = Query("topup,booking", description="CSV: topup,booking"),
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
"""
Vereinheitlichte Übersicht über Aufladungen (Top-ups) und Getränkebuchungen.
Global nach Datum sortiert, danach paginiert.
"""
want: Set[str] = {t.strip().lower() for t in types.split(",") if t.strip()}
rows: list[LedgerItem] = []
if "topup" in want:
topups = (
db.query(Topup)
.filter(Topup.user_id == current_user.id)
.order_by(Topup.id.desc())
.all()
)
for t in topups:
rows.append(
LedgerItem(
id=f"topup-{t.id}",
type="topup",
amount_cents=int(t.amount_cents or 0),
created_at=(t.created_at.isoformat() if getattr(t, "created_at", None) else ""),
status=(str(t.status.value) if hasattr(t, "status") and t.status is not None else None),
note=(t.note or None),
)
)
if "booking" in want:
bookings = (
db.query(Booking)
.filter(Booking.user_id == current_user.id)
.order_by(Booking.id.desc())
.all()
)
for b in bookings:
total = getattr(b, "total_cents", None)
if total is None:
price = getattr(b, "price_cents", None)
if price is None and hasattr(b, "product") and getattr(b, "product") is not None:
price = getattr(b.product, "price_cents", 0)
total = (price or 0) * (getattr(b, "amount", 1) or 1)
rows.append(
LedgerItem(
id=f"booking-{b.id}",
type="booking",
amount_cents=-int(total or 0), # Buchungen als negative Werte
created_at=(b.created_at.isoformat() if getattr(b, "created_at", None) else ""),
product_id=getattr(b, "product_id", None),
product_name=(getattr(b.product, "name", None) if hasattr(b, "product") and getattr(b, "product") is not None else None),
)
)
# global sortieren und erst dann paginieren
rows.sort(key=lambda r: r.created_at, reverse=True)
return rows[offset : offset + limit]
# ------- ab hier NEU: auf Modulebene, nicht eingerückt! -------
class LedgerAdd(BaseModel):
amount_cents: int # darf +/ sein (Korrektur/Einlage)
note: Optional[str] = None
@router.post("/", response_model=LedgerItem)
def add_ledger_entry(
payload: LedgerAdd = Body(...),
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
# Topup erzeugen (als Kassenbewegung)
try:
from app.models.topup import TopupStatus # falls Enum existiert
status_value = TopupStatus.confirmed
except Exception:
status_value = "confirmed"
t = Topup(
user_id=current_user.id,
amount_cents=int(payload.amount_cents or 0),
note=(payload.note or "Manuelle Kassenkorrektur"),
status=status_value,
)
db.add(t)
db.commit()
db.refresh(t)
return LedgerItem(
id=f"topup-{t.id}",
type="topup",
amount_cents=int(t.amount_cents or 0),
created_at=(t.created_at.isoformat() if getattr(t, "created_at", None) else ""),
status=(str(t.status.value) if hasattr(t, "status") and t.status is not None else "confirmed"),
note=(t.note or None),
)