Fix some UI issues 2
This commit is contained in:
@@ -93,13 +93,17 @@ class ProductionService:
|
||||
batch_dict = batch_data.model_dump()
|
||||
batch_dict["tenant_id"] = tenant_id
|
||||
|
||||
# Validate recipe exists if provided
|
||||
# Validate recipe exists and get quality configuration
|
||||
recipe_quality_config = None
|
||||
if batch_data.recipe_id:
|
||||
recipe_details = await self.recipes_client.get_recipe_by_id(
|
||||
str(tenant_id), str(batch_data.recipe_id)
|
||||
)
|
||||
if not recipe_details:
|
||||
raise ValueError(f"Recipe {batch_data.recipe_id} not found")
|
||||
|
||||
# Extract quality configuration from recipe
|
||||
recipe_quality_config = recipe_details.get("quality_check_configuration")
|
||||
|
||||
# Check ingredient availability
|
||||
if batch_data.recipe_id:
|
||||
@@ -118,10 +122,15 @@ class ProductionService:
|
||||
|
||||
# Create the batch
|
||||
batch = await batch_repo.create_batch(batch_dict)
|
||||
|
||||
logger.info("Production batch created",
|
||||
batch_id=str(batch.id), tenant_id=str(tenant_id))
|
||||
|
||||
|
||||
# Inherit quality templates from recipe if configured
|
||||
if recipe_quality_config and recipe_quality_config.get("auto_create_quality_checks", True):
|
||||
await self._setup_batch_quality_checks(session, batch, recipe_quality_config, tenant_id)
|
||||
|
||||
logger.info("Production batch created with quality inheritance",
|
||||
batch_id=str(batch.id), tenant_id=str(tenant_id),
|
||||
has_quality_config=bool(recipe_quality_config))
|
||||
|
||||
return batch
|
||||
|
||||
except Exception as e:
|
||||
@@ -129,6 +138,132 @@ class ProductionService:
|
||||
error=str(e), tenant_id=str(tenant_id))
|
||||
raise
|
||||
|
||||
async def _setup_batch_quality_checks(
|
||||
self,
|
||||
session,
|
||||
batch: ProductionBatch,
|
||||
quality_config: Dict[str, Any],
|
||||
tenant_id: UUID
|
||||
):
|
||||
"""Set up quality checks for a production batch based on recipe configuration"""
|
||||
try:
|
||||
# Initialize pending and completed quality checks structures
|
||||
pending_quality_checks = {}
|
||||
completed_quality_checks = {}
|
||||
|
||||
# Process each stage configuration
|
||||
stages = quality_config.get("stages", {})
|
||||
for stage_name, stage_config in stages.items():
|
||||
template_ids = stage_config.get("template_ids", [])
|
||||
required_checks = stage_config.get("required_checks", [])
|
||||
optional_checks = stage_config.get("optional_checks", [])
|
||||
min_quality_score = stage_config.get("min_quality_score")
|
||||
blocking_on_failure = stage_config.get("blocking_on_failure", True)
|
||||
|
||||
# Set up pending checks for this stage
|
||||
if template_ids or required_checks or optional_checks:
|
||||
pending_quality_checks[stage_name] = {
|
||||
"template_ids": [str(tid) for tid in template_ids],
|
||||
"required_checks": required_checks,
|
||||
"optional_checks": optional_checks,
|
||||
"min_quality_score": min_quality_score,
|
||||
"blocking_on_failure": blocking_on_failure,
|
||||
"status": "pending"
|
||||
}
|
||||
|
||||
# Initialize completed structure for this stage
|
||||
completed_quality_checks[stage_name] = {
|
||||
"checks": [],
|
||||
"overall_score": None,
|
||||
"passed": None,
|
||||
"completed_at": None
|
||||
}
|
||||
|
||||
# Update batch with quality check configuration
|
||||
batch.pending_quality_checks = pending_quality_checks
|
||||
batch.completed_quality_checks = completed_quality_checks
|
||||
|
||||
# Save the updated batch
|
||||
await session.commit()
|
||||
|
||||
logger.info("Quality checks setup completed for batch",
|
||||
batch_id=str(batch.id),
|
||||
stages_configured=list(stages.keys()),
|
||||
total_templates=sum(len(stage.get("template_ids", [])) for stage in stages.values()))
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Error setting up batch quality checks",
|
||||
error=str(e), batch_id=str(batch.id))
|
||||
raise
|
||||
|
||||
async def update_batch_stage_with_quality_checks(
|
||||
self,
|
||||
tenant_id: UUID,
|
||||
batch_id: UUID,
|
||||
new_stage: str
|
||||
) -> ProductionBatch:
|
||||
"""Update batch stage and create required quality checks"""
|
||||
try:
|
||||
async with self.database_manager.get_session() as session:
|
||||
batch_repo = ProductionBatchRepository(session)
|
||||
|
||||
# Get the batch
|
||||
batch = await batch_repo.get_batch(tenant_id, batch_id)
|
||||
if not batch:
|
||||
raise ValueError(f"Batch {batch_id} not found")
|
||||
|
||||
# Update current stage
|
||||
old_stage = batch.current_process_stage
|
||||
batch.current_process_stage = new_stage
|
||||
|
||||
# Check if there are pending quality checks for this stage
|
||||
pending_checks = batch.pending_quality_checks or {}
|
||||
stage_checks = pending_checks.get(new_stage)
|
||||
|
||||
if stage_checks and stage_checks.get("template_ids"):
|
||||
# Create quality check records from templates
|
||||
from app.repositories.quality_template_repository import QualityTemplateRepository
|
||||
|
||||
template_repo = QualityTemplateRepository(session)
|
||||
quality_repo = QualityCheckRepository(session)
|
||||
|
||||
template_ids = [UUID(tid) for tid in stage_checks["template_ids"]]
|
||||
templates = await template_repo.get_templates_by_ids(str(tenant_id), template_ids)
|
||||
|
||||
# Create quality checks for each template
|
||||
for template in templates:
|
||||
quality_check_data = {
|
||||
"tenant_id": tenant_id,
|
||||
"batch_id": batch_id,
|
||||
"template_id": template.id,
|
||||
"check_type": template.check_type,
|
||||
"process_stage": new_stage,
|
||||
"check_time": datetime.utcnow(),
|
||||
"quality_score": 0.0, # To be filled when check is performed
|
||||
"pass_fail": False, # To be updated when check is performed
|
||||
"defect_count": 0,
|
||||
"target_weight": template.target_value,
|
||||
"target_temperature": template.target_value if template.check_type == "temperature" else None,
|
||||
"tolerance_percentage": template.tolerance_percentage
|
||||
}
|
||||
|
||||
await quality_repo.create_quality_check(quality_check_data)
|
||||
|
||||
logger.info("Created quality checks for batch stage transition",
|
||||
batch_id=str(batch_id),
|
||||
stage=new_stage,
|
||||
checks_created=len(templates))
|
||||
|
||||
# Save batch changes
|
||||
await session.commit()
|
||||
|
||||
return batch
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Error updating batch stage with quality checks",
|
||||
error=str(e), batch_id=str(batch_id), new_stage=new_stage)
|
||||
raise
|
||||
|
||||
async def get_production_batches_list(
|
||||
self,
|
||||
tenant_id: UUID,
|
||||
|
||||
@@ -18,7 +18,9 @@ from ..schemas.recipes import (
|
||||
RecipeSearchRequest,
|
||||
RecipeDuplicateRequest,
|
||||
RecipeFeasibilityResponse,
|
||||
RecipeStatisticsResponse
|
||||
RecipeStatisticsResponse,
|
||||
RecipeQualityConfiguration,
|
||||
RecipeQualityConfigurationUpdate
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -350,4 +352,136 @@ async def get_recipe_categories(
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting recipe categories: {e}")
|
||||
raise HTTPException(status_code=500, detail="Internal server error")
|
||||
|
||||
|
||||
# Quality Configuration Endpoints
|
||||
|
||||
@router.get("/{tenant_id}/recipes/{recipe_id}/quality-configuration", response_model=RecipeQualityConfiguration)
|
||||
async def get_recipe_quality_configuration(
|
||||
tenant_id: UUID,
|
||||
recipe_id: UUID,
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Get quality configuration for a specific recipe"""
|
||||
try:
|
||||
recipe_service = RecipeService(db)
|
||||
|
||||
# Get recipe with quality configuration
|
||||
recipe = await recipe_service.get_recipe(tenant_id, recipe_id)
|
||||
if not recipe:
|
||||
raise HTTPException(status_code=404, detail="Recipe not found")
|
||||
|
||||
# Return quality configuration or default structure
|
||||
quality_config = recipe.get("quality_check_configuration")
|
||||
if not quality_config:
|
||||
quality_config = {
|
||||
"stages": {},
|
||||
"overall_quality_threshold": 7.0,
|
||||
"critical_stage_blocking": True,
|
||||
"auto_create_quality_checks": True,
|
||||
"quality_manager_approval_required": False
|
||||
}
|
||||
|
||||
return quality_config
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting recipe quality configuration: {e}")
|
||||
raise HTTPException(status_code=500, detail="Internal server error")
|
||||
|
||||
|
||||
@router.put("/{tenant_id}/recipes/{recipe_id}/quality-configuration", response_model=RecipeQualityConfiguration)
|
||||
async def update_recipe_quality_configuration(
|
||||
tenant_id: UUID,
|
||||
recipe_id: UUID,
|
||||
quality_config: RecipeQualityConfigurationUpdate,
|
||||
user_id: UUID = Depends(get_user_id),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Update quality configuration for a specific recipe"""
|
||||
try:
|
||||
recipe_service = RecipeService(db)
|
||||
|
||||
# Verify recipe exists and belongs to tenant
|
||||
recipe = await recipe_service.get_recipe(tenant_id, recipe_id)
|
||||
if not recipe:
|
||||
raise HTTPException(status_code=404, detail="Recipe not found")
|
||||
|
||||
# Update recipe with quality configuration
|
||||
updated_recipe = await recipe_service.update_recipe_quality_configuration(
|
||||
tenant_id, recipe_id, quality_config.dict(exclude_unset=True), user_id
|
||||
)
|
||||
|
||||
return updated_recipe["quality_check_configuration"]
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Error updating recipe quality configuration: {e}")
|
||||
raise HTTPException(status_code=500, detail="Internal server error")
|
||||
|
||||
|
||||
@router.post("/{tenant_id}/recipes/{recipe_id}/quality-configuration/stages/{stage}/templates")
|
||||
async def add_quality_templates_to_stage(
|
||||
tenant_id: UUID,
|
||||
recipe_id: UUID,
|
||||
stage: str,
|
||||
template_ids: List[UUID],
|
||||
user_id: UUID = Depends(get_user_id),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Add quality templates to a specific recipe stage"""
|
||||
try:
|
||||
recipe_service = RecipeService(db)
|
||||
|
||||
# Verify recipe exists
|
||||
recipe = await recipe_service.get_recipe(tenant_id, recipe_id)
|
||||
if not recipe:
|
||||
raise HTTPException(status_code=404, detail="Recipe not found")
|
||||
|
||||
# Add templates to stage
|
||||
await recipe_service.add_quality_templates_to_stage(
|
||||
tenant_id, recipe_id, stage, template_ids, user_id
|
||||
)
|
||||
|
||||
return {"message": f"Added {len(template_ids)} templates to {stage} stage"}
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Error adding quality templates to recipe stage: {e}")
|
||||
raise HTTPException(status_code=500, detail="Internal server error")
|
||||
|
||||
|
||||
@router.delete("/{tenant_id}/recipes/{recipe_id}/quality-configuration/stages/{stage}/templates/{template_id}")
|
||||
async def remove_quality_template_from_stage(
|
||||
tenant_id: UUID,
|
||||
recipe_id: UUID,
|
||||
stage: str,
|
||||
template_id: UUID,
|
||||
user_id: UUID = Depends(get_user_id),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Remove a quality template from a specific recipe stage"""
|
||||
try:
|
||||
recipe_service = RecipeService(db)
|
||||
|
||||
# Verify recipe exists
|
||||
recipe = await recipe_service.get_recipe(tenant_id, recipe_id)
|
||||
if not recipe:
|
||||
raise HTTPException(status_code=404, detail="Recipe not found")
|
||||
|
||||
# Remove template from stage
|
||||
await recipe_service.remove_quality_template_from_stage(
|
||||
tenant_id, recipe_id, stage, template_id, user_id
|
||||
)
|
||||
|
||||
return {"message": f"Removed template from {stage} stage"}
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Error removing quality template from recipe stage: {e}")
|
||||
raise HTTPException(status_code=500, detail="Internal server error")
|
||||
@@ -12,6 +12,34 @@ from enum import Enum
|
||||
from ..models.recipes import RecipeStatus, MeasurementUnit
|
||||
|
||||
|
||||
# Quality Template Association Schemas
|
||||
class QualityStageConfiguration(BaseModel):
|
||||
"""Schema for quality checks configuration per production stage"""
|
||||
template_ids: List[UUID] = Field(default_factory=list, description="Quality template IDs for this stage")
|
||||
required_checks: List[str] = Field(default_factory=list, description="Required quality check types")
|
||||
optional_checks: List[str] = Field(default_factory=list, description="Optional quality check types")
|
||||
blocking_on_failure: bool = Field(default=True, description="Block stage progression on critical failures")
|
||||
min_quality_score: Optional[float] = Field(None, ge=0, le=10, description="Minimum quality score to pass stage")
|
||||
|
||||
|
||||
class RecipeQualityConfiguration(BaseModel):
|
||||
"""Schema for recipe quality configuration across all stages"""
|
||||
stages: Dict[str, QualityStageConfiguration] = Field(default_factory=dict, description="Quality configuration per stage")
|
||||
overall_quality_threshold: float = Field(default=7.0, ge=0, le=10, description="Overall quality threshold for batch")
|
||||
critical_stage_blocking: bool = Field(default=True, description="Block progression if critical checks fail")
|
||||
auto_create_quality_checks: bool = Field(default=True, description="Automatically create quality checks for batches")
|
||||
quality_manager_approval_required: bool = Field(default=False, description="Require quality manager approval")
|
||||
|
||||
|
||||
class RecipeQualityConfigurationUpdate(BaseModel):
|
||||
"""Schema for updating recipe quality configuration"""
|
||||
stages: Optional[Dict[str, QualityStageConfiguration]] = None
|
||||
overall_quality_threshold: Optional[float] = Field(None, ge=0, le=10)
|
||||
critical_stage_blocking: Optional[bool] = None
|
||||
auto_create_quality_checks: Optional[bool] = None
|
||||
quality_manager_approval_required: Optional[bool] = None
|
||||
|
||||
|
||||
class RecipeIngredientCreate(BaseModel):
|
||||
"""Schema for creating recipe ingredients"""
|
||||
ingredient_id: UUID
|
||||
@@ -90,6 +118,7 @@ class RecipeCreate(BaseModel):
|
||||
preparation_notes: Optional[str] = None
|
||||
storage_instructions: Optional[str] = None
|
||||
quality_standards: Optional[str] = None
|
||||
quality_check_configuration: Optional[RecipeQualityConfiguration] = None
|
||||
serves_count: Optional[int] = Field(None, ge=1)
|
||||
nutritional_info: Optional[Dict[str, Any]] = None
|
||||
allergen_info: Optional[Dict[str, Any]] = None
|
||||
@@ -128,6 +157,7 @@ class RecipeUpdate(BaseModel):
|
||||
preparation_notes: Optional[str] = None
|
||||
storage_instructions: Optional[str] = None
|
||||
quality_standards: Optional[str] = None
|
||||
quality_check_configuration: Optional[RecipeQualityConfigurationUpdate] = None
|
||||
serves_count: Optional[int] = Field(None, ge=1)
|
||||
nutritional_info: Optional[Dict[str, Any]] = None
|
||||
allergen_info: Optional[Dict[str, Any]] = None
|
||||
@@ -175,6 +205,7 @@ class RecipeResponse(BaseModel):
|
||||
preparation_notes: Optional[str] = None
|
||||
storage_instructions: Optional[str] = None
|
||||
quality_standards: Optional[str] = None
|
||||
quality_check_configuration: Optional[RecipeQualityConfiguration] = None
|
||||
serves_count: Optional[int] = None
|
||||
nutritional_info: Optional[Dict[str, Any]] = None
|
||||
allergen_info: Optional[Dict[str, Any]] = None
|
||||
|
||||
@@ -261,4 +261,125 @@ class RecipeService:
|
||||
return {
|
||||
"success": False,
|
||||
"error": str(e)
|
||||
}
|
||||
}
|
||||
|
||||
# Quality Configuration Methods
|
||||
|
||||
async def update_recipe_quality_configuration(
|
||||
self,
|
||||
tenant_id: UUID,
|
||||
recipe_id: UUID,
|
||||
quality_config_update: Dict[str, Any],
|
||||
user_id: UUID
|
||||
) -> Dict[str, Any]:
|
||||
"""Update quality configuration for a recipe"""
|
||||
try:
|
||||
# Get current recipe
|
||||
recipe = await self.recipe_repo.get_recipe(tenant_id, recipe_id)
|
||||
if not recipe:
|
||||
raise ValueError("Recipe not found")
|
||||
|
||||
# Get existing quality configuration or create default
|
||||
current_config = recipe.get("quality_check_configuration", {
|
||||
"stages": {},
|
||||
"overall_quality_threshold": 7.0,
|
||||
"critical_stage_blocking": True,
|
||||
"auto_create_quality_checks": True,
|
||||
"quality_manager_approval_required": False
|
||||
})
|
||||
|
||||
# Merge with updates
|
||||
if "stages" in quality_config_update:
|
||||
current_config["stages"].update(quality_config_update["stages"])
|
||||
|
||||
for key in ["overall_quality_threshold", "critical_stage_blocking",
|
||||
"auto_create_quality_checks", "quality_manager_approval_required"]:
|
||||
if key in quality_config_update:
|
||||
current_config[key] = quality_config_update[key]
|
||||
|
||||
# Update recipe with new configuration
|
||||
recipe_update = RecipeUpdate(quality_check_configuration=current_config)
|
||||
await self.recipe_repo.update_recipe(tenant_id, recipe_id, recipe_update, user_id)
|
||||
|
||||
# Return updated recipe
|
||||
updated_recipe = await self.recipe_repo.get_recipe(tenant_id, recipe_id)
|
||||
return updated_recipe
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error updating recipe quality configuration: {e}")
|
||||
raise
|
||||
|
||||
async def add_quality_templates_to_stage(
|
||||
self,
|
||||
tenant_id: UUID,
|
||||
recipe_id: UUID,
|
||||
stage: str,
|
||||
template_ids: List[UUID],
|
||||
user_id: UUID
|
||||
):
|
||||
"""Add quality templates to a specific recipe stage"""
|
||||
try:
|
||||
# Get current recipe
|
||||
recipe = await self.recipe_repo.get_recipe(tenant_id, recipe_id)
|
||||
if not recipe:
|
||||
raise ValueError("Recipe not found")
|
||||
|
||||
# Get existing quality configuration
|
||||
quality_config = recipe.get("quality_check_configuration", {"stages": {}})
|
||||
|
||||
# Initialize stage if it doesn't exist
|
||||
if stage not in quality_config["stages"]:
|
||||
quality_config["stages"][stage] = {
|
||||
"template_ids": [],
|
||||
"required_checks": [],
|
||||
"optional_checks": [],
|
||||
"blocking_on_failure": True,
|
||||
"min_quality_score": None
|
||||
}
|
||||
|
||||
# Add template IDs (avoid duplicates)
|
||||
stage_config = quality_config["stages"][stage]
|
||||
existing_ids = set(stage_config.get("template_ids", []))
|
||||
new_ids = [str(tid) for tid in template_ids if str(tid) not in existing_ids]
|
||||
stage_config["template_ids"].extend(new_ids)
|
||||
|
||||
# Update recipe
|
||||
recipe_update = RecipeUpdate(quality_check_configuration=quality_config)
|
||||
await self.recipe_repo.update_recipe(tenant_id, recipe_id, recipe_update, user_id)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error adding quality templates to stage: {e}")
|
||||
raise
|
||||
|
||||
async def remove_quality_template_from_stage(
|
||||
self,
|
||||
tenant_id: UUID,
|
||||
recipe_id: UUID,
|
||||
stage: str,
|
||||
template_id: UUID,
|
||||
user_id: UUID
|
||||
):
|
||||
"""Remove a quality template from a specific recipe stage"""
|
||||
try:
|
||||
# Get current recipe
|
||||
recipe = await self.recipe_repo.get_recipe(tenant_id, recipe_id)
|
||||
if not recipe:
|
||||
raise ValueError("Recipe not found")
|
||||
|
||||
# Get existing quality configuration
|
||||
quality_config = recipe.get("quality_check_configuration", {"stages": {}})
|
||||
|
||||
# Remove template ID from stage
|
||||
if stage in quality_config["stages"]:
|
||||
stage_config = quality_config["stages"][stage]
|
||||
template_ids = stage_config.get("template_ids", [])
|
||||
template_ids = [tid for tid in template_ids if str(tid) != str(template_id)]
|
||||
stage_config["template_ids"] = template_ids
|
||||
|
||||
# Update recipe
|
||||
recipe_update = RecipeUpdate(quality_check_configuration=quality_config)
|
||||
await self.recipe_repo.update_recipe(tenant_id, recipe_id, recipe_update, user_id)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error removing quality template from stage: {e}")
|
||||
raise
|
||||
Reference in New Issue
Block a user