refaktor címtár

This commit is contained in:
Roo
2026-07-01 19:49:58 +00:00
parent 6e627d0ebe
commit 7654913d21
198 changed files with 3485 additions and 1609 deletions

View File

@@ -28,7 +28,9 @@ from app.models.core_logic import SubscriptionTier, OrganizationSubscription
from app.models.marketplace.organization import Organization, OrganizationMember
from app.models.fleet.organization import ContactPerson
from app.models.identity import User, Person
from app.services.address_manager import AddressManager
from app.models.vehicle.history import AuditLog, LogSeverity
from app.schemas.address import AddressIn, AddressOut
from app.schemas.organization import (
OrganizationMemberCreate,
OrganizationMemberUpdate,
@@ -207,30 +209,36 @@ class GarageDetailsResponse(BaseModel):
is_active: bool
is_verified: bool
org_type: str
# Cím adatok (elsődleges)
# Cím adatok (elsődleges) — denormalizált mezők (backward compatible)
address_city: Optional[str] = None
address_zip: Optional[str] = None
address_street_name: Optional[str] = None
address_street_type: Optional[str] = None
address_house_number: Optional[str] = None
# P0: Unified AddressOut nested object
address_detail: Optional[AddressOut] = None
tax_number: Optional[str] = None
reg_number: Optional[str] = None
# ── Kapcsolattartó (Contact Person) ──
contact_person_name: Optional[str] = None
contact_email: Optional[str] = None
contact_phone: Optional[str] = None
# ── Számlázási cím (Billing Address) ──
# ── Számlázási cím (Billing Address) — denormalizált mezők (backward compatible) ──
billing_zip: Optional[str] = None
billing_city: Optional[str] = None
billing_street_name: Optional[str] = None
billing_street_type: Optional[str] = None
billing_house_number: Optional[str] = None
# ── Értesítési cím (Notification Address) ──
# P0: Unified AddressOut nested object for billing address
billing_address_detail: Optional[AddressOut] = None
# ── Értesítési cím (Notification Address) — denormalizált mezők (backward compatible) ──
notification_zip: Optional[str] = None
notification_city: Optional[str] = None
notification_street_name: Optional[str] = None
notification_street_type: Optional[str] = None
notification_house_number: Optional[str] = None
# P0: Unified AddressOut nested object for notification address
notification_address_detail: Optional[AddressOut] = None
# Előfizetés
subscription: Optional[SubscriptionSummary] = None
# Kapcsolattartó (ContactPerson modellből)
@@ -421,6 +429,9 @@ async def list_organizations(
# Resolve email: prefer contact_email, fallback to owner's email
resolved_email = org.contact_email or (org.owner.email if org.owner else None)
# P1 FIX: Use relationship-based city instead of ghost column org.address_city
resolved_city = org.address.city if org.address else None
garage_list.append(GarageListItem(
id=org.id,
name=org.name,
@@ -432,7 +443,7 @@ async def list_organizations(
is_verified=org.is_verified,
is_deleted=org.is_deleted,
org_type=org.org_type.value if hasattr(org.org_type, "value") else str(org.org_type),
city=org.address_city,
city=resolved_city,
region=org.region,
segment=org.segment,
member_count=member_counts.get(org.id, 0),
@@ -1078,6 +1089,19 @@ async def get_organization_details(
f"({org.full_name}) — {len(members_list)} members"
)
# ── P0 REFACTORED: Build unified AddressOut objects from relationships ──
address_detail = None
if org.address:
address_detail = AddressOut.model_validate(org.address)
billing_address_detail = None
if org.billing_address:
billing_address_detail = AddressOut.model_validate(org.billing_address)
notification_address_detail = None
if org.notification_address:
notification_address_detail = AddressOut.model_validate(org.notification_address)
return GarageDetailsResponse(
id=org.id,
name=org.name,
@@ -1087,11 +1111,13 @@ async def get_organization_details(
is_active=org.is_active,
is_verified=org.is_verified,
org_type=org.org_type.value if hasattr(org.org_type, "value") else str(org.org_type),
address_city=org.address_city,
address_zip=org.address_zip,
address_street_name=org.address_street_name,
address_street_type=org.address_street_type,
address_house_number=org.address_house_number,
# ── P0 REFACTORED: Denormalized fields populated from relationship ──
address_city=org.address.city if org.address else None,
address_zip=org.address.zip if org.address else None,
address_street_name=org.address.street_name if org.address else None,
address_street_type=org.address.street_type if org.address else None,
address_house_number=org.address.house_number if org.address else None,
address_detail=address_detail,
tax_number=org.tax_number,
reg_number=org.reg_number,
# ── P0: Zero-Duplication Kapcsolattartó (owner fallback) ──
@@ -1099,17 +1125,19 @@ async def get_organization_details(
contact_email=resolved_contact_email,
contact_phone=resolved_contact_phone,
# ── Számlázási cím ──
billing_zip=org.billing_zip,
billing_city=org.billing_city,
billing_street_name=org.billing_street_name,
billing_street_type=org.billing_street_type,
billing_house_number=org.billing_house_number,
billing_zip=org.billing_address.zip if org.billing_address else None,
billing_city=org.billing_address.city if org.billing_address else None,
billing_street_name=org.billing_address.street_name if org.billing_address else None,
billing_street_type=org.billing_address.street_type if org.billing_address else None,
billing_house_number=org.billing_address.house_number if org.billing_address else None,
billing_address_detail=billing_address_detail,
# ── Értesítési cím ──
notification_zip=org.notification_zip,
notification_city=org.notification_city,
notification_street_name=org.notification_street_name,
notification_street_type=org.notification_street_type,
notification_house_number=org.notification_house_number,
notification_zip=org.notification_address.zip if org.notification_address else None,
notification_city=org.notification_address.city if org.notification_address else None,
notification_street_name=org.notification_address.street_name if org.notification_address else None,
notification_street_type=org.notification_address.street_type if org.notification_address else None,
notification_house_number=org.notification_address.house_number if org.notification_address else None,
notification_address_detail=notification_address_detail,
subscription=subscription_summary,
primary_contact=primary_contact,
created_at=org.created_at.isoformat() if org.created_at else None,
@@ -1224,28 +1252,34 @@ class OrganizationUpdate(BaseModel):
# ── Org-level email/phone (used for individual garages to propagate to Person/User) ──
email: Optional[str] = Field(None, max_length=255, description="Szervezeti e-mail (individual garages esetén a User rekordba propagálódik)")
phone: Optional[str] = Field(None, max_length=50, description="Szervezeti telefon (individual garages esetén a Person rekordba propagálódik)")
# ── Elsődleges cím (Primary Address) ──
address_zip: Optional[str] = Field(None, max_length=10, description="Irányítószám")
address_city: Optional[str] = Field(None, max_length=100, description="Város")
address_street_name: Optional[str] = Field(None, max_length=150, description="Utca neve")
address_street_type: Optional[str] = Field(None, max_length=50, description="Utca típusa (utca, tér, stb.)")
address_house_number: Optional[str] = Field(None, max_length=20, description="Házszám")
# ── Elsődleges cím (Primary Address) — DEPRECATED: Use address_detail instead ──
address_zip: Optional[str] = Field(None, max_length=10, description="[DEPRECATED] Használd az address_detail mezőt! Irányítószám")
address_city: Optional[str] = Field(None, max_length=100, description="[DEPRECATED] Használd az address_detail mezőt! Város")
address_street_name: Optional[str] = Field(None, max_length=150, description="[DEPRECATED] Használd az address_detail mezőt! Utca neve")
address_street_type: Optional[str] = Field(None, max_length=50, description="[DEPRECATED] Használd az address_detail mezőt! Utca típusa (utca, tér, stb.)")
address_house_number: Optional[str] = Field(None, max_length=20, description="[DEPRECATED] Használd az address_detail mezőt! Házszám")
# P0: Unified AddressIn nested object for primary address
address_detail: Optional[AddressIn] = None
# ── Kapcsolattartó (Contact Person) ──
contact_person_name: Optional[str] = Field(None, max_length=255, description="Kapcsolattartó neve")
contact_email: Optional[str] = Field(None, max_length=255, description="Kapcsolattartó e-mail címe")
contact_phone: Optional[str] = Field(None, max_length=50, description="Kapcsolattartó telefonszáma")
# ── Számlázási cím (Billing Address) ──
billing_zip: Optional[str] = Field(None, max_length=10, description="Számlázási irányítószám")
billing_city: Optional[str] = Field(None, max_length=100, description="Számlázási város")
billing_street_name: Optional[str] = Field(None, max_length=150, description="Számlázási utca neve")
billing_street_type: Optional[str] = Field(None, max_length=50, description="Számlázási utca típusa")
billing_house_number: Optional[str] = Field(None, max_length=20, description="Számlázási házszám")
# ── Értesítési cím (Notification Address) ──
notification_zip: Optional[str] = Field(None, max_length=10, description="Értesítési irányítószám")
notification_city: Optional[str] = Field(None, max_length=100, description="Értesítési város")
notification_street_name: Optional[str] = Field(None, max_length=150, description="Értesítési utca neve")
notification_street_type: Optional[str] = Field(None, max_length=50, description="Értesítési utca típusa")
notification_house_number: Optional[str] = Field(None, max_length=20, description="Értesítési házszám")
# ── Számlázási cím (Billing Address) — DEPRECATED: Use billing_address_detail instead ──
billing_zip: Optional[str] = Field(None, max_length=10, description="[DEPRECATED] Használd a billing_address_detail mezőt! Számlázási irányítószám")
billing_city: Optional[str] = Field(None, max_length=100, description="[DEPRECATED] Használd a billing_address_detail mezőt! Számlázási város")
billing_street_name: Optional[str] = Field(None, max_length=150, description="[DEPRECATED] Használd a billing_address_detail mezőt! Számlázási utca neve")
billing_street_type: Optional[str] = Field(None, max_length=50, description="[DEPRECATED] Használd a billing_address_detail mezőt! Számlázási utca típusa")
billing_house_number: Optional[str] = Field(None, max_length=20, description="[DEPRECATED] Használd a billing_address_detail mezőt! Számlázási házszám")
# P0: Unified AddressIn nested object for billing address
billing_address_detail: Optional[AddressIn] = None
# ── Értesítési cím (Notification Address) — DEPRECATED: Use notification_address_detail instead ──
notification_zip: Optional[str] = Field(None, max_length=10, description="[DEPRECATED] Használd a notification_address_detail mezőt! Értesítési irányítószám")
notification_city: Optional[str] = Field(None, max_length=100, description="[DEPRECATED] Használd a notification_address_detail mezőt! Értesítési város")
notification_street_name: Optional[str] = Field(None, max_length=150, description="[DEPRECATED] Használd a notification_address_detail mezőt! Értesítési utca neve")
notification_street_type: Optional[str] = Field(None, max_length=50, description="[DEPRECATED] Használd a notification_address_detail mezőt! Értesítési utca típusa")
notification_house_number: Optional[str] = Field(None, max_length=20, description="[DEPRECATED] Használd a notification_address_detail mezőt! Értesítési házszám")
# P0: Unified AddressIn nested object for notification address
notification_address_detail: Optional[AddressIn] = None
@router.put(
@@ -1279,10 +1313,34 @@ async def update_organization(
detail=f"Szervezet nem található ID-vel: {org_id}",
)
# 2. Összegyűjtjük a változásokat
# 2. P0 REFACTORED: Address FK logic via AddressManager
raw_data = payload.model_dump(exclude_none=True)
# ── Primary address via AddressManager ──
address_detail: Optional[AddressIn] = raw_data.pop("address_detail", None)
if address_detail:
org.address_id = await AddressManager.create_or_update(
db, org.address_id, address_detail
)
# ── Billing address via AddressManager ──
billing_address_detail: Optional[AddressIn] = raw_data.pop("billing_address_detail", None)
if billing_address_detail:
org.billing_address_id = await AddressManager.create_or_update(
db, org.billing_address_id, billing_address_detail
)
# ── Notification address via AddressManager ──
notification_address_detail: Optional[AddressIn] = raw_data.pop("notification_address_detail", None)
if notification_address_detail:
org.notification_address_id = await AddressManager.create_or_update(
db, org.notification_address_id, notification_address_detail
)
# 3. Összegyűjtjük a változásokat (non-address fields)
update_fields = {}
for field_name in payload.model_dump(exclude_none=True):
value = getattr(payload, field_name)
for field_name in raw_data:
value = raw_data[field_name]
if value is not None and hasattr(org, field_name):
setattr(org, field_name, value)
update_fields[field_name] = value
@@ -1333,6 +1391,11 @@ async def update_organization(
f"({org.full_name}): fields={list(update_fields.keys())}"
)
# ── P0 REFACTORED: Build unified AddressOut from relationship ──
resp_address_detail = None
if org.address:
resp_address_detail = AddressOut.model_validate(org.address)
return {
"status": "success",
"message": f"A(z) '{org.full_name}' garázs adatai frissítve.",
@@ -1345,11 +1408,7 @@ async def update_organization(
"display_name": org.display_name,
"tax_number": org.tax_number,
"reg_number": org.reg_number,
"address_zip": org.address_zip,
"address_city": org.address_city,
"address_street_name": org.address_street_name,
"address_street_type": org.address_street_type,
"address_house_number": org.address_house_number,
"address_detail": resp_address_detail,
"status": org.status,
"is_active": org.is_active,
"contact_email": org.contact_email,