diff --git a/backend/app/api/v1/__pycache__/api.cpython-312.pyc b/backend/app/api/v1/__pycache__/api.cpython-312.pyc index 2742d46..e3466a7 100644 Binary files a/backend/app/api/v1/__pycache__/api.cpython-312.pyc and b/backend/app/api/v1/__pycache__/api.cpython-312.pyc differ diff --git a/backend/app/api/v1/api.py b/backend/app/api/v1/api.py index 06a7569..9637aa4 100755 --- a/backend/app/api/v1/api.py +++ b/backend/app/api/v1/api.py @@ -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"]) \ No newline at end of file +# 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"]) \ No newline at end of file diff --git a/backend/app/api/v1/endpoints/__pycache__/assets.cpython-312.pyc b/backend/app/api/v1/endpoints/__pycache__/assets.cpython-312.pyc new file mode 100644 index 0000000..bb82254 Binary files /dev/null and b/backend/app/api/v1/endpoints/__pycache__/assets.cpython-312.pyc differ diff --git a/backend/app/api/v1/endpoints/__pycache__/organizations.cpython-312.pyc b/backend/app/api/v1/endpoints/__pycache__/organizations.cpython-312.pyc new file mode 100644 index 0000000..aa2ce1d Binary files /dev/null and b/backend/app/api/v1/endpoints/__pycache__/organizations.cpython-312.pyc differ diff --git a/backend/app/api/v1/endpoints/assets.py b/backend/app/api/v1/endpoints/assets.py new file mode 100644 index 0000000..9ebc461 --- /dev/null +++ b/backend/app/api/v1/endpoints/assets.py @@ -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 \ No newline at end of file diff --git a/backend/app/api/v1/endpoints/organizations.py b/backend/app/api/v1/endpoints/organizations.py new file mode 100644 index 0000000..5419ca3 --- /dev/null +++ b/backend/app/api/v1/endpoints/organizations.py @@ -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} \ No newline at end of file diff --git a/backend/app/core/__pycache__/config.cpython-312.pyc b/backend/app/core/__pycache__/config.cpython-312.pyc index 48a77db..2754663 100644 Binary files a/backend/app/core/__pycache__/config.cpython-312.pyc and b/backend/app/core/__pycache__/config.cpython-312.pyc differ diff --git a/backend/app/core/__pycache__/validators.cpython-312.pyc b/backend/app/core/__pycache__/validators.cpython-312.pyc new file mode 100644 index 0000000..57a7936 Binary files /dev/null and b/backend/app/core/__pycache__/validators.cpython-312.pyc differ diff --git a/backend/app/core/validators.py b/backend/app/core/validators.py new file mode 100644 index 0000000..f59bf8f --- /dev/null +++ b/backend/app/core/validators.py @@ -0,0 +1,47 @@ +import re + +class VINValidator: + @staticmethod + def validate(vin: str) -> bool: + """VIN (Vehicle Identification Number) ellenőrzése ISO 3779 szerint.""" + vin = vin.upper().strip() + + # Alapvető formátum: 17 karakter, tiltott betűk (I, O, Q) nélkül + if not re.match(r"^[A-Z0-9]{17}$", vin) or any(c in vin for c in "IOQ"): + return False + + # Karakterértékek táblázata + values = { + 'A':1, 'B':2, 'C':3, 'D':4, 'E':5, 'F':6, 'G':7, 'H':8, 'J':1, 'K':2, 'L':3, 'M':4, + 'N':5, 'P':7, 'R':9, 'S':2, 'T':3, 'U':4, 'V':5, 'W':6, 'X':7, 'Y':8, 'Z':9, + '0':0, '1':1, '2':2, '3':3, '4':4, '5':5, '6':6, '7':7, '8':8, '9':9 + } + + # Súlyozás a pozíciók alapján + weights = [8, 7, 6, 5, 4, 3, 2, 10, 0, 9, 8, 7, 6, 5, 4, 3, 2] + + try: + # 1. Összegzés: érték * súly + total = sum(values[vin[i]] * weights[i] for i in range(17)) + + # 2. Maradék számítás 11-el + check_digit = total % 11 + + # 3. A 10-es maradékot 'X'-nek jelöljük + expected = 'X' if check_digit == 10 else str(check_digit) + + # 4. Összevetés a 9. karakterrel (index 8) + return vin[8] == expected + except KeyError: + return False + + @staticmethod + def get_factory_data(vin: str) -> dict: + """Kinyeri az alapadatokat a VIN-ből (WMI, Évjárat, Gyártó ország).""" + # Ez a 'Mágikus Gomb' alapja + countries = {"1": "USA", "2": "Kanada", "J": "Japán", "W": "Németország", "S": "Anglia"} + return { + "country": countries.get(vin[0], "Ismeretlen"), + "year_code": vin[9], # Modellév kódja + "wmi": vin[0:3] # World Manufacturer Identifier + } \ No newline at end of file diff --git a/backend/app/models/__pycache__/organization.cpython-312.pyc b/backend/app/models/__pycache__/organization.cpython-312.pyc index 915fd29..06b63af 100644 Binary files a/backend/app/models/__pycache__/organization.cpython-312.pyc and b/backend/app/models/__pycache__/organization.cpython-312.pyc differ diff --git a/backend/app/models/__pycache__/vehicle.cpython-312.pyc b/backend/app/models/__pycache__/vehicle.cpython-312.pyc index f5cb6b2..775573c 100644 Binary files a/backend/app/models/__pycache__/vehicle.cpython-312.pyc and b/backend/app/models/__pycache__/vehicle.cpython-312.pyc differ diff --git a/backend/app/models/organization.py b/backend/app/models/organization.py index da087ee..f5c9e30 100755 --- a/backend/app/models/organization.py +++ b/backend/app/models/organization.py @@ -1,14 +1,18 @@ import enum -from sqlalchemy import Column, Integer, String, Boolean, Enum, DateTime, ForeignKey +from sqlalchemy import Column, Integer, String, Boolean, DateTime, ForeignKey, JSON +from sqlalchemy.dialects.postgresql import ENUM as PG_ENUM from sqlalchemy.orm import relationship from sqlalchemy.sql import func from app.db.base import Base class OrgType(str, enum.Enum): - INDIVIDUAL = "individual" - SERVICE = "service" - FLEET_OWNER = "fleet_owner" - CLUB = "club" + # A tagok neveit kisbetűre állítjuk, hogy egyezzenek a Postgres Enum értékekkel + individual = "individual" + service = "service" + service_provider = "service_provider" + fleet_owner = "fleet_owner" + club = "club" + business = "business" class Organization(Base): __tablename__ = "organizations" @@ -16,24 +20,38 @@ class Organization(Base): id = Column(Integer, primary_key=True, index=True) name = Column(String, nullable=False) - # A stabilitás miatt VARCHAR-ként kezeljük a DB-ben - org_type = Column(String(50), default="individual") - # A flotta technikai tulajdonosa (User) + # PG_ENUM használata a Python Enum-mal szinkronizálva + org_type = Column( + PG_ENUM(OrgType, name="orgtype", inherit_schema=True), + default=OrgType.individual + ) + + tax_number = Column(String(20), unique=True, index=True) + reg_number = Column(String(50)) + headquarters_address = Column(String(255)) + country_code = Column(String(2), default="HU") + + status = Column(String(30), default="pending_verification") + is_deleted = Column(Boolean, default=False) + + notification_settings = Column(JSON, default={ + "notify_owner": True, + "notify_manager": True, + "notify_contact": True, + "alert_days_before": [30, 15, 7, 1] + }) + external_integration_config = Column(JSON, default={}) + owner_id = Column(Integer, ForeignKey("data.users.id"), nullable=True) - - # Üzleti szabályok is_active = Column(Boolean, default=True) is_transferable = Column(Boolean, default=True) - - # Verifikáció is_verified = Column(Boolean, default=False) verification_expires_at = Column(DateTime(timezone=True), nullable=True) created_at = Column(DateTime(timezone=True), server_default=func.now()) updated_at = Column(DateTime(timezone=True), onupdate=func.now()) - # Kapcsolatok (Asset-re hivatkozunk a Vehicle helyett) assets = relationship("Asset", back_populates="organization", cascade="all, delete-orphan") members = relationship("OrganizationMember", back_populates="organization") owner = relationship("User", back_populates="owned_organizations") @@ -48,5 +66,5 @@ class OrganizationMember(Base): organization = relationship("Organization", back_populates="members") -# --- EZT A SORT TEDD KÍVÜLRE, A MARGÓRA --- +# Kompatibilitási réteg a korábbi kódokhoz Organization.vehicles = Organization.assets \ No newline at end of file diff --git a/backend/app/models/vehicle.py b/backend/app/models/vehicle.py index f898bae..2e8752d 100755 --- a/backend/app/models/vehicle.py +++ b/backend/app/models/vehicle.py @@ -20,15 +20,17 @@ class VehicleCatalog(Base): engine_type = Column(String(50)) engine_power_kw = Column(Integer) - # --- EZ A SOR HIÁNYZOTT --- + # Robot státusz és gyári adatok verification_status = Column(String(20), default="verified") - factory_specs = Column(JSON, default={}) maintenance_plan = Column(JSON, default={}) created_at = Column(DateTime(timezone=True), server_default=func.now()) updated_at = Column(DateTime(timezone=True), onupdate=func.now()) + # Kapcsolat az egyedi példányok felé + assets = relationship("Asset", back_populates="catalog_entry") + # 2. EGYEDI ESZKÖZ (Asset) - A felhasználó tulajdona class Asset(Base): __tablename__ = "assets" @@ -52,13 +54,16 @@ class Asset(Base): factory_config = Column(JSON, default={}) aftermarket_mods = Column(JSON, default={}) + # Állapot és láthatóság (EZ HIÁNYZOTT) status = Column(String(50), default="active") + privacy_level = Column(String(20), default="private") + created_at = Column(DateTime(timezone=True), server_default=func.now()) updated_at = Column(DateTime(timezone=True), onupdate=func.now()) # Kapcsolatok organization = relationship("Organization", back_populates="assets") - catalog_entry = relationship("VehicleCatalog") + catalog_entry = relationship("VehicleCatalog", back_populates="assets") events = relationship("AssetEvent", back_populates="asset", cascade="all, delete-orphan") ratings = relationship("AssetRating", back_populates="asset") diff --git a/backend/app/schemas/__pycache__/asset.cpython-312.pyc b/backend/app/schemas/__pycache__/asset.cpython-312.pyc new file mode 100644 index 0000000..27aea29 Binary files /dev/null and b/backend/app/schemas/__pycache__/asset.cpython-312.pyc differ diff --git a/backend/app/schemas/__pycache__/organization.cpython-312.pyc b/backend/app/schemas/__pycache__/organization.cpython-312.pyc new file mode 100644 index 0000000..4c34b25 Binary files /dev/null and b/backend/app/schemas/__pycache__/organization.cpython-312.pyc differ diff --git a/backend/app/schemas/asset.py b/backend/app/schemas/asset.py new file mode 100644 index 0000000..efdd68d --- /dev/null +++ b/backend/app/schemas/asset.py @@ -0,0 +1,27 @@ +from pydantic import BaseModel, Field, field_validator +from typing import Optional +from uuid import UUID +from datetime import datetime + +class AssetCreate(BaseModel): + catalog_id: int = Field(..., description="A kiválasztott katalógus elem ID-ja") + vin: str = Field(..., min_length=17, max_length=17, description="17 karakteres alvázszám") + license_plate: str = Field(..., min_length=1, max_length=20) + name: Optional[str] = Field(None, description="Egyedi elnevezés (pl. 'Céges furgon')") + organization_id: int = Field(..., description="Melyik flottába kerüljön") + + # Opcionális: Kezdő km óra állás, szín, stb. később bővíthető + +class AssetResponse(BaseModel): + id: UUID + uai: str + catalog_id: int + organization_id: int + name: str + asset_type: str + current_plate_number: str + status: str + created_at: datetime + + class Config: + from_attributes = True \ No newline at end of file diff --git a/backend/app/schemas/organization.py b/backend/app/schemas/organization.py new file mode 100644 index 0000000..a0d8c76 --- /dev/null +++ b/backend/app/schemas/organization.py @@ -0,0 +1,20 @@ +from pydantic import BaseModel, Field +from typing import Optional, List + +class ContactCreate(BaseModel): + full_name: str + email: str + phone: Optional[str] + contact_type: str = "primary" + +class CorpOnboardIn(BaseModel): + name: str + tax_number: str + country_code: str = "HU" + reg_number: Optional[str] + headquarters_address: str + contacts: Optional[List[ContactCreate]] = [] + +class CorpOnboardResponse(BaseModel): + organization_id: int + status: str = "pending_verification" \ No newline at end of file diff --git a/backend/app/services/robot_manager.py b/backend/app/services/robot_manager.py index 6afcbcc..d2dbe7d 100644 --- a/backend/app/services/robot_manager.py +++ b/backend/app/services/robot_manager.py @@ -1,5 +1,7 @@ import asyncio -from .harvester_robot import CarHarvester # A korábbi CarHarvester-t nevezzük át így +from datetime import datetime +# Frissített importok az új fájlnevekhez: +from .harvester_cars import CarHarvester from .harvester_bikes import BikeHarvester from .harvester_trucks import TruckHarvester @@ -7,6 +9,8 @@ class RobotManager: @staticmethod async def run_full_sync(db): """Sorban lefuttatja az összes robotot.""" + print(f"🕒 Szinkronizáció indítva: {datetime.now()}") + robots = [ CarHarvester(), BikeHarvester(), @@ -15,8 +19,22 @@ class RobotManager: for robot in robots: try: - await robot.run(db) - # Pihenő a kategóriák között, hogy ne kapjunk IP-tiltást + # Itt a robot lekéri az API-tól az ÖSSZES márkát és frissít + await robot.run(db) await asyncio.sleep(5) except Exception as e: - print(f"❌ Hiba a {robot.category} robotnál: {e}") \ No newline at end of file + print(f"❌ Hiba a {robot.category} robotnál: {e}") + + @staticmethod + async def schedule_nightly_run(db): + """ + Egyszerű ciklus, ami figyeli az időt. + Ha éjjel 2 óra van, elindítja a teljes szinkront. + """ + while True: + now = datetime.now() + # Ha hajnali 2 és 2:01 között vagyunk, indítás + if now.hour == 2 and now.minute == 0: + await RobotManager.run_full_sync(db) + await asyncio.sleep(70) # Várunk, hogy ne induljon el többször ugyanabban a percben + await asyncio.sleep(30) # 30 másodpercenként ellenőrizzük az időt \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index cd45319..5c2fa5e 100755 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,6 +10,7 @@ services: - ./backend:/app - ./alembic.ini:/app/alembic.ini - ./migrations:/app/migrations + - /mnt/nas/app_data:/mnt/nas/app_data environment: PYTHONPATH: /app DATABASE_URL: ${MIGRATION_DATABASE_URL} diff --git a/docs/V01_gemini/06_Database_Guide.md b/docs/V01_gemini/06_Database_Guide.md index e2ee2ef..e30cca2 100644 --- a/docs/V01_gemini/06_Database_Guide.md +++ b/docs/V01_gemini/06_Database_Guide.md @@ -96,4 +96,14 @@ A rendszer fel van készítve az EU-s piacra: - **Person:** Egyéni teljesítmény, megbízhatóság. - **Service (Szerviz):** Szolgáltatási minőség. - **Vehicle (Jármű):** Műszaki állapot és előélet. - - *Megjegyzés:* A Cég (mint flotta) nem kap önálló értékelést, a hírneve a tagjai és járművei minősítéséből adódik össze. \ No newline at end of file + - *Megjegyzés:* A Cég (mint flotta) nem kap önálló értékelést, a hírneve a tagjai és járművei minősítéséből adódik össze. + + ## 4. CRM és Szervezeti Kapcsolattartók +A `data.organization_contacts` tábla felelős a flottákhoz tartozó humán kapcsolattartók kezeléséért. +- **Dinamikus beállítások:** A `data.organizations` tábla `notification_settings` (JSONB) mezője szabályozza, ki és mikor kapjon értesítést. +- **Külső szinkron:** Az `external_crm_id` biztosítja a kapcsolatot külső vállalatirányítási rendszerekkel (API-n keresztül). + +## 4.1 Szervezeti és CRM Adatmodell +- **data.organizations**: Bővítve `tax_number`, `reg_number`, `headquarters_address` és `is_deleted` mezőkkel. +- **data.organization_contacts**: Új tábla a Mini-CRM funkciókhoz (kapcsolattartók típus szerint: billing, primary, operational). +- **Audit**: Minden státuszmódosítás és adatváltozás snapshot-olva az `audit_logs` táblába. \ No newline at end of file diff --git a/docs/V01_gemini/07_REGISTRATION_INVITATION_AND_API.md b/docs/V01_gemini/07_REGISTRATION_INVITATION_AND_API.md index a9ea2b0..77d8336 100644 --- a/docs/V01_gemini/07_REGISTRATION_INVITATION_AND_API.md +++ b/docs/V01_gemini/07_REGISTRATION_INVITATION_AND_API.md @@ -158,4 +158,10 @@ PASSWORD_RESET_TOKEN_EXPIRE_HOURS=1 EMAIL_PROVIDER=sendgrid EMAILS_FROM_EMAIL=info@profibot.hu EMAILS_FROM_NAME='Profibot Service Finder' -SENDGRID_API_KEY=SG.xxxxxxxxxxxxxxxxxxxx \ No newline at end of file +SENDGRID_API_KEY=SG.xxxxxxxxxxxxxxxxxxxx + +## 4. Corporate Onboarding (Céges regisztráció) +A folyamat célja, hogy egy már létező Person (vagy új User) saját szervezetet (Organization) alapítson. +- **Többszintű validáció:** Kötelező adószám (VAT/Tax ID) ellenőrzés (HU esetén formátum + VIES API). +- **Hierarchia:** A regisztráló automatikusan `owner` rangot kap. +- **Izoláció:** Minden cég saját mappastruktúrát kap a NAS-on az okmányok izolált kezelése érdekében. \ No newline at end of file diff --git a/docs/V01_gemini/15_Changelog.md b/docs/V01_gemini/15_Changelog.md index 9a9fe0e..4ea211e 100644 --- a/docs/V01_gemini/15_Changelog.md +++ b/docs/V01_gemini/15_Changelog.md @@ -145,4 +145,11 @@ A fejlesztések rendben tartásához javaslom a **`17_DEVELOPER_NOTES_AND_PITFAL ### ✨ Multi-Robot System - **Kategória Robotok:** Elkészült a Car, Bike és Truck harvester moduláris szerkezete. - **Robot Manager:** Központi vezérlő az ütemezett és sorrendi adatgyűjtéshez. -- **Katalógus Kereső:** Üzembe helyezve a `/catalog/search` végpont a Swaggerben. \ No newline at end of file +- **Katalógus Kereső:** Üzembe helyezve a `/catalog/search` végpont a Swaggerben. + +## [0.4.5] - 2026-02-07 +### ✨ Asset Management & Infrastructure +- **Asset Endpoint:** `POST /api/v1/assets/` élesítve VIN validációval. +- **NAS Integration:** Automata mappastruktúra létrehozása az eszközöknek (`/assets/{uuid}`). +- **Data Model:** `privacy_level` és `status` mezők hozzáadva az Asset modellhez. +- **Bugfix:** SQLAlchemy `TypeError` javítva a modell és a séma szinkronizálásával. \ No newline at end of file diff --git a/docs/V01_gemini/19_ADMIN_AND_PERMISSIONS_SPEC.md b/docs/V01_gemini/19_ADMIN_AND_PERMISSIONS_SPEC.md index d3a8d15..9e44683 100644 --- a/docs/V01_gemini/19_ADMIN_AND_PERMISSIONS_SPEC.md +++ b/docs/V01_gemini/19_ADMIN_AND_PERMISSIONS_SPEC.md @@ -32,4 +32,35 @@ Minden módosítás előtt a rendszer menti az aktuális rekord állapotát (JSO ## 5. Adminisztrátori Meghívók - Adminisztrátort csak kézi meghívóval lehet felvenni. -- **Lejárati idő:** Minden admin meghívó token 24 óráig érvényes. \ No newline at end of file +- **Lejárati idő:** Minden admin meghívó token 24 óráig érvényes. + +## 6. Értesítési Engine és Lejárati Figyelmeztetések + +A rendszer proaktív figyelmeztető rendszert alkalmaz minden előfizetői szinten (Individual és Corporate egyaránt). + +### A) Előfizetés és Pénzügyi Értesítések +- **Hatókör:** Minden fizetős csomag (Lite+, VIP, VIP+, Corporate). +- **Logika:** Automatikus értesítés küldése 30, 15, 7 és 1 nappal a csomag lejárta előtt. +- **Csatornák:** Push notification, Email és a Mini-CRM kontakt személyek értesítése. + +### B) Jármű Okmányok és Technikai Lejáratok +A rendszer figyeli az eszközökhöz rögzített metaadatokat: +- **Forgalmi engedély:** Műszaki vizsga lejárata. +- **Biztosítás:** Kötelező (KGFB) és CASCO fordulónapok. +- **Lízing/Szerződés:** Szerződéses futamidő vége. +- **Okmányok:** Hajólevél, lajstrom, emelőgép vizsga stb. + +### C) CRM Kontaktok és Kapcsolattartás +Minden szervezet (Organization) esetében kötelező megadni legalább egy **Adminisztratív Kontaktot**. +- **Több cég kezelése:** Egy Person több szervezetben is betölthet `owner` vagy `fleet_manager` szerepkört. +- **CRM Mezők:** Név, beosztás, közvetlen elérhetőség (fizetésért felelős, operatív felelős). + +## 7. Corporate Onboarding és Validációs Szintek +A cégek rögzítése háromlépcsős ellenőrzésen esik át: +1. **Tier 1 (Automata):** Adószám alapú validáció (HU/VIES API). +2. **Tier 2 (AI/OCR):** Feltöltött dokumentumok (Alapító okirat) intelligens elemzése. +3. **Tier 3 (Human):** Adminisztrátori jóváhagyás (L2/L3 szint), ha az automata folyamat bizonytalan. + +## 8. B2B Jutalék és MLM Kivételek +- **Direct Referral:** Cég által meghívott másik cég esetén csak 1. szintű (L1) jutalék jár. +- **MLM Korlát:** Szervezetek nem építhetnek többszintű hálózatot, a kifizetés fix üzleti megállapodás alapú. \ No newline at end of file