135 lines
5.4 KiB
Python
Executable File
135 lines
5.4 KiB
Python
Executable File
# /opt/docker/dev/service_finder/backend/app/services/fleet_service.py
|
|
import logging
|
|
from uuid import UUID
|
|
from typing import Optional, Dict, Any
|
|
from decimal import Decimal
|
|
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
from sqlalchemy import select, func
|
|
from sqlalchemy.orm import selectinload
|
|
|
|
from app.models import Asset, AssetEvent, AssetCost, AssetTelemetry
|
|
from app.models import ServiceProvider, ModerationStatus
|
|
from app.schemas.fleet import EventCreate, TCOStats
|
|
from app.services.gamification_service import gamification_service
|
|
from app.services.config_service import config # 2.0 Dinamikus konfig
|
|
|
|
logger = logging.getLogger("Fleet-Service-2.2")
|
|
|
|
class FleetService:
|
|
"""
|
|
Sentinel Master Fleet Service 2.2.
|
|
Kezeli a járműflotta eseményeit és a TCO elemzéseket admin-vezérelt szabályokkal.
|
|
"""
|
|
|
|
@staticmethod
|
|
async def add_vehicle_event(db: AsyncSession, asset_id: UUID, event_data: EventCreate, user_id: int):
|
|
"""
|
|
Esemény rögzítése dinamikus jutalmazással és anomália figyeléssel.
|
|
"""
|
|
try:
|
|
# 1. Asset és Telemetria betöltése
|
|
stmt = select(Asset).where(Asset.id == asset_id).options(selectinload(Asset.telemetry))
|
|
res = await db.execute(stmt)
|
|
asset = res.scalar_one_or_none()
|
|
if not asset: return None
|
|
|
|
# 2. ADMIN KONFIGURÁCIÓ LEKÉRÉSE (Hierarchikus: User > Region > Global)
|
|
# Lekérjük az eseménytípushoz tartozó jutalmakat
|
|
event_rewards = await config.get_setting(
|
|
db,
|
|
"FLEET_EVENT_REWARDS",
|
|
scope_level="user",
|
|
scope_id=str(user_id),
|
|
default={
|
|
"refuel": {"xp": 30, "social": 5},
|
|
"service": {"xp": 100, "social": 20},
|
|
"inspection": {"xp": 50, "social": 10},
|
|
"default": {"xp": 20, "social": 2}
|
|
}
|
|
)
|
|
|
|
# 3. SZOLGÁLTATÓ KEZELÉSE
|
|
provider_id = event_data.provider_id
|
|
if not event_data.is_diy and event_data.provider_name and not provider_id:
|
|
p_stmt = select(ServiceProvider).where(func.lower(ServiceProvider.name) == event_data.provider_name.lower())
|
|
existing = (await db.execute(p_stmt)).scalar_one_or_none()
|
|
if existing:
|
|
provider_id = existing.id
|
|
else:
|
|
new_p = ServiceProvider(
|
|
name=event_data.provider_name,
|
|
added_by_user_id=user_id,
|
|
status=ModerationStatus.pending
|
|
)
|
|
db.add(new_p)
|
|
await db.flush()
|
|
provider_id = new_p.id
|
|
|
|
# 4. ANOMÁLIA DETEKCIÓ (Admin-vezérelt küszöbökkel)
|
|
current_mileage = asset.telemetry.current_mileage if asset.telemetry else 0
|
|
is_odometer_anomaly = event_data.odometer_value < current_mileage
|
|
|
|
# 5. ESEMÉNY RÖGZÍTÉSE
|
|
new_event = AssetEvent(
|
|
asset_id=asset_id,
|
|
event_type=event_data.event_type,
|
|
recorded_mileage=event_data.odometer_value,
|
|
provider_id=provider_id,
|
|
is_anomaly=is_odometer_anomaly,
|
|
data=event_data.model_dump(exclude={"provider_id", "provider_name"})
|
|
)
|
|
db.add(new_event)
|
|
|
|
# 6. DINAMIKUS GAMIFIKÁCIÓ
|
|
# Kikeresjük a konkrét eseménytípushoz tartozó pontokat
|
|
rewards = event_rewards.get(event_data.event_type, event_rewards["default"])
|
|
|
|
await gamification_service.process_activity(
|
|
db,
|
|
user_id,
|
|
xp_amount=rewards["xp"],
|
|
social_amount=rewards["social"],
|
|
reason=f"FLEET_EVENT_{event_data.event_type.upper()}"
|
|
)
|
|
|
|
await db.commit()
|
|
return new_event
|
|
|
|
except Exception as e:
|
|
await db.rollback()
|
|
logger.error(f"Fleet Event Error: {e}")
|
|
raise e
|
|
|
|
@staticmethod
|
|
async def calculate_tco(db: AsyncSession, asset_id: UUID) -> TCOStats:
|
|
"""
|
|
TCO számítás dinamikus pénznemkezeléssel és KM-alapú költséganalízissel.
|
|
"""
|
|
# 1. Admin beállítások (Pl. alapértelmezett pénznem a riportokhoz)
|
|
report_currency = await config.get_setting(db, "finance_base_currency", default="EUR")
|
|
|
|
# 2. Költségek összesítése kategóriánként
|
|
result = await db.execute(
|
|
select(AssetCost.cost_type, func.sum(AssetCost.amount_eur))
|
|
.where(AssetCost.asset_id == asset_id)
|
|
.group_by(AssetCost.cost_type)
|
|
)
|
|
|
|
breakdown = {row[0]: float(row[1]) for row in result.all()}
|
|
total_eur = sum(breakdown.values())
|
|
|
|
# 3. KM alapú költség (Telemetria bevonása)
|
|
telemetry_stmt = select(AssetTelemetry).where(AssetTelemetry.asset_id == asset_id)
|
|
telemetry = (await db.execute(telemetry_stmt)).scalar_one_or_none()
|
|
|
|
mileage = telemetry.current_mileage if telemetry and telemetry.current_mileage > 0 else 1
|
|
cost_per_km = total_eur / mileage
|
|
|
|
return TCOStats(
|
|
asset_id=asset_id,
|
|
total_cost_eur=total_eur,
|
|
breakdown=breakdown,
|
|
cost_per_km=round(cost_per_km, 4),
|
|
currency=report_currency
|
|
) |