refaktor címtár
This commit is contained in:
140
backend/app/services/address_service.py
Normal file
140
backend/app/services/address_service.py
Normal file
@@ -0,0 +1,140 @@
|
||||
"""
|
||||
Address Service (P0 Refactoring).
|
||||
|
||||
Provides helper functions for creating, updating, and resolving
|
||||
Address records in the system.addresses table.
|
||||
|
||||
Usage:
|
||||
from app.services.address_service import create_or_update_address
|
||||
"""
|
||||
import uuid
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.models.identity.address import Address, GeoPostalCode
|
||||
from app.schemas.address import AddressIn
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def _resolve_postal_code(db: AsyncSession, zip_code: str, city: str) -> Optional[int]:
|
||||
"""Find or create a GeoPostalCode record and return its ID.
|
||||
|
||||
Looks up by zip_code first; if not found, creates a new record.
|
||||
"""
|
||||
stmt = select(GeoPostalCode).where(GeoPostalCode.zip_code == zip_code)
|
||||
result = await db.execute(stmt)
|
||||
postal = result.scalar_one_or_none()
|
||||
|
||||
if not postal:
|
||||
postal = GeoPostalCode(
|
||||
zip_code=zip_code,
|
||||
city=city,
|
||||
country_code="HU",
|
||||
)
|
||||
db.add(postal)
|
||||
await db.flush()
|
||||
logger.info(f"Created new GeoPostalCode: {zip_code} - {city} (id={postal.id})")
|
||||
|
||||
return postal.id
|
||||
|
||||
|
||||
async def create_address(db: AsyncSession, addr_in: AddressIn) -> Address:
|
||||
"""Create a new Address record from AddressIn data.
|
||||
|
||||
If zip and city are provided, resolves/creates a GeoPostalCode record
|
||||
and links it via postal_code_id.
|
||||
"""
|
||||
address = Address(
|
||||
id=uuid.uuid4(),
|
||||
street_name=addr_in.street_name,
|
||||
street_type=addr_in.street_type,
|
||||
house_number=addr_in.house_number,
|
||||
stairwell=addr_in.stairwell,
|
||||
floor=addr_in.floor,
|
||||
door=addr_in.door,
|
||||
parcel_id=addr_in.parcel_id,
|
||||
full_address_text=addr_in.full_address_text,
|
||||
latitude=addr_in.latitude,
|
||||
longitude=addr_in.longitude,
|
||||
)
|
||||
|
||||
# Resolve postal code if zip is provided
|
||||
if addr_in.zip and addr_in.city:
|
||||
postal_code_id = await _resolve_postal_code(db, addr_in.zip, addr_in.city)
|
||||
address.postal_code_id = postal_code_id
|
||||
|
||||
db.add(address)
|
||||
await db.flush()
|
||||
logger.info(f"Created Address {address.id}")
|
||||
return address
|
||||
|
||||
|
||||
async def update_address(db: AsyncSession, address: Address, addr_in: AddressIn) -> Address:
|
||||
"""Update an existing Address record from AddressIn data.
|
||||
|
||||
Only updates fields that are explicitly set (not None) in addr_in.
|
||||
If zip/city changes, resolves the new GeoPostalCode.
|
||||
"""
|
||||
update_fields = addr_in.model_dump(exclude_unset=True)
|
||||
|
||||
# Handle zip/city -> postal_code_id resolution
|
||||
if "zip" in update_fields or "city" in update_fields:
|
||||
new_zip = update_fields.get("zip") or address.zip
|
||||
new_city = update_fields.get("city") or address.city
|
||||
if new_zip and new_city:
|
||||
postal_code_id = await _resolve_postal_code(db, new_zip, new_city)
|
||||
address.postal_code_id = postal_code_id
|
||||
|
||||
# Map AddressIn field names to Address model attributes
|
||||
field_mapping = {
|
||||
"zip": None, # handled above
|
||||
"city": None, # handled above
|
||||
"street_name": "street_name",
|
||||
"street_type": "street_type",
|
||||
"house_number": "house_number",
|
||||
"stairwell": "stairwell",
|
||||
"floor": "floor",
|
||||
"door": "door",
|
||||
"parcel_id": "parcel_id",
|
||||
"full_address_text": "full_address_text",
|
||||
"latitude": "latitude",
|
||||
"longitude": "longitude",
|
||||
}
|
||||
|
||||
for addr_field, model_attr in field_mapping.items():
|
||||
if model_attr and addr_field in update_fields:
|
||||
setattr(address, model_attr, update_fields[addr_field])
|
||||
|
||||
await db.flush()
|
||||
logger.info(f"Updated Address {address.id}")
|
||||
return address
|
||||
|
||||
|
||||
async def create_or_update_address(
|
||||
db: AsyncSession,
|
||||
addr_in: Optional[AddressIn],
|
||||
existing_address: Optional[Address] = None,
|
||||
) -> Optional[Address]:
|
||||
"""Create or update an Address record based on whether one already exists.
|
||||
|
||||
This is the main helper used by PATCH endpoints.
|
||||
|
||||
Args:
|
||||
db: Database session.
|
||||
addr_in: The incoming address data (or None to skip).
|
||||
existing_address: The existing Address record (or None to create new).
|
||||
|
||||
Returns:
|
||||
The Address record, or None if addr_in was None.
|
||||
"""
|
||||
if addr_in is None:
|
||||
return existing_address # Keep existing if no new data
|
||||
|
||||
if existing_address:
|
||||
return await update_address(db, existing_address, addr_in)
|
||||
else:
|
||||
return await create_address(db, addr_in)
|
||||
Reference in New Issue
Block a user