# ================================================================ # 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_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.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.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" )