FEAT: Corporate onboarding implemented with Tax ID validation (HU) and isolated NAS storage

This commit is contained in:
2026-02-07 13:42:46 +00:00
parent c59c441a40
commit 7249aa5809
23 changed files with 399 additions and 29 deletions

View File

@@ -1,10 +1,16 @@
from fastapi import APIRouter
from app.api.v1.endpoints import auth, catalog # <--- Hozzáadtuk a catalog-ot
from app.api.v1.endpoints import auth, catalog, assets, organizations
api_router = APIRouter()
# Autentikáció és KYC végpontok
# Felhasználó és Identitás
api_router.include_router(auth.router, prefix="/auth", tags=["Authentication"])
# Jármű Katalógus és Robot kereső végpontok
api_router.include_router(catalog.router, prefix="/catalog", tags=["Vehicle Catalog"])
# Katalógus és Jármű Robotok
api_router.include_router(catalog.router, prefix="/catalog", tags=["Vehicle Catalog"])
# Egyedi Eszközök (Assets)
api_router.include_router(assets.router, prefix="/assets", tags=["Assets"])
# Szervezetek és Onboarding
api_router.include_router(organizations.router, prefix="/organizations", tags=["Organizations"])

View File

@@ -0,0 +1,104 @@
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
from app.db.session import get_db
from app.schemas.asset import AssetCreate, AssetResponse
from app.models.vehicle import Asset, VehicleCatalog
from app.models.organization import Organization
from app.core.validators import VINValidator
from app.core.config import settings
import os
import logging
router = APIRouter()
logger = logging.getLogger(__name__)
@router.post("/", response_model=AssetResponse, status_code=status.HTTP_201_CREATED)
async def create_asset(
asset_in: AssetCreate,
db: AsyncSession = Depends(get_db)
# Később ide jön: current_user: User = Depends(get_current_active_user)
):
"""
Új jármű (Asset) rögzítése a flottába.
- Validálja a VIN-t (MOD 11 checksum).
- Ellenőrzi a Katalógus elemet.
- Létrehozza a NAS mappát a dokumentumoknak.
"""
# 1. VIN Validáció (Szigorú Checksum)
# A GEM protokoll szerint kötelező a validátor használata
is_valid_vin = VINValidator.validate(asset_in.vin)
if not is_valid_vin:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Érvénytelen alvázszám (VIN)! A Checksum ellenőrzés sikertelen."
)
# 2. Katalógus elem ellenőrzése
stmt_catalog = select(VehicleCatalog).where(VehicleCatalog.id == asset_in.catalog_id)
result_catalog = await db.execute(stmt_catalog)
catalog_item = result_catalog.scalar_one_or_none()
if not catalog_item:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="A kiválasztott járműtípus nem található a katalógusban."
)
# 3. Szervezet ellenőrzése (Létezik-e, és van-e joga - jogultságkezelés később)
stmt_org = select(Organization).where(Organization.id == asset_in.organization_id)
result_org = await db.execute(stmt_org)
org_item = result_org.scalar_one_or_none()
if not org_item:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="A megadott flotta/szervezet nem található."
)
# 4. Asset Duplikáció ellenőrzése (Ugyanaz a VIN nem szerepelhet 2x aktívként)
# Megj: A UAI mező tárolja a VIN-t (Unique Asset Identifier)
stmt_exist = select(Asset).where(
Asset.uai == asset_in.vin.upper(),
Asset.status != "deleted"
)
result_exist = await db.execute(stmt_exist)
if result_exist.scalar_one_or_none():
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail="Ez a jármű (VIN) már szerepel a rendszerben!"
)
# 5. Mentés az adatbázisba
new_asset = Asset(
uai=asset_in.vin.upper(), # A VIN a fő azonosító
catalog_id=asset_in.catalog_id,
organization_id=asset_in.organization_id,
asset_type=catalog_item.category, # 'car', 'van', 'motorcycle', etc.
name=asset_in.name or f"{catalog_item.brand} {catalog_item.model}",
current_plate_number=asset_in.license_plate.upper(),
status="active",
privacy_level="private" # Alapértelmezett
)
db.add(new_asset)
await db.commit()
await db.refresh(new_asset)
# 6. NAS Tároló Létrehozása
# Útvonal: /mnt/nas/app_data/assets/{uuid}/
# A settings.NAS_STORAGE_PATH-ot használjuk (GEM protokoll)
try:
# Ha a settings-ben nincs definiálva, fallback a hardcoded path-ra (biztonsági háló)
base_path = getattr(settings, "NAS_STORAGE_PATH", "/mnt/nas/app_data/assets")
asset_path = os.path.join(base_path, str(new_asset.id))
os.makedirs(asset_path, exist_ok=True)
logger.info(f"NAS mappa létrehozva: {asset_path}")
except OSError as e:
logger.error(f"CRITICAL: Nem sikerült létrehozni a NAS mappát: {e}")
# Nem dobunk hibát a usernek, mert az adatbázisba bekerült, de riasztunk logban
return new_asset

View File

@@ -0,0 +1,70 @@
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
from app.db.session import get_db
from app.schemas.organization import CorpOnboardIn, CorpOnboardResponse
from app.models.organization import Organization, OrgType
from app.core.config import settings
import os
import re
import logging
router = APIRouter()
logger = logging.getLogger(__name__)
@router.post("/onboard", response_model=CorpOnboardResponse, status_code=status.HTTP_201_CREATED)
async def onboard_organization(
org_in: CorpOnboardIn,
db: AsyncSession = Depends(get_db)
):
"""
Új szervezet (cég/szerviz) rögzítése.
- Magyar adószám validáció (XXXXXXXX-Y-ZZ).
- Duplikáció ellenőrzés adószám alapján.
- NAS mappa és DB rekord létrehozása.
"""
# 1. Magyar adószám validáció
if org_in.country_code == "HU":
if not re.match(r"^\d{8}-\d-\d{2}$", org_in.tax_number):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Érvénytelen magyar adószám formátum! (Példa: 12345678-1-12)"
)
# 2. Duplikáció ellenőrzés
stmt_exist = select(Organization).where(Organization.tax_number == org_in.tax_number)
result_exist = await db.execute(stmt_exist)
if result_exist.scalar_one_or_none():
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail="Ezzel az adószámmal már regisztráltak céget!"
)
# 3. Mentés (Dinamikus státusszal és kisbetűs Enummal)
new_org = Organization(
name=org_in.name,
tax_number=org_in.tax_number,
reg_number=org_in.reg_number,
headquarters_address=org_in.headquarters_address,
country_code=org_in.country_code,
org_type=OrgType.business, # Most már kisbetűs 'business' kerül beküldésre
status="pending_verification"
)
db.add(new_org)
await db.flush() # ID generálás a NAS-hoz
# 4. NAS Mappa létrehozása
try:
base_path = getattr(settings, "NAS_STORAGE_PATH", "/mnt/nas/app_data")
org_path = os.path.join(base_path, "organizations", str(new_org.id))
os.makedirs(org_path, exist_ok=True)
logger.info(f"NAS mappa létrehozva szervezetnek: {org_path}")
except Exception as e:
logger.error(f"NAS hiba az onboardingnál: {e}")
await db.commit()
await db.refresh(new_org)
return {"organization_id": new_org.id, "status": new_org.status}