céges meghívó kezelése,
This commit is contained in:
@@ -1,13 +1,23 @@
|
||||
"""
|
||||
Provider végpontok – Szolgáltató keresés és gyors felvétel (crowdsourced).
|
||||
|
||||
4-Level Category Architecture (2026-06-17):
|
||||
============================================
|
||||
- GET /categories/tree — Teljes hierarchikus fa a frontend checkboxai számára
|
||||
- GET /categories/autocomplete — Szöveges kereső Szint 2 és Szint 3 címkékhez
|
||||
- GET /categories — Meglévő endpoint (ExpertiseTag lista)
|
||||
- GET /search — Szolgáltató keresés
|
||||
- POST /quick-add — Gyors szolgáltató felvétel
|
||||
- PUT /{provider_id} — Szolgáltató szerkesztés (hibrid mentés)
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Optional, List
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy import select, or_
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.orm import joinedload
|
||||
|
||||
from app.db.session import get_db
|
||||
from app.api.deps import get_current_user
|
||||
@@ -20,6 +30,8 @@ from app.schemas.provider import (
|
||||
ProviderUpdateIn,
|
||||
ProviderUpdateResponse,
|
||||
ExpertiseCategoryOut,
|
||||
CategoryTreeNode,
|
||||
CategoryAutocompleteItem,
|
||||
)
|
||||
from app.services.provider_service import search_providers, quick_add_provider, update_provider
|
||||
|
||||
@@ -27,6 +39,114 @@ router = APIRouter()
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# ──────────────────────────────────────────────
|
||||
# 4-LEVEL CATEGORY ENDPOINTS (2026-06-17)
|
||||
# ──────────────────────────────────────────────
|
||||
|
||||
@router.get("/categories/tree", response_model=List[CategoryTreeNode])
|
||||
async def get_category_tree(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Visszaadja a teljes kategória hierarchikus fát a frontend checkboxai számára.
|
||||
|
||||
Level 0: Járműtípus (Vehicle Type)
|
||||
Level 1: Iparág/Főcsoport (Industry)
|
||||
Level 2: Szakma (Profession)
|
||||
Level 3: Specifikus feladat/címke (Specific Tag)
|
||||
|
||||
Rekurzívan építi fel a fa struktúrát a parent_id és path mezők alapján.
|
||||
"""
|
||||
try:
|
||||
# Összes tag betöltése
|
||||
stmt = select(ExpertiseTag).order_by(ExpertiseTag.level, ExpertiseTag.id)
|
||||
result = await db.execute(stmt)
|
||||
all_tags = result.scalars().all()
|
||||
|
||||
# Indexelés ID alapján
|
||||
tag_map = {t.id: t for t in all_tags}
|
||||
|
||||
# Fa építése: csak a gyökér elemeket (parent_id IS NULL) adjuk vissza
|
||||
def build_tree(parent_id: Optional[int] = None) -> List[CategoryTreeNode]:
|
||||
nodes = []
|
||||
for tag in all_tags:
|
||||
if tag.parent_id == parent_id:
|
||||
children = build_tree(tag.id)
|
||||
nodes.append(CategoryTreeNode(
|
||||
id=tag.id,
|
||||
key=tag.key,
|
||||
name_hu=tag.name_hu,
|
||||
name_en=tag.name_en,
|
||||
level=tag.level,
|
||||
path=tag.path,
|
||||
is_official=tag.is_official,
|
||||
children=children,
|
||||
))
|
||||
return nodes
|
||||
|
||||
tree = build_tree(parent_id=None)
|
||||
return tree
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Hiba a kategória fa lekérése során: {e}", exc_info=True)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Hiba történt a kategória fa lekérése során.",
|
||||
)
|
||||
|
||||
|
||||
@router.get("/categories/autocomplete", response_model=List[CategoryAutocompleteItem])
|
||||
async def autocomplete_categories(
|
||||
q: str = Query(..., min_length=2, max_length=100, description="Keresőszó"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Szöveges kereső a Szint 2 (Szakma) és Szint 3 (Specifikus feladat) címkékhez.
|
||||
|
||||
Csak azokat listázza, ahol is_official=True.
|
||||
A keresés a name_hu, name_en és key mezőkben történik (ILIKE).
|
||||
"""
|
||||
try:
|
||||
stmt = select(ExpertiseTag).where(
|
||||
ExpertiseTag.level >= 2,
|
||||
ExpertiseTag.is_official == True,
|
||||
or_(
|
||||
ExpertiseTag.name_hu.ilike(f"%{q}%"),
|
||||
ExpertiseTag.name_en.ilike(f"%{q}%"),
|
||||
ExpertiseTag.key.ilike(f"%{q}%"),
|
||||
),
|
||||
).order_by(ExpertiseTag.level, ExpertiseTag.name_hu).limit(20)
|
||||
|
||||
result = await db.execute(stmt)
|
||||
tags = result.scalars().all()
|
||||
|
||||
return [
|
||||
CategoryAutocompleteItem(
|
||||
id=t.id,
|
||||
key=t.key,
|
||||
name_hu=t.name_hu,
|
||||
name_en=t.name_en,
|
||||
level=t.level,
|
||||
path=t.path,
|
||||
parent_id=t.parent_id,
|
||||
)
|
||||
for t in tags
|
||||
]
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Hiba a kategória autocomplete során: {e}", exc_info=True)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Hiba történt a kategória keresés során.",
|
||||
)
|
||||
|
||||
|
||||
# ──────────────────────────────────────────────
|
||||
# EXISTING ENDPOINTS (extended)
|
||||
# ──────────────────────────────────────────────
|
||||
|
||||
@router.get("/categories", response_model=List[ExpertiseCategoryOut])
|
||||
async def list_expertise_categories(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
@@ -34,9 +154,10 @@ async def list_expertise_categories(
|
||||
):
|
||||
"""
|
||||
Visszaadja az összes expertise kategóriát a frontend dropdown számára.
|
||||
Kibővítve a 4-level hierarchy mezőkkel (level, parent_id, path).
|
||||
"""
|
||||
try:
|
||||
stmt = select(ExpertiseTag).order_by(ExpertiseTag.id)
|
||||
stmt = select(ExpertiseTag).order_by(ExpertiseTag.level, ExpertiseTag.id)
|
||||
result = await db.execute(stmt)
|
||||
tags = result.scalars().all()
|
||||
return [
|
||||
@@ -46,6 +167,9 @@ async def list_expertise_categories(
|
||||
name_hu=t.name_hu,
|
||||
name_en=t.name_en,
|
||||
category=t.category,
|
||||
level=t.level,
|
||||
parent_id=t.parent_id,
|
||||
path=t.path,
|
||||
)
|
||||
for t in tags
|
||||
]
|
||||
@@ -62,6 +186,7 @@ async def search_service_providers(
|
||||
q: Optional[str] = Query(None, min_length=2, max_length=200, description="Keresőszó"),
|
||||
category: Optional[str] = Query(None, max_length=50, description="Kategória szűrő (expertise_tags.key)"),
|
||||
city: Optional[str] = Query(None, max_length=100, description="Város szűrő"),
|
||||
category_ids: Optional[str] = Query(None, description="Kategória ID-k vesszővel elválasztva (pl. '201,205') — hierarchikus szűrés"),
|
||||
limit: int = Query(20, ge=1, le=100, description="Találatok száma oldalanként"),
|
||||
offset: int = Query(0, ge=0, description="Lapozási offset"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
@@ -76,17 +201,35 @@ async def search_service_providers(
|
||||
- **crowd_added**: Közösség által hozzáadott adatok (marketplace.service_providers)
|
||||
|
||||
A találatok forrás szerint csoportosítva, lapozva érkeznek.
|
||||
|
||||
**category_ids**: Ha meg van adva, csak azok a szervezetek jelennek meg,
|
||||
amelyek ServiceProfile-jához tartozik legalább egy megadott ExpertiseTag
|
||||
(közvetlenül vagy hierarchikusan a path LIKE segítségével).
|
||||
"""
|
||||
try:
|
||||
# Parse category_ids: vesszővel elválasztott string -> List[int]
|
||||
parsed_category_ids: Optional[List[int]] = None
|
||||
if category_ids:
|
||||
try:
|
||||
parsed_category_ids = [int(x.strip()) for x in category_ids.split(",") if x.strip()]
|
||||
except (ValueError, TypeError):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
||||
detail="Érvénytelen category_ids formátum. Vesszővel elválasztott számok szükségesek (pl. '201,205').",
|
||||
)
|
||||
|
||||
result = await search_providers(
|
||||
db=db,
|
||||
q=q,
|
||||
category=category,
|
||||
city=city,
|
||||
category_ids=parsed_category_ids,
|
||||
limit=limit,
|
||||
offset=offset,
|
||||
)
|
||||
return result
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Hiba a szolgáltató keresés során: {e}", exc_info=True)
|
||||
raise HTTPException(
|
||||
@@ -143,6 +286,14 @@ async def update_service_provider(
|
||||
|
||||
Frissíti a szervezet alapadatait (név, cím) és a kapcsolódó
|
||||
ServiceProfile kapcsolati mezőit (telefon, email, weboldal, címkék).
|
||||
|
||||
4-Level Category Hybrid Save (2026-06-17):
|
||||
- category_ids: A kiválasztott kategória ID-k (Szint 0-3 checkboxokból)
|
||||
-> ServiceExpertise kapcsolatok frissítése
|
||||
- new_tags: A user által gépelt új címkenevek
|
||||
-> Létrehozás ExpertiseTag-ban (is_official=False, level=3)
|
||||
-> ServiceExpertise kapcsolat létrehozása
|
||||
- tags: Meglévő specialization_tags frissítése
|
||||
"""
|
||||
try:
|
||||
result = await update_provider(
|
||||
@@ -157,6 +308,11 @@ async def update_service_provider(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=str(e),
|
||||
)
|
||||
except PermissionError as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail=str(e),
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Hiba a szolgáltató frissítése során (id={provider_id}): {e}", exc_info=True)
|
||||
raise HTTPException(
|
||||
|
||||
Reference in New Issue
Block a user