# /opt/docker/dev/service_finder/backend/app/services/config_service.py from typing import Any, Optional, Dict import logging import os import json from decimal import Decimal from datetime import datetime, timezone from sqlalchemy import select, text from sqlalchemy.ext.asyncio import AsyncSession # Modellek importálása a központi helyről from app.models import ExchangeRate, AssetCost, AssetTelemetry from app.models.system.system import SystemParameter, ParameterScope from app.db.session import AsyncSessionLocal logger = logging.getLogger(__name__) class CostService: # A cost_in típusát 'Any'-re állítottam ideiglenesen, hogy ne dobjon újabb ImportError-t a hiányzó Pydantic séma miatt async def record_cost(self, db: AsyncSession, cost_in: Any, user_id: int): try: # 1. Árfolyam lekérése (EUR Pivot) rate_stmt = select(ExchangeRate).where( ExchangeRate.target_currency == cost_in.currency_local ).order_by(ExchangeRate.id.desc()).limit(1) rate_res = await db.execute(rate_stmt) rate_obj = rate_res.scalar_one_or_none() exchange_rate = rate_obj.rate if rate_obj else Decimal("1.0") # 2. Kalkuláció amt_eur = Decimal(str(cost_in.amount_local)) / exchange_rate # 3. Mentés az új AssetCost modellbe 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, exchange_rate_used=exchange_rate, mileage_at_cost=cost_in.mileage_at_cost, date=cost_in.date or datetime.now(timezone.utc) ) db.add(new_cost) # 4. Telemetria szinkron if cost_in.mileage_at_cost: tel_stmt = select(AssetTelemetry).where(AssetTelemetry.asset_id == cost_in.asset_id) telemetry = (await db.execute(tel_stmt)).scalar_one_or_none() if telemetry and cost_in.mileage_at_cost > (telemetry.current_mileage or 0): telemetry.current_mileage = cost_in.mileage_at_cost await db.commit() return new_cost except Exception as e: await db.rollback() raise e class ConfigService: """ Egyszerű konfigurációs szolgáltatás a SystemParameter tábla lekérdezéséhez. Támogatja a különböző típusú értékek lekérését alapértelmezett értékkel. """ @staticmethod async def get(db: AsyncSession, key: str, default: Any = None, scope_level: ParameterScope = ParameterScope.GLOBAL, scope_id: Optional[str] = None) -> Any: """ Általános lekérdezés a SystemParameter táblából. Args: db: AsyncSession key: A konfigurációs kulcs default: Alapértelmezett érték, ha a kulcs nem található scope_level: A paraméter scope-ja (global, country, region, user) scope_id: A scope azonosítója (pl. országkód, user_id) Returns: A talált érték (a megfelelő típusban) vagy a default. """ from sqlalchemy import select, and_, cast, String try: # Convert scope_level to lowercase string for comparison # PostgreSQL enum expects lowercase values, but Python Enum may be uppercase scope_str = scope_level.value.lower() if hasattr(scope_level, 'value') else str(scope_level).lower() # Build query with cast to avoid strict enum type mismatch query = select(SystemParameter).where( and_( SystemParameter.key == key, cast(SystemParameter.scope_level, String) == scope_str, SystemParameter.is_active == True ) ) if scope_id is None: query = query.where(SystemParameter.scope_id.is_(None)) else: query = query.where(SystemParameter.scope_id == scope_id) result = await db.execute(query) param = result.scalar_one_or_none() if param is None: # Opcionálisan beilleszthetjük a default értéket a táblába # await ConfigService._insert_default(db, key, default, scope_level, scope_id) return default # A value oszlop JSONB, lehet dict, list, string, number, bool db_value = param.value # Típuskonverzió a default típusa alapján if default is None: return db_value if isinstance(default, int): if isinstance(db_value, (int, float, str)): try: return int(db_value) except (ValueError, TypeError): return default return default elif isinstance(default, float): if isinstance(db_value, (int, float, str)): try: return float(db_value) except (ValueError, TypeError): return default return default elif isinstance(default, bool): if isinstance(db_value, bool): return db_value elif isinstance(db_value, str): return db_value.lower() in ('true', '1', 'yes', 'on') elif isinstance(db_value, int): return db_value != 0 return default elif isinstance(default, str): if isinstance(db_value, str): return db_value elif isinstance(db_value, (dict, list)): return json.dumps(db_value) else: return str(db_value) elif isinstance(default, dict) and isinstance(db_value, dict): return db_value elif isinstance(default, list) and isinstance(db_value, list): return db_value else: # Egyébként visszaadjuk a db_value-t return db_value except Exception as e: logger.warning(f"ConfigService.get error for key '{key}': {e}") return default async def get_setting(self, db: AsyncSession, key: str, default: Any = None, region_code: Optional[str] = None, org_id: Optional[int] = None, **kwargs) -> Any: """ Általános beállítás lekérése a régi kód kompatibilitásához. Args: db: AsyncSession key: A konfigurációs kulcs default: Alapértelmezett érték region_code: Országkód (pl. "HU") - COUNTRY scope org_id: Szervezet azonosító - ORGANIZATION scope **kwargs: További paraméterek (pl. user_id) Returns: A talált érték vagy default. """ from app.models.system.system import ParameterScope # Scope meghatározása if org_id is not None: scope_level = ParameterScope.ORGANIZATION scope_id = str(org_id) elif region_code is not None: scope_level = ParameterScope.COUNTRY scope_id = region_code else: scope_level = ParameterScope.GLOBAL scope_id = None # További scope-ok (pl. user) a kwargs-ból if 'user_id' in kwargs: scope_level = ParameterScope.USER scope_id = str(kwargs['user_id']) return await ConfigService.get(db, key, default, scope_level, scope_id) @staticmethod async def get_int(db: AsyncSession, key: str, default: int, scope_level: ParameterScope = ParameterScope.GLOBAL, scope_id: Optional[str] = None) -> int: """Egész szám lekérése.""" value = await ConfigService.get(db, key, default, scope_level, scope_id) if isinstance(value, int): return value try: return int(value) except (ValueError, TypeError): return default @staticmethod async def get_str(db: AsyncSession, key: str, default: str, scope_level: ParameterScope = ParameterScope.GLOBAL, scope_id: Optional[str] = None) -> str: """Szöveg lekérése.""" value = await ConfigService.get(db, key, default, scope_level, scope_id) if isinstance(value, str): return value return str(value) @staticmethod async def get_bool(db: AsyncSession, key: str, default: bool, scope_level: ParameterScope = ParameterScope.GLOBAL, scope_id: Optional[str] = None) -> bool: """Logikai érték lekérése.""" value = await ConfigService.get(db, key, default, scope_level, scope_id) if isinstance(value, bool): return value if isinstance(value, str): return value.lower() in ('true', '1', 'yes', 'on') if isinstance(value, int): return value != 0 return default @staticmethod async def get_float(db: AsyncSession, key: str, default: float, scope_level: ParameterScope = ParameterScope.GLOBAL, scope_id: Optional[str] = None) -> float: """Lebegőpontos szám lekérése.""" value = await ConfigService.get(db, key, default, scope_level, scope_id) if isinstance(value, float): return value try: return float(value) except (ValueError, TypeError): return default @staticmethod async def get_json(db: AsyncSession, key: str, default: dict, scope_level: ParameterScope = ParameterScope.GLOBAL, scope_id: Optional[str] = None) -> dict: """JSON objektum lekérése.""" value = await ConfigService.get(db, key, default, scope_level, scope_id) if isinstance(value, dict): return value if isinstance(value, str): try: return json.loads(value) except json.JSONDecodeError: return default return default @staticmethod async def _insert_default(db: AsyncSession, key: str, default: Any, scope_level: ParameterScope, scope_id: Optional[str] = None) -> None: """Opcionális: beszúrja a default értéket a táblába, hogy látható legyen az Admin UI-ban.""" try: from app.models.system.system import SystemParameter param = SystemParameter( key=key, category="auto_inserted", value=default if isinstance(default, (dict, list)) else {"value": default}, scope_level=scope_level, scope_id=scope_id, is_active=True, description=f"Auto-inserted default value for {key}" ) db.add(param) await db.commit() except Exception as e: logger.debug(f"Could not insert default for {key}: {e}") await db.rollback() # A példány, amit a többi modul (pl. az auth_service, ai_service) importálni próbál config = ConfigService()