Files
WealthySmart/backend/app/api/v1/endpoints/salarios.py
Carlos Escalante 3cdd7d9eab Constraint and query pass: FK delete rules, grouped queries, tiebreakers
- FK rules via migration 7884505b16b3 (verified on dev with a rolled-
  back probe): transactions/recurring items SET NULL on category delete
  (this also fixes the 500 that DELETE /categories raised for in-use
  categories), water readings CASCADE with their receipt. Models declare
  ondelete to stay in sync. (ARCH-09, BE-11)
- compute_actuals_by_source: 3 grouped queries replace 10 single-
  aggregate round-trips; behavior pinned by the existing cycle suite.
  (BE-21)
- compute_cc_by_category prefetches category names (ARCH-15); agent
  pension latest-per-fund resolved in SQL (BE-04); municipal water
  consumption batched into one grouped query (BE-05).
- Deterministic pagination: date DESC, id DESC on all transaction
  listings. (ARCH-12)
- New agent-tool tests (session binding, JSON-safe output, batched
  queries): 64 tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 14:45:46 -06:00

59 lines
1.8 KiB
Python

from datetime import datetime
from typing import Optional
from fastapi import APIRouter, Depends, Query
from pydantic import BaseModel
from sqlmodel import Session, col, func, select
from app.auth import get_current_user
from app.db import get_session
from app.models.models import Transaction, TransactionRead, TransactionType
from app.services.exchange_rate import get_converted_amount_expr
router = APIRouter(prefix="/salarios", tags=["salarios"])
SALARIO_TYPES = (TransactionType.SALARY, TransactionType.DEPOSITO)
class SalariosSummary(BaseModel):
count: int
total_amount: float
latest_date: Optional[datetime] = None
@router.get("/", response_model=list[TransactionRead])
def list_salarios(
limit: int = Query(default=50, le=500),
offset: int = 0,
session: Session = Depends(get_session),
_user: str = Depends(get_current_user),
):
query = (
select(Transaction)
.where(col(Transaction.transaction_type).in_(SALARIO_TYPES))
.order_by(col(Transaction.date).desc(), col(Transaction.id).desc())
.offset(offset)
.limit(limit)
)
return session.exec(query).all()
@router.get("/summary", response_model=SalariosSummary)
def salarios_summary(
session: Session = Depends(get_session),
_user: str = Depends(get_current_user),
):
amount_crc = get_converted_amount_expr(session)
result = session.exec(
select(
func.count(),
func.coalesce(func.sum(amount_crc), 0),
func.max(Transaction.date),
).where(col(Transaction.transaction_type).in_(SALARIO_TYPES))
).first()
return SalariosSummary(
count=result[0] if result else 0,
total_amount=float(result[1]) if result else 0.0,
latest_date=result[2] if result else None,
)