141 lines
4.4 KiB
Python
141 lines
4.4 KiB
Python
"""
|
|
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)
|