feat: implement pivot-currency model, rbac smart tokens & fix circular imports
This commit is contained in:
97
backend/app/services/cost_service.py
Normal file
97
backend/app/services/cost_service.py
Normal file
@@ -0,0 +1,97 @@
|
||||
import logging
|
||||
from decimal import Decimal
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select, desc
|
||||
from app.models.asset import AssetCost, AssetTelemetry, ExchangeRate
|
||||
from app.models.gamification import UserStats
|
||||
from app.models.system_config import SystemParameter
|
||||
from app.schemas.asset_cost import AssetCostCreate
|
||||
from datetime import datetime
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class CostService:
|
||||
@staticmethod
|
||||
async def get_param(db: AsyncSession, key: str, default: any) -> any:
|
||||
"""Rendszerparaméter lekérése (pl. XP szorzó)."""
|
||||
stmt = select(SystemParameter).where(SystemParameter.key == key)
|
||||
res = await db.execute(stmt)
|
||||
param = res.scalar_one_or_none()
|
||||
return param.value if param else default
|
||||
|
||||
async def record_cost(self, db: AsyncSession, cost_in: AssetCostCreate, user_id: int):
|
||||
"""
|
||||
Költség rögzítése: EUR konverzió + Telemetria + XP.
|
||||
"""
|
||||
try:
|
||||
# 1. Árfolyam lekérése (EUR alapú pivot)
|
||||
# Megkeressük a legfrissebb rögzített árfolyamot a megadott devizához
|
||||
rate_stmt = select(ExchangeRate).where(
|
||||
ExchangeRate.target_currency == cost_in.currency_local
|
||||
).order_by(desc(ExchangeRate.updated_at)).limit(1)
|
||||
|
||||
rate_res = await db.execute(rate_stmt)
|
||||
rate_obj = rate_res.scalar_one_or_none()
|
||||
|
||||
# Ha nincs rögzített árfolyam, 1.0-val számolunk (vagy hibát dobhatunk a konfigurációtól függően)
|
||||
exchange_rate = rate_obj.rate if rate_obj else Decimal("1.0")
|
||||
|
||||
# EUR kalkuláció: Helyi összeg / Árfolyam (Pl. 40000 HUF / 400 = 100 EUR)
|
||||
amt_eur = Decimal(str(cost_in.amount_local)) / exchange_rate if exchange_rate > 0 else Decimal("0")
|
||||
|
||||
# 2. Költség rekord létrehozása
|
||||
new_cost = AssetCost(
|
||||
asset_id=cost_in.asset_id,
|
||||
organization_id=cost_in.organization_id,
|
||||
driver_id=user_id,
|
||||
cost_type=cost_in.cost_type,
|
||||
amount_local=cost_in.amount_local,
|
||||
currency_local=cost_in.currency_local,
|
||||
amount_eur=amt_eur,
|
||||
net_amount_local=cost_in.net_amount_local,
|
||||
vat_rate=cost_in.vat_rate,
|
||||
exchange_rate_used=exchange_rate,
|
||||
mileage_at_cost=cost_in.mileage_at_cost,
|
||||
date=cost_in.date or datetime.now(),
|
||||
data=cost_in.data or {}
|
||||
)
|
||||
db.add(new_cost)
|
||||
|
||||
# 3. Telemetria frissítése (Ha érkezett kilométeróra állás)
|
||||
if cost_in.mileage_at_cost:
|
||||
tel_stmt = select(AssetTelemetry).where(AssetTelemetry.asset_id == cost_in.asset_id)
|
||||
res = await db.execute(tel_stmt)
|
||||
telemetry = res.scalar_one_or_none()
|
||||
|
||||
if telemetry:
|
||||
# Megakadályozzuk a "visszatekerést"
|
||||
if cost_in.mileage_at_cost > (telemetry.current_mileage or 0):
|
||||
telemetry.current_mileage = cost_in.mileage_at_cost
|
||||
else:
|
||||
# Ha még nem volt telemetria adat, létrehozzuk
|
||||
new_telemetry = AssetTelemetry(
|
||||
asset_id=cost_in.asset_id,
|
||||
current_mileage=cost_in.mileage_at_cost
|
||||
)
|
||||
db.add(new_telemetry)
|
||||
|
||||
# 4. Gamification XP jóváírás
|
||||
xp_reward = await self.get_param(db, "XP_PER_COST_LOG", 50)
|
||||
stats_stmt = select(UserStats).where(UserStats.user_id == user_id)
|
||||
stats_res = await db.execute(stats_stmt)
|
||||
user_stats = stats_res.scalar_one_or_none()
|
||||
|
||||
if user_stats:
|
||||
user_stats.total_xp += int(xp_reward)
|
||||
logger.info(f"User {user_id} earned {xp_reward} XP for cost logging.")
|
||||
|
||||
await db.commit()
|
||||
await db.refresh(new_cost)
|
||||
return new_cost
|
||||
|
||||
except Exception as e:
|
||||
await db.rollback()
|
||||
logger.error(f"Error in record_cost: {str(e)}")
|
||||
raise e
|
||||
|
||||
cost_service = CostService()
|
||||
Reference in New Issue
Block a user