# ================================================================ # services/orders/app/api/orders.py # ================================================================ """ Orders API endpoints - ATOMIC CRUD operations only """ from datetime import date from typing import List, Optional 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 ( OrderCreate, OrderUpdate, OrderResponse ) 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() ) # ===== Order CRUD Endpoints ===== @router.post( route_builder.build_base_route("orders"), response_model=OrderResponse, status_code=status.HTTP_201_CREATED ) @require_user_role(['admin', 'owner', 'member']) async def create_order( order_data: OrderCreate, 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 order""" try: # Ensure tenant_id matches order_data.tenant_id = tenant_id order = await orders_service.create_order( db, order_data, user_id=UUID(current_user["sub"]) ) logger.info("Order created successfully", order_id=str(order.id), order_number=order.order_number) return order except ValueError as e: logger.warning("Invalid order data", error=str(e)) raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=str(e) ) except Exception as e: logger.error("Error creating order", error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to create order" ) @router.get( route_builder.build_base_route("orders"), response_model=List[OrderResponse] ) async def get_orders( tenant_id: UUID = Path(...), status_filter: Optional[str] = Query(None, description="Filter by order status"), start_date: Optional[date] = Query(None, description="Start date for date range filter"), end_date: Optional[date] = Query(None, description="End date for date range filter"), skip: int = Query(0, ge=0, description="Number of orders to skip"), limit: int = Query(100, ge=1, le=1000, description="Number of orders to return"), current_user: dict = Depends(get_current_user_dep), orders_service: OrdersService = Depends(get_orders_service), db = Depends(get_db) ): """Get orders with filtering and pagination""" try: # Determine which repository method to use based on filters if status_filter: orders = await orders_service.order_repo.get_orders_by_status( db, tenant_id, status_filter, skip, limit ) elif start_date and end_date: orders = await orders_service.order_repo.get_orders_by_date_range( db, tenant_id, start_date, end_date, skip, limit ) else: orders = await orders_service.order_repo.get_multi( db, tenant_id, skip, limit, order_by="order_date", order_desc=True ) return [OrderResponse.from_orm(order) for order in orders] except Exception as e: logger.error("Error getting orders", error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to retrieve orders" ) @router.get( route_builder.build_base_route("{order_id}"), response_model=OrderResponse ) async def get_order( tenant_id: UUID = Path(...), order_id: UUID = Path(...), current_user: dict = Depends(get_current_user_dep), orders_service: OrdersService = Depends(get_orders_service), db = Depends(get_db) ): """Get order details with items""" try: order = await orders_service.get_order_with_items(db, order_id, tenant_id) if not order: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Order not found" ) return order except HTTPException: raise except Exception as e: logger.error("Error getting order", order_id=str(order_id), error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to retrieve order" ) @router.put( route_builder.build_base_route("{order_id}"), response_model=OrderResponse ) @require_user_role(['admin', 'owner', 'member']) async def update_order( order_data: OrderUpdate, tenant_id: UUID = Path(...), order_id: UUID = Path(...), current_user: dict = Depends(get_current_user_dep), orders_service: OrdersService = Depends(get_orders_service), db = Depends(get_db) ): """Update order information""" try: # Get existing order order = await orders_service.order_repo.get(db, order_id, tenant_id) if not order: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Order not found" ) # Update order updated_order = await orders_service.order_repo.update( db, db_obj=order, obj_in=order_data.dict(exclude_unset=True), updated_by=UUID(current_user["sub"]) ) logger.info("Order updated successfully", order_id=str(order_id)) return OrderResponse.from_orm(updated_order) except HTTPException: raise except Exception as e: logger.error("Error updating order", order_id=str(order_id), error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to update order" ) @router.delete( route_builder.build_base_route("{order_id}"), status_code=status.HTTP_204_NO_CONTENT ) @require_user_role(['admin', 'owner']) async def delete_order( tenant_id: UUID = Path(...), order_id: UUID = Path(...), current_user: dict = Depends(get_current_user_dep), orders_service: OrdersService = Depends(get_orders_service), db = Depends(get_db) ): """Delete an order (soft delete)""" try: order = await orders_service.order_repo.get(db, order_id, tenant_id) if not order: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Order not found" ) await orders_service.order_repo.delete(db, order_id, tenant_id) logger.info("Order deleted successfully", order_id=str(order_id)) except HTTPException: raise except Exception as e: logger.error("Error deleting order", order_id=str(order_id), error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to delete order" )