Create new services: inventory, recipes, suppliers

This commit is contained in:
Urtzi Alfaro
2025-08-13 17:39:35 +02:00
parent fbe7470ad9
commit 16b8a9d50c
151 changed files with 35799 additions and 857 deletions

View File

@@ -0,0 +1,37 @@
# services/recipes/app/schemas/__init__.py
from .recipes import (
RecipeCreate,
RecipeUpdate,
RecipeResponse,
RecipeIngredientCreate,
RecipeIngredientResponse,
RecipeSearchRequest,
RecipeFeasibilityResponse
)
from .production import (
ProductionBatchCreate,
ProductionBatchUpdate,
ProductionBatchResponse,
ProductionIngredientConsumptionCreate,
ProductionIngredientConsumptionResponse,
ProductionScheduleCreate,
ProductionScheduleResponse
)
__all__ = [
"RecipeCreate",
"RecipeUpdate",
"RecipeResponse",
"RecipeIngredientCreate",
"RecipeIngredientResponse",
"RecipeSearchRequest",
"RecipeFeasibilityResponse",
"ProductionBatchCreate",
"ProductionBatchUpdate",
"ProductionBatchResponse",
"ProductionIngredientConsumptionCreate",
"ProductionIngredientConsumptionResponse",
"ProductionScheduleCreate",
"ProductionScheduleResponse"
]

View File

@@ -0,0 +1,257 @@
# services/recipes/app/schemas/production.py
"""
Pydantic schemas for production-related API requests and responses
"""
from pydantic import BaseModel, Field
from typing import List, Optional, Dict, Any
from uuid import UUID
from datetime import datetime, date
from enum import Enum
from ..models.recipes import ProductionStatus, ProductionPriority, MeasurementUnit
class ProductionIngredientConsumptionCreate(BaseModel):
"""Schema for creating production ingredient consumption"""
recipe_ingredient_id: UUID
ingredient_id: UUID
stock_id: Optional[UUID] = None
planned_quantity: float = Field(..., gt=0)
actual_quantity: float = Field(..., gt=0)
unit: MeasurementUnit
consumption_notes: Optional[str] = None
staff_member: Optional[UUID] = None
ingredient_condition: Optional[str] = None
quality_impact: Optional[str] = None
substitution_used: bool = False
substitution_details: Optional[str] = None
class ProductionIngredientConsumptionResponse(BaseModel):
"""Schema for production ingredient consumption responses"""
id: UUID
tenant_id: UUID
production_batch_id: UUID
recipe_ingredient_id: UUID
ingredient_id: UUID
stock_id: Optional[UUID] = None
planned_quantity: float
actual_quantity: float
unit: str
variance_quantity: Optional[float] = None
variance_percentage: Optional[float] = None
unit_cost: Optional[float] = None
total_cost: Optional[float] = None
consumption_time: datetime
consumption_notes: Optional[str] = None
staff_member: Optional[UUID] = None
ingredient_condition: Optional[str] = None
quality_impact: Optional[str] = None
substitution_used: bool
substitution_details: Optional[str] = None
class Config:
from_attributes = True
class ProductionBatchCreate(BaseModel):
"""Schema for creating production batches"""
recipe_id: UUID
batch_number: str = Field(..., min_length=1, max_length=100)
production_date: date
planned_start_time: Optional[datetime] = None
planned_end_time: Optional[datetime] = None
planned_quantity: float = Field(..., gt=0)
batch_size_multiplier: float = Field(default=1.0, gt=0)
priority: ProductionPriority = ProductionPriority.NORMAL
assigned_staff: Optional[List[UUID]] = None
production_notes: Optional[str] = None
customer_order_reference: Optional[str] = None
pre_order_quantity: Optional[float] = Field(None, ge=0)
shelf_quantity: Optional[float] = Field(None, ge=0)
class ProductionBatchUpdate(BaseModel):
"""Schema for updating production batches"""
batch_number: Optional[str] = Field(None, min_length=1, max_length=100)
production_date: Optional[date] = None
planned_start_time: Optional[datetime] = None
actual_start_time: Optional[datetime] = None
planned_end_time: Optional[datetime] = None
actual_end_time: Optional[datetime] = None
planned_quantity: Optional[float] = Field(None, gt=0)
actual_quantity: Optional[float] = Field(None, ge=0)
batch_size_multiplier: Optional[float] = Field(None, gt=0)
status: Optional[ProductionStatus] = None
priority: Optional[ProductionPriority] = None
assigned_staff: Optional[List[UUID]] = None
production_notes: Optional[str] = None
quality_score: Optional[float] = Field(None, ge=1, le=10)
quality_notes: Optional[str] = None
defect_rate: Optional[float] = Field(None, ge=0, le=100)
rework_required: Optional[bool] = None
labor_cost: Optional[float] = Field(None, ge=0)
overhead_cost: Optional[float] = Field(None, ge=0)
production_temperature: Optional[float] = None
production_humidity: Optional[float] = Field(None, ge=0, le=100)
oven_temperature: Optional[float] = None
baking_time_minutes: Optional[int] = Field(None, ge=0)
waste_quantity: Optional[float] = Field(None, ge=0)
waste_reason: Optional[str] = None
customer_order_reference: Optional[str] = None
pre_order_quantity: Optional[float] = Field(None, ge=0)
shelf_quantity: Optional[float] = Field(None, ge=0)
class ProductionBatchResponse(BaseModel):
"""Schema for production batch responses"""
id: UUID
tenant_id: UUID
recipe_id: UUID
batch_number: str
production_date: date
planned_start_time: Optional[datetime] = None
actual_start_time: Optional[datetime] = None
planned_end_time: Optional[datetime] = None
actual_end_time: Optional[datetime] = None
planned_quantity: float
actual_quantity: Optional[float] = None
yield_percentage: Optional[float] = None
batch_size_multiplier: float
status: str
priority: str
assigned_staff: Optional[List[UUID]] = None
production_notes: Optional[str] = None
quality_score: Optional[float] = None
quality_notes: Optional[str] = None
defect_rate: Optional[float] = None
rework_required: bool
planned_material_cost: Optional[float] = None
actual_material_cost: Optional[float] = None
labor_cost: Optional[float] = None
overhead_cost: Optional[float] = None
total_production_cost: Optional[float] = None
cost_per_unit: Optional[float] = None
production_temperature: Optional[float] = None
production_humidity: Optional[float] = None
oven_temperature: Optional[float] = None
baking_time_minutes: Optional[int] = None
waste_quantity: float
waste_reason: Optional[str] = None
efficiency_percentage: Optional[float] = None
customer_order_reference: Optional[str] = None
pre_order_quantity: Optional[float] = None
shelf_quantity: Optional[float] = None
created_at: datetime
updated_at: datetime
created_by: Optional[UUID] = None
completed_by: Optional[UUID] = None
ingredient_consumptions: Optional[List[ProductionIngredientConsumptionResponse]] = None
class Config:
from_attributes = True
class ProductionBatchSearchRequest(BaseModel):
"""Schema for production batch search requests"""
search_term: Optional[str] = None
status: Optional[ProductionStatus] = None
priority: Optional[ProductionPriority] = None
start_date: Optional[date] = None
end_date: Optional[date] = None
recipe_id: Optional[UUID] = None
limit: int = Field(default=100, ge=1, le=1000)
offset: int = Field(default=0, ge=0)
class ProductionScheduleCreate(BaseModel):
"""Schema for creating production schedules"""
schedule_date: date
schedule_name: Optional[str] = Field(None, max_length=255)
estimated_production_hours: Optional[float] = Field(None, gt=0)
estimated_material_cost: Optional[float] = Field(None, ge=0)
available_staff_hours: Optional[float] = Field(None, gt=0)
oven_capacity_hours: Optional[float] = Field(None, gt=0)
production_capacity_limit: Optional[float] = Field(None, gt=0)
schedule_notes: Optional[str] = None
preparation_instructions: Optional[str] = None
special_requirements: Optional[Dict[str, Any]] = None
class ProductionScheduleUpdate(BaseModel):
"""Schema for updating production schedules"""
schedule_name: Optional[str] = Field(None, max_length=255)
total_planned_batches: Optional[int] = Field(None, ge=0)
total_planned_items: Optional[float] = Field(None, ge=0)
estimated_production_hours: Optional[float] = Field(None, gt=0)
estimated_material_cost: Optional[float] = Field(None, ge=0)
is_published: Optional[bool] = None
is_completed: Optional[bool] = None
completion_percentage: Optional[float] = Field(None, ge=0, le=100)
available_staff_hours: Optional[float] = Field(None, gt=0)
oven_capacity_hours: Optional[float] = Field(None, gt=0)
production_capacity_limit: Optional[float] = Field(None, gt=0)
schedule_notes: Optional[str] = None
preparation_instructions: Optional[str] = None
special_requirements: Optional[Dict[str, Any]] = None
class ProductionScheduleResponse(BaseModel):
"""Schema for production schedule responses"""
id: UUID
tenant_id: UUID
schedule_date: date
schedule_name: Optional[str] = None
total_planned_batches: int
total_planned_items: float
estimated_production_hours: Optional[float] = None
estimated_material_cost: Optional[float] = None
is_published: bool
is_completed: bool
completion_percentage: Optional[float] = None
available_staff_hours: Optional[float] = None
oven_capacity_hours: Optional[float] = None
production_capacity_limit: Optional[float] = None
schedule_notes: Optional[str] = None
preparation_instructions: Optional[str] = None
special_requirements: Optional[Dict[str, Any]] = None
created_at: datetime
updated_at: datetime
created_by: Optional[UUID] = None
published_by: Optional[UUID] = None
published_at: Optional[datetime] = None
class Config:
from_attributes = True
class ProductionStatisticsResponse(BaseModel):
"""Schema for production statistics responses"""
total_batches: int
completed_batches: int
failed_batches: int
success_rate: float
average_yield_percentage: float
average_quality_score: float
total_production_cost: float
status_breakdown: List[Dict[str, Any]]
class StartProductionRequest(BaseModel):
"""Schema for starting production batch"""
staff_member: Optional[UUID] = None
production_notes: Optional[str] = None
ingredient_consumptions: List[ProductionIngredientConsumptionCreate]
class CompleteProductionRequest(BaseModel):
"""Schema for completing production batch"""
actual_quantity: float = Field(..., gt=0)
quality_score: Optional[float] = Field(None, ge=1, le=10)
quality_notes: Optional[str] = None
defect_rate: Optional[float] = Field(None, ge=0, le=100)
waste_quantity: Optional[float] = Field(None, ge=0)
waste_reason: Optional[str] = None
production_notes: Optional[str] = None
staff_member: Optional[UUID] = None

View File

@@ -0,0 +1,237 @@
# services/recipes/app/schemas/recipes.py
"""
Pydantic schemas for recipe-related API requests and responses
"""
from pydantic import BaseModel, Field
from typing import List, Optional, Dict, Any
from uuid import UUID
from datetime import datetime
from enum import Enum
from ..models.recipes import RecipeStatus, MeasurementUnit
class RecipeIngredientCreate(BaseModel):
"""Schema for creating recipe ingredients"""
ingredient_id: UUID
quantity: float = Field(..., gt=0)
unit: MeasurementUnit
alternative_quantity: Optional[float] = None
alternative_unit: Optional[MeasurementUnit] = None
preparation_method: Optional[str] = None
ingredient_notes: Optional[str] = None
is_optional: bool = False
ingredient_order: int = Field(..., ge=1)
ingredient_group: Optional[str] = None
substitution_options: Optional[Dict[str, Any]] = None
substitution_ratio: Optional[float] = None
class RecipeIngredientUpdate(BaseModel):
"""Schema for updating recipe ingredients"""
ingredient_id: Optional[UUID] = None
quantity: Optional[float] = Field(None, gt=0)
unit: Optional[MeasurementUnit] = None
alternative_quantity: Optional[float] = None
alternative_unit: Optional[MeasurementUnit] = None
preparation_method: Optional[str] = None
ingredient_notes: Optional[str] = None
is_optional: Optional[bool] = None
ingredient_order: Optional[int] = Field(None, ge=1)
ingredient_group: Optional[str] = None
substitution_options: Optional[Dict[str, Any]] = None
substitution_ratio: Optional[float] = None
class RecipeIngredientResponse(BaseModel):
"""Schema for recipe ingredient responses"""
id: UUID
tenant_id: UUID
recipe_id: UUID
ingredient_id: UUID
quantity: float
unit: str
quantity_in_base_unit: Optional[float] = None
alternative_quantity: Optional[float] = None
alternative_unit: Optional[str] = None
preparation_method: Optional[str] = None
ingredient_notes: Optional[str] = None
is_optional: bool
ingredient_order: int
ingredient_group: Optional[str] = None
substitution_options: Optional[Dict[str, Any]] = None
substitution_ratio: Optional[float] = None
unit_cost: Optional[float] = None
total_cost: Optional[float] = None
cost_updated_at: Optional[datetime] = None
class Config:
from_attributes = True
class RecipeCreate(BaseModel):
"""Schema for creating recipes"""
name: str = Field(..., min_length=1, max_length=255)
recipe_code: Optional[str] = Field(None, max_length=100)
version: str = Field(default="1.0", max_length=20)
finished_product_id: UUID
description: Optional[str] = None
category: Optional[str] = Field(None, max_length=100)
cuisine_type: Optional[str] = Field(None, max_length=100)
difficulty_level: int = Field(default=1, ge=1, le=5)
yield_quantity: float = Field(..., gt=0)
yield_unit: MeasurementUnit
prep_time_minutes: Optional[int] = Field(None, ge=0)
cook_time_minutes: Optional[int] = Field(None, ge=0)
total_time_minutes: Optional[int] = Field(None, ge=0)
rest_time_minutes: Optional[int] = Field(None, ge=0)
instructions: Optional[Dict[str, Any]] = None
preparation_notes: Optional[str] = None
storage_instructions: Optional[str] = None
quality_standards: Optional[str] = None
serves_count: Optional[int] = Field(None, ge=1)
nutritional_info: Optional[Dict[str, Any]] = None
allergen_info: Optional[Dict[str, Any]] = None
dietary_tags: Optional[Dict[str, Any]] = None
batch_size_multiplier: float = Field(default=1.0, gt=0)
minimum_batch_size: Optional[float] = Field(None, gt=0)
maximum_batch_size: Optional[float] = Field(None, gt=0)
optimal_production_temperature: Optional[float] = None
optimal_humidity: Optional[float] = Field(None, ge=0, le=100)
quality_check_points: Optional[Dict[str, Any]] = None
common_issues: Optional[Dict[str, Any]] = None
is_seasonal: bool = False
season_start_month: Optional[int] = Field(None, ge=1, le=12)
season_end_month: Optional[int] = Field(None, ge=1, le=12)
is_signature_item: bool = False
target_margin_percentage: Optional[float] = Field(None, ge=0)
ingredients: List[RecipeIngredientCreate] = Field(..., min_items=1)
class RecipeUpdate(BaseModel):
"""Schema for updating recipes"""
name: Optional[str] = Field(None, min_length=1, max_length=255)
recipe_code: Optional[str] = Field(None, max_length=100)
version: Optional[str] = Field(None, max_length=20)
description: Optional[str] = None
category: Optional[str] = Field(None, max_length=100)
cuisine_type: Optional[str] = Field(None, max_length=100)
difficulty_level: Optional[int] = Field(None, ge=1, le=5)
yield_quantity: Optional[float] = Field(None, gt=0)
yield_unit: Optional[MeasurementUnit] = None
prep_time_minutes: Optional[int] = Field(None, ge=0)
cook_time_minutes: Optional[int] = Field(None, ge=0)
total_time_minutes: Optional[int] = Field(None, ge=0)
rest_time_minutes: Optional[int] = Field(None, ge=0)
instructions: Optional[Dict[str, Any]] = None
preparation_notes: Optional[str] = None
storage_instructions: Optional[str] = None
quality_standards: Optional[str] = None
serves_count: Optional[int] = Field(None, ge=1)
nutritional_info: Optional[Dict[str, Any]] = None
allergen_info: Optional[Dict[str, Any]] = None
dietary_tags: Optional[Dict[str, Any]] = None
batch_size_multiplier: Optional[float] = Field(None, gt=0)
minimum_batch_size: Optional[float] = Field(None, gt=0)
maximum_batch_size: Optional[float] = Field(None, gt=0)
optimal_production_temperature: Optional[float] = None
optimal_humidity: Optional[float] = Field(None, ge=0, le=100)
quality_check_points: Optional[Dict[str, Any]] = None
common_issues: Optional[Dict[str, Any]] = None
status: Optional[RecipeStatus] = None
is_seasonal: Optional[bool] = None
season_start_month: Optional[int] = Field(None, ge=1, le=12)
season_end_month: Optional[int] = Field(None, ge=1, le=12)
is_signature_item: Optional[bool] = None
target_margin_percentage: Optional[float] = Field(None, ge=0)
ingredients: Optional[List[RecipeIngredientCreate]] = None
class RecipeResponse(BaseModel):
"""Schema for recipe responses"""
id: UUID
tenant_id: UUID
name: str
recipe_code: Optional[str] = None
version: str
finished_product_id: UUID
description: Optional[str] = None
category: Optional[str] = None
cuisine_type: Optional[str] = None
difficulty_level: int
yield_quantity: float
yield_unit: str
prep_time_minutes: Optional[int] = None
cook_time_minutes: Optional[int] = None
total_time_minutes: Optional[int] = None
rest_time_minutes: Optional[int] = None
estimated_cost_per_unit: Optional[float] = None
last_calculated_cost: Optional[float] = None
cost_calculation_date: Optional[datetime] = None
target_margin_percentage: Optional[float] = None
suggested_selling_price: Optional[float] = None
instructions: Optional[Dict[str, Any]] = None
preparation_notes: Optional[str] = None
storage_instructions: Optional[str] = None
quality_standards: Optional[str] = None
serves_count: Optional[int] = None
nutritional_info: Optional[Dict[str, Any]] = None
allergen_info: Optional[Dict[str, Any]] = None
dietary_tags: Optional[Dict[str, Any]] = None
batch_size_multiplier: float
minimum_batch_size: Optional[float] = None
maximum_batch_size: Optional[float] = None
optimal_production_temperature: Optional[float] = None
optimal_humidity: Optional[float] = None
quality_check_points: Optional[Dict[str, Any]] = None
common_issues: Optional[Dict[str, Any]] = None
status: str
is_seasonal: bool
season_start_month: Optional[int] = None
season_end_month: Optional[int] = None
is_signature_item: bool
created_at: datetime
updated_at: datetime
created_by: Optional[UUID] = None
updated_by: Optional[UUID] = None
ingredients: Optional[List[RecipeIngredientResponse]] = None
class Config:
from_attributes = True
class RecipeSearchRequest(BaseModel):
"""Schema for recipe search requests"""
search_term: Optional[str] = None
status: Optional[RecipeStatus] = None
category: Optional[str] = None
is_seasonal: Optional[bool] = None
is_signature: Optional[bool] = None
difficulty_level: Optional[int] = Field(None, ge=1, le=5)
limit: int = Field(default=100, ge=1, le=1000)
offset: int = Field(default=0, ge=0)
class RecipeDuplicateRequest(BaseModel):
"""Schema for recipe duplication requests"""
new_name: str = Field(..., min_length=1, max_length=255)
class RecipeFeasibilityResponse(BaseModel):
"""Schema for recipe feasibility check responses"""
recipe_id: UUID
recipe_name: str
batch_multiplier: float
feasible: bool
missing_ingredients: List[Dict[str, Any]] = []
insufficient_ingredients: List[Dict[str, Any]] = []
class RecipeStatisticsResponse(BaseModel):
"""Schema for recipe statistics responses"""
total_recipes: int
active_recipes: int
signature_recipes: int
seasonal_recipes: int
category_breakdown: List[Dict[str, Any]]