FIX: Import error and enhanced atomized address structure for organizations

This commit is contained in:
2026-02-07 14:37:39 +00:00
parent 7249aa5809
commit e370ca3021
8 changed files with 150 additions and 30 deletions

View File

@@ -1,9 +1,12 @@
from fastapi import APIRouter, Depends, HTTPException, status from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select from sqlalchemy import select
from typing import List
from app.db.session import get_db from app.db.session import get_db
from app.schemas.organization import CorpOnboardIn, CorpOnboardResponse from app.schemas.organization import CorpOnboardIn, CorpOnboardResponse
from app.models.organization import Organization, OrgType from app.models.organization import Organization, OrgType, OrganizationMember
# JAVÍTOTT IMPORT: A User modell helye a projektben
from app.models.user import User
from app.core.config import settings from app.core.config import settings
import os import os
import re import re
@@ -18,18 +21,15 @@ async def onboard_organization(
db: AsyncSession = Depends(get_db) db: AsyncSession = Depends(get_db)
): ):
""" """
Új szervezet (cég/szerviz) rögzítése. Új szervezet (cég/szerviz) rögzítése bővített névvel és atomizált címmel.
- 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ó # 1. Magyar adószám validáció (XXXXXXXX-Y-ZZ)
if org_in.country_code == "HU": if org_in.country_code == "HU":
if not re.match(r"^\d{8}-\d-\d{2}$", org_in.tax_number): if not re.match(r"^\d{8}-\d-\d{2}$", org_in.tax_number):
raise HTTPException( raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST, status_code=status.HTTP_400_BAD_REQUEST,
detail="Érvénytelen magyar adószám formátum! (Példa: 12345678-1-12)" detail="Érvénytelen magyar adószám formátum!"
) )
# 2. Duplikáció ellenőrzés # 2. Duplikáció ellenőrzés
@@ -41,30 +41,76 @@ async def onboard_organization(
detail="Ezzel az adószámmal már regisztráltak céget!" detail="Ezzel az adószámmal már regisztráltak céget!"
) )
# 3. Mentés (Dinamikus státusszal és kisbetűs Enummal) # 3. Biztosítunk egy tulajdonost (MVP fix: keresünk egy létező usert)
user_stmt = select(User).limit(1)
user_res = await db.execute(user_stmt)
test_user = user_res.scalar_one_or_none()
if not test_user:
raise HTTPException(status_code=400, detail="Nincs regisztrált felhasználó a rendszerben!")
# 4. Mentés (Szervezet létrehozása atomizált adatokkal és név-hierarchiával)
new_org = Organization( new_org = Organization(
full_name=org_in.full_name,
name=org_in.name, name=org_in.name,
display_name=org_in.display_name,
tax_number=org_in.tax_number, tax_number=org_in.tax_number,
reg_number=org_in.reg_number, reg_number=org_in.reg_number,
headquarters_address=org_in.headquarters_address, address_zip=org_in.address_zip,
address_city=org_in.address_city,
address_street_name=org_in.address_street_name,
address_street_type=org_in.address_street_type,
address_house_number=org_in.address_house_number,
address_hrsz=org_in.address_hrsz,
address_stairwell=org_in.address_stairwell,
address_floor=org_in.address_floor,
address_door=org_in.address_door,
country_code=org_in.country_code, country_code=org_in.country_code,
org_type=OrgType.business, # Most már kisbetűs 'business' kerül beküldésre org_type=OrgType.business,
status="pending_verification" status="pending_verification"
) )
db.add(new_org) db.add(new_org)
await db.flush() # ID generálás a NAS-hoz await db.flush()
# 4. NAS Mappa létrehozása # 5. TULAJDONOS RÖGZÍTÉSE (Membership lánc)
owner_member = OrganizationMember(
organization_id=new_org.id,
user_id=test_user.id,
role="owner"
)
db.add(owner_member)
# 6. NAS Mappa létrehozása (Org izoláció)
try: try:
base_path = getattr(settings, "NAS_STORAGE_PATH", "/mnt/nas/app_data") base_path = getattr(settings, "NAS_STORAGE_PATH", "/mnt/nas/app_data")
org_path = os.path.join(base_path, "organizations", str(new_org.id)) org_path = os.path.join(base_path, "organizations", str(new_org.id))
os.makedirs(org_path, exist_ok=True) os.makedirs(os.path.join(org_path, "documents"), exist_ok=True)
logger.info(f"NAS mappa létrehozva szervezetnek: {org_path}") logger.info(f"NAS mappa struktúra kész: {org_path}")
except Exception as e: except Exception as e:
logger.error(f"NAS hiba az onboardingnál: {e}") logger.error(f"NAS hiba: {e}")
await db.commit() await db.commit()
await db.refresh(new_org) await db.refresh(new_org)
return {"organization_id": new_org.id, "status": new_org.status} return {"organization_id": new_org.id, "status": new_org.status}
@router.get("/my", response_model=List[CorpOnboardResponse])
async def get_my_organizations(
db: AsyncSession = Depends(get_db)
):
"""
A bejelentkezett felhasználóhoz tartozó összes cég/szervezet listázása.
"""
# MVP Teszt: Kézzel keresünk egy létező usert (később: current_user.id)
user_stmt = select(User).limit(1)
user_res = await db.execute(user_stmt)
test_user = user_res.scalar_one_or_none()
if not test_user:
return []
stmt = select(Organization).join(OrganizationMember).where(OrganizationMember.user_id == test_user.id)
result = await db.execute(stmt)
orgs = result.scalars().all()
return [{"organization_id": o.id, "status": o.status} for o in orgs]

View File

@@ -6,7 +6,6 @@ from sqlalchemy.sql import func
from app.db.base import Base from app.db.base import Base
class OrgType(str, enum.Enum): class OrgType(str, enum.Enum):
# A tagok neveit kisbetűre állítjuk, hogy egyezzenek a Postgres Enum értékekkel
individual = "individual" individual = "individual"
service = "service" service = "service"
service_provider = "service_provider" service_provider = "service_provider"
@@ -19,7 +18,27 @@ class Organization(Base):
__table_args__ = {"schema": "data"} __table_args__ = {"schema": "data"}
id = Column(Integer, primary_key=True, index=True) id = Column(Integer, primary_key=True, index=True)
name = Column(String, nullable=False)
# --- NÉVKEZELÉS ---
full_name = Column(String, nullable=False) # Teljes hivatalos név
name = Column(String, nullable=False) # Rövidített cégnév (pl. ProfiBot Kft.)
display_name = Column(String(50)) # Alkalmazáson belüli rövidítés (pl. ProfiBot)
# --- ATOMIZÁLT CÍMKEZELÉS ---
address_zip = Column(String(10))
address_city = Column(String(100))
address_street_name = Column(String(150))
address_street_type = Column(String(50)) # utca, út, tér, dűlő, stb.
address_house_number = Column(String(20))
address_hrsz = Column(String(50)) # Helyrajzi szám
address_stairwell = Column(String(20))
address_floor = Column(String(20))
address_door = Column(String(20))
country_code = Column(String(2), default="HU")
# --- ÜZLETI ADATOK ---
tax_number = Column(String(20), unique=True, index=True)
reg_number = Column(String(50))
# PG_ENUM használata a Python Enum-mal szinkronizálva # PG_ENUM használata a Python Enum-mal szinkronizálva
org_type = Column( org_type = Column(
@@ -27,11 +46,6 @@ class Organization(Base):
default=OrgType.individual 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") status = Column(String(30), default="pending_verification")
is_deleted = Column(Boolean, default=False) is_deleted = Column(Boolean, default=False)
@@ -52,6 +66,7 @@ class Organization(Base):
created_at = Column(DateTime(timezone=True), server_default=func.now()) created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), onupdate=func.now()) updated_at = Column(DateTime(timezone=True), onupdate=func.now())
# Kapcsolatok
assets = relationship("Asset", back_populates="organization", cascade="all, delete-orphan") assets = relationship("Asset", back_populates="organization", cascade="all, delete-orphan")
members = relationship("OrganizationMember", back_populates="organization") members = relationship("OrganizationMember", back_populates="organization")
owner = relationship("User", back_populates="owned_organizations") owner = relationship("User", back_populates="owned_organizations")

View File

@@ -4,17 +4,32 @@ from typing import Optional, List
class ContactCreate(BaseModel): class ContactCreate(BaseModel):
full_name: str full_name: str
email: str email: str
phone: Optional[str] phone: Optional[str] = None
contact_type: str = "primary" contact_type: str = "primary"
class CorpOnboardIn(BaseModel): class CorpOnboardIn(BaseModel):
name: str # Névkezelés
full_name: str = Field(..., description="Teljes hivatalos név")
name: str = Field(..., description="Rövidített cégnév (pl. ProfiBot Kft.)")
display_name: str = Field(..., description="Alkalmazáson belüli rövidítés (pl. ProfiBot)")
tax_number: str tax_number: str
country_code: str = "HU" country_code: str = "HU"
reg_number: Optional[str] reg_number: Optional[str] = None
headquarters_address: str
contacts: Optional[List[ContactCreate]] = [] # Atomizált Címkezelés
address_zip: str
address_city: str
address_street_name: Optional[str] = None
address_street_type: Optional[str] = None # utca, út, tér, dűlő
address_house_number: Optional[str] = None
address_hrsz: Optional[str] = None # Helyrajzi szám (ha nincs utca/házszám)
address_stairwell: Optional[str] = None
address_floor: Optional[str] = None
address_door: Optional[str] = None
contacts: List[ContactCreate] = []
class CorpOnboardResponse(BaseModel): class CorpOnboardResponse(BaseModel):
organization_id: int organization_id: int
status: str = "pending_verification" status: str

View File

@@ -152,4 +152,20 @@ A fejlesztések rendben tartásához javaslom a **`17_DEVELOPER_NOTES_AND_PITFAL
- **Asset Endpoint:** `POST /api/v1/assets/` élesítve VIN validációval. - **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}`). - **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. - **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. - **Bugfix:** SQLAlchemy `TypeError` javítva a modell és a séma szinkronizálásával.
## [0.5.0] - 2026-02-07
### ✨ Corporate & CRM Foundation
- **Corporate Onboarding:** `POST /api/v1/organizations/onboard` végpont élesítve.
- **Validation:** Magyar adószám (HU) formátum ellenőrzés beépítve.
- **Status Management:** Bevezetve a `pending_verification` állapot a szervezetekhez.
- **Database:** PostgreSQL `orgtype` Enum szinkronizálva a Python modellel (kisbetűs mapping).
- **NAS:** Automata szervezeti mappa-izoláció (`/organizations/{id}`).
[0.5.1] - 2026-02-07
FIX: ModuleNotFoundError javítva az importok szinkronizálásával.
DATA: Atomizált címmezők hozzáadva a data.organizations táblához.
LOGIC: Háromszintű névkezelés (Hivatalos, Rövid, Display) bevezetve.

View File

@@ -176,3 +176,31 @@ A rendszer az alábbi kategóriákat különbözteti meg az életút- és költs
- **Construction:** Munkagépek (markolók, daruk). - **Construction:** Munkagépek (markolók, daruk).
- **Agriculture:** Mezőgazdasági vontatók, kombájnok. - **Agriculture:** Mezőgazdasági vontatók, kombájnok.
- **Micro-mobility:** E-roller, e-bike flották. - **Micro-mobility:** E-roller, e-bike flották.
# 18. ASSET ÉS FLOTTA SPECIFIKÁCIÓ (v1.1)
## 1. Dokumentum Tárolási és Feldolgozási Stratégia
A rendszer a tárhelyköltségek optimalizálása és a gyors elérés érdekében hibrid tárolást alkalmaz:
### A) Tárolási típusok
- **Vault (Tartós):** Jogilag kritikus okmányok (Alapító okirat, Forgalmi, Adásvételi).
- Tárolás: NAS, hash-elt fájlnévvel.
- Elérhetőség: Korlátlan ideig, amíg az Asset/Szervezet aktív.
- **Ephemeral (Ideiglenes):** Napi bizonylatok (Parkolási jegy, Tankolási nyugta).
- Folyamat: Feltöltés -> OCR adatkinyerés -> Adatbázis rögzítés -> Kép törlése (90 nap után).
### B) Képoptimalizálási Motor
Minden feltöltött dokumentum (JPG/PNG) automata feldolgozáson esik át:
- Átmretezés: Max 1600px szélesség.
- Formátum konverzió: WebP (veszteségmentes tömörítés).
- Eredmény: ~80-90%-os tárhely megtakarítás olvashatóság vesztése nélkül.
## 2. Címkezelési Protokoll (Atomizált Adatok)
A pontos szűrés és a hivatalos iratok generálása érdekében a címeket az alábbi bontásban tároljuk:
- Irányítószám (IRSZ)
- Település (Város)
- Közterület neve
- Közterület jellege (utca, út, tér, stb. - választható listából)
- Házszám (emelet, ajtó, lépcsőház kiegészítéssel)
Címkezelés (v2.0): Minden magánszemély és szervezet címét atomizált formában tároljuk (IRSZ, Város, Utca, Házszám, HRSZ). Ez alapfeltétele a későbbi flotta-riportoknak és a pontos térképi megjelenítésnek.