feat: add customer multiple addresses management

- Add CustomerAddressService with CRUD operations
- Add shop API endpoints for address management (GET, POST, PUT, DELETE)
- Add set default endpoint for address type
- Implement addresses.html with full UI (cards, modals, Alpine.js)
- Integrate saved addresses in checkout flow
  - Address selector dropdowns for shipping/billing
  - Auto-select default addresses
  - Save new address checkbox option
- Add country_iso field alongside country_name
- Add address exceptions (NotFound, LimitExceeded, InvalidType)
- Max 10 addresses per customer limit
- One default address per type (shipping/billing)
- Add unit tests for CustomerAddressService
- Add integration tests for shop addresses API

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-02 19:16:35 +01:00
parent ea0218746f
commit b5b32fb351
15 changed files with 3940 additions and 17 deletions

View File

@@ -6,6 +6,13 @@ This module provides frontend-friendly exceptions with consistent error codes,
messages, and HTTP status mappings.
"""
# Address exceptions
from .address import (
AddressLimitExceededException,
AddressNotFoundException,
InvalidAddressTypeException,
)
# Admin exceptions
from .admin import (
AdminOperationException,

38
app/exceptions/address.py Normal file
View File

@@ -0,0 +1,38 @@
# app/exceptions/address.py
"""
Address-related custom exceptions.
Used for customer address management operations.
"""
from .base import BusinessLogicException, ResourceNotFoundException
class AddressNotFoundException(ResourceNotFoundException):
"""Raised when a customer address is not found."""
def __init__(self, address_id: str | int):
super().__init__(
resource_type="Address",
identifier=str(address_id),
)
class AddressLimitExceededException(BusinessLogicException):
"""Raised when customer exceeds maximum number of addresses."""
def __init__(self, max_addresses: int = 10):
super().__init__(
message=f"Maximum number of addresses ({max_addresses}) reached",
error_code="ADDRESS_LIMIT_EXCEEDED",
)
class InvalidAddressTypeException(BusinessLogicException):
"""Raised when an invalid address type is provided."""
def __init__(self, address_type: str):
super().__init__(
message=f"Invalid address type '{address_type}'. Must be 'shipping' or 'billing'",
error_code="INVALID_ADDRESS_TYPE",
)