# /opt/docker/dev/service_finder/backend/app/services/cost_service.py import uuid import logging from decimal import Decimal from typing import Any, Dict from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select, desc, func from app.models import AssetCost, AssetTelemetry, ExchangeRate from app.services.gamification_service import GamificationService from app.services.config_service import config from app.schemas.asset_cost import AssetCostCreate from datetime import datetime logger = logging.getLogger(__name__) class CostService: """ Industrial Cost & Telemetry Service. Összeköti a pénzügyi kiadásokat, az OCR bizonylatokat és a jármű állapotát. """ async def record_cost(self, db: AsyncSession, cost_in: AssetCostCreate, user_id: int): """ Teljes körű költségrögzítés: Konverzió + Telemetria + OCR + XP. """ try: # 1. Dinamikus konfiguráció lekérése base_currency = await config.get_setting(db, "finance_base_currency", default="EUR") base_xp = await config.get_setting(db, "xp_per_cost_log", default=50) ocr_multiplier = await config.get_setting(db, "xp_multiplier_ocr_cost", default=1.5) # 2. Intelligens Árfolyamkezelés exchange_rate = Decimal("1.0") if cost_in.currency_local != base_currency: 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() exchange_rate = rate_obj.rate if rate_obj else Decimal("1.0") amt_base = Decimal(str(cost_in.amount_local)) / exchange_rate if exchange_rate > 0 else Decimal("0") # 3. Költség rekord rögzítése (Kapcsolva a Robot 1 OCR dokumentumához) 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_base, 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(), # OCR Kapcsolat document_id=cost_in.document_id, is_ai_generated=cost_in.document_id is not None, data=cost_in.data or {} ) db.add(new_cost) # 4. Automatikus Telemetria (Kilométeróra frissítés) if cost_in.mileage_at_cost: await self._sync_telemetry(db, cost_in.asset_id, cost_in.mileage_at_cost) # 5. Gamification (Értékesebb az adat, ha van róla fotó/OCR) final_xp = base_xp if new_cost.is_ai_generated: final_xp = int(base_xp * float(ocr_multiplier)) await GamificationService.award_points( db, user_id=user_id, amount=final_xp, reason=f"EXPENSE_LOG_{cost_in.cost_type}" ) await db.commit() await db.refresh(new_cost) return new_cost except Exception as e: await db.rollback() logger.error(f"CostService Error: {e}") raise e async def _sync_telemetry(self, db: AsyncSession, asset_id: int, mileage: int): """ Segédfüggvény: Biztonságos óraállás frissítés. """ stmt = select(AssetTelemetry).where(AssetTelemetry.asset_id == asset_id) res = await db.execute(stmt) telemetry = res.scalar_one_or_none() if telemetry: # Csak akkor frissítünk, ha az új érték nagyobb (nincs visszatekerés) if mileage > (telemetry.current_mileage or 0): telemetry.current_mileage = mileage telemetry.last_updated = datetime.now() else: db.add(AssetTelemetry(asset_id=asset_id, current_mileage=mileage)) async def get_asset_financial_summary(self, db: AsyncSession, asset_id: uuid.UUID) -> Dict[str, Any]: """ Dinamikus pénzügyi összesítő SQL szintű aggregációval. MB 2.0: Nem loopolunk Pythonban, a DB számol! """ # 1. Lekérjük az összesített adatokat kategóriánként (Local és EUR) stmt = ( select( AssetCost.cost_type, func.sum(AssetCost.amount_local).label("total_local"), func.sum(AssetCost.amount_eur).label("total_eur"), func.count(AssetCost.id).label("transaction_count") ) .where(AssetCost.asset_id == asset_id) .group_by(AssetCost.cost_type) ) res = await db.execute(stmt) rows = res.all() summary = { "by_category": {}, "grand_total_local": Decimal("0.0"), "grand_total_eur": Decimal("0.0"), "total_transactions": 0 } for row in rows: cat = row.cost_type or "OTHER" summary["by_category"][cat] = { "local": float(row.total_local), "eur": float(row.total_eur), "count": row.transaction_count } summary["grand_total_local"] += row.total_local summary["grand_total_eur"] += row.total_eur summary["total_transactions"] += row.transaction_count # Decimal konverzió a JSON-höz summary["grand_total_local"] = float(summary["grand_total_local"]) summary["grand_total_eur"] = float(summary["grand_total_eur"]) return summary cost_service = CostService()