2025-08-13 17:39:35 +02:00
|
|
|
# services/suppliers/app/api/purchase_orders.py
|
|
|
|
|
"""
|
|
|
|
|
Purchase Order API endpoints
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, Query, Path
|
|
|
|
|
from typing import List, Optional
|
|
|
|
|
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, PurchaseOrderStatusUpdate, PurchaseOrderApproval,
|
|
|
|
|
PurchaseOrderStatistics
|
|
|
|
|
)
|
|
|
|
|
from app.models.suppliers import PurchaseOrderStatus
|
2025-08-15 22:40:19 +02:00
|
|
|
from shared.auth.decorators import get_current_user_dep
|
|
|
|
|
from typing import Dict, Any
|
2025-08-13 17:39:35 +02:00
|
|
|
|
|
|
|
|
router = APIRouter(prefix="/purchase-orders", tags=["purchase-orders"])
|
|
|
|
|
logger = structlog.get_logger()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/", response_model=PurchaseOrderResponse)
|
|
|
|
|
async def create_purchase_order(
|
|
|
|
|
po_data: PurchaseOrderCreate,
|
2025-08-15 22:40:19 +02:00
|
|
|
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
2025-08-13 17:39:35 +02:00
|
|
|
db: Session = Depends(get_db)
|
|
|
|
|
):
|
|
|
|
|
"""Create a new purchase order"""
|
2025-08-15 22:40:19 +02:00
|
|
|
# require_permissions(current_user, ["purchase_orders:create"])
|
2025-08-13 17:39:35 +02:00
|
|
|
|
|
|
|
|
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("/", 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"),
|
2025-08-15 22:40:19 +02:00
|
|
|
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
2025-08-13 17:39:35 +02:00
|
|
|
db: Session = Depends(get_db)
|
|
|
|
|
):
|
|
|
|
|
"""List purchase orders with optional filters"""
|
2025-08-15 22:40:19 +02:00
|
|
|
# require_permissions(current_user, ["purchase_orders:read"])
|
2025-08-13 17:39:35 +02:00
|
|
|
|
|
|
|
|
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("/statistics", response_model=PurchaseOrderStatistics)
|
|
|
|
|
async def get_purchase_order_statistics(
|
2025-08-15 22:40:19 +02:00
|
|
|
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
2025-08-13 17:39:35 +02:00
|
|
|
db: Session = Depends(get_db)
|
|
|
|
|
):
|
|
|
|
|
"""Get purchase order statistics for dashboard"""
|
2025-08-15 22:40:19 +02:00
|
|
|
# require_permissions(current_user, ["purchase_orders:read"])
|
2025-08-13 17:39:35 +02:00
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
service = PurchaseOrderService(db)
|
|
|
|
|
stats = await service.get_purchase_order_statistics(current_user.tenant_id)
|
|
|
|
|
return PurchaseOrderStatistics(**stats)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error("Error getting purchase order statistics", error=str(e))
|
|
|
|
|
raise HTTPException(status_code=500, detail="Failed to retrieve statistics")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/pending-approval", response_model=List[PurchaseOrderSummary])
|
|
|
|
|
async def get_orders_requiring_approval(
|
2025-08-15 22:40:19 +02:00
|
|
|
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
2025-08-13 17:39:35 +02:00
|
|
|
db: Session = Depends(get_db)
|
|
|
|
|
):
|
|
|
|
|
"""Get purchase orders requiring approval"""
|
2025-08-15 22:40:19 +02:00
|
|
|
# require_permissions(current_user, ["purchase_orders:approve"])
|
2025-08-13 17:39:35 +02:00
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
service = PurchaseOrderService(db)
|
|
|
|
|
orders = await service.get_orders_requiring_approval(current_user.tenant_id)
|
|
|
|
|
return [PurchaseOrderSummary.from_orm(order) for order in orders]
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error("Error getting orders requiring approval", error=str(e))
|
|
|
|
|
raise HTTPException(status_code=500, detail="Failed to retrieve orders requiring approval")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/overdue", response_model=List[PurchaseOrderSummary])
|
|
|
|
|
async def get_overdue_orders(
|
2025-08-15 22:40:19 +02:00
|
|
|
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
2025-08-13 17:39:35 +02:00
|
|
|
db: Session = Depends(get_db)
|
|
|
|
|
):
|
|
|
|
|
"""Get overdue purchase orders"""
|
2025-08-15 22:40:19 +02:00
|
|
|
# require_permissions(current_user, ["purchase_orders:read"])
|
2025-08-13 17:39:35 +02:00
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
service = PurchaseOrderService(db)
|
|
|
|
|
orders = await service.get_overdue_orders(current_user.tenant_id)
|
|
|
|
|
return [PurchaseOrderSummary.from_orm(order) for order in orders]
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error("Error getting overdue orders", error=str(e))
|
|
|
|
|
raise HTTPException(status_code=500, detail="Failed to retrieve overdue orders")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/{po_id}", response_model=PurchaseOrderResponse)
|
|
|
|
|
async def get_purchase_order(
|
|
|
|
|
po_id: UUID = Path(..., description="Purchase order ID"),
|
2025-08-15 22:40:19 +02:00
|
|
|
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
2025-08-13 17:39:35 +02:00
|
|
|
db: Session = Depends(get_db)
|
|
|
|
|
):
|
|
|
|
|
"""Get purchase order by ID with items"""
|
2025-08-15 22:40:19 +02:00
|
|
|
# require_permissions(current_user, ["purchase_orders:read"])
|
2025-08-13 17:39:35 +02:00
|
|
|
|
|
|
|
|
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("/{po_id}", response_model=PurchaseOrderResponse)
|
|
|
|
|
async def update_purchase_order(
|
|
|
|
|
po_data: PurchaseOrderUpdate,
|
|
|
|
|
po_id: UUID = Path(..., description="Purchase order ID"),
|
2025-08-15 22:40:19 +02:00
|
|
|
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
2025-08-13 17:39:35 +02:00
|
|
|
db: Session = Depends(get_db)
|
|
|
|
|
):
|
|
|
|
|
"""Update purchase order information"""
|
2025-08-15 22:40:19 +02:00
|
|
|
# require_permissions(current_user, ["purchase_orders:update"])
|
2025-08-13 17:39:35 +02:00
|
|
|
|
|
|
|
|
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")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.patch("/{po_id}/status", response_model=PurchaseOrderResponse)
|
|
|
|
|
async def update_purchase_order_status(
|
|
|
|
|
status_data: PurchaseOrderStatusUpdate,
|
|
|
|
|
po_id: UUID = Path(..., description="Purchase order ID"),
|
2025-08-15 22:40:19 +02:00
|
|
|
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
2025-08-13 17:39:35 +02:00
|
|
|
db: Session = Depends(get_db)
|
|
|
|
|
):
|
|
|
|
|
"""Update purchase order status"""
|
2025-08-15 22:40:19 +02:00
|
|
|
# require_permissions(current_user, ["purchase_orders:update"])
|
2025-08-13 17:39:35 +02:00
|
|
|
|
|
|
|
|
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_order_status(
|
|
|
|
|
po_id=po_id,
|
|
|
|
|
status=status_data.status,
|
|
|
|
|
updated_by=current_user.user_id,
|
|
|
|
|
notes=status_data.notes
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
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 status", po_id=str(po_id), error=str(e))
|
|
|
|
|
raise HTTPException(status_code=500, detail="Failed to update purchase order status")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/{po_id}/approve", response_model=PurchaseOrderResponse)
|
|
|
|
|
async def approve_purchase_order(
|
|
|
|
|
approval_data: PurchaseOrderApproval,
|
|
|
|
|
po_id: UUID = Path(..., description="Purchase order ID"),
|
2025-08-15 22:40:19 +02:00
|
|
|
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
2025-08-13 17:39:35 +02:00
|
|
|
db: Session = Depends(get_db)
|
|
|
|
|
):
|
|
|
|
|
"""Approve or reject a purchase order"""
|
2025-08-15 22:40:19 +02:00
|
|
|
# require_permissions(current_user, ["purchase_orders:approve"])
|
2025-08-13 17:39:35 +02:00
|
|
|
|
|
|
|
|
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")
|
|
|
|
|
|
|
|
|
|
if approval_data.action == "approve":
|
|
|
|
|
purchase_order = await service.approve_purchase_order(
|
|
|
|
|
po_id=po_id,
|
|
|
|
|
approved_by=current_user.user_id,
|
|
|
|
|
approval_notes=approval_data.notes
|
|
|
|
|
)
|
|
|
|
|
elif approval_data.action == "reject":
|
|
|
|
|
if not approval_data.notes:
|
|
|
|
|
raise HTTPException(status_code=400, detail="Rejection reason is required")
|
|
|
|
|
purchase_order = await service.reject_purchase_order(
|
|
|
|
|
po_id=po_id,
|
|
|
|
|
rejection_reason=approval_data.notes,
|
|
|
|
|
rejected_by=current_user.user_id
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
raise HTTPException(status_code=400, detail="Invalid action")
|
|
|
|
|
|
|
|
|
|
if not purchase_order:
|
|
|
|
|
raise HTTPException(
|
|
|
|
|
status_code=400,
|
|
|
|
|
detail="Purchase order is not in pending approval status"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
return PurchaseOrderResponse.from_orm(purchase_order)
|
|
|
|
|
except HTTPException:
|
|
|
|
|
raise
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error("Error processing purchase order approval", po_id=str(po_id), error=str(e))
|
|
|
|
|
raise HTTPException(status_code=500, detail="Failed to process purchase order approval")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/{po_id}/send-to-supplier", response_model=PurchaseOrderResponse)
|
|
|
|
|
async def send_to_supplier(
|
|
|
|
|
po_id: UUID = Path(..., description="Purchase order ID"),
|
|
|
|
|
send_email: bool = Query(True, description="Send email notification to supplier"),
|
2025-08-15 22:40:19 +02:00
|
|
|
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
2025-08-13 17:39:35 +02:00
|
|
|
db: Session = Depends(get_db)
|
|
|
|
|
):
|
|
|
|
|
"""Send purchase order to supplier"""
|
2025-08-15 22:40:19 +02:00
|
|
|
# require_permissions(current_user, ["purchase_orders:send"])
|
2025-08-13 17:39:35 +02:00
|
|
|
|
|
|
|
|
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.send_to_supplier(
|
|
|
|
|
po_id=po_id,
|
|
|
|
|
sent_by=current_user.user_id,
|
|
|
|
|
send_email=send_email
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
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 sending purchase order to supplier", po_id=str(po_id), error=str(e))
|
|
|
|
|
raise HTTPException(status_code=500, detail="Failed to send purchase order to supplier")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/{po_id}/confirm-supplier-receipt", response_model=PurchaseOrderResponse)
|
|
|
|
|
async def confirm_supplier_receipt(
|
|
|
|
|
po_id: UUID = Path(..., description="Purchase order ID"),
|
|
|
|
|
supplier_reference: Optional[str] = Query(None, description="Supplier's order reference"),
|
2025-08-15 22:40:19 +02:00
|
|
|
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
2025-08-13 17:39:35 +02:00
|
|
|
db: Session = Depends(get_db)
|
|
|
|
|
):
|
|
|
|
|
"""Confirm supplier has received and accepted the order"""
|
2025-08-15 22:40:19 +02:00
|
|
|
# require_permissions(current_user, ["purchase_orders:update"])
|
2025-08-13 17:39:35 +02:00
|
|
|
|
|
|
|
|
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.confirm_supplier_receipt(
|
|
|
|
|
po_id=po_id,
|
|
|
|
|
supplier_reference=supplier_reference,
|
|
|
|
|
confirmed_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 confirming supplier receipt", po_id=str(po_id), error=str(e))
|
|
|
|
|
raise HTTPException(status_code=500, detail="Failed to confirm supplier receipt")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/{po_id}/cancel", response_model=PurchaseOrderResponse)
|
|
|
|
|
async def cancel_purchase_order(
|
|
|
|
|
po_id: UUID = Path(..., description="Purchase order ID"),
|
|
|
|
|
cancellation_reason: str = Query(..., description="Reason for cancellation"),
|
2025-08-15 22:40:19 +02:00
|
|
|
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
2025-08-13 17:39:35 +02:00
|
|
|
db: Session = Depends(get_db)
|
|
|
|
|
):
|
|
|
|
|
"""Cancel a purchase order"""
|
2025-08-15 22:40:19 +02:00
|
|
|
# require_permissions(current_user, ["purchase_orders:cancel"])
|
2025-08-13 17:39:35 +02:00
|
|
|
|
|
|
|
|
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.cancel_purchase_order(
|
|
|
|
|
po_id=po_id,
|
|
|
|
|
cancellation_reason=cancellation_reason,
|
|
|
|
|
cancelled_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 cancelling purchase order", po_id=str(po_id), error=str(e))
|
|
|
|
|
raise HTTPException(status_code=500, detail="Failed to cancel purchase order")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/supplier/{supplier_id}", response_model=List[PurchaseOrderSummary])
|
|
|
|
|
async def get_orders_by_supplier(
|
|
|
|
|
supplier_id: UUID = Path(..., description="Supplier ID"),
|
|
|
|
|
limit: int = Query(20, ge=1, le=100, description="Number of orders to return"),
|
2025-08-15 22:40:19 +02:00
|
|
|
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
2025-08-13 17:39:35 +02:00
|
|
|
db: Session = Depends(get_db)
|
|
|
|
|
):
|
|
|
|
|
"""Get recent purchase orders for a specific supplier"""
|
2025-08-15 22:40:19 +02:00
|
|
|
# require_permissions(current_user, ["purchase_orders:read"])
|
2025-08-13 17:39:35 +02:00
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
service = PurchaseOrderService(db)
|
|
|
|
|
orders = await service.get_orders_by_supplier(
|
|
|
|
|
tenant_id=current_user.tenant_id,
|
|
|
|
|
supplier_id=supplier_id,
|
|
|
|
|
limit=limit
|
|
|
|
|
)
|
|
|
|
|
return [PurchaseOrderSummary.from_orm(order) for order in orders]
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error("Error getting orders by supplier", supplier_id=str(supplier_id), error=str(e))
|
|
|
|
|
raise HTTPException(status_code=500, detail="Failed to retrieve orders by supplier")
|
|
|
|
|
|
|
|
|
|
|
2025-08-14 16:47:34 +02:00
|
|
|
@router.get("/inventory-products/{inventory_product_id}/history")
|
|
|
|
|
async def get_inventory_product_purchase_history(
|
|
|
|
|
inventory_product_id: UUID = Path(..., description="Inventory Product ID"),
|
2025-08-13 17:39:35 +02:00
|
|
|
days_back: int = Query(90, ge=1, le=365, description="Number of days to look back"),
|
2025-08-15 22:40:19 +02:00
|
|
|
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
2025-08-13 17:39:35 +02:00
|
|
|
db: Session = Depends(get_db)
|
|
|
|
|
):
|
2025-08-14 16:47:34 +02:00
|
|
|
"""Get purchase history for a specific inventory product"""
|
2025-08-15 22:40:19 +02:00
|
|
|
# require_permissions(current_user, ["purchase_orders:read"])
|
2025-08-13 17:39:35 +02:00
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
service = PurchaseOrderService(db)
|
2025-08-14 16:47:34 +02:00
|
|
|
history = await service.get_inventory_product_purchase_history(
|
2025-08-13 17:39:35 +02:00
|
|
|
tenant_id=current_user.tenant_id,
|
2025-08-14 16:47:34 +02:00
|
|
|
inventory_product_id=inventory_product_id,
|
2025-08-13 17:39:35 +02:00
|
|
|
days_back=days_back
|
|
|
|
|
)
|
|
|
|
|
return history
|
|
|
|
|
except Exception as e:
|
2025-08-14 16:47:34 +02:00
|
|
|
logger.error("Error getting inventory product purchase history", inventory_product_id=str(inventory_product_id), error=str(e))
|
|
|
|
|
raise HTTPException(status_code=500, detail="Failed to retrieve inventory product purchase history")
|
2025-08-13 17:39:35 +02:00
|
|
|
|
|
|
|
|
|
2025-08-14 16:47:34 +02:00
|
|
|
@router.get("/inventory-products/top-purchased")
|
|
|
|
|
async def get_top_purchased_inventory_products(
|
2025-08-13 17:39:35 +02:00
|
|
|
days_back: int = Query(30, ge=1, le=365, description="Number of days to look back"),
|
2025-08-14 16:47:34 +02:00
|
|
|
limit: int = Query(10, ge=1, le=50, description="Number of top inventory products to return"),
|
2025-08-15 22:40:19 +02:00
|
|
|
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
2025-08-13 17:39:35 +02:00
|
|
|
db: Session = Depends(get_db)
|
|
|
|
|
):
|
2025-08-14 16:47:34 +02:00
|
|
|
"""Get most purchased inventory products by value"""
|
2025-08-15 22:40:19 +02:00
|
|
|
# require_permissions(current_user, ["purchase_orders:read"])
|
2025-08-13 17:39:35 +02:00
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
service = PurchaseOrderService(db)
|
2025-08-14 16:47:34 +02:00
|
|
|
products = await service.get_top_purchased_inventory_products(
|
2025-08-13 17:39:35 +02:00
|
|
|
tenant_id=current_user.tenant_id,
|
|
|
|
|
days_back=days_back,
|
|
|
|
|
limit=limit
|
|
|
|
|
)
|
2025-08-14 16:47:34 +02:00
|
|
|
return products
|
2025-08-13 17:39:35 +02:00
|
|
|
except Exception as e:
|
2025-08-14 16:47:34 +02:00
|
|
|
logger.error("Error getting top purchased inventory products", error=str(e))
|
|
|
|
|
raise HTTPException(status_code=500, detail="Failed to retrieve top purchased inventory products")
|