433 lines
14 KiB
Python
433 lines
14 KiB
Python
# services/production/app/api/quality_templates.py
|
|
"""
|
|
Quality Check Templates API - CRUD operations on quality check templates
|
|
"""
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, Path, Query, status
|
|
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, RouteCategory
|
|
from app.core.database import get_db
|
|
from app.services.quality_template_service import QualityTemplateService
|
|
from app.models.production import ProcessStage, QualityCheckTemplate
|
|
from app.schemas.quality_templates import (
|
|
QualityCheckTemplateCreate,
|
|
QualityCheckTemplateUpdate,
|
|
QualityCheckTemplateResponse,
|
|
QualityCheckTemplateList,
|
|
QualityCheckType
|
|
)
|
|
|
|
logger = structlog.get_logger()
|
|
route_builder = RouteBuilder('production')
|
|
router = APIRouter(tags=["quality-templates"])
|
|
|
|
|
|
# ===== Quality Template CRUD Endpoints =====
|
|
|
|
@router.get(
|
|
route_builder.build_base_route("quality-templates"),
|
|
response_model=QualityCheckTemplateList
|
|
)
|
|
async def list_quality_templates(
|
|
tenant_id: UUID = Path(...),
|
|
stage: Optional[ProcessStage] = Query(None, description="Filter by process stage"),
|
|
check_type: Optional[QualityCheckType] = Query(None, description="Filter by check type"),
|
|
is_active: Optional[bool] = Query(True, description="Filter by active status"),
|
|
skip: int = Query(0, ge=0, description="Number of templates to skip"),
|
|
limit: int = Query(100, ge=1, le=1000, description="Number of templates to return"),
|
|
current_user: dict = Depends(get_current_user_dep),
|
|
db = Depends(get_db)
|
|
):
|
|
"""
|
|
List quality check templates with filtering and pagination
|
|
|
|
Filters:
|
|
- stage: Filter by applicable process stage
|
|
- check_type: Filter by type of quality check
|
|
- is_active: Filter by active status (default: True)
|
|
"""
|
|
try:
|
|
service = QualityTemplateService(db)
|
|
|
|
templates, total = await service.get_templates(
|
|
tenant_id=str(tenant_id),
|
|
stage=stage,
|
|
check_type=check_type.value if check_type else None,
|
|
is_active=is_active,
|
|
skip=skip,
|
|
limit=limit
|
|
)
|
|
|
|
logger.info("Retrieved quality templates",
|
|
tenant_id=str(tenant_id),
|
|
total=total,
|
|
filters={"stage": stage, "check_type": check_type, "is_active": is_active})
|
|
|
|
return QualityCheckTemplateList(
|
|
templates=[QualityCheckTemplateResponse.from_orm(t) for t in templates],
|
|
total=total,
|
|
skip=skip,
|
|
limit=limit
|
|
)
|
|
|
|
except Exception as e:
|
|
logger.error("Error listing quality templates",
|
|
error=str(e), tenant_id=str(tenant_id))
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail="Failed to retrieve quality templates"
|
|
)
|
|
|
|
|
|
@router.post(
|
|
route_builder.build_base_route("quality-templates"),
|
|
response_model=QualityCheckTemplateResponse,
|
|
status_code=status.HTTP_201_CREATED
|
|
)
|
|
@require_user_role(['admin', 'owner', 'member'])
|
|
async def create_quality_template(
|
|
template_data: QualityCheckTemplateCreate,
|
|
tenant_id: UUID = Path(...),
|
|
current_user: dict = Depends(get_current_user_dep),
|
|
db = Depends(get_db)
|
|
):
|
|
"""Create a new quality check template"""
|
|
try:
|
|
service = QualityTemplateService(db)
|
|
|
|
# Add created_by from current user
|
|
template_dict = template_data.dict()
|
|
template_dict['created_by'] = UUID(current_user["sub"])
|
|
template_create = QualityCheckTemplateCreate(**template_dict)
|
|
|
|
# Create template via service (handles validation and business rules)
|
|
template = await service.create_template(
|
|
tenant_id=str(tenant_id),
|
|
template_data=template_create
|
|
)
|
|
|
|
logger.info("Created quality template",
|
|
template_id=str(template.id),
|
|
template_name=template.name,
|
|
tenant_id=str(tenant_id))
|
|
|
|
return QualityCheckTemplateResponse.from_orm(template)
|
|
|
|
except ValueError as e:
|
|
# Business rule validation errors
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail=str(e)
|
|
)
|
|
except Exception as e:
|
|
logger.error("Error creating quality template",
|
|
error=str(e), tenant_id=str(tenant_id))
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail="Failed to create quality template"
|
|
)
|
|
|
|
|
|
@router.get(
|
|
route_builder.build_resource_detail_route("quality-templates", "template_id"),
|
|
response_model=QualityCheckTemplateResponse
|
|
)
|
|
async def get_quality_template(
|
|
tenant_id: UUID = Path(...),
|
|
template_id: UUID = Path(...),
|
|
current_user: dict = Depends(get_current_user_dep),
|
|
db = Depends(get_db)
|
|
):
|
|
"""Get a specific quality check template"""
|
|
try:
|
|
service = QualityTemplateService(db)
|
|
|
|
template = await service.get_template(
|
|
tenant_id=str(tenant_id),
|
|
template_id=template_id
|
|
)
|
|
|
|
if not template:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Quality template not found"
|
|
)
|
|
|
|
return QualityCheckTemplateResponse.from_orm(template)
|
|
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
logger.error("Error getting quality template",
|
|
error=str(e),
|
|
template_id=str(template_id),
|
|
tenant_id=str(tenant_id))
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail="Failed to retrieve quality template"
|
|
)
|
|
|
|
|
|
@router.put(
|
|
route_builder.build_resource_detail_route("quality-templates", "template_id"),
|
|
response_model=QualityCheckTemplateResponse
|
|
)
|
|
@require_user_role(['admin', 'owner', 'member'])
|
|
async def update_quality_template(
|
|
template_data: QualityCheckTemplateUpdate,
|
|
tenant_id: UUID = Path(...),
|
|
template_id: UUID = Path(...),
|
|
current_user: dict = Depends(get_current_user_dep),
|
|
db = Depends(get_db)
|
|
):
|
|
"""Update a quality check template"""
|
|
try:
|
|
service = QualityTemplateService(db)
|
|
|
|
# Update template via service (handles validation and business rules)
|
|
template = await service.update_template(
|
|
tenant_id=str(tenant_id),
|
|
template_id=template_id,
|
|
template_data=template_data
|
|
)
|
|
|
|
if not template:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Quality template not found"
|
|
)
|
|
|
|
logger.info("Updated quality template",
|
|
template_id=str(template_id),
|
|
tenant_id=str(tenant_id))
|
|
|
|
return QualityCheckTemplateResponse.from_orm(template)
|
|
|
|
except ValueError as e:
|
|
# Business rule validation errors
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail=str(e)
|
|
)
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
logger.error("Error updating quality template",
|
|
error=str(e),
|
|
template_id=str(template_id),
|
|
tenant_id=str(tenant_id))
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail="Failed to update quality template"
|
|
)
|
|
|
|
|
|
@router.delete(
|
|
route_builder.build_resource_detail_route("quality-templates", "template_id"),
|
|
status_code=status.HTTP_204_NO_CONTENT
|
|
)
|
|
@require_user_role(['admin', 'owner'])
|
|
async def delete_quality_template(
|
|
tenant_id: UUID = Path(...),
|
|
template_id: UUID = Path(...),
|
|
current_user: dict = Depends(get_current_user_dep),
|
|
db = Depends(get_db)
|
|
):
|
|
"""
|
|
Delete a quality check template
|
|
|
|
Note: Service layer determines whether to use soft or hard delete
|
|
based on business rules (checking dependencies, etc.)
|
|
"""
|
|
try:
|
|
service = QualityTemplateService(db)
|
|
|
|
# Delete template via service (handles business rules)
|
|
success = await service.delete_template(
|
|
tenant_id=str(tenant_id),
|
|
template_id=template_id
|
|
)
|
|
|
|
if not success:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Quality template not found"
|
|
)
|
|
|
|
logger.info("Deleted quality template",
|
|
template_id=str(template_id),
|
|
tenant_id=str(tenant_id))
|
|
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
await db.rollback()
|
|
logger.error("Error deleting quality template",
|
|
error=str(e),
|
|
template_id=str(template_id),
|
|
tenant_id=str(tenant_id))
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail="Failed to delete quality template"
|
|
)
|
|
|
|
|
|
# ===== Additional Quality Template Operations =====
|
|
|
|
@router.get(
|
|
route_builder.build_custom_route(
|
|
RouteCategory.BASE,
|
|
["quality-templates", "stages", "{stage}"]
|
|
),
|
|
response_model=QualityCheckTemplateList
|
|
)
|
|
async def get_templates_for_stage(
|
|
tenant_id: UUID = Path(...),
|
|
stage: ProcessStage = Path(...),
|
|
is_active: bool = Query(True, description="Filter by active status"),
|
|
current_user: dict = Depends(get_current_user_dep),
|
|
db = Depends(get_db)
|
|
):
|
|
"""Get all quality templates applicable to a specific process stage"""
|
|
try:
|
|
service = QualityTemplateService(db)
|
|
|
|
templates = await service.get_templates_for_stage(
|
|
tenant_id=str(tenant_id),
|
|
stage=stage,
|
|
is_active=is_active
|
|
)
|
|
|
|
logger.info("Retrieved templates for stage",
|
|
tenant_id=str(tenant_id),
|
|
stage=stage,
|
|
count=len(templates))
|
|
|
|
return QualityCheckTemplateList(
|
|
templates=[QualityCheckTemplateResponse.from_orm(t) for t in templates],
|
|
total=len(templates),
|
|
skip=0,
|
|
limit=len(templates)
|
|
)
|
|
|
|
except Exception as e:
|
|
logger.error("Error getting templates for stage",
|
|
error=str(e),
|
|
stage=stage,
|
|
tenant_id=str(tenant_id))
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail="Failed to retrieve templates for stage"
|
|
)
|
|
|
|
|
|
@router.post(
|
|
route_builder.build_resource_action_route("quality-templates", "template_id", "duplicate"),
|
|
response_model=QualityCheckTemplateResponse,
|
|
status_code=status.HTTP_201_CREATED
|
|
)
|
|
@require_user_role(['admin', 'owner', 'member'])
|
|
async def duplicate_quality_template(
|
|
tenant_id: UUID = Path(...),
|
|
template_id: UUID = Path(...),
|
|
current_user: dict = Depends(get_current_user_dep),
|
|
db = Depends(get_db)
|
|
):
|
|
"""Duplicate an existing quality check template"""
|
|
try:
|
|
service = QualityTemplateService(db)
|
|
|
|
# Duplicate template via service (handles business rules)
|
|
duplicate = await service.duplicate_template(
|
|
tenant_id=str(tenant_id),
|
|
template_id=template_id
|
|
)
|
|
|
|
if not duplicate:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Quality template not found"
|
|
)
|
|
|
|
logger.info("Duplicated quality template",
|
|
original_id=str(template_id),
|
|
duplicate_id=str(duplicate.id),
|
|
tenant_id=str(tenant_id))
|
|
|
|
return QualityCheckTemplateResponse.from_orm(duplicate)
|
|
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
logger.error("Error duplicating quality template",
|
|
error=str(e),
|
|
template_id=str(template_id),
|
|
tenant_id=str(tenant_id))
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail="Failed to duplicate quality template"
|
|
)
|
|
|
|
|
|
@router.post(
|
|
route_builder.build_operations_route("quality-templates/validate"),
|
|
response_model=dict
|
|
)
|
|
@require_user_role(['admin', 'owner', 'member'])
|
|
async def validate_quality_template(
|
|
template_data: dict,
|
|
tenant_id: UUID = Path(...),
|
|
current_user: dict = Depends(get_current_user_dep),
|
|
):
|
|
"""
|
|
Validate quality template configuration without creating it
|
|
|
|
Returns validation result with any errors found
|
|
"""
|
|
try:
|
|
errors = []
|
|
|
|
# Basic validation
|
|
if not template_data.get('name'):
|
|
errors.append("Template name is required")
|
|
|
|
if not template_data.get('check_type'):
|
|
errors.append("Check type is required")
|
|
|
|
# Validate measurement fields
|
|
check_type = template_data.get('check_type')
|
|
if check_type in ['measurement', 'temperature', 'weight']:
|
|
if template_data.get('min_value') is not None and template_data.get('max_value') is not None:
|
|
if template_data['min_value'] >= template_data['max_value']:
|
|
errors.append("Minimum value must be less than maximum value")
|
|
|
|
# Validate weight
|
|
weight = template_data.get('weight', 1.0)
|
|
if weight < 0 or weight > 10:
|
|
errors.append("Weight must be between 0 and 10")
|
|
|
|
is_valid = len(errors) == 0
|
|
|
|
logger.info("Validated quality template",
|
|
tenant_id=str(tenant_id),
|
|
valid=is_valid,
|
|
error_count=len(errors))
|
|
|
|
return {
|
|
"valid": is_valid,
|
|
"errors": errors
|
|
}
|
|
|
|
except Exception as e:
|
|
logger.error("Error validating quality template",
|
|
error=str(e), tenant_id=str(tenant_id))
|
|
return {
|
|
"valid": False,
|
|
"errors": [f"Validation error: {str(e)}"]
|
|
}
|