Files
service-finder/backend/app/services/logbook_service.py
2026-03-13 10:22:41 +00:00

185 lines
7.3 KiB
Python

# /opt/docker/dev/service_finder/backend/app/services/logbook_service.py
"""
Logbook Service - GPS, OBDII és előfizetési szűrő kezelése.
"""
import logging
from typing import Optional, Tuple, Any
from decimal import Decimal
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
from app.models.asset import VehicleLogbook
from app.models.gamification import UserStats
from app.models.identity import User
from app.models.system import SystemParameter
logger = logging.getLogger("Logbook-Service-2.0")
class LogbookService:
"""
Útnyilvántartás kezelése GPS koordinátákkal, OBDII adatokkal és előfizetési szintű jogosultságokkal.
"""
@staticmethod
async def get_system_parameter(db: AsyncSession, key: str, default: Any = None) -> Any:
"""
Lekéri a rendszerparamétert a system.system_parameters táblából.
Elsőként a global scope-ot keresi (scope_level='global', scope_id=NULL).
Ha nem talál, visszaadja a default értéket.
"""
stmt = select(SystemParameter).where(
SystemParameter.key == key,
SystemParameter.scope_level == 'global',
SystemParameter.scope_id.is_(None),
SystemParameter.is_active == True
).order_by(SystemParameter.updated_at.desc())
result = await db.execute(stmt)
param = result.scalar_one_or_none()
if param and 'value' in param.value:
return param.value['value']
return default
@staticmethod
async def get_user_rank(db: AsyncSession, user_id: int) -> int:
"""
Lekérdezi a felhasználó aktuális rankját (current_level) a UserStats táblából.
Ha nincs rekord, alapértelmezett 0 (ingyenes szint).
"""
stmt = select(UserStats.current_level).where(UserStats.user_id == user_id)
result = await db.execute(stmt)
rank = result.scalar_one_or_none()
return rank if rank is not None else 0
@staticmethod
async def check_subscription_guard(
db: AsyncSession,
user_id: int,
wants_gps: bool = False,
wants_obd: bool = False
) -> Tuple[bool, str]:
"""
Ellenőrzi, hogy a felhasználó előfizetési szintje engedélyezi-e a GPS/OBDII adatok rögzítését.
Szabályok:
- Rank >= LOGBOOK_GPS_MIN_RANK (alapértelmezett 50): engedélyezett a GPS távolság és koordináták
- Rank >= 90 (VIP/Admin): minden engedélyezett (GPS, OBDII, gyorsulás)
- Rank < LOGBOOK_GPS_MIN_RANK: csak manuális distance_km és trip_type rögzíthető
Visszatérés: (allowed: bool, message: str)
"""
rank = await LogbookService.get_user_rank(db, user_id)
gps_min_rank = await LogbookService.get_system_parameter(db, 'LOGBOOK_GPS_MIN_RANK', 50)
vip_min_rank = 90 # Fix VIP küszöb
if rank >= vip_min_rank:
return True, "VIP/Admin szint: minden adat rögzíthető"
if rank >= gps_min_rank:
if wants_gps or wants_obd:
return True, f"PREMIUM szint (rank {rank} >= {gps_min_rank}): GPS és OBDII adatok rögzíthetők"
return True, "PREMIUM szint"
# Ingyenes felhasználó
if wants_gps or wants_obd:
return False, f"Ingyenes felhasználók (rank {rank} < {gps_min_rank}) nem rögzíthetnek GPS koordinátákat vagy OBDII adatokat. Csak manuális distance_km és trip_type engedélyezett."
return True, "Ingyenes szint: csak manuális adatok"
@staticmethod
async def create_logbook_entry(
db: AsyncSession,
asset_id: str,
driver_id: int,
trip_type: str,
start_mileage: int,
end_mileage: Optional[int] = None,
distance_km: Optional[float] = None,
start_lat: Optional[float] = None,
start_lng: Optional[float] = None,
end_lat: Optional[float] = None,
end_lng: Optional[float] = None,
gps_calculated_distance: Optional[float] = None,
obd_verified: bool = False,
max_acceleration: Optional[float] = None,
average_speed: Optional[float] = None,
) -> VehicleLogbook:
"""
Új útnyilvántartás bejegyzés létrehozása előfizetési szűrővel.
Automatikusan ellenőrzi, hogy a felhasználó rankja engedélyezi-e a GPS/OBDII mezők kitöltését.
Ha nem, a GPS és OBDII mezők null-ra állnak, és csak a manuális distance_km marad.
"""
# Ellenőrizzük a jogosultságot
wants_gps = any([start_lat, start_lng, end_lat, end_lng, gps_calculated_distance])
wants_obd = obd_verified or max_acceleration is not None or average_speed is not None
allowed, message = await LogbookService.check_subscription_guard(
db, driver_id, wants_gps, wants_obd
)
if not allowed:
# Ha nem engedélyezett, nullázzuk a tiltott mezőket
logger.warning(f"User {driver_id} attempted to log GPS/OBDII without permission. {message}")
start_lat = start_lng = end_lat = end_lng = gps_calculated_distance = None
obd_verified = False
max_acceleration = average_speed = None
# Új bejegyzés létrehozása
new_entry = VehicleLogbook(
asset_id=asset_id,
driver_id=driver_id,
trip_type=trip_type,
start_mileage=start_mileage,
end_mileage=end_mileage,
distance_km=distance_km,
start_lat=start_lat,
start_lng=start_lng,
end_lat=end_lat,
end_lng=end_lng,
gps_calculated_distance=gps_calculated_distance,
obd_verified=obd_verified,
max_acceleration=max_acceleration,
average_speed=average_speed,
)
db.add(new_entry)
await db.commit()
await db.refresh(new_entry)
logger.info(f"Logbook entry created for asset {asset_id}, driver {driver_id}, trip_type {trip_type}")
return new_entry
@staticmethod
async def calculate_official_distance(
start_coords: Tuple[float, float],
end_coords: Tuple[float, float]
) -> Optional[float]:
"""
TODO: OSRM/Google Maps API hívással számolja ki a legrövidebb útvonal távolságát.
Egyelőre placeholder, később implementálandó.
Visszatérés: távolság kilométerben (float) vagy None, ha nem sikerült.
"""
# TODO: Integrálni OSRM vagy Google Maps Distance Matrix API-t
# Példa: https://project-osrm.org/docs/v5.24.0/api/#route-service
# Jelenleg egyszerű haversine formula alapján számolunk
from math import radians, sin, cos, sqrt, atan2
lat1, lon1 = start_coords
lat2, lon2 = end_coords
R = 6371.0 # Föld sugara km-ben
lat1_rad = radians(lat1)
lon1_rad = radians(lon1)
lat2_rad = radians(lat2)
lon2_rad = radians(lon2)
dlon = lon2_rad - lon1_rad
dlat = lat2_rad - lat1_rad
a = sin(dlat / 2)**2 + cos(lat1_rad) * cos(lat2_rad) * sin(dlon / 2)**2
c = 2 * atan2(sqrt(a), sqrt(1 - a))
distance_km = R * c
return round(distance_km, 2)