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

@@ -1,3 +1,4 @@
from sqlalchemy import event, text
from sqlalchemy import Column, Integer, Float, String, ForeignKey
from sqlalchemy.orm import relationship
from config.database import Base
@@ -10,12 +11,47 @@ class Opex(Base):
scenario_id = Column(Integer, ForeignKey("scenario.id"), nullable=False)
amount = Column(Float, nullable=False)
description = Column(String, nullable=True)
currency_code = Column(String(3), nullable=False, default="USD")
currency_id = Column(Integer, ForeignKey("currency.id"), nullable=False)
scenario = relationship("Scenario", back_populates="opex_items")
currency = relationship("Currency", back_populates="opex_items")
def __repr__(self):
return (
f"<Opex id={self.id} scenario_id={self.scenario_id} "
f"amount={self.amount} currency={self.currency_code}>"
f"amount={self.amount} currency_id={self.currency_id}>"
)
@property
def currency_code(self) -> str:
return self.currency.code if self.currency else None
@currency_code.setter
def currency_code(self, value: str) -> None:
setattr(self, "_currency_code_pending",
(value or "USD").strip().upper())
def _resolve_currency_opex(mapper, connection, target):
if getattr(target, "currency_id", None):
return
code = getattr(target, "_currency_code_pending", None) or "USD"
row = connection.execute(text("SELECT id FROM currency WHERE code = :code"), {
"code": code}).fetchone()
if row:
cid = row[0]
else:
res = connection.execute(
text("INSERT INTO currency (code, name, symbol, is_active) VALUES (:code, :name, :symbol, :active)"),
{"code": code, "name": code, "symbol": None, "active": True},
)
try:
cid = res.lastrowid
except Exception:
cid = connection.execute(text("SELECT id FROM currency WHERE code = :code"), {
"code": code}).scalar()
target.currency_id = cid
event.listen(Opex, "before_insert", _resolve_currency_opex)
event.listen(Opex, "before_update", _resolve_currency_opex)