Files
bakery-ia/services/suppliers/app/api/suppliers.py

205 lines
7.8 KiB
Python
Raw Normal View History

# services/suppliers/app/api/suppliers.py
"""
2025-10-06 15:27:01 +02:00
Supplier CRUD API endpoints (ATOMIC)
"""
2025-10-06 15:27:01 +02:00
from fastapi import APIRouter, Depends, HTTPException, Query, Path
from typing import List, Optional, Dict, Any
from uuid import UUID
import structlog
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.database import get_db
from app.services.supplier_service import SupplierService
from app.schemas.suppliers import (
SupplierCreate, SupplierUpdate, SupplierResponse, SupplierSummary,
2025-10-06 15:27:01 +02:00
SupplierSearchParams
)
2025-08-15 22:40:19 +02:00
from shared.auth.decorators import get_current_user_dep
2025-10-06 15:27:01 +02:00
from shared.routing import RouteBuilder
from shared.auth.access_control import require_user_role
from shared.security import create_audit_logger, AuditSeverity, AuditAction
2025-10-06 15:27:01 +02:00
# Create route builder for consistent URL structure
route_builder = RouteBuilder('suppliers')
router = APIRouter(tags=["suppliers"])
logger = structlog.get_logger()
audit_logger = create_audit_logger("suppliers-service")
2025-10-06 15:27:01 +02:00
@router.post(route_builder.build_base_route("suppliers"), response_model=SupplierResponse)
@require_user_role(['admin', 'owner', 'member'])
async def create_supplier(
supplier_data: SupplierCreate,
tenant_id: str = Path(..., description="Tenant ID"),
2025-08-15 22:40:19 +02:00
current_user: Dict[str, Any] = Depends(get_current_user_dep),
db: AsyncSession = Depends(get_db)
):
2025-10-06 15:27:01 +02:00
"""Create a new supplier"""
try:
service = SupplierService(db)
supplier = await service.create_supplier(
tenant_id=UUID(tenant_id),
supplier_data=supplier_data,
created_by=current_user.user_id
)
return SupplierResponse.from_orm(supplier)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
logger.error("Error creating supplier", error=str(e))
raise HTTPException(status_code=500, detail="Failed to create supplier")
2025-10-06 15:27:01 +02:00
@router.get(route_builder.build_base_route("suppliers"), response_model=List[SupplierSummary])
async def list_suppliers(
tenant_id: str = Path(..., description="Tenant ID"),
search_term: Optional[str] = Query(None, description="Search term"),
supplier_type: Optional[str] = Query(None, description="Supplier type filter"),
status: Optional[str] = Query(None, description="Status filter"),
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"),
db: AsyncSession = Depends(get_db)
):
"""List suppliers with optional filters"""
try:
service = SupplierService(db)
search_params = SupplierSearchParams(
search_term=search_term,
supplier_type=supplier_type,
status=status,
limit=limit,
offset=offset
)
suppliers = await service.search_suppliers(
tenant_id=UUID(tenant_id),
search_params=search_params
)
return [SupplierSummary.from_orm(supplier) for supplier in suppliers]
except Exception as e:
logger.error("Error listing suppliers", error=str(e))
raise HTTPException(status_code=500, detail="Failed to retrieve suppliers")
2025-10-06 15:27:01 +02:00
@router.get(route_builder.build_resource_detail_route("suppliers", "supplier_id"), response_model=SupplierResponse)
async def get_supplier(
supplier_id: UUID = Path(..., description="Supplier ID"),
tenant_id: str = Path(..., description="Tenant ID"),
db: AsyncSession = Depends(get_db)
):
"""Get supplier by ID"""
try:
service = SupplierService(db)
supplier = await service.get_supplier(supplier_id)
2025-10-06 15:27:01 +02:00
if not supplier:
raise HTTPException(status_code=404, detail="Supplier not found")
2025-10-06 15:27:01 +02:00
return SupplierResponse.from_orm(supplier)
except HTTPException:
raise
except Exception as e:
logger.error("Error getting supplier", supplier_id=str(supplier_id), error=str(e))
raise HTTPException(status_code=500, detail="Failed to retrieve supplier")
2025-10-06 15:27:01 +02:00
@router.put(route_builder.build_resource_detail_route("suppliers", "supplier_id"), response_model=SupplierResponse)
@require_user_role(['admin', 'owner', 'member'])
async def update_supplier(
supplier_data: SupplierUpdate,
supplier_id: UUID = Path(..., description="Supplier ID"),
2025-08-15 22:40:19 +02:00
current_user: Dict[str, Any] = Depends(get_current_user_dep),
db: AsyncSession = Depends(get_db)
):
"""Update supplier information"""
try:
service = SupplierService(db)
2025-10-06 15:27:01 +02:00
# Check supplier exists
existing_supplier = await service.get_supplier(supplier_id)
if not existing_supplier:
raise HTTPException(status_code=404, detail="Supplier not found")
2025-10-06 15:27:01 +02:00
supplier = await service.update_supplier(
supplier_id=supplier_id,
supplier_data=supplier_data,
updated_by=current_user.user_id
)
2025-10-06 15:27:01 +02:00
if not supplier:
raise HTTPException(status_code=404, detail="Supplier not found")
2025-10-06 15:27:01 +02:00
return SupplierResponse.from_orm(supplier)
except HTTPException:
raise
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
logger.error("Error updating supplier", supplier_id=str(supplier_id), error=str(e))
raise HTTPException(status_code=500, detail="Failed to update supplier")
2025-10-06 15:27:01 +02:00
@router.delete(route_builder.build_resource_detail_route("suppliers", "supplier_id"))
@require_user_role(['admin', 'owner'])
async def delete_supplier(
supplier_id: UUID = Path(..., description="Supplier ID"),
tenant_id: str = Path(..., description="Tenant ID"),
current_user: Dict[str, Any] = Depends(get_current_user_dep),
db: AsyncSession = Depends(get_db)
):
"""Delete supplier (soft delete, Admin+ only)"""
try:
service = SupplierService(db)
2025-10-06 15:27:01 +02:00
# Check supplier exists
existing_supplier = await service.get_supplier(supplier_id)
if not existing_supplier:
raise HTTPException(status_code=404, detail="Supplier not found")
2025-10-06 15:27:01 +02:00
# Capture supplier data before deletion
supplier_data = {
"supplier_name": existing_supplier.name,
"supplier_type": existing_supplier.supplier_type,
"contact_person": existing_supplier.contact_person,
"email": existing_supplier.email
}
success = await service.delete_supplier(supplier_id)
if not success:
raise HTTPException(status_code=404, detail="Supplier not found")
2025-10-06 15:27:01 +02:00
# Log audit event for supplier deletion
try:
# Get sync db session for audit logging
from app.core.database import SessionLocal
sync_db = SessionLocal()
try:
await audit_logger.log_deletion(
db_session=sync_db,
tenant_id=tenant_id,
user_id=current_user["user_id"],
resource_type="supplier",
resource_id=str(supplier_id),
resource_data=supplier_data,
description=f"Admin {current_user.get('email', 'unknown')} deleted supplier",
endpoint=f"/suppliers/{supplier_id}",
method="DELETE"
)
sync_db.commit()
finally:
sync_db.close()
except Exception as audit_error:
logger.warning("Failed to log audit event", error=str(audit_error))
logger.info("Deleted supplier",
supplier_id=str(supplier_id),
tenant_id=tenant_id,
user_id=current_user["user_id"])
return {"message": "Supplier deleted successfully"}
except HTTPException:
raise
except Exception as e:
logger.error("Error deleting supplier", supplier_id=str(supplier_id), error=str(e))
2025-10-06 15:27:01 +02:00
raise HTTPException(status_code=500, detail="Failed to delete supplier")