# ================================================================ # services/orders/app/api/orders.py # ================================================================ """ Orders API endpoints for Orders Service """ from datetime import date, datetime from typing import List, Optional from uuid import UUID from fastapi import APIRouter, Depends, HTTPException, Path, Query, status from fastapi.responses import JSONResponse import structlog from shared.auth.decorators import get_current_user_dep from app.core.database import get_db from app.services.orders_service import OrdersService from app.schemas.order_schemas import ( OrderCreate, OrderUpdate, OrderResponse, CustomerCreate, CustomerUpdate, CustomerResponse, OrdersDashboardSummary, DemandRequirements, ProcurementPlanningData ) logger = structlog.get_logger() 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(), notification_client=None # Notification client not available ) # ===== Dashboard and Analytics Endpoints ===== @router.get("/tenants/{tenant_id}/orders/dashboard-summary", response_model=OrdersDashboardSummary) async def get_dashboard_summary( tenant_id: UUID = Path(...), current_user: dict = Depends(get_current_user_dep), orders_service: OrdersService = Depends(get_orders_service), db = Depends(get_db) ): """Get comprehensive dashboard summary for orders""" try: summary = await orders_service.get_dashboard_summary(db, tenant_id) logger.info("Dashboard summary retrieved", tenant_id=str(tenant_id), total_orders=summary.total_orders_today) return summary except Exception as e: logger.error("Error getting dashboard summary", tenant_id=str(tenant_id), error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to retrieve dashboard summary" ) @router.get("/tenants/{tenant_id}/orders/demand-requirements", response_model=DemandRequirements) async def get_demand_requirements( tenant_id: UUID = Path(...), target_date: date = Query(..., description="Date for demand analysis"), current_user: dict = Depends(get_current_user_dep), orders_service: OrdersService = Depends(get_orders_service), db = Depends(get_db) ): """Get demand requirements for production planning""" try: requirements = await orders_service.get_demand_requirements(db, tenant_id, target_date) logger.info("Demand requirements calculated", tenant_id=str(tenant_id), target_date=str(target_date), total_orders=requirements.total_orders) return requirements except Exception as e: logger.error("Error getting demand requirements", tenant_id=str(tenant_id), error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to calculate demand requirements" ) # ===== Order Management Endpoints ===== @router.post("/tenants/{tenant_id}/orders", response_model=OrderResponse, status_code=status.HTTP_201_CREATED) 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("/tenants/{tenant_id}/orders/{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.get("/tenants/{tenant_id}/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.put("/tenants/{tenant_id}/orders/{order_id}/status", response_model=OrderResponse) async def update_order_status( new_status: str, tenant_id: UUID = Path(...), order_id: UUID = Path(...), reason: Optional[str] = Query(None, description="Reason for status change"), current_user: dict = Depends(get_current_user_dep), orders_service: OrdersService = Depends(get_orders_service), db = Depends(get_db) ): """Update order status""" try: # Validate status valid_statuses = ["pending", "confirmed", "in_production", "ready", "out_for_delivery", "delivered", "cancelled", "failed"] if new_status not in valid_statuses: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=f"Invalid status. Must be one of: {', '.join(valid_statuses)}" ) order = await orders_service.update_order_status( db, order_id, tenant_id, new_status, user_id=UUID(current_user["sub"]), reason=reason ) if not order: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Order not found" ) logger.info("Order status updated", order_id=str(order_id), new_status=new_status) return order except HTTPException: raise except Exception as e: logger.error("Error updating order status", order_id=str(order_id), error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to update order status" ) # ===== Customer Management Endpoints ===== @router.post("/tenants/{tenant_id}/customers", response_model=CustomerResponse, status_code=status.HTTP_201_CREATED) 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("/tenants/{tenant_id}/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("/tenants/{tenant_id}/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""" 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" ) # ===== Business Intelligence Endpoints ===== @router.get("/tenants/{tenant_id}/orders/business-model") async def detect_business_model( tenant_id: UUID = Path(...), current_user: dict = Depends(get_current_user_dep), orders_service: OrdersService = Depends(get_orders_service), db = Depends(get_db) ): """Detect business model based on order patterns""" try: business_model = await orders_service.detect_business_model(db, tenant_id) return { "business_model": business_model, "confidence": "high" if business_model else "unknown", "detected_at": datetime.now().isoformat() } except Exception as e: logger.error("Error detecting business model", error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to detect business model" ) # ===== Health and Status Endpoints ===== @router.get("/tenants/{tenant_id}/orders/status") async def get_service_status( tenant_id: UUID = Path(...), current_user: dict = Depends(get_current_user_dep) ): """Get orders service status""" try: return { "service": "orders-service", "status": "healthy", "timestamp": datetime.now().isoformat(), "tenant_id": str(tenant_id) } except Exception as e: logger.error("Error getting service status", error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to get service status" )