egyedi jármű szerkesztés előtti mentés

This commit is contained in:
Roo
2026-06-12 07:56:15 +00:00
parent 0a3fd8de74
commit ef8df9608c
29 changed files with 3863 additions and 396 deletions

View File

@@ -3,7 +3,7 @@ from __future__ import annotations
import logging
import uuid
from typing import List, Optional, Dict, Any, TYPE_CHECKING
from datetime import datetime
from datetime import datetime, timezone
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, func, and_, distinct
from sqlalchemy.orm import selectinload
@@ -62,10 +62,21 @@ class AssetService:
allowed_limit = await AssetService.get_user_vehicle_limit(db, user_id, target_org_id)
# Csak aktív járművek számítanak a limitbe (draft-ok nem)
count_stmt = select(func.count(Asset.id)).where(
Asset.current_organization_id == target_org_id,
Asset.status == "active"
)
# FIX: Count only the CURRENT USER's active vehicles, not ALL vehicles in the org
if target_org_id is not None:
# Organization mode: count vehicles owned by this user within the org
count_stmt = select(func.count(Asset.id)).where(
Asset.current_organization_id == target_org_id,
Asset.owner_person_id == user.person_id,
Asset.status == "active"
)
else:
# Personal mode: count vehicles owned personally by this user
count_stmt = select(func.count(Asset.id)).where(
Asset.current_organization_id.is_(None),
Asset.owner_person_id == user.person_id,
Asset.status == "active"
)
current_count = (await db.execute(count_stmt)).scalar()
# Determine status based on data completeness (use Pydantic validator's logic)
@@ -86,7 +97,15 @@ class AssetService:
else:
status = "active"
# SAFETY: Ensure limit is at least 1 — no registered user should ever have 0 limit
allowed_limit = max(allowed_limit or 1, 1)
logger.info(
f"Limit check for user {user_id} (org={target_org_id}): "
f"current_count={current_count}, allowed_limit={allowed_limit}, status={status}"
)
if current_count >= allowed_limit and status == "active":
logger.error(f"QUOTA BLOCK TRIGGERED - User Person ID: {user.person_id}, Active Count: {current_count}, Limit: {allowed_limit}")
raise ValueError(f"Limit túllépés! A csomagod {allowed_limit} aktív autót engedélyez.")
# 2. LÉTEZIK-E MÁR A JÁRMŰ? (csak ha van VIN)
@@ -400,7 +419,11 @@ class AssetService:
# Get user-specific limit (based on role or subscription plan)
user_limit = limits.get(user_role)
if user_limit is None:
user_limit = limits.get(subscription_plan.lower(), 100)
# FIX: If role not found (e.g. "user" not in dict), try subscription plan
user_limit = limits.get(subscription_plan.lower())
if user_limit is None:
# FIX: Ultimate fallback - safe default for free/basic users
user_limit = limits.get("free", 1)
# Get organization-specific limit (if configured)
org_limit = None
@@ -431,6 +454,98 @@ class AssetService:
# Fallback to a reasonable default
return 100
@staticmethod
async def archive_vehicle(
db: AsyncSession,
asset_id: uuid.UUID,
user_id: int,
final_mileage: int,
archive_reason: str,
) -> Asset:
"""
Strict Soft Delete — Jármű biztonságos kivezetése a garázsból.
A művelet:
1. Frissíti a current_mileage értékét a megadott final_mileage-ra
2. Állítja: status = 'archived'
3. Nullázza az owner_person_id és owner_org_id mezőket
4. Elmenti az archive_info metaadatokat az individual_equipment JSONB-be
5. Naplózza a biztonsági auditba
"""
from app.models.identity import User
from sqlalchemy.orm import selectinload
# 1. Lekérjük a járművet — eager load catalog reláció a MissingGreenlet hiba elkerülésére
stmt = select(Asset).where(Asset.id == asset_id).options(selectinload(Asset.catalog))
result = await db.execute(stmt)
asset = result.scalar_one_or_none()
if not asset:
raise HTTPException(status_code=404, detail="Jármű nem található")
# 2. Jogosultság ellenőrzése — csak a tulajdonos vagy admin archiválhat
user_stmt = select(User).where(User.id == user_id)
user = (await db.execute(user_stmt)).scalar_one_or_none()
if not user:
raise HTTPException(status_code=404, detail="Felhasználó nem található")
is_owner = (asset.owner_person_id == user.person_id)
is_admin = user.role in ("admin", "superadmin") if hasattr(user, 'role') else False
if not is_owner and not is_admin:
raise HTTPException(
status_code=403,
detail="Nincs jogosultságod a jármű eltávolításához"
)
# 3. Már archiválva van?
if asset.status == "archived":
raise HTTPException(status_code=400, detail="A jármű már archiválva van")
# 4. Mentjük az archive_info metaadatokat
now_iso = datetime.now(timezone.utc).isoformat()
equipment = dict(asset.individual_equipment or {})
equipment['archive_info'] = {
'reason': archive_reason,
'archived_at': now_iso,
'previous_owner_person_id': asset.owner_person_id,
'previous_owner_org_id': asset.owner_org_id,
'final_mileage': final_mileage,
}
# 5. Frissítjük a jármű adatait
asset.current_mileage = final_mileage
asset.status = "archived"
asset.owner_person_id = None
asset.owner_org_id = None
asset.individual_equipment = equipment
asset.updated_at = datetime.now(timezone.utc)
# 6. Biztonsági audit naplózás
await security_service.log_event(
db,
user_id=user_id,
action="VEHICLE_ARCHIVED",
severity=LogSeverity.warning,
target_type="Asset",
target_id=str(asset.id),
new_data={
"reason": archive_reason,
"final_mileage": final_mileage,
"archived_at": now_iso,
}
)
await db.commit()
await db.refresh(asset)
logger.info(
f"Vehicle {asset.id} ({asset.license_plate}) archived by user {user_id}. "
f"Reason: {archive_reason}, final_mileage: {final_mileage}"
)
return asset
@staticmethod
async def _award_first_car_badge(db: AsyncSession, user_id: int, org_id: int):
"""