# ================================================================ # services/orders/app/api/order_operations.py # ================================================================ """ Order Operations API endpoints - BUSINESS logic operations Includes status updates, demand calculation, dashboard, and business intelligence """ from datetime import date, datetime 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.routing import RouteBuilder from app.core.database import get_db from app.services.orders_service import OrdersService from app.schemas.order_schemas import ( OrderResponse, OrdersDashboardSummary, DemandRequirements ) 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() ) # ===== Dashboard and Analytics Endpoints ===== @router.get( route_builder.build_operations_route("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( route_builder.build_base_route("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 Status Management ===== @router.put( route_builder.build_base_route("{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 with validation and history tracking""" 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" ) # ===== Business Intelligence Endpoints ===== @router.get( route_builder.build_base_route("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( route_builder.build_base_route("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" )