refaktor címjegyzék

This commit is contained in:
Roo
2026-07-02 11:52:22 +00:00
parent 7654913d21
commit 07b59032ce
192 changed files with 2936 additions and 1376 deletions

View File

@@ -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"])

View File

@@ -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

View File

@@ -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,

View 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.",
)

View File

@@ -444,7 +444,7 @@ class AssetEvent(Base):
asset: Mapped["Asset"] = relationship("Asset", back_populates="events")
user: Mapped[Optional["User"]] = relationship("User")
organization: Mapped[Optional["Organization"]] = relationship("Organization")
cost: Mapped[Optional["AssetCost"]] = relationship("AssetCost", foreign_keys=[cost_id])
cost: Mapped[Optional["AssetCost"]] = relationship("AssetCost", foreign_keys=[cost_id], viewonly=True)
# Kapcsolat a hivatkozott költséghez
linked_expense: Mapped[Optional["AssetCost"]] = relationship(

View File

@@ -9,6 +9,7 @@ Usage:
from app.schemas.address import AddressIn, AddressOut
"""
import uuid
from pydantic import BaseModel, Field, ConfigDict
from typing import Optional
from datetime import datetime
@@ -42,7 +43,7 @@ class AddressOut(BaseModel):
Includes the full Address object data, with zip/city resolved
via the postal_code relationship.
"""
id: str # UUID as string for JSON serialization
id: uuid.UUID # UUID as native UUID for JSON serialization
zip: Optional[str] = None
city: Optional[str] = None
street_name: Optional[str] = None