# 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.models import AuditLog from app.schemas.equipment import ( EquipmentCreate, EquipmentUpdate, EquipmentResponse, EquipmentListResponse, EquipmentDeletionSummary ) 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 with the production service's AuditLog model audit_logger = create_audit_logger("production-service", AuditLog) 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), db = Depends(get_db) ): """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 the equipment creation await audit_logger.log_event( db_session=db, tenant_id=str(tenant_id), user_id=current_user.get('user_id'), action=AuditAction.CREATE.value, resource_type="equipment", resource_id=str(equipment.id), severity=AuditSeverity.INFO.value, audit_metadata={"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), db = Depends(get_db) ): """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 the equipment update await audit_logger.log_event( db_session=db, tenant_id=str(tenant_id), user_id=current_user.get('user_id'), action=AuditAction.UPDATE.value, resource_type="equipment", resource_id=str(equipment_id), severity=AuditSeverity.INFO.value, audit_metadata={"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.get( route_builder.build_base_route("equipment/{equipment_id}/deletion-summary"), response_model=EquipmentDeletionSummary ) async def get_equipment_deletion_summary( tenant_id: UUID = Path(...), equipment_id: UUID = Path(...), current_user: dict = Depends(get_current_user_dep), production_service: ProductionService = Depends(get_production_service) ): """Get deletion summary for equipment (dependency check)""" try: summary = await production_service.get_equipment_deletion_summary(tenant_id, equipment_id) logger.info("Retrieved equipment deletion summary", equipment_id=str(equipment_id), tenant_id=str(tenant_id)) return EquipmentDeletionSummary(**summary) except Exception as e: logger.error("Error getting equipment deletion summary", error=str(e), equipment_id=str(equipment_id), tenant_id=str(tenant_id)) raise HTTPException(status_code=500, detail="Failed to get deletion summary") @router.delete( route_builder.build_base_route("equipment/{equipment_id}") ) async def delete_equipment( tenant_id: UUID = Path(...), equipment_id: UUID = Path(...), permanent: bool = Query(False, description="Permanent delete (hard delete) if true"), current_user: dict = Depends(get_current_user_dep), production_service: ProductionService = Depends(get_production_service), db = Depends(get_db) ): """Delete an equipment item. Use permanent=true for hard delete (requires admin role)""" try: # Hard delete requires admin role if permanent: user_role = current_user.get('role', '').lower() if user_role not in ['admin', 'owner']: raise HTTPException( status_code=403, detail="Hard delete requires admin or owner role" ) success = await production_service.hard_delete_equipment(tenant_id, equipment_id) delete_type = "hard_delete" severity = AuditSeverity.CRITICAL.value else: success = await production_service.delete_equipment(tenant_id, equipment_id) delete_type = "soft_delete" severity = AuditSeverity.WARNING.value if not success: raise HTTPException(status_code=404, detail="Equipment not found") logger.info(f"{'Hard' if permanent else 'Soft'} deleted equipment", equipment_id=str(equipment_id), tenant_id=str(tenant_id)) # Audit log the equipment deletion await audit_logger.log_event( db_session=db, tenant_id=str(tenant_id), user_id=current_user.get('user_id'), action=AuditAction.DELETE.value, resource_type="equipment", resource_id=str(equipment_id), severity=severity, audit_metadata={"action": delete_type, "permanent": permanent} ) return {"message": f"Equipment {'permanently deleted' if permanent else '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")