Improve teh securty of teh DB
This commit is contained in:
229
services/production/app/api/equipment.py
Normal file
229
services/production/app/api/equipment.py
Normal file
@@ -0,0 +1,229 @@
|
||||
# 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")
|
||||
@@ -21,6 +21,7 @@ from app.models.production import (
|
||||
)
|
||||
from shared.utils.demo_dates import adjust_date_for_demo, BASE_REFERENCE_DATE
|
||||
from shared.utils.alert_generator import generate_equipment_alerts
|
||||
from shared.messaging.rabbitmq import RabbitMQClient
|
||||
|
||||
logger = structlog.get_logger()
|
||||
router = APIRouter(prefix="/internal/demo", tags=["internal"])
|
||||
@@ -432,14 +433,39 @@ async def clone_demo_data(
|
||||
# Commit cloned data first
|
||||
await db.commit()
|
||||
|
||||
# Generate equipment maintenance and status alerts
|
||||
# Generate equipment maintenance and status alerts with RabbitMQ publishing
|
||||
rabbitmq_client = None
|
||||
try:
|
||||
alerts_count = await generate_equipment_alerts(db, virtual_uuid, session_time)
|
||||
# Initialize RabbitMQ client for alert publishing
|
||||
rabbitmq_host = os.getenv("RABBITMQ_HOST", "rabbitmq-service")
|
||||
rabbitmq_user = os.getenv("RABBITMQ_USER", "bakery")
|
||||
rabbitmq_password = os.getenv("RABBITMQ_PASSWORD", "forecast123")
|
||||
rabbitmq_port = os.getenv("RABBITMQ_PORT", "5672")
|
||||
rabbitmq_vhost = os.getenv("RABBITMQ_VHOST", "/")
|
||||
rabbitmq_url = f"amqp://{rabbitmq_user}:{rabbitmq_password}@{rabbitmq_host}:{rabbitmq_port}{rabbitmq_vhost}"
|
||||
|
||||
rabbitmq_client = RabbitMQClient(rabbitmq_url, service_name="production")
|
||||
await rabbitmq_client.connect()
|
||||
|
||||
# Generate alerts and publish to RabbitMQ
|
||||
alerts_count = await generate_equipment_alerts(
|
||||
db,
|
||||
virtual_uuid,
|
||||
session_time,
|
||||
rabbitmq_client=rabbitmq_client
|
||||
)
|
||||
stats["alerts_generated"] += alerts_count
|
||||
await db.commit()
|
||||
logger.info(f"Generated {alerts_count} equipment alerts")
|
||||
except Exception as alert_error:
|
||||
logger.warning(f"Alert generation failed: {alert_error}", exc_info=True)
|
||||
finally:
|
||||
# Clean up RabbitMQ connection
|
||||
if rabbitmq_client:
|
||||
try:
|
||||
await rabbitmq_client.disconnect()
|
||||
except Exception as cleanup_error:
|
||||
logger.warning(f"Error disconnecting RabbitMQ: {cleanup_error}")
|
||||
|
||||
total_records = sum(stats.values())
|
||||
duration_ms = int((datetime.now(timezone.utc) - start_time).total_seconds() * 1000)
|
||||
|
||||
@@ -23,6 +23,7 @@ from app.api import (
|
||||
production_dashboard,
|
||||
analytics,
|
||||
quality_templates,
|
||||
equipment,
|
||||
internal_demo
|
||||
)
|
||||
|
||||
@@ -166,6 +167,7 @@ service.setup_custom_middleware()
|
||||
# Include standardized routers
|
||||
# NOTE: Register more specific routes before generic parameterized routes
|
||||
service.add_router(quality_templates.router) # Register first to avoid route conflicts
|
||||
service.add_router(equipment.router)
|
||||
service.add_router(production_batches.router)
|
||||
service.add_router(production_schedules.router)
|
||||
service.add_router(production_operations.router)
|
||||
|
||||
152
services/production/app/repositories/equipment_repository.py
Normal file
152
services/production/app/repositories/equipment_repository.py
Normal file
@@ -0,0 +1,152 @@
|
||||
"""
|
||||
Equipment Repository
|
||||
"""
|
||||
|
||||
from typing import Optional, List, Dict, Any
|
||||
from sqlalchemy import select, func, and_
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from uuid import UUID
|
||||
import structlog
|
||||
|
||||
from app.repositories.base import ProductionBaseRepository
|
||||
from app.models.production import Equipment, EquipmentStatus, EquipmentType
|
||||
|
||||
logger = structlog.get_logger()
|
||||
|
||||
|
||||
class EquipmentRepository(ProductionBaseRepository):
|
||||
"""Repository for equipment operations"""
|
||||
|
||||
def __init__(self, session: AsyncSession):
|
||||
super().__init__(Equipment, session)
|
||||
|
||||
async def get_equipment_filtered(
|
||||
self,
|
||||
filters: Dict[str, Any],
|
||||
page: int = 1,
|
||||
page_size: int = 50
|
||||
) -> List[Equipment]:
|
||||
"""Get equipment list with filters and pagination"""
|
||||
try:
|
||||
# Build base query
|
||||
query = select(Equipment).filter(Equipment.tenant_id == UUID(filters.get("tenant_id")))
|
||||
|
||||
# Apply status filter
|
||||
if "status" in filters and filters["status"]:
|
||||
query = query.filter(Equipment.status == filters["status"])
|
||||
|
||||
# Apply type filter
|
||||
if "type" in filters and filters["type"]:
|
||||
query = query.filter(Equipment.type == filters["type"])
|
||||
|
||||
# Apply active filter
|
||||
if "is_active" in filters and filters["is_active"] is not None:
|
||||
query = query.filter(Equipment.is_active == filters["is_active"])
|
||||
|
||||
# Apply pagination
|
||||
query = query.order_by(Equipment.created_at.desc())
|
||||
query = query.offset((page - 1) * page_size).limit(page_size)
|
||||
|
||||
result = await self.session.execute(query)
|
||||
return list(result.scalars().all())
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Error getting filtered equipment", error=str(e), filters=filters)
|
||||
raise
|
||||
|
||||
async def count_equipment_filtered(self, filters: Dict[str, Any]) -> int:
|
||||
"""Count equipment matching filters"""
|
||||
try:
|
||||
# Build base query
|
||||
query = select(func.count(Equipment.id)).filter(
|
||||
Equipment.tenant_id == UUID(filters.get("tenant_id"))
|
||||
)
|
||||
|
||||
# Apply status filter
|
||||
if "status" in filters and filters["status"]:
|
||||
query = query.filter(Equipment.status == filters["status"])
|
||||
|
||||
# Apply type filter
|
||||
if "type" in filters and filters["type"]:
|
||||
query = query.filter(Equipment.type == filters["type"])
|
||||
|
||||
# Apply active filter
|
||||
if "is_active" in filters and filters["is_active"] is not None:
|
||||
query = query.filter(Equipment.is_active == filters["is_active"])
|
||||
|
||||
result = await self.session.execute(query)
|
||||
return result.scalar() or 0
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Error counting filtered equipment", error=str(e), filters=filters)
|
||||
raise
|
||||
|
||||
async def get_equipment_by_id(self, tenant_id: UUID, equipment_id: UUID) -> Optional[Equipment]:
|
||||
"""Get equipment by ID and tenant"""
|
||||
try:
|
||||
query = select(Equipment).filter(
|
||||
and_(
|
||||
Equipment.id == equipment_id,
|
||||
Equipment.tenant_id == tenant_id
|
||||
)
|
||||
)
|
||||
result = await self.session.execute(query)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Error getting equipment by ID",
|
||||
error=str(e),
|
||||
equipment_id=str(equipment_id),
|
||||
tenant_id=str(tenant_id))
|
||||
raise
|
||||
|
||||
async def create_equipment(self, equipment_data: Dict[str, Any]) -> Equipment:
|
||||
"""Create new equipment"""
|
||||
try:
|
||||
equipment = Equipment(**equipment_data)
|
||||
self.session.add(equipment)
|
||||
await self.session.flush()
|
||||
await self.session.refresh(equipment)
|
||||
return equipment
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Error creating equipment", error=str(e), data=equipment_data)
|
||||
raise
|
||||
|
||||
async def update_equipment(
|
||||
self,
|
||||
equipment_id: UUID,
|
||||
updates: Dict[str, Any]
|
||||
) -> Optional[Equipment]:
|
||||
"""Update equipment"""
|
||||
try:
|
||||
equipment = await self.get(equipment_id)
|
||||
if not equipment:
|
||||
return None
|
||||
|
||||
for key, value in updates.items():
|
||||
if hasattr(equipment, key) and value is not None:
|
||||
setattr(equipment, key, value)
|
||||
|
||||
await self.session.flush()
|
||||
await self.session.refresh(equipment)
|
||||
return equipment
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Error updating equipment", error=str(e), equipment_id=str(equipment_id))
|
||||
raise
|
||||
|
||||
async def delete_equipment(self, equipment_id: UUID) -> bool:
|
||||
"""Soft delete equipment (set is_active to False)"""
|
||||
try:
|
||||
equipment = await self.get(equipment_id)
|
||||
if not equipment:
|
||||
return False
|
||||
|
||||
equipment.is_active = False
|
||||
await self.session.flush()
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Error deleting equipment", error=str(e), equipment_id=str(equipment_id))
|
||||
raise
|
||||
171
services/production/app/schemas/equipment.py
Normal file
171
services/production/app/schemas/equipment.py
Normal file
@@ -0,0 +1,171 @@
|
||||
# services/production/app/schemas/equipment.py
|
||||
"""
|
||||
Equipment schemas for Production Service
|
||||
"""
|
||||
|
||||
from pydantic import BaseModel, Field, ConfigDict
|
||||
from typing import Optional, List
|
||||
from datetime import datetime
|
||||
from uuid import UUID
|
||||
|
||||
from app.models.production import EquipmentType, EquipmentStatus
|
||||
|
||||
|
||||
class EquipmentCreate(BaseModel):
|
||||
"""Schema for creating new equipment"""
|
||||
name: str = Field(..., min_length=1, max_length=255, description="Equipment name")
|
||||
type: EquipmentType = Field(..., description="Equipment type")
|
||||
model: Optional[str] = Field(None, max_length=100, description="Equipment model")
|
||||
serial_number: Optional[str] = Field(None, max_length=100, description="Serial number")
|
||||
location: Optional[str] = Field(None, max_length=255, description="Physical location")
|
||||
status: EquipmentStatus = Field(default=EquipmentStatus.OPERATIONAL, description="Equipment status")
|
||||
|
||||
# Installation and maintenance
|
||||
install_date: Optional[datetime] = Field(None, description="Installation date")
|
||||
last_maintenance_date: Optional[datetime] = Field(None, description="Last maintenance date")
|
||||
next_maintenance_date: Optional[datetime] = Field(None, description="Next scheduled maintenance date")
|
||||
maintenance_interval_days: Optional[int] = Field(None, ge=1, description="Maintenance interval in days")
|
||||
|
||||
# Performance metrics
|
||||
efficiency_percentage: Optional[float] = Field(None, ge=0, le=100, description="Current efficiency percentage")
|
||||
uptime_percentage: Optional[float] = Field(None, ge=0, le=100, description="Overall uptime percentage")
|
||||
energy_usage_kwh: Optional[float] = Field(None, ge=0, description="Current energy usage in kWh")
|
||||
|
||||
# Specifications
|
||||
power_kw: Optional[float] = Field(None, ge=0, description="Power consumption in kilowatts")
|
||||
capacity: Optional[float] = Field(None, ge=0, description="Equipment capacity")
|
||||
weight_kg: Optional[float] = Field(None, ge=0, description="Weight in kilograms")
|
||||
|
||||
# Temperature monitoring
|
||||
current_temperature: Optional[float] = Field(None, description="Current temperature")
|
||||
target_temperature: Optional[float] = Field(None, description="Target temperature")
|
||||
|
||||
# Notes
|
||||
notes: Optional[str] = Field(None, description="Additional notes")
|
||||
|
||||
model_config = ConfigDict(
|
||||
json_schema_extra={
|
||||
"example": {
|
||||
"name": "Horno Principal #1",
|
||||
"type": "oven",
|
||||
"model": "Miwe Condo CO 4.1212",
|
||||
"serial_number": "MCO-2021-001",
|
||||
"location": "Área de Horneado - Zona A",
|
||||
"status": "operational",
|
||||
"install_date": "2021-03-15T00:00:00Z",
|
||||
"maintenance_interval_days": 90,
|
||||
"efficiency_percentage": 92.0,
|
||||
"uptime_percentage": 98.5,
|
||||
"power_kw": 45.0,
|
||||
"capacity": 24.0
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class EquipmentUpdate(BaseModel):
|
||||
"""Schema for updating equipment"""
|
||||
name: Optional[str] = Field(None, min_length=1, max_length=255)
|
||||
type: Optional[EquipmentType] = None
|
||||
model: Optional[str] = Field(None, max_length=100)
|
||||
serial_number: Optional[str] = Field(None, max_length=100)
|
||||
location: Optional[str] = Field(None, max_length=255)
|
||||
status: Optional[EquipmentStatus] = None
|
||||
|
||||
# Installation and maintenance
|
||||
install_date: Optional[datetime] = None
|
||||
last_maintenance_date: Optional[datetime] = None
|
||||
next_maintenance_date: Optional[datetime] = None
|
||||
maintenance_interval_days: Optional[int] = Field(None, ge=1)
|
||||
|
||||
# Performance metrics
|
||||
efficiency_percentage: Optional[float] = Field(None, ge=0, le=100)
|
||||
uptime_percentage: Optional[float] = Field(None, ge=0, le=100)
|
||||
energy_usage_kwh: Optional[float] = Field(None, ge=0)
|
||||
|
||||
# Specifications
|
||||
power_kw: Optional[float] = Field(None, ge=0)
|
||||
capacity: Optional[float] = Field(None, ge=0)
|
||||
weight_kg: Optional[float] = Field(None, ge=0)
|
||||
|
||||
# Temperature monitoring
|
||||
current_temperature: Optional[float] = None
|
||||
target_temperature: Optional[float] = None
|
||||
|
||||
# Notes
|
||||
notes: Optional[str] = None
|
||||
|
||||
# Status flag
|
||||
is_active: Optional[bool] = None
|
||||
|
||||
model_config = ConfigDict(
|
||||
json_schema_extra={
|
||||
"example": {
|
||||
"status": "maintenance",
|
||||
"last_maintenance_date": "2024-01-15T00:00:00Z",
|
||||
"next_maintenance_date": "2024-04-15T00:00:00Z",
|
||||
"efficiency_percentage": 88.0
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class EquipmentResponse(BaseModel):
|
||||
"""Schema for equipment response"""
|
||||
id: UUID
|
||||
tenant_id: UUID
|
||||
name: str
|
||||
type: EquipmentType
|
||||
model: Optional[str] = None
|
||||
serial_number: Optional[str] = None
|
||||
location: Optional[str] = None
|
||||
status: EquipmentStatus
|
||||
|
||||
# Installation and maintenance
|
||||
install_date: Optional[datetime] = None
|
||||
last_maintenance_date: Optional[datetime] = None
|
||||
next_maintenance_date: Optional[datetime] = None
|
||||
maintenance_interval_days: Optional[int] = None
|
||||
|
||||
# Performance metrics
|
||||
efficiency_percentage: Optional[float] = None
|
||||
uptime_percentage: Optional[float] = None
|
||||
energy_usage_kwh: Optional[float] = None
|
||||
|
||||
# Specifications
|
||||
power_kw: Optional[float] = None
|
||||
capacity: Optional[float] = None
|
||||
weight_kg: Optional[float] = None
|
||||
|
||||
# Temperature monitoring
|
||||
current_temperature: Optional[float] = None
|
||||
target_temperature: Optional[float] = None
|
||||
|
||||
# Status
|
||||
is_active: bool
|
||||
notes: Optional[str] = None
|
||||
|
||||
# Timestamps
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class EquipmentListResponse(BaseModel):
|
||||
"""Schema for paginated equipment list response"""
|
||||
equipment: List[EquipmentResponse]
|
||||
total_count: int
|
||||
page: int
|
||||
page_size: int
|
||||
|
||||
model_config = ConfigDict(
|
||||
json_schema_extra={
|
||||
"example": {
|
||||
"equipment": [],
|
||||
"total_count": 10,
|
||||
"page": 1,
|
||||
"page_size": 50
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -1386,4 +1386,146 @@ class ProductionService:
|
||||
except Exception as e:
|
||||
logger.error("Error getting batch with transformations",
|
||||
error=str(e), batch_id=str(batch_id), tenant_id=str(tenant_id))
|
||||
return {}
|
||||
return {}
|
||||
|
||||
# ================================================================
|
||||
# EQUIPMENT MANAGEMENT METHODS
|
||||
# ================================================================
|
||||
|
||||
async def get_equipment_list(
|
||||
self,
|
||||
tenant_id: UUID,
|
||||
filters: Dict[str, Any],
|
||||
page: int = 1,
|
||||
page_size: int = 50
|
||||
) -> Dict[str, Any]:
|
||||
"""Get list of equipment with filtering and pagination"""
|
||||
try:
|
||||
async with self.database_manager.get_session() as session:
|
||||
from app.repositories.equipment_repository import EquipmentRepository
|
||||
equipment_repo = EquipmentRepository(session)
|
||||
|
||||
# Apply filters
|
||||
filter_dict = {k: v for k, v in filters.items() if v is not None}
|
||||
filter_dict["tenant_id"] = str(tenant_id)
|
||||
|
||||
# Get equipment with pagination
|
||||
equipment_list = await equipment_repo.get_equipment_filtered(filter_dict, page, page_size)
|
||||
total_count = await equipment_repo.count_equipment_filtered(filter_dict)
|
||||
|
||||
# Convert to response format
|
||||
from app.schemas.equipment import EquipmentResponse
|
||||
equipment_responses = [
|
||||
EquipmentResponse.model_validate(eq) for eq in equipment_list
|
||||
]
|
||||
|
||||
return {
|
||||
"equipment": equipment_responses,
|
||||
"total_count": total_count,
|
||||
"page": page,
|
||||
"page_size": page_size
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Error getting equipment list",
|
||||
error=str(e), tenant_id=str(tenant_id))
|
||||
raise
|
||||
|
||||
async def get_equipment(self, tenant_id: UUID, equipment_id: UUID):
|
||||
"""Get a specific equipment item"""
|
||||
try:
|
||||
async with self.database_manager.get_session() as session:
|
||||
from app.repositories.equipment_repository import EquipmentRepository
|
||||
equipment_repo = EquipmentRepository(session)
|
||||
|
||||
equipment = await equipment_repo.get_equipment_by_id(tenant_id, equipment_id)
|
||||
|
||||
if not equipment:
|
||||
return None
|
||||
|
||||
logger.info("Retrieved equipment",
|
||||
equipment_id=str(equipment_id), tenant_id=str(tenant_id))
|
||||
|
||||
return equipment
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Error getting equipment",
|
||||
error=str(e), equipment_id=str(equipment_id), tenant_id=str(tenant_id))
|
||||
raise
|
||||
|
||||
async def create_equipment(self, tenant_id: UUID, equipment_data):
|
||||
"""Create a new equipment item"""
|
||||
try:
|
||||
async with self.database_manager.get_session() as session:
|
||||
from app.repositories.equipment_repository import EquipmentRepository
|
||||
equipment_repo = EquipmentRepository(session)
|
||||
|
||||
# Prepare equipment data
|
||||
equipment_dict = equipment_data.model_dump()
|
||||
equipment_dict["tenant_id"] = tenant_id
|
||||
|
||||
# Create equipment
|
||||
equipment = await equipment_repo.create_equipment(equipment_dict)
|
||||
|
||||
logger.info("Created equipment",
|
||||
equipment_id=str(equipment.id), tenant_id=str(tenant_id))
|
||||
|
||||
return equipment
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Error creating equipment",
|
||||
error=str(e), tenant_id=str(tenant_id))
|
||||
raise
|
||||
|
||||
async def update_equipment(self, tenant_id: UUID, equipment_id: UUID, equipment_update):
|
||||
"""Update an equipment item"""
|
||||
try:
|
||||
async with self.database_manager.get_session() as session:
|
||||
from app.repositories.equipment_repository import EquipmentRepository
|
||||
equipment_repo = EquipmentRepository(session)
|
||||
|
||||
# First verify equipment belongs to tenant
|
||||
equipment = await equipment_repo.get_equipment_by_id(tenant_id, equipment_id)
|
||||
if not equipment:
|
||||
return None
|
||||
|
||||
# Update equipment
|
||||
updated_equipment = await equipment_repo.update_equipment(
|
||||
equipment_id,
|
||||
equipment_update.model_dump(exclude_none=True)
|
||||
)
|
||||
|
||||
logger.info("Updated equipment",
|
||||
equipment_id=str(equipment_id), tenant_id=str(tenant_id))
|
||||
|
||||
return updated_equipment
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Error updating equipment",
|
||||
error=str(e), equipment_id=str(equipment_id), tenant_id=str(tenant_id))
|
||||
raise
|
||||
|
||||
async def delete_equipment(self, tenant_id: UUID, equipment_id: UUID) -> bool:
|
||||
"""Delete (soft delete) an equipment item"""
|
||||
try:
|
||||
async with self.database_manager.get_session() as session:
|
||||
from app.repositories.equipment_repository import EquipmentRepository
|
||||
equipment_repo = EquipmentRepository(session)
|
||||
|
||||
# First verify equipment belongs to tenant
|
||||
equipment = await equipment_repo.get_equipment_by_id(tenant_id, equipment_id)
|
||||
if not equipment:
|
||||
return False
|
||||
|
||||
# Soft delete equipment
|
||||
success = await equipment_repo.delete_equipment(equipment_id)
|
||||
|
||||
logger.info("Deleted equipment",
|
||||
equipment_id=str(equipment_id), tenant_id=str(tenant_id))
|
||||
|
||||
return success
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Error deleting equipment",
|
||||
error=str(e), equipment_id=str(equipment_id), tenant_id=str(tenant_id))
|
||||
raise
|
||||
Reference in New Issue
Block a user