feat: Implement currency management with models, routes, and UI updates; add backfill script for existing records

This commit is contained in:
2025-10-21 10:33:08 +02:00
parent fcea39deb0
commit 672cafa5b9
14 changed files with 478 additions and 10 deletions

View File

@@ -16,7 +16,8 @@ class _CostBase(BaseModel):
scenario_id: int
amount: float
description: Optional[str] = None
currency_code: str = "USD"
currency_code: Optional[str] = "USD"
currency_id: Optional[int] = None
@field_validator("currency_code")
@classmethod
@@ -31,8 +32,12 @@ class CapexCreate(_CostBase):
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
@@ -41,12 +46,41 @@ class OpexCreate(_CostBase):
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)):
db_item = Capex(**item.model_dump())
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)
@@ -61,7 +95,19 @@ def list_capex(db: Session = Depends(get_db)):
# Opex endpoints
@router.post("/opex", response_model=OpexRead)
def create_opex(item: OpexCreate, db: Session = Depends(get_db)):
db_item = Opex(**item.model_dump())
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)