Files
bakery-ia/services/orders/app/api/customers.py
2025-10-12 18:47:33 +02:00

263 lines
8.3 KiB
Python

# ================================================================
# services/orders/app/api/customers.py
# ================================================================
"""
Customers API endpoints - ATOMIC CRUD operations
"""
from typing import List
from uuid import UUID
from fastapi import APIRouter, Depends, HTTPException, Path, Query, status
import structlog
from shared.auth.decorators import get_current_user_dep
from shared.auth.access_control import require_user_role
from shared.routing import RouteBuilder
from app.core.database import get_db
from app.services.orders_service import OrdersService
from app.schemas.order_schemas import (
CustomerCreate,
CustomerUpdate,
CustomerResponse
)
logger = structlog.get_logger()
# Create route builder for consistent URL structure
route_builder = RouteBuilder('orders')
router = APIRouter()
# ===== Dependency Injection =====
async def get_orders_service(db = Depends(get_db)) -> OrdersService:
"""Get orders service with dependencies"""
from app.repositories.order_repository import (
OrderRepository,
CustomerRepository,
OrderItemRepository,
OrderStatusHistoryRepository
)
from shared.clients import (
get_inventory_client,
get_production_client,
get_sales_client
)
return OrdersService(
order_repo=OrderRepository(),
customer_repo=CustomerRepository(),
order_item_repo=OrderItemRepository(),
status_history_repo=OrderStatusHistoryRepository(),
inventory_client=get_inventory_client(),
production_client=get_production_client(),
sales_client=get_sales_client()
)
# ===== Customer CRUD Endpoints =====
@router.post(
route_builder.build_base_route("customers"),
response_model=CustomerResponse,
status_code=status.HTTP_201_CREATED
)
@require_user_role(['admin', 'owner', 'member'])
async def create_customer(
customer_data: CustomerCreate,
tenant_id: UUID = Path(...),
current_user: dict = Depends(get_current_user_dep),
orders_service: OrdersService = Depends(get_orders_service),
db = Depends(get_db)
):
"""Create a new customer"""
try:
# Ensure tenant_id matches
customer_data.tenant_id = tenant_id
# Check if customer code already exists
existing_customer = await orders_service.customer_repo.get_by_customer_code(
db, customer_data.customer_code, tenant_id
)
if existing_customer:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Customer code already exists"
)
customer = await orders_service.customer_repo.create(
db,
obj_in=customer_data.dict(),
created_by=UUID(current_user["sub"])
)
logger.info("Customer created successfully",
customer_id=str(customer.id),
customer_code=customer.customer_code)
return CustomerResponse.from_orm(customer)
except HTTPException:
raise
except Exception as e:
logger.error("Error creating customer", error=str(e))
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Failed to create customer"
)
@router.get(
route_builder.build_base_route("customers"),
response_model=List[CustomerResponse]
)
async def get_customers(
tenant_id: UUID = Path(...),
active_only: bool = Query(True, description="Filter for active customers only"),
skip: int = Query(0, ge=0, description="Number of customers to skip"),
limit: int = Query(100, ge=1, le=1000, description="Number of customers to return"),
current_user: dict = Depends(get_current_user_dep),
orders_service: OrdersService = Depends(get_orders_service),
db = Depends(get_db)
):
"""Get customers with filtering and pagination"""
try:
if active_only:
customers = await orders_service.customer_repo.get_active_customers(
db, tenant_id, skip, limit
)
else:
customers = await orders_service.customer_repo.get_multi(
db, tenant_id, skip, limit, order_by="name"
)
return [CustomerResponse.from_orm(customer) for customer in customers]
except Exception as e:
logger.error("Error getting customers", error=str(e))
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Failed to retrieve customers"
)
@router.get(
route_builder.build_resource_detail_route("customers", "customer_id"),
response_model=CustomerResponse
)
async def get_customer(
tenant_id: UUID = Path(...),
customer_id: UUID = Path(...),
current_user: dict = Depends(get_current_user_dep),
orders_service: OrdersService = Depends(get_orders_service),
db = Depends(get_db)
):
"""Get customer details by ID"""
try:
customer = await orders_service.customer_repo.get(db, customer_id, tenant_id)
if not customer:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Customer not found"
)
return CustomerResponse.from_orm(customer)
except HTTPException:
raise
except Exception as e:
logger.error("Error getting customer",
customer_id=str(customer_id),
error=str(e))
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Failed to retrieve customer"
)
@router.put(
route_builder.build_resource_detail_route("customers", "customer_id"),
response_model=CustomerResponse
)
@require_user_role(['admin', 'owner', 'member'])
async def update_customer(
customer_data: CustomerUpdate,
tenant_id: UUID = Path(...),
customer_id: UUID = Path(...),
current_user: dict = Depends(get_current_user_dep),
orders_service: OrdersService = Depends(get_orders_service),
db = Depends(get_db)
):
"""Update customer information"""
try:
# Get existing customer
customer = await orders_service.customer_repo.get(db, customer_id, tenant_id)
if not customer:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Customer not found"
)
# Update customer
updated_customer = await orders_service.customer_repo.update(
db,
db_obj=customer,
obj_in=customer_data.dict(exclude_unset=True),
updated_by=UUID(current_user["sub"])
)
logger.info("Customer updated successfully",
customer_id=str(customer_id))
return CustomerResponse.from_orm(updated_customer)
except HTTPException:
raise
except Exception as e:
logger.error("Error updating customer",
customer_id=str(customer_id),
error=str(e))
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Failed to update customer"
)
@router.delete(
route_builder.build_resource_detail_route("customers", "customer_id"),
status_code=status.HTTP_204_NO_CONTENT
)
@require_user_role(['admin', 'owner'])
async def delete_customer(
tenant_id: UUID = Path(...),
customer_id: UUID = Path(...),
current_user: dict = Depends(get_current_user_dep),
orders_service: OrdersService = Depends(get_orders_service),
db = Depends(get_db)
):
"""Delete a customer (soft delete)"""
try:
customer = await orders_service.customer_repo.get(db, customer_id, tenant_id)
if not customer:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Customer not found"
)
await orders_service.customer_repo.delete(db, customer_id, tenant_id)
logger.info("Customer deleted successfully",
customer_id=str(customer_id))
except HTTPException:
raise
except Exception as e:
logger.error("Error deleting customer",
customer_id=str(customer_id),
error=str(e))
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Failed to delete customer"
)