# app/api/v1/vendor/orders.py """ Vendor order management endpoints. Vendor Context: Uses token_vendor_id from JWT token (authenticated vendor API pattern). The get_current_vendor_api dependency guarantees token_vendor_id is present. """ import logging from fastapi import APIRouter, Depends, Query from pydantic import BaseModel, Field from sqlalchemy.orm import Session from app.api.deps import get_current_vendor_api from app.core.database import get_db from app.services.order_inventory_service import order_inventory_service from app.services.order_service import order_service from models.schema.auth import UserContext from app.modules.orders.schemas import ( OrderDetailResponse, OrderListResponse, OrderResponse, OrderUpdate, ) router = APIRouter(prefix="/orders") logger = logging.getLogger(__name__) @router.get("", response_model=OrderListResponse) def get_vendor_orders( skip: int = Query(0, ge=0), limit: int = Query(100, ge=1, le=1000), status: str | None = Query(None, description="Filter by order status"), customer_id: int | None = Query(None, description="Filter by customer"), current_user: UserContext = Depends(get_current_vendor_api), db: Session = Depends(get_db), ): """ Get all orders for vendor. Supports filtering by: - status: Order status (pending, processing, shipped, delivered, cancelled) - customer_id: Filter orders from specific customer Vendor is determined from JWT token (vendor_id claim). Requires Authorization header (API endpoint). """ orders, total = order_service.get_vendor_orders( db=db, vendor_id=current_user.token_vendor_id, skip=skip, limit=limit, status=status, customer_id=customer_id, ) return OrderListResponse( orders=[OrderResponse.model_validate(o) for o in orders], total=total, skip=skip, limit=limit, ) @router.get("/{order_id}", response_model=OrderDetailResponse) def get_order_details( order_id: int, current_user: UserContext = Depends(get_current_vendor_api), db: Session = Depends(get_db), ): """ Get detailed order information including items and addresses. Requires Authorization header (API endpoint). """ order = order_service.get_order( db=db, vendor_id=current_user.token_vendor_id, order_id=order_id ) return OrderDetailResponse.model_validate(order) @router.put("/{order_id}/status", response_model=OrderResponse) def update_order_status( order_id: int, order_update: OrderUpdate, current_user: UserContext = Depends(get_current_vendor_api), db: Session = Depends(get_db), ): """ Update order status and tracking information. Valid statuses: - pending: Order placed, awaiting processing - processing: Order being prepared - shipped: Order shipped to customer - delivered: Order delivered - cancelled: Order cancelled - refunded: Order refunded Requires Authorization header (API endpoint). """ order = order_service.update_order_status( db=db, vendor_id=current_user.token_vendor_id, order_id=order_id, order_update=order_update, ) db.commit() logger.info( f"Order {order.order_number} status updated to {order.status} " f"by user {current_user.username}" ) return OrderResponse.model_validate(order) # ============================================================================ # Partial Shipment Endpoints # ============================================================================ class ShipItemRequest(BaseModel): """Request to ship specific quantity of an order item.""" quantity: int | None = Field( None, ge=1, description="Quantity to ship (default: remaining quantity)" ) class ShipItemResponse(BaseModel): """Response from shipping an item.""" order_id: int item_id: int fulfilled_quantity: int shipped_quantity: int | None = None remaining_quantity: int | None = None is_fully_shipped: bool | None = None message: str | None = None class ShipmentStatusItemResponse(BaseModel): """Item-level shipment status.""" item_id: int product_id: int product_name: str quantity: int shipped_quantity: int remaining_quantity: int is_fully_shipped: bool is_partially_shipped: bool class ShipmentStatusResponse(BaseModel): """Order shipment status response.""" order_id: int order_number: str order_status: str is_fully_shipped: bool is_partially_shipped: bool shipped_item_count: int total_item_count: int total_shipped_units: int total_ordered_units: int items: list[ShipmentStatusItemResponse] @router.get("/{order_id}/shipment-status", response_model=ShipmentStatusResponse) def get_shipment_status( order_id: int, current_user: UserContext = Depends(get_current_vendor_api), db: Session = Depends(get_db), ): """ Get detailed shipment status for an order. Returns item-level shipment status showing what has been shipped and what remains. Useful for partial shipment tracking. Requires Authorization header (API endpoint). """ result = order_inventory_service.get_shipment_status( db=db, vendor_id=current_user.token_vendor_id, order_id=order_id, ) return ShipmentStatusResponse( order_id=result["order_id"], order_number=result["order_number"], order_status=result["order_status"], is_fully_shipped=result["is_fully_shipped"], is_partially_shipped=result["is_partially_shipped"], shipped_item_count=result["shipped_item_count"], total_item_count=result["total_item_count"], total_shipped_units=result["total_shipped_units"], total_ordered_units=result["total_ordered_units"], items=[ShipmentStatusItemResponse(**item) for item in result["items"]], ) @router.post("/{order_id}/items/{item_id}/ship", response_model=ShipItemResponse) def ship_order_item( order_id: int, item_id: int, request: ShipItemRequest | None = None, current_user: UserContext = Depends(get_current_vendor_api), db: Session = Depends(get_db), ): """ Ship a specific order item (supports partial shipment). Fulfills inventory and updates the item's shipped quantity. If quantity is not specified, ships the remaining quantity. Example use cases: - Ship all of an item: POST /orders/{id}/items/{item_id}/ship - Ship partial: POST /orders/{id}/items/{item_id}/ship with {"quantity": 2} Requires Authorization header (API endpoint). """ quantity = request.quantity if request else None result = order_inventory_service.fulfill_item( db=db, vendor_id=current_user.token_vendor_id, order_id=order_id, item_id=item_id, quantity=quantity, skip_missing=True, ) # Update order status based on shipment state order = order_service.get_order(db, current_user.token_vendor_id, order_id) if order.is_fully_shipped and order.status != "shipped": order_service.update_order_status( db=db, vendor_id=current_user.token_vendor_id, order_id=order_id, order_update=OrderUpdate(status="shipped"), ) logger.info(f"Order {order.order_number} fully shipped") elif order.is_partially_shipped and order.status not in ( "partially_shipped", "shipped", ): order_service.update_order_status( db=db, vendor_id=current_user.token_vendor_id, order_id=order_id, order_update=OrderUpdate(status="partially_shipped"), ) logger.info(f"Order {order.order_number} partially shipped") db.commit() logger.info( f"Shipped item {item_id} of order {order_id}: " f"{result.get('fulfilled_quantity', 0)} units" ) return ShipItemResponse(**result)