refaktor címjegyzék
This commit is contained in:
@@ -7,7 +7,7 @@ from app.api.v1.endpoints import (
|
||||
gamification, translations, users, reports, dictionaries,
|
||||
admin_packages, admin_services, admin_organizations, admin_users, admin_persons, constants, providers,
|
||||
subscriptions, marketing, admin_permissions, regions,
|
||||
admin_gamification, admin_providers,
|
||||
admin_gamification, admin_providers, locations,
|
||||
)
|
||||
|
||||
api_router = APIRouter()
|
||||
@@ -46,4 +46,5 @@ api_router.include_router(subscriptions.router, prefix="/subscriptions", tags=["
|
||||
api_router.include_router(marketing.router, prefix="/marketing", tags=["Marketing & Ads"])
|
||||
api_router.include_router(admin_permissions.router, prefix="", tags=["Admin Permissions (RBAC)"])
|
||||
api_router.include_router(admin_gamification.router, prefix="/admin/gamification", tags=["Admin Gamification"])
|
||||
api_router.include_router(admin_providers.router, prefix="/admin/providers", tags=["Admin Provider Moderation"])
|
||||
api_router.include_router(admin_providers.router, prefix="/admin/providers", tags=["Admin Provider Moderation"])
|
||||
api_router.include_router(locations.router, prefix="", tags=["Location Autocomplete"])
|
||||
@@ -1338,10 +1338,25 @@ async def update_organization(
|
||||
)
|
||||
|
||||
# 3. Összegyűjtjük a változásokat (non-address fields)
|
||||
# P0 BUGFIX: Filter out deprecated denormalized address fields that were dropped from the DB
|
||||
# These columns (address_city, billing_zip, etc.) were physically removed from fleet.organizations
|
||||
# in cleanup_ghost_columns_2026_07.sql. The Organization model no longer has these attributes.
|
||||
DEPRECATED_ADDRESS_FIELDS = {
|
||||
"address_zip", "address_city", "address_street_name", "address_street_type", "address_house_number",
|
||||
"billing_zip", "billing_city", "billing_street_name", "billing_street_type", "billing_house_number",
|
||||
"notification_zip", "notification_city", "notification_street_name", "notification_street_type", "notification_house_number",
|
||||
}
|
||||
update_fields = {}
|
||||
for field_name in raw_data:
|
||||
value = raw_data[field_name]
|
||||
if value is not None and hasattr(org, field_name):
|
||||
# Skip deprecated address fields that no longer exist on the ORM model
|
||||
if field_name in DEPRECATED_ADDRESS_FIELDS:
|
||||
logger.warning(
|
||||
f"Skipping deprecated field '{field_name}' for org {org_id} — "
|
||||
f"column was dropped from database. Use address_detail instead."
|
||||
)
|
||||
continue
|
||||
setattr(org, field_name, value)
|
||||
update_fields[field_name] = value
|
||||
|
||||
|
||||
@@ -38,7 +38,8 @@ from app.models.identity.social import (
|
||||
SourceType,
|
||||
ProviderValidation,
|
||||
)
|
||||
from app.models.marketplace.service import ServiceStaging, ServiceProfile
|
||||
from app.models.marketplace.service import ServiceProfile
|
||||
from app.models.marketplace.staged_data import ServiceStaging
|
||||
from app.models.gamification.gamification import UserContribution
|
||||
from app.models.vehicle.history import AuditLog
|
||||
from app.services.gamification_service import gamification_service
|
||||
@@ -82,10 +83,15 @@ class ProviderDetail(BaseModel):
|
||||
contact_email: Optional[str] = None
|
||||
website: Optional[str] = None
|
||||
category: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
status: str
|
||||
source: str
|
||||
source_table: str = "service_providers"
|
||||
validation_score: int = 0
|
||||
trust_score: Optional[int] = None
|
||||
rejection_reason: Optional[str] = None
|
||||
raw_data: Optional[Dict[str, Any]] = None
|
||||
audit_trail: Optional[Dict[str, Any]] = None
|
||||
evidence_image_path: Optional[str] = None
|
||||
added_by_user_id: Optional[int] = None
|
||||
category_ids: Optional[List[int]] = None
|
||||
@@ -564,13 +570,7 @@ async def get_provider_detail(
|
||||
name=sp.name,
|
||||
address=sp.address or "",
|
||||
city=sp.city,
|
||||
address_detail=AddressOut(
|
||||
zip=sp.address_zip,
|
||||
city=sp.city,
|
||||
street_name=sp.address_street_name,
|
||||
street_type=sp.address_street_type,
|
||||
house_number=sp.address_house_number,
|
||||
),
|
||||
address_detail=None,
|
||||
plus_code=sp.plus_code,
|
||||
contact_phone=sp.contact_phone,
|
||||
contact_email=sp.contact_email,
|
||||
@@ -586,6 +586,9 @@ async def get_provider_detail(
|
||||
supported_vehicle_classes=sp.supported_vehicle_classes or [],
|
||||
specializations=sp.specializations or {},
|
||||
opening_hours=opening_hours or {},
|
||||
# P0 BUGFIX: Include raw_data for ServiceProvider records so the frontend
|
||||
# "Nyers adatok / Crawler" tab shows actual data instead of empty/mock state
|
||||
raw_data=sp.raw_data or {},
|
||||
created_at=sp.created_at,
|
||||
)
|
||||
|
||||
@@ -600,10 +603,21 @@ async def get_provider_detail(
|
||||
name=ss.name,
|
||||
address=ss.full_address or "",
|
||||
city=ss.city,
|
||||
contact_phone=ss.contact_phone,
|
||||
contact_email=ss.contact_email,
|
||||
website=ss.website,
|
||||
description=ss.description,
|
||||
status=ss.status,
|
||||
source="bot",
|
||||
source=ss.source or "bot",
|
||||
source_table="service_staging",
|
||||
validation_score=0,
|
||||
# BUGFIX: Use trust_score from staging as validation_score instead of hardcoded 0
|
||||
# The staging table doesn't have a separate validation_score column,
|
||||
# but trust_score reflects the bot's confidence in the data quality
|
||||
validation_score=ss.trust_score or 0,
|
||||
trust_score=ss.trust_score,
|
||||
rejection_reason=ss.rejection_reason,
|
||||
raw_data=ss.raw_data or {},
|
||||
audit_trail=ss.audit_trail or {},
|
||||
created_at=ss.created_at,
|
||||
)
|
||||
|
||||
@@ -1336,13 +1350,7 @@ async def update_provider(
|
||||
name=provider.name,
|
||||
address=provider.address or "",
|
||||
city=provider.city,
|
||||
address_detail=AddressOut(
|
||||
zip=provider.address_zip,
|
||||
city=provider.city,
|
||||
street_name=provider.address_street_name,
|
||||
street_type=provider.address_street_type,
|
||||
house_number=provider.address_house_number,
|
||||
),
|
||||
address_detail=None,
|
||||
plus_code=provider.plus_code,
|
||||
contact_phone=provider.contact_phone,
|
||||
contact_email=provider.contact_email,
|
||||
|
||||
93
backend/app/api/v1/endpoints/locations.py
Normal file
93
backend/app/api/v1/endpoints/locations.py
Normal file
@@ -0,0 +1,93 @@
|
||||
"""
|
||||
🌍 Location Autocomplete API Endpoint.
|
||||
|
||||
Provides autocomplete search for postal codes and cities from the
|
||||
system.geo_postal_codes table. Used by the frontend SmartLocationInput
|
||||
component to let users quickly find and select a zip/city pair.
|
||||
|
||||
Schema: system.geo_postal_codes
|
||||
"""
|
||||
import logging
|
||||
from typing import List, Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
||||
from pydantic import BaseModel, Field
|
||||
from sqlalchemy import select, or_
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.api.deps import get_db
|
||||
from app.models.identity.address import GeoPostalCode
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
class LocationAutocompleteItem(BaseModel):
|
||||
"""Egy találat a település autocomplete keresőben."""
|
||||
id: int
|
||||
zip_code: str
|
||||
city: str
|
||||
country_code: str = "HU"
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
@router.get("/locations/autocomplete", response_model=List[LocationAutocompleteItem])
|
||||
async def autocomplete_locations(
|
||||
q: str = Query(..., min_length=1, max_length=100, description="Keresőszó (irányítószám vagy városrészlet)"),
|
||||
country_code: str = Query("HU", max_length=5, description="Országkód szűrő (alapértelmezett: HU)"),
|
||||
limit: int = Query(15, ge=1, le=50, description="Max találatok száma"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""
|
||||
GET /api/v1/locations/autocomplete?q=budapest
|
||||
|
||||
Autocomplete keresés a system.geo_postal_codes táblában.
|
||||
A keresés az irányítószámra (zip_code) és a város névre (city) is
|
||||
történik ILIKE operátorral.
|
||||
|
||||
Példák:
|
||||
/api/v1/locations/autocomplete?q=1011 -> 1011 Budapest
|
||||
/api/v1/locations/autocomplete?q=budapest -> 1011 Budapest, 1012 Budapest, ...
|
||||
/api/v1/locations/autocomplete?q=101 -> 1011 Budapest, 1012 Budapest, ...
|
||||
/api/v1/locations/autocomplete?q=bud&country_code=AT -> 5020 Salzburg, ...
|
||||
"""
|
||||
if not q.strip():
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="A keresőszó (q) nem lehet üres.",
|
||||
)
|
||||
|
||||
try:
|
||||
stmt = (
|
||||
select(GeoPostalCode)
|
||||
.where(
|
||||
GeoPostalCode.country_code == country_code,
|
||||
or_(
|
||||
GeoPostalCode.zip_code.ilike(f"%{q.strip()}%"),
|
||||
GeoPostalCode.city.ilike(f"%{q.strip()}%"),
|
||||
),
|
||||
)
|
||||
.order_by(GeoPostalCode.zip_code, GeoPostalCode.city)
|
||||
.limit(limit)
|
||||
)
|
||||
result = await db.execute(stmt)
|
||||
items = result.scalars().all()
|
||||
|
||||
return [
|
||||
LocationAutocompleteItem(
|
||||
id=item.id,
|
||||
zip_code=item.zip_code,
|
||||
city=item.city,
|
||||
country_code=item.country_code,
|
||||
)
|
||||
for item in items
|
||||
]
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Hiba a location autocomplete során: {e}", exc_info=True)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Hiba történt a település keresés során.",
|
||||
)
|
||||
Reference in New Issue
Block a user