Add quality template logic
This commit is contained in:
@@ -16,7 +16,7 @@ import type {
|
||||
} from '../types/qualityTemplates';
|
||||
|
||||
class QualityTemplateService {
|
||||
private readonly baseURL = '/production/api/v1/quality-templates';
|
||||
private readonly baseURL = '/tenants';
|
||||
|
||||
/**
|
||||
* Create a new quality check template
|
||||
@@ -25,10 +25,10 @@ class QualityTemplateService {
|
||||
tenantId: string,
|
||||
templateData: QualityCheckTemplateCreate
|
||||
): Promise<QualityCheckTemplate> {
|
||||
const response = await apiClient.post(this.baseURL, templateData, {
|
||||
const data = await apiClient.post(`${this.baseURL}/${tenantId}/production/quality-templates`, templateData, {
|
||||
headers: { 'X-Tenant-ID': tenantId }
|
||||
});
|
||||
return response.data;
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -38,11 +38,11 @@ class QualityTemplateService {
|
||||
tenantId: string,
|
||||
params?: QualityTemplateQueryParams
|
||||
): Promise<QualityCheckTemplateList> {
|
||||
const response = await apiClient.get(this.baseURL, {
|
||||
const data = await apiClient.get(`${this.baseURL}/${tenantId}/production/quality-templates`, {
|
||||
params,
|
||||
headers: { 'X-Tenant-ID': tenantId }
|
||||
});
|
||||
return response.data;
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -52,10 +52,10 @@ class QualityTemplateService {
|
||||
tenantId: string,
|
||||
templateId: string
|
||||
): Promise<QualityCheckTemplate> {
|
||||
const response = await apiClient.get(`${this.baseURL}/${templateId}`, {
|
||||
const data = await apiClient.get(`${this.baseURL}/${tenantId}/production/quality-templates/${templateId}`, {
|
||||
headers: { 'X-Tenant-ID': tenantId }
|
||||
});
|
||||
return response.data;
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -66,17 +66,17 @@ class QualityTemplateService {
|
||||
templateId: string,
|
||||
templateData: QualityCheckTemplateUpdate
|
||||
): Promise<QualityCheckTemplate> {
|
||||
const response = await apiClient.put(`${this.baseURL}/${templateId}`, templateData, {
|
||||
const data = await apiClient.put(`${this.baseURL}/${tenantId}/production/quality-templates/${templateId}`, templateData, {
|
||||
headers: { 'X-Tenant-ID': tenantId }
|
||||
});
|
||||
return response.data;
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a quality check template
|
||||
*/
|
||||
async deleteTemplate(tenantId: string, templateId: string): Promise<void> {
|
||||
await apiClient.delete(`${this.baseURL}/${templateId}`, {
|
||||
await apiClient.delete(`${this.baseURL}/${tenantId}/production/quality-templates/${templateId}`, {
|
||||
headers: { 'X-Tenant-ID': tenantId }
|
||||
});
|
||||
}
|
||||
@@ -89,11 +89,11 @@ class QualityTemplateService {
|
||||
stage: ProcessStage,
|
||||
isActive: boolean = true
|
||||
): Promise<QualityCheckTemplateList> {
|
||||
const response = await apiClient.get(`${this.baseURL}/stages/${stage}`, {
|
||||
const data = await apiClient.get(`${this.baseURL}/${tenantId}/production/quality-templates/stages/${stage}`, {
|
||||
params: { is_active: isActive },
|
||||
headers: { 'X-Tenant-ID': tenantId }
|
||||
});
|
||||
return response.data;
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -103,10 +103,10 @@ class QualityTemplateService {
|
||||
tenantId: string,
|
||||
templateId: string
|
||||
): Promise<QualityCheckTemplate> {
|
||||
const response = await apiClient.post(`${this.baseURL}/${templateId}/duplicate`, {}, {
|
||||
const data = await apiClient.post(`${this.baseURL}/${tenantId}/production/quality-templates/${templateId}/duplicate`, {}, {
|
||||
headers: { 'X-Tenant-ID': tenantId }
|
||||
});
|
||||
return response.data;
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -116,10 +116,10 @@ class QualityTemplateService {
|
||||
tenantId: string,
|
||||
executionData: QualityCheckExecutionRequest
|
||||
): Promise<QualityCheckExecutionResponse> {
|
||||
const response = await apiClient.post('/production/api/v1/quality-checks/execute', executionData, {
|
||||
const data = await apiClient.post(`${this.baseURL}/${tenantId}/production/quality-checks/execute`, executionData, {
|
||||
headers: { 'X-Tenant-ID': tenantId }
|
||||
});
|
||||
return response.data;
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -130,11 +130,11 @@ class QualityTemplateService {
|
||||
batchId: string,
|
||||
stage?: ProcessStage
|
||||
): Promise<any[]> {
|
||||
const response = await apiClient.get('/production/api/v1/quality-checks', {
|
||||
const data = await apiClient.get(`${this.baseURL}/${tenantId}/production/quality-checks`, {
|
||||
params: { batch_id: batchId, process_stage: stage },
|
||||
headers: { 'X-Tenant-ID': tenantId }
|
||||
});
|
||||
return response.data;
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -168,15 +168,15 @@ class QualityTemplateService {
|
||||
templateData: Partial<QualityCheckTemplateCreate | QualityCheckTemplateUpdate>
|
||||
): Promise<{ valid: boolean; errors: string[] }> {
|
||||
try {
|
||||
const response = await apiClient.post(`${this.baseURL}/validate`, templateData, {
|
||||
const data = await apiClient.post(`${this.baseURL}/${tenantId}/production/quality-templates/validate`, templateData, {
|
||||
headers: { 'X-Tenant-ID': tenantId }
|
||||
});
|
||||
return response.data;
|
||||
return data;
|
||||
} catch (error: any) {
|
||||
if (error.response?.status === 400) {
|
||||
return {
|
||||
valid: false,
|
||||
errors: [error.response.data.detail || 'Validation failed']
|
||||
errors: [error.response?.data?.detail || 'Validation failed']
|
||||
};
|
||||
}
|
||||
throw error;
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import React from 'react';
|
||||
import { QualityTemplateManager } from '../../../../components/domain/production';
|
||||
|
||||
/**
|
||||
* QualityTemplatesPage - Page wrapper for the QualityTemplateManager component
|
||||
*
|
||||
* This page provides access to quality template management functionality,
|
||||
* allowing users to create, edit, duplicate, and manage quality control templates
|
||||
* that are used during production processes.
|
||||
*/
|
||||
const QualityTemplatesPage: React.FC = () => {
|
||||
return (
|
||||
<div className="container mx-auto px-4 py-6">
|
||||
<QualityTemplateManager />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default QualityTemplatesPage;
|
||||
@@ -37,6 +37,7 @@ const OrganizationsPage = React.lazy(() => import('../pages/app/settings/organiz
|
||||
// Database pages
|
||||
const DatabasePage = React.lazy(() => import('../pages/app/database/DatabasePage'));
|
||||
const ModelsConfigPage = React.lazy(() => import('../pages/app/database/models/ModelsConfigPage'));
|
||||
const QualityTemplatesPage = React.lazy(() => import('../pages/app/database/quality-templates/QualityTemplatesPage'));
|
||||
|
||||
// Data pages
|
||||
const WeatherPage = React.lazy(() => import('../pages/app/data/weather/WeatherPage'));
|
||||
@@ -190,6 +191,16 @@ export const AppRouter: React.FC = () => {
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/app/database/quality-templates"
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<AppShell>
|
||||
<QualityTemplatesPage />
|
||||
</AppShell>
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/app/database/maquinaria"
|
||||
element={
|
||||
|
||||
@@ -137,6 +137,7 @@ export const ROUTES = {
|
||||
SETTINGS_BILLING: '/settings/billing',
|
||||
SETTINGS_BAKERY_CONFIG: '/app/database/bakery-config',
|
||||
SETTINGS_TEAM: '/app/database/team',
|
||||
QUALITY_TEMPLATES: '/app/database/quality-templates',
|
||||
|
||||
// Reports
|
||||
REPORTS: '/reports',
|
||||
@@ -353,6 +354,17 @@ export const routesConfig: RouteConfig[] = [
|
||||
showInNavigation: true,
|
||||
showInBreadcrumbs: true,
|
||||
},
|
||||
{
|
||||
path: '/app/database/quality-templates',
|
||||
name: 'QualityTemplates',
|
||||
component: 'QualityTemplatesPage',
|
||||
title: 'Plantillas de Calidad',
|
||||
icon: 'settings',
|
||||
requiresAuth: true,
|
||||
requiredRoles: ROLE_COMBINATIONS.MANAGEMENT_ACCESS,
|
||||
showInNavigation: true,
|
||||
showInBreadcrumbs: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@ from app.schemas.production import (
|
||||
ProductionStatusEnum
|
||||
)
|
||||
from app.core.config import settings
|
||||
from .quality_templates import router as quality_templates_router
|
||||
|
||||
logger = structlog.get_logger()
|
||||
|
||||
@@ -1096,6 +1095,211 @@ async def get_yield_metrics(
|
||||
return metrics
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Error getting yield metrics",
|
||||
logger.error("Error getting yield metrics",
|
||||
error=str(e), tenant_id=str(tenant_id))
|
||||
raise HTTPException(status_code=500, detail="Failed to get yield metrics")
|
||||
raise HTTPException(status_code=500, detail="Failed to get yield metrics")
|
||||
|
||||
|
||||
# ================================================================
|
||||
# QUALITY TEMPLATES ENDPOINTS
|
||||
# ================================================================
|
||||
|
||||
from app.repositories.quality_template_repository import QualityTemplateRepository
|
||||
from app.schemas.quality_templates import (
|
||||
QualityCheckTemplateCreate,
|
||||
QualityCheckTemplateUpdate,
|
||||
QualityCheckTemplateResponse,
|
||||
QualityCheckTemplateList
|
||||
)
|
||||
|
||||
@router.get("/tenants/{tenant_id}/production/quality-templates", response_model=QualityCheckTemplateList)
|
||||
async def get_quality_templates(
|
||||
tenant_id: UUID = Path(...),
|
||||
stage: Optional[str] = Query(None, description="Filter by process stage"),
|
||||
check_type: Optional[str] = Query(None, description="Filter by check type"),
|
||||
is_active: Optional[bool] = Query(True, description="Filter by active status"),
|
||||
skip: int = Query(0, ge=0),
|
||||
limit: int = Query(100, ge=1, le=1000),
|
||||
current_user: dict = Depends(get_current_user_dep),
|
||||
db=Depends(get_db)
|
||||
):
|
||||
"""Get quality check templates for tenant"""
|
||||
try:
|
||||
repo = QualityTemplateRepository(db)
|
||||
|
||||
# Convert stage string to ProcessStage enum if provided
|
||||
stage_enum = None
|
||||
if stage:
|
||||
try:
|
||||
stage_enum = ProcessStage(stage)
|
||||
except ValueError:
|
||||
raise HTTPException(status_code=400, detail=f"Invalid stage: {stage}")
|
||||
|
||||
templates, total = await repo.get_templates_by_tenant(
|
||||
tenant_id=str(tenant_id),
|
||||
stage=stage_enum,
|
||||
check_type=check_type,
|
||||
is_active=is_active,
|
||||
skip=skip,
|
||||
limit=limit
|
||||
)
|
||||
|
||||
return QualityCheckTemplateList(
|
||||
templates=[QualityCheckTemplateResponse.from_orm(t) for t in templates],
|
||||
total=total,
|
||||
skip=skip,
|
||||
limit=limit
|
||||
)
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error("Error getting quality templates",
|
||||
error=str(e), tenant_id=str(tenant_id))
|
||||
raise HTTPException(status_code=500, detail="Failed to get quality templates")
|
||||
|
||||
|
||||
@router.post("/tenants/{tenant_id}/production/quality-templates", response_model=QualityCheckTemplateResponse)
|
||||
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:
|
||||
repo = QualityTemplateRepository(db)
|
||||
|
||||
# Add tenant_id to the template data
|
||||
create_data = template_data.dict()
|
||||
create_data['tenant_id'] = str(tenant_id)
|
||||
|
||||
template = await repo.create(create_data)
|
||||
return QualityCheckTemplateResponse.from_orm(template)
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=400, 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=500, detail="Failed to create quality template")
|
||||
|
||||
|
||||
@router.get("/tenants/{tenant_id}/production/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:
|
||||
repo = QualityTemplateRepository(db)
|
||||
template = await repo.get_by_tenant_and_id(str(tenant_id), template_id)
|
||||
if not template:
|
||||
raise HTTPException(status_code=404, 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), tenant_id=str(tenant_id), template_id=str(template_id))
|
||||
raise HTTPException(status_code=500, detail="Failed to get quality template")
|
||||
|
||||
|
||||
@router.put("/tenants/{tenant_id}/production/quality-templates/{template_id}", response_model=QualityCheckTemplateResponse)
|
||||
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:
|
||||
repo = QualityTemplateRepository(db)
|
||||
# First check if template exists and belongs to tenant
|
||||
existing = await repo.get_by_tenant_and_id(str(tenant_id), template_id)
|
||||
if not existing:
|
||||
raise HTTPException(status_code=404, detail="Quality template not found")
|
||||
|
||||
template = await repo.update(template_id, template_data.dict(exclude_unset=True))
|
||||
return QualityCheckTemplateResponse.from_orm(template)
|
||||
except HTTPException:
|
||||
raise
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
except Exception as e:
|
||||
logger.error("Error updating quality template",
|
||||
error=str(e), tenant_id=str(tenant_id), template_id=str(template_id))
|
||||
raise HTTPException(status_code=500, detail="Failed to update quality template")
|
||||
|
||||
|
||||
@router.delete("/tenants/{tenant_id}/production/quality-templates/{template_id}")
|
||||
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"""
|
||||
try:
|
||||
repo = QualityTemplateRepository(db)
|
||||
# First check if template exists and belongs to tenant
|
||||
existing = await repo.get_by_tenant_and_id(str(tenant_id), template_id)
|
||||
if not existing:
|
||||
raise HTTPException(status_code=404, detail="Quality template not found")
|
||||
|
||||
await repo.delete(template_id)
|
||||
return {"message": "Quality template deleted successfully"}
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error("Error deleting quality template",
|
||||
error=str(e), tenant_id=str(tenant_id), template_id=str(template_id))
|
||||
raise HTTPException(status_code=500, detail="Failed to delete quality template")
|
||||
|
||||
|
||||
@router.post("/tenants/{tenant_id}/production/quality-templates/{template_id}/duplicate", response_model=QualityCheckTemplateResponse)
|
||||
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:
|
||||
repo = QualityTemplateRepository(db)
|
||||
# Get original template
|
||||
original = await repo.get_by_tenant_and_id(str(tenant_id), template_id)
|
||||
if not original:
|
||||
raise HTTPException(status_code=404, detail="Quality template not found")
|
||||
|
||||
# Create duplicate data
|
||||
duplicate_data = {
|
||||
"tenant_id": original.tenant_id,
|
||||
"name": f"{original.name} (Copy)",
|
||||
"template_code": None, # Will be auto-generated
|
||||
"check_type": original.check_type,
|
||||
"category": original.category,
|
||||
"description": original.description,
|
||||
"instructions": original.instructions,
|
||||
"criteria": original.criteria,
|
||||
"is_required": original.is_required,
|
||||
"is_critical": original.is_critical,
|
||||
"weight": original.weight,
|
||||
"min_value": original.min_value,
|
||||
"max_value": original.max_value,
|
||||
"unit": original.unit,
|
||||
"tolerance_percentage": original.tolerance_percentage,
|
||||
"applicable_stages": original.applicable_stages,
|
||||
"created_by": original.created_by
|
||||
}
|
||||
|
||||
template = await repo.create(duplicate_data)
|
||||
return QualityCheckTemplateResponse.from_orm(template)
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error("Error duplicating quality template",
|
||||
error=str(e), tenant_id=str(tenant_id), template_id=str(template_id))
|
||||
raise HTTPException(status_code=500, detail="Failed to duplicate quality template")
|
||||
@@ -1,174 +0,0 @@
|
||||
# services/production/app/api/quality_templates.py
|
||||
"""
|
||||
Quality Check Template API endpoints for production service
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, Query
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List, Optional
|
||||
from uuid import UUID
|
||||
|
||||
from ..core.database import get_db
|
||||
from ..models.production import QualityCheckTemplate, ProcessStage
|
||||
from ..services.quality_template_service import QualityTemplateService
|
||||
from ..schemas.quality_templates import (
|
||||
QualityCheckTemplateCreate,
|
||||
QualityCheckTemplateUpdate,
|
||||
QualityCheckTemplateResponse,
|
||||
QualityCheckTemplateList
|
||||
)
|
||||
from shared.auth.tenant_access import get_current_tenant_id
|
||||
|
||||
router = APIRouter(prefix="/quality-templates", tags=["quality-templates"])
|
||||
|
||||
|
||||
@router.post("", response_model=QualityCheckTemplateResponse, status_code=status.HTTP_201_CREATED)
|
||||
async def create_quality_template(
|
||||
template_data: QualityCheckTemplateCreate,
|
||||
tenant_id: str = Depends(get_current_tenant_id),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Create a new quality check template"""
|
||||
try:
|
||||
service = QualityTemplateService(db)
|
||||
template = await service.create_template(tenant_id, template_data)
|
||||
return QualityCheckTemplateResponse.from_orm(template)
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
|
||||
|
||||
|
||||
@router.get("", response_model=QualityCheckTemplateList)
|
||||
async def get_quality_templates(
|
||||
tenant_id: str = Depends(get_current_tenant_id),
|
||||
stage: Optional[ProcessStage] = Query(None, description="Filter by process stage"),
|
||||
check_type: Optional[str] = Query(None, description="Filter by check type"),
|
||||
is_active: Optional[bool] = Query(True, description="Filter by active status"),
|
||||
skip: int = Query(0, ge=0),
|
||||
limit: int = Query(100, ge=1, le=1000),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Get quality check templates for tenant"""
|
||||
try:
|
||||
service = QualityTemplateService(db)
|
||||
templates, total = await service.get_templates(
|
||||
tenant_id=tenant_id,
|
||||
stage=stage,
|
||||
check_type=check_type,
|
||||
is_active=is_active,
|
||||
skip=skip,
|
||||
limit=limit
|
||||
)
|
||||
|
||||
return QualityCheckTemplateList(
|
||||
templates=[QualityCheckTemplateResponse.from_orm(t) for t in templates],
|
||||
total=total,
|
||||
skip=skip,
|
||||
limit=limit
|
||||
)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
|
||||
|
||||
|
||||
@router.get("/{template_id}", response_model=QualityCheckTemplateResponse)
|
||||
async def get_quality_template(
|
||||
template_id: UUID,
|
||||
tenant_id: str = Depends(get_current_tenant_id),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Get a specific quality check template"""
|
||||
try:
|
||||
service = QualityTemplateService(db)
|
||||
template = await service.get_template(tenant_id, template_id)
|
||||
if not template:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Template not found")
|
||||
|
||||
return QualityCheckTemplateResponse.from_orm(template)
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
|
||||
|
||||
|
||||
@router.put("/{template_id}", response_model=QualityCheckTemplateResponse)
|
||||
async def update_quality_template(
|
||||
template_id: UUID,
|
||||
template_data: QualityCheckTemplateUpdate,
|
||||
tenant_id: str = Depends(get_current_tenant_id),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Update a quality check template"""
|
||||
try:
|
||||
service = QualityTemplateService(db)
|
||||
template = await service.update_template(tenant_id, template_id, template_data)
|
||||
if not template:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Template not found")
|
||||
|
||||
return QualityCheckTemplateResponse.from_orm(template)
|
||||
except HTTPException:
|
||||
raise
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
|
||||
|
||||
|
||||
@router.delete("/{template_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_quality_template(
|
||||
template_id: UUID,
|
||||
tenant_id: str = Depends(get_current_tenant_id),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Delete a quality check template"""
|
||||
try:
|
||||
service = QualityTemplateService(db)
|
||||
success = await service.delete_template(tenant_id, template_id)
|
||||
if not success:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Template not found")
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
|
||||
|
||||
|
||||
@router.get("/stages/{stage}", response_model=QualityCheckTemplateList)
|
||||
async def get_templates_for_stage(
|
||||
stage: ProcessStage,
|
||||
tenant_id: str = Depends(get_current_tenant_id),
|
||||
is_active: Optional[bool] = Query(True, description="Filter by active status"),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Get quality check templates applicable to a specific process stage"""
|
||||
try:
|
||||
service = QualityTemplateService(db)
|
||||
templates = await service.get_templates_for_stage(tenant_id, stage, is_active)
|
||||
|
||||
return QualityCheckTemplateList(
|
||||
templates=[QualityCheckTemplateResponse.from_orm(t) for t in templates],
|
||||
total=len(templates),
|
||||
skip=0,
|
||||
limit=len(templates)
|
||||
)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
|
||||
|
||||
|
||||
@router.post("/{template_id}/duplicate", response_model=QualityCheckTemplateResponse, status_code=status.HTTP_201_CREATED)
|
||||
async def duplicate_quality_template(
|
||||
template_id: UUID,
|
||||
tenant_id: str = Depends(get_current_tenant_id),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Duplicate an existing quality check template"""
|
||||
try:
|
||||
service = QualityTemplateService(db)
|
||||
template = await service.duplicate_template(tenant_id, template_id)
|
||||
if not template:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Template not found")
|
||||
|
||||
return QualityCheckTemplateResponse.from_orm(template)
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
|
||||
@@ -14,7 +14,6 @@ import structlog
|
||||
from app.core.config import settings
|
||||
from app.core.database import init_database, get_db_health
|
||||
from app.api.production import router as production_router
|
||||
from app.api.quality_templates import router as quality_templates_router
|
||||
from app.services.production_alert_service import ProductionAlertService
|
||||
|
||||
# Configure logging
|
||||
@@ -74,7 +73,6 @@ app.add_middleware(
|
||||
|
||||
# Include routers
|
||||
app.include_router(production_router, prefix="/api/v1")
|
||||
app.include_router(quality_templates_router, prefix="/api/v1")
|
||||
|
||||
|
||||
@app.get("/health")
|
||||
|
||||
@@ -43,6 +43,17 @@ class EquipmentStatus(str, enum.Enum):
|
||||
WARNING = "warning"
|
||||
|
||||
|
||||
class ProcessStage(str, enum.Enum):
|
||||
"""Production process stages where quality checks can occur"""
|
||||
MIXING = "mixing"
|
||||
PROOFING = "proofing"
|
||||
SHAPING = "shaping"
|
||||
BAKING = "baking"
|
||||
COOLING = "cooling"
|
||||
PACKAGING = "packaging"
|
||||
FINISHING = "finishing"
|
||||
|
||||
|
||||
class EquipmentType(str, enum.Enum):
|
||||
"""Equipment type enumeration"""
|
||||
OVEN = "oven"
|
||||
@@ -329,17 +340,6 @@ class ProductionCapacity(Base):
|
||||
}
|
||||
|
||||
|
||||
class ProcessStage(str, enum.Enum):
|
||||
"""Production process stages where quality checks can occur"""
|
||||
MIXING = "mixing"
|
||||
PROOFING = "proofing"
|
||||
SHAPING = "shaping"
|
||||
BAKING = "baking"
|
||||
COOLING = "cooling"
|
||||
PACKAGING = "packaging"
|
||||
FINISHING = "finishing"
|
||||
|
||||
|
||||
class QualityCheckTemplate(Base):
|
||||
"""Quality check templates for tenant-specific quality standards"""
|
||||
__tablename__ = "quality_check_templates"
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
"""
|
||||
Quality Template Repository for Production Service
|
||||
"""
|
||||
|
||||
from typing import List, Optional, Tuple
|
||||
from sqlalchemy import and_, or_, func, select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from uuid import UUID
|
||||
import structlog
|
||||
|
||||
from .base import ProductionBaseRepository
|
||||
from ..models.production import QualityCheckTemplate, ProcessStage
|
||||
|
||||
logger = structlog.get_logger()
|
||||
|
||||
|
||||
class QualityTemplateRepository(ProductionBaseRepository):
|
||||
"""Repository for quality check template operations"""
|
||||
|
||||
def __init__(self, session: AsyncSession):
|
||||
super().__init__(QualityCheckTemplate, session)
|
||||
|
||||
async def get_templates_by_tenant(
|
||||
self,
|
||||
tenant_id: str,
|
||||
stage: Optional[ProcessStage] = None,
|
||||
check_type: Optional[str] = None,
|
||||
is_active: Optional[bool] = True,
|
||||
skip: int = 0,
|
||||
limit: int = 100
|
||||
) -> Tuple[List[QualityCheckTemplate], int]:
|
||||
"""Get quality check templates with filtering and pagination"""
|
||||
|
||||
filters = [QualityCheckTemplate.tenant_id == tenant_id]
|
||||
|
||||
if is_active is not None:
|
||||
filters.append(QualityCheckTemplate.is_active == is_active)
|
||||
|
||||
if check_type:
|
||||
filters.append(QualityCheckTemplate.check_type == check_type)
|
||||
|
||||
if stage:
|
||||
filters.append(
|
||||
or_(
|
||||
func.json_contains(
|
||||
QualityCheckTemplate.applicable_stages,
|
||||
f'"{stage.value}"'
|
||||
),
|
||||
QualityCheckTemplate.applicable_stages.is_(None)
|
||||
)
|
||||
)
|
||||
|
||||
# Get total count with SQLAlchemy conditions
|
||||
count_query = select(func.count(QualityCheckTemplate.id)).where(and_(*filters))
|
||||
count_result = await self.session.execute(count_query)
|
||||
total = count_result.scalar()
|
||||
|
||||
# Get templates with ordering
|
||||
query = select(QualityCheckTemplate).where(and_(*filters)).order_by(
|
||||
QualityCheckTemplate.is_critical.desc(),
|
||||
QualityCheckTemplate.is_required.desc(),
|
||||
QualityCheckTemplate.name
|
||||
).offset(skip).limit(limit)
|
||||
|
||||
result = await self.session.execute(query)
|
||||
templates = result.scalars().all()
|
||||
|
||||
return templates, total
|
||||
|
||||
async def get_by_tenant_and_id(
|
||||
self,
|
||||
tenant_id: str,
|
||||
template_id: UUID
|
||||
) -> Optional[QualityCheckTemplate]:
|
||||
"""Get a specific quality check template by tenant and ID"""
|
||||
|
||||
return await self.get_by_filters(
|
||||
and_(
|
||||
QualityCheckTemplate.tenant_id == tenant_id,
|
||||
QualityCheckTemplate.id == template_id
|
||||
)
|
||||
)
|
||||
|
||||
async def get_templates_for_stage(
|
||||
self,
|
||||
tenant_id: str,
|
||||
stage: ProcessStage,
|
||||
is_active: Optional[bool] = True
|
||||
) -> List[QualityCheckTemplate]:
|
||||
"""Get all quality check templates applicable to a specific process stage"""
|
||||
|
||||
filters = [
|
||||
QualityCheckTemplate.tenant_id == tenant_id,
|
||||
or_(
|
||||
func.json_contains(
|
||||
QualityCheckTemplate.applicable_stages,
|
||||
f'"{stage.value}"'
|
||||
),
|
||||
QualityCheckTemplate.applicable_stages.is_(None)
|
||||
)
|
||||
]
|
||||
|
||||
if is_active is not None:
|
||||
filters.append(QualityCheckTemplate.is_active == is_active)
|
||||
|
||||
return await self.get_multi(
|
||||
filters=and_(*filters),
|
||||
order_by=[
|
||||
QualityCheckTemplate.is_critical.desc(),
|
||||
QualityCheckTemplate.is_required.desc(),
|
||||
QualityCheckTemplate.weight.desc(),
|
||||
QualityCheckTemplate.name
|
||||
]
|
||||
)
|
||||
|
||||
async def check_template_code_exists(
|
||||
self,
|
||||
tenant_id: str,
|
||||
template_code: str,
|
||||
exclude_id: Optional[UUID] = None
|
||||
) -> bool:
|
||||
"""Check if a template code already exists for the tenant"""
|
||||
|
||||
filters = [
|
||||
QualityCheckTemplate.tenant_id == tenant_id,
|
||||
QualityCheckTemplate.template_code == template_code
|
||||
]
|
||||
|
||||
if exclude_id:
|
||||
filters.append(QualityCheckTemplate.id != exclude_id)
|
||||
|
||||
existing = await self.get_by_filters(and_(*filters))
|
||||
return existing is not None
|
||||
|
||||
async def get_templates_by_ids(
|
||||
self,
|
||||
tenant_id: str,
|
||||
template_ids: List[UUID]
|
||||
) -> List[QualityCheckTemplate]:
|
||||
"""Get quality check templates by list of IDs"""
|
||||
|
||||
return await self.get_multi(
|
||||
filters=and_(
|
||||
QualityCheckTemplate.tenant_id == tenant_id,
|
||||
QualityCheckTemplate.id.in_(template_ids)
|
||||
),
|
||||
order_by=[
|
||||
QualityCheckTemplate.is_critical.desc(),
|
||||
QualityCheckTemplate.is_required.desc(),
|
||||
QualityCheckTemplate.weight.desc()
|
||||
]
|
||||
)
|
||||
@@ -104,7 +104,7 @@ class ProductionAlertService(BaseAlertService, AlertServiceMixin):
|
||||
JOIN production_capacity pc ON pc.equipment_id = p.equipment_id
|
||||
WHERE p.planned_date >= CURRENT_DATE
|
||||
AND p.planned_date <= CURRENT_DATE + INTERVAL '3 days'
|
||||
AND p.status IN ('planned', 'in_progress')
|
||||
AND p.status IN ('PENDING', 'IN_PROGRESS')
|
||||
AND p.tenant_id = $1
|
||||
GROUP BY p.tenant_id, p.planned_date
|
||||
)
|
||||
@@ -226,10 +226,10 @@ class ProductionAlertService(BaseAlertService, AlertServiceMixin):
|
||||
COALESCE(pb.priority::text, 'medium') as priority_level,
|
||||
1 as affected_orders -- Default to 1 since we can't count orders
|
||||
FROM production_batches pb
|
||||
WHERE pb.status IN ('IN_PROGRESS', 'DELAYED')
|
||||
WHERE pb.status IN ('IN_PROGRESS', 'ON_HOLD', 'QUALITY_CHECK')
|
||||
AND (
|
||||
(pb.planned_end_time < NOW() AND pb.status = 'IN_PROGRESS')
|
||||
OR pb.status = 'DELAYED'
|
||||
OR pb.status IN ('ON_HOLD', 'QUALITY_CHECK')
|
||||
)
|
||||
AND pb.planned_end_time > NOW() - INTERVAL '24 hours'
|
||||
ORDER BY
|
||||
@@ -831,7 +831,7 @@ class ProductionAlertService(BaseAlertService, AlertServiceMixin):
|
||||
FROM production_batches pb
|
||||
JOIN recipe_ingredients ri ON ri.recipe_id = pb.recipe_id
|
||||
WHERE ri.ingredient_id = $1
|
||||
AND pb.status IN ('planned', 'in_progress')
|
||||
AND pb.status IN ('PENDING', 'IN_PROGRESS')
|
||||
AND pb.planned_completion_time > NOW()
|
||||
"""
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ class QualityTemplateService:
|
||||
def __init__(self, db: Session):
|
||||
self.db = db
|
||||
|
||||
async def create_template(
|
||||
def create_template(
|
||||
self,
|
||||
tenant_id: str,
|
||||
template_data: QualityCheckTemplateCreate
|
||||
@@ -50,7 +50,7 @@ class QualityTemplateService:
|
||||
|
||||
return template
|
||||
|
||||
async def get_templates(
|
||||
def get_templates(
|
||||
self,
|
||||
tenant_id: str,
|
||||
stage: Optional[ProcessStage] = None,
|
||||
@@ -93,7 +93,7 @@ class QualityTemplateService:
|
||||
|
||||
return templates, total
|
||||
|
||||
async def get_template(
|
||||
def get_template(
|
||||
self,
|
||||
tenant_id: str,
|
||||
template_id: UUID
|
||||
@@ -107,7 +107,7 @@ class QualityTemplateService:
|
||||
)
|
||||
).first()
|
||||
|
||||
async def update_template(
|
||||
def update_template(
|
||||
self,
|
||||
tenant_id: str,
|
||||
template_id: UUID,
|
||||
@@ -115,7 +115,7 @@ class QualityTemplateService:
|
||||
) -> Optional[QualityCheckTemplate]:
|
||||
"""Update a quality check template"""
|
||||
|
||||
template = await self.get_template(tenant_id, template_id)
|
||||
template = self.get_template(tenant_id, template_id)
|
||||
if not template:
|
||||
return None
|
||||
|
||||
@@ -143,14 +143,14 @@ class QualityTemplateService:
|
||||
|
||||
return template
|
||||
|
||||
async def delete_template(
|
||||
def delete_template(
|
||||
self,
|
||||
tenant_id: str,
|
||||
template_id: UUID
|
||||
) -> bool:
|
||||
"""Delete a quality check template"""
|
||||
|
||||
template = await self.get_template(tenant_id, template_id)
|
||||
template = self.get_template(tenant_id, template_id)
|
||||
if not template:
|
||||
return False
|
||||
|
||||
@@ -165,7 +165,7 @@ class QualityTemplateService:
|
||||
|
||||
return True
|
||||
|
||||
async def get_templates_for_stage(
|
||||
def get_templates_for_stage(
|
||||
self,
|
||||
tenant_id: str,
|
||||
stage: ProcessStage,
|
||||
@@ -198,14 +198,14 @@ class QualityTemplateService:
|
||||
QualityCheckTemplate.name
|
||||
).all()
|
||||
|
||||
async def duplicate_template(
|
||||
def duplicate_template(
|
||||
self,
|
||||
tenant_id: str,
|
||||
template_id: UUID
|
||||
) -> Optional[QualityCheckTemplate]:
|
||||
"""Duplicate an existing quality check template"""
|
||||
|
||||
original = await self.get_template(tenant_id, template_id)
|
||||
original = self.get_template(tenant_id, template_id)
|
||||
if not original:
|
||||
return None
|
||||
|
||||
@@ -234,9 +234,9 @@ class QualityTemplateService:
|
||||
}
|
||||
|
||||
create_data = QualityCheckTemplateCreate(**duplicate_data)
|
||||
return await self.create_template(tenant_id, create_data)
|
||||
return self.create_template(tenant_id, create_data)
|
||||
|
||||
async def get_templates_by_recipe_config(
|
||||
def get_templates_by_recipe_config(
|
||||
self,
|
||||
tenant_id: str,
|
||||
stage: ProcessStage,
|
||||
@@ -268,7 +268,7 @@ class QualityTemplateService:
|
||||
|
||||
return templates
|
||||
|
||||
async def validate_template_configuration(
|
||||
def validate_template_configuration(
|
||||
self,
|
||||
tenant_id: str,
|
||||
template_data: dict
|
||||
|
||||
Reference in New Issue
Block a user