# services/suppliers/app/api/purchase_orders.py """ Purchase Order CRUD API endpoints (ATOMIC) """ from fastapi import APIRouter, Depends, HTTPException, Query, Path from typing import List, Optional, Dict, Any from uuid import UUID import structlog from sqlalchemy.orm import Session from app.core.database import get_db from app.services.purchase_order_service import PurchaseOrderService from app.schemas.suppliers import ( PurchaseOrderCreate, PurchaseOrderUpdate, PurchaseOrderResponse, PurchaseOrderSummary, PurchaseOrderSearchParams ) from app.models.suppliers import PurchaseOrderStatus from shared.auth.decorators import get_current_user_dep from shared.routing import RouteBuilder from shared.auth.access_control import require_user_role # Create route builder for consistent URL structure route_builder = RouteBuilder('suppliers') router = APIRouter(tags=["purchase-orders"]) logger = structlog.get_logger() @router.post(route_builder.build_base_route("purchase-orders"), response_model=PurchaseOrderResponse) @require_user_role(['admin', 'owner', 'member']) async def create_purchase_order( po_data: PurchaseOrderCreate, current_user: Dict[str, Any] = Depends(get_current_user_dep), db: Session = Depends(get_db) ): """Create a new purchase order""" # require_permissions(current_user, ["purchase_orders:create"]) try: service = PurchaseOrderService(db) purchase_order = await service.create_purchase_order( tenant_id=current_user.tenant_id, po_data=po_data, created_by=current_user.user_id ) return PurchaseOrderResponse.from_orm(purchase_order) except ValueError as e: raise HTTPException(status_code=400, detail=str(e)) except Exception as e: logger.error("Error creating purchase order", error=str(e)) raise HTTPException(status_code=500, detail="Failed to create purchase order") @router.get(route_builder.build_base_route("purchase-orders"), response_model=List[PurchaseOrderSummary]) async def list_purchase_orders( supplier_id: Optional[UUID] = Query(None, description="Filter by supplier ID"), status: Optional[str] = Query(None, description="Filter by status"), priority: Optional[str] = Query(None, description="Filter by priority"), date_from: Optional[str] = Query(None, description="Filter from date (YYYY-MM-DD)"), date_to: Optional[str] = Query(None, description="Filter to date (YYYY-MM-DD)"), search_term: Optional[str] = Query(None, description="Search term"), limit: int = Query(50, ge=1, le=1000, description="Number of results to return"), offset: int = Query(0, ge=0, description="Number of results to skip"), current_user: Dict[str, Any] = Depends(get_current_user_dep), db: Session = Depends(get_db) ): """List purchase orders with optional filters""" # require_permissions(current_user, ["purchase_orders:read"]) try: from datetime import datetime # Parse date filters date_from_parsed = None date_to_parsed = None if date_from: try: date_from_parsed = datetime.fromisoformat(date_from) except ValueError: raise HTTPException(status_code=400, detail="Invalid date_from format") if date_to: try: date_to_parsed = datetime.fromisoformat(date_to) except ValueError: raise HTTPException(status_code=400, detail="Invalid date_to format") # Validate status status_enum = None if status: try: status_enum = PurchaseOrderStatus(status.upper()) except ValueError: raise HTTPException(status_code=400, detail="Invalid status") service = PurchaseOrderService(db) search_params = PurchaseOrderSearchParams( supplier_id=supplier_id, status=status_enum, priority=priority, date_from=date_from_parsed, date_to=date_to_parsed, search_term=search_term, limit=limit, offset=offset ) orders = await service.search_purchase_orders( tenant_id=current_user.tenant_id, search_params=search_params ) return [PurchaseOrderSummary.from_orm(order) for order in orders] except HTTPException: raise except Exception as e: logger.error("Error listing purchase orders", error=str(e)) raise HTTPException(status_code=500, detail="Failed to retrieve purchase orders") @router.get(route_builder.build_resource_detail_route("purchase-orders", "po_id"), response_model=PurchaseOrderResponse) async def get_purchase_order( po_id: UUID = Path(..., description="Purchase order ID"), current_user: Dict[str, Any] = Depends(get_current_user_dep), db: Session = Depends(get_db) ): """Get purchase order by ID with items""" # require_permissions(current_user, ["purchase_orders:read"]) try: service = PurchaseOrderService(db) purchase_order = await service.get_purchase_order(po_id) if not purchase_order: raise HTTPException(status_code=404, detail="Purchase order not found") # Check tenant access if purchase_order.tenant_id != current_user.tenant_id: raise HTTPException(status_code=403, detail="Access denied") return PurchaseOrderResponse.from_orm(purchase_order) except HTTPException: raise except Exception as e: logger.error("Error getting purchase order", po_id=str(po_id), error=str(e)) raise HTTPException(status_code=500, detail="Failed to retrieve purchase order") @router.put(route_builder.build_resource_detail_route("purchase-orders", "po_id"), response_model=PurchaseOrderResponse) @require_user_role(['admin', 'owner', 'member']) async def update_purchase_order( po_data: PurchaseOrderUpdate, po_id: UUID = Path(..., description="Purchase order ID"), current_user: Dict[str, Any] = Depends(get_current_user_dep), db: Session = Depends(get_db) ): """Update purchase order information""" # require_permissions(current_user, ["purchase_orders:update"]) try: service = PurchaseOrderService(db) # Check order exists and belongs to tenant existing_order = await service.get_purchase_order(po_id) if not existing_order: raise HTTPException(status_code=404, detail="Purchase order not found") if existing_order.tenant_id != current_user.tenant_id: raise HTTPException(status_code=403, detail="Access denied") purchase_order = await service.update_purchase_order( po_id=po_id, po_data=po_data, updated_by=current_user.user_id ) if not purchase_order: raise HTTPException(status_code=404, detail="Purchase order not found") return PurchaseOrderResponse.from_orm(purchase_order) except HTTPException: raise except ValueError as e: raise HTTPException(status_code=400, detail=str(e)) except Exception as e: logger.error("Error updating purchase order", po_id=str(po_id), error=str(e)) raise HTTPException(status_code=500, detail="Failed to update purchase order")