230 lines
8.1 KiB
Python
230 lines
8.1 KiB
Python
|
|
# services/production/app/api/equipment.py
|
||
|
|
"""
|
||
|
|
Equipment API - CRUD operations on Equipment model
|
||
|
|
"""
|
||
|
|
|
||
|
|
from fastapi import APIRouter, Depends, HTTPException, Path, Query
|
||
|
|
from typing import Optional
|
||
|
|
from uuid import UUID
|
||
|
|
import structlog
|
||
|
|
|
||
|
|
from shared.auth.decorators import get_current_user_dep
|
||
|
|
from shared.auth.access_control import require_user_role
|
||
|
|
from shared.routing import RouteBuilder
|
||
|
|
from shared.security import create_audit_logger, AuditSeverity, AuditAction
|
||
|
|
from app.core.database import get_db
|
||
|
|
from app.services.production_service import ProductionService
|
||
|
|
from app.schemas.equipment import (
|
||
|
|
EquipmentCreate,
|
||
|
|
EquipmentUpdate,
|
||
|
|
EquipmentResponse,
|
||
|
|
EquipmentListResponse
|
||
|
|
)
|
||
|
|
from app.models.production import EquipmentStatus, EquipmentType
|
||
|
|
from app.core.config import settings
|
||
|
|
|
||
|
|
logger = structlog.get_logger()
|
||
|
|
route_builder = RouteBuilder('production')
|
||
|
|
router = APIRouter(tags=["production-equipment"])
|
||
|
|
|
||
|
|
# Initialize audit logger
|
||
|
|
audit_logger = create_audit_logger("production-service")
|
||
|
|
|
||
|
|
|
||
|
|
def get_production_service() -> ProductionService:
|
||
|
|
"""Dependency injection for production service"""
|
||
|
|
from app.core.database import database_manager
|
||
|
|
return ProductionService(database_manager, settings)
|
||
|
|
|
||
|
|
|
||
|
|
@router.get(
|
||
|
|
route_builder.build_base_route("equipment"),
|
||
|
|
response_model=EquipmentListResponse
|
||
|
|
)
|
||
|
|
async def list_equipment(
|
||
|
|
tenant_id: UUID = Path(...),
|
||
|
|
status: Optional[EquipmentStatus] = Query(None, description="Filter by status"),
|
||
|
|
type: Optional[EquipmentType] = Query(None, description="Filter by equipment type"),
|
||
|
|
is_active: Optional[bool] = Query(None, description="Filter by active status"),
|
||
|
|
page: int = Query(1, ge=1, description="Page number"),
|
||
|
|
page_size: int = Query(50, ge=1, le=100, description="Page size"),
|
||
|
|
current_user: dict = Depends(get_current_user_dep),
|
||
|
|
production_service: ProductionService = Depends(get_production_service)
|
||
|
|
):
|
||
|
|
"""List equipment with filters: status, type, active status"""
|
||
|
|
try:
|
||
|
|
filters = {
|
||
|
|
"status": status,
|
||
|
|
"type": type,
|
||
|
|
"is_active": is_active
|
||
|
|
}
|
||
|
|
|
||
|
|
equipment_list = await production_service.get_equipment_list(tenant_id, filters, page, page_size)
|
||
|
|
|
||
|
|
logger.info("Retrieved equipment list",
|
||
|
|
tenant_id=str(tenant_id), filters=filters)
|
||
|
|
|
||
|
|
return equipment_list
|
||
|
|
|
||
|
|
except Exception as e:
|
||
|
|
logger.error("Error listing equipment",
|
||
|
|
error=str(e), tenant_id=str(tenant_id))
|
||
|
|
raise HTTPException(status_code=500, detail="Failed to list equipment")
|
||
|
|
|
||
|
|
|
||
|
|
@router.post(
|
||
|
|
route_builder.build_base_route("equipment"),
|
||
|
|
response_model=EquipmentResponse
|
||
|
|
)
|
||
|
|
async def create_equipment(
|
||
|
|
equipment_data: EquipmentCreate,
|
||
|
|
tenant_id: UUID = Path(...),
|
||
|
|
current_user: dict = Depends(get_current_user_dep),
|
||
|
|
production_service: ProductionService = Depends(get_production_service)
|
||
|
|
):
|
||
|
|
"""Create a new equipment item"""
|
||
|
|
try:
|
||
|
|
equipment = await production_service.create_equipment(tenant_id, equipment_data)
|
||
|
|
|
||
|
|
logger.info("Created equipment",
|
||
|
|
equipment_id=str(equipment.id), tenant_id=str(tenant_id))
|
||
|
|
|
||
|
|
# Audit log
|
||
|
|
await audit_logger.log(
|
||
|
|
action=AuditAction.CREATE,
|
||
|
|
resource_type="equipment",
|
||
|
|
resource_id=str(equipment.id),
|
||
|
|
user_id=current_user.get('user_id'),
|
||
|
|
tenant_id=str(tenant_id),
|
||
|
|
severity=AuditSeverity.INFO,
|
||
|
|
details={"equipment_name": equipment.name, "equipment_type": equipment.type.value}
|
||
|
|
)
|
||
|
|
|
||
|
|
return EquipmentResponse.model_validate(equipment)
|
||
|
|
|
||
|
|
except ValueError as e:
|
||
|
|
logger.warning("Validation error creating equipment",
|
||
|
|
error=str(e), tenant_id=str(tenant_id))
|
||
|
|
raise HTTPException(status_code=400, detail=str(e))
|
||
|
|
|
||
|
|
except Exception as e:
|
||
|
|
logger.error("Error creating equipment",
|
||
|
|
error=str(e), tenant_id=str(tenant_id))
|
||
|
|
raise HTTPException(status_code=500, detail="Failed to create equipment")
|
||
|
|
|
||
|
|
|
||
|
|
@router.get(
|
||
|
|
route_builder.build_base_route("equipment/{equipment_id}"),
|
||
|
|
response_model=EquipmentResponse
|
||
|
|
)
|
||
|
|
async def get_equipment(
|
||
|
|
tenant_id: UUID = Path(...),
|
||
|
|
equipment_id: UUID = Path(...),
|
||
|
|
current_user: dict = Depends(get_current_user_dep),
|
||
|
|
production_service: ProductionService = Depends(get_production_service)
|
||
|
|
):
|
||
|
|
"""Get a specific equipment item"""
|
||
|
|
try:
|
||
|
|
equipment = await production_service.get_equipment(tenant_id, equipment_id)
|
||
|
|
|
||
|
|
if not equipment:
|
||
|
|
raise HTTPException(status_code=404, detail="Equipment not found")
|
||
|
|
|
||
|
|
logger.info("Retrieved equipment",
|
||
|
|
equipment_id=str(equipment_id), tenant_id=str(tenant_id))
|
||
|
|
|
||
|
|
return EquipmentResponse.model_validate(equipment)
|
||
|
|
|
||
|
|
except HTTPException:
|
||
|
|
raise
|
||
|
|
except Exception as e:
|
||
|
|
logger.error("Error retrieving equipment",
|
||
|
|
error=str(e), equipment_id=str(equipment_id), tenant_id=str(tenant_id))
|
||
|
|
raise HTTPException(status_code=500, detail="Failed to retrieve equipment")
|
||
|
|
|
||
|
|
|
||
|
|
@router.put(
|
||
|
|
route_builder.build_base_route("equipment/{equipment_id}"),
|
||
|
|
response_model=EquipmentResponse
|
||
|
|
)
|
||
|
|
async def update_equipment(
|
||
|
|
equipment_data: EquipmentUpdate,
|
||
|
|
tenant_id: UUID = Path(...),
|
||
|
|
equipment_id: UUID = Path(...),
|
||
|
|
current_user: dict = Depends(get_current_user_dep),
|
||
|
|
production_service: ProductionService = Depends(get_production_service)
|
||
|
|
):
|
||
|
|
"""Update an equipment item"""
|
||
|
|
try:
|
||
|
|
equipment = await production_service.update_equipment(tenant_id, equipment_id, equipment_data)
|
||
|
|
|
||
|
|
if not equipment:
|
||
|
|
raise HTTPException(status_code=404, detail="Equipment not found")
|
||
|
|
|
||
|
|
logger.info("Updated equipment",
|
||
|
|
equipment_id=str(equipment_id), tenant_id=str(tenant_id))
|
||
|
|
|
||
|
|
# Audit log
|
||
|
|
await audit_logger.log(
|
||
|
|
action=AuditAction.UPDATE,
|
||
|
|
resource_type="equipment",
|
||
|
|
resource_id=str(equipment_id),
|
||
|
|
user_id=current_user.get('user_id'),
|
||
|
|
tenant_id=str(tenant_id),
|
||
|
|
severity=AuditSeverity.INFO,
|
||
|
|
details={"updates": equipment_data.model_dump(exclude_unset=True)}
|
||
|
|
)
|
||
|
|
|
||
|
|
return EquipmentResponse.model_validate(equipment)
|
||
|
|
|
||
|
|
except HTTPException:
|
||
|
|
raise
|
||
|
|
except ValueError as e:
|
||
|
|
logger.warning("Validation error updating equipment",
|
||
|
|
error=str(e), equipment_id=str(equipment_id), tenant_id=str(tenant_id))
|
||
|
|
raise HTTPException(status_code=400, detail=str(e))
|
||
|
|
except Exception as e:
|
||
|
|
logger.error("Error updating equipment",
|
||
|
|
error=str(e), equipment_id=str(equipment_id), tenant_id=str(tenant_id))
|
||
|
|
raise HTTPException(status_code=500, detail="Failed to update equipment")
|
||
|
|
|
||
|
|
|
||
|
|
@router.delete(
|
||
|
|
route_builder.build_base_route("equipment/{equipment_id}")
|
||
|
|
)
|
||
|
|
async def delete_equipment(
|
||
|
|
tenant_id: UUID = Path(...),
|
||
|
|
equipment_id: UUID = Path(...),
|
||
|
|
current_user: dict = Depends(get_current_user_dep),
|
||
|
|
production_service: ProductionService = Depends(get_production_service)
|
||
|
|
):
|
||
|
|
"""Delete (soft delete) an equipment item"""
|
||
|
|
try:
|
||
|
|
success = await production_service.delete_equipment(tenant_id, equipment_id)
|
||
|
|
|
||
|
|
if not success:
|
||
|
|
raise HTTPException(status_code=404, detail="Equipment not found")
|
||
|
|
|
||
|
|
logger.info("Deleted equipment",
|
||
|
|
equipment_id=str(equipment_id), tenant_id=str(tenant_id))
|
||
|
|
|
||
|
|
# Audit log
|
||
|
|
await audit_logger.log(
|
||
|
|
action=AuditAction.DELETE,
|
||
|
|
resource_type="equipment",
|
||
|
|
resource_id=str(equipment_id),
|
||
|
|
user_id=current_user.get('user_id'),
|
||
|
|
tenant_id=str(tenant_id),
|
||
|
|
severity=AuditSeverity.WARNING,
|
||
|
|
details={"action": "soft_delete"}
|
||
|
|
)
|
||
|
|
|
||
|
|
return {"message": "Equipment deleted successfully"}
|
||
|
|
|
||
|
|
except HTTPException:
|
||
|
|
raise
|
||
|
|
except Exception as e:
|
||
|
|
logger.error("Error deleting equipment",
|
||
|
|
error=str(e), equipment_id=str(equipment_id), tenant_id=str(tenant_id))
|
||
|
|
raise HTTPException(status_code=500, detail="Failed to delete equipment")
|