from typing import List, Optional from fastapi import APIRouter, Depends from pydantic import BaseModel, ConfigDict, field_validator from sqlalchemy.orm import Session from models.capex import Capex from models.opex import Opex from routes.dependencies import get_db router = APIRouter(prefix="/api/costs", tags=["Costs"]) # Pydantic schemas for CAPEX and OPEX class _CostBase(BaseModel): scenario_id: int amount: float description: Optional[str] = None currency_code: Optional[str] = "USD" currency_id: Optional[int] = None @field_validator("currency_code") @classmethod def _normalize_currency(cls, value: Optional[str]) -> str: code = (value or "USD").strip().upper() return code[:3] if len(code) > 3 else code class CapexCreate(_CostBase): pass class CapexRead(_CostBase): id: int # use from_attributes so Pydantic reads attributes off SQLAlchemy model model_config = ConfigDict(from_attributes=True) # optionally include nested currency info currency: Optional["CurrencyRead"] = None class OpexCreate(_CostBase): pass class OpexRead(_CostBase): id: int model_config = ConfigDict(from_attributes=True) currency: Optional["CurrencyRead"] = None class CurrencyRead(BaseModel): id: int code: str name: Optional[str] = None symbol: Optional[str] = None is_active: Optional[bool] = True model_config = ConfigDict(from_attributes=True) # forward refs CapexRead.model_rebuild() OpexRead.model_rebuild() # Capex endpoints @router.post("/capex", response_model=CapexRead) def create_capex(item: CapexCreate, db: Session = Depends(get_db)): payload = item.model_dump() # Prefer explicit currency_id if supplied cid = payload.get("currency_id") if not cid: code = (payload.pop("currency_code", "USD") or "USD").strip().upper() currency_cls = __import__( "models.currency", fromlist=["Currency"]).Currency currency = db.query(currency_cls).filter_by(code=code).one_or_none() if currency is None: currency = currency_cls(code=code, name=code, symbol=None) db.add(currency) db.flush() payload["currency_id"] = currency.id db_item = Capex(**payload) db.add(db_item) db.commit() db.refresh(db_item) return db_item @router.get("/capex", response_model=List[CapexRead]) def list_capex(db: Session = Depends(get_db)): return db.query(Capex).all() # Opex endpoints @router.post("/opex", response_model=OpexRead) def create_opex(item: OpexCreate, db: Session = Depends(get_db)): payload = item.model_dump() cid = payload.get("currency_id") if not cid: code = (payload.pop("currency_code", "USD") or "USD").strip().upper() currency_cls = __import__( "models.currency", fromlist=["Currency"]).Currency currency = db.query(currency_cls).filter_by(code=code).one_or_none() if currency is None: currency = currency_cls(code=code, name=code, symbol=None) db.add(currency) db.flush() payload["currency_id"] = currency.id db_item = Opex(**payload) db.add(db_item) db.commit() db.refresh(db_item) return db_item @router.get("/opex", response_model=List[OpexRead]) def list_opex(db: Session = Depends(get_db)): return db.query(Opex).all()