Fix issues 4

This commit is contained in:
Urtzi Alfaro
2025-08-17 15:21:10 +02:00
parent cafd316c4b
commit f33f5d242a
6 changed files with 255 additions and 45 deletions

View File

@@ -30,24 +30,61 @@ class IngredientRepository(BaseRepository[Ingredient, IngredientCreate, Ingredie
create_data = ingredient_data.model_dump()
create_data['tenant_id'] = tenant_id
# Map 'category' from schema to appropriate model fields
# Handle product_type enum conversion
product_type_value = create_data.get('product_type', 'ingredient')
if 'product_type' in create_data:
from app.models.inventory import ProductType
try:
# Convert string to enum object
if isinstance(product_type_value, str):
for enum_member in ProductType:
if enum_member.value == product_type_value or enum_member.name == product_type_value:
create_data['product_type'] = enum_member
break
else:
# If not found, default to INGREDIENT
create_data['product_type'] = ProductType.INGREDIENT
# If it's already an enum, keep it
except Exception:
# Fallback to INGREDIENT if any issues
create_data['product_type'] = ProductType.INGREDIENT
# Handle category mapping based on product type
if 'category' in create_data:
category_value = create_data.pop('category')
# For now, assume all items are ingredients and map to ingredient_category
# Convert string to enum object
from app.models.inventory import IngredientCategory
try:
# Find the enum member by value
for enum_member in IngredientCategory:
if enum_member.value == category_value:
create_data['ingredient_category'] = enum_member
break
else:
# If not found, default to OTHER
create_data['ingredient_category'] = IngredientCategory.OTHER
except Exception:
# Fallback to OTHER if any issues
create_data['ingredient_category'] = IngredientCategory.OTHER
if product_type_value == 'finished_product' or product_type_value == 'FINISHED_PRODUCT':
# Map to product_category for finished products
from app.models.inventory import ProductCategory
if category_value:
try:
# Find the enum member by value
for enum_member in ProductCategory:
if enum_member.value == category_value:
create_data['product_category'] = enum_member
break
else:
# If not found, default to OTHER
create_data['product_category'] = ProductCategory.OTHER_PRODUCTS
except Exception:
# Fallback to OTHER if any issues
create_data['product_category'] = ProductCategory.OTHER_PRODUCTS
else:
# Map to ingredient_category for ingredients
from app.models.inventory import IngredientCategory
if category_value:
try:
# Find the enum member by value
for enum_member in IngredientCategory:
if enum_member.value == category_value:
create_data['ingredient_category'] = enum_member
break
else:
# If not found, default to OTHER
create_data['ingredient_category'] = IngredientCategory.OTHER
except Exception:
# Fallback to OTHER if any issues
create_data['ingredient_category'] = IngredientCategory.OTHER
# Convert unit_of_measure string to enum object
if 'unit_of_measure' in create_data:
@@ -81,6 +118,92 @@ class IngredientRepository(BaseRepository[Ingredient, IngredientCreate, Ingredie
logger.error("Failed to create ingredient", error=str(e), tenant_id=tenant_id)
raise
async def update(self, record_id: Any, obj_in: IngredientUpdate, **kwargs) -> Optional[Ingredient]:
"""Override update to handle product_type and category enum conversions"""
try:
# Prepare data and map schema fields to model fields
update_data = obj_in.model_dump(exclude_unset=True)
# Handle product_type enum conversion
if 'product_type' in update_data:
product_type_value = update_data['product_type']
from app.models.inventory import ProductType
try:
# Convert string to enum object
if isinstance(product_type_value, str):
for enum_member in ProductType:
if enum_member.value == product_type_value or enum_member.name == product_type_value:
update_data['product_type'] = enum_member
break
else:
# If not found, keep original value (don't update)
del update_data['product_type']
# If it's already an enum, keep it
except Exception:
# Remove invalid product_type to avoid update
del update_data['product_type']
# Handle category mapping based on product type
if 'category' in update_data:
category_value = update_data.pop('category')
product_type_value = update_data.get('product_type', 'ingredient')
# Get current product if we need to determine type
if 'product_type' not in update_data:
current_record = await self.get_by_id(record_id)
if current_record:
product_type_value = current_record.product_type.value if current_record.product_type else 'ingredient'
if product_type_value == 'finished_product' or product_type_value == 'FINISHED_PRODUCT':
# Map to product_category for finished products
from app.models.inventory import ProductCategory
if category_value:
try:
for enum_member in ProductCategory:
if enum_member.value == category_value:
update_data['product_category'] = enum_member
# Clear ingredient_category when setting product_category
update_data['ingredient_category'] = None
break
except Exception:
pass
else:
# Map to ingredient_category for ingredients
from app.models.inventory import IngredientCategory
if category_value:
try:
for enum_member in IngredientCategory:
if enum_member.value == category_value:
update_data['ingredient_category'] = enum_member
# Clear product_category when setting ingredient_category
update_data['product_category'] = None
break
except Exception:
pass
# Handle unit_of_measure enum conversion
if 'unit_of_measure' in update_data:
unit_value = update_data['unit_of_measure']
from app.models.inventory import UnitOfMeasure
try:
if isinstance(unit_value, str):
for enum_member in UnitOfMeasure:
if enum_member.value == unit_value:
update_data['unit_of_measure'] = enum_member
break
else:
# If not found, keep original value
del update_data['unit_of_measure']
except Exception:
del update_data['unit_of_measure']
# Call parent update method
return await super().update(record_id, update_data, **kwargs)
except Exception as e:
logger.error("Failed to update ingredient", error=str(e), record_id=record_id)
raise
async def get_ingredients_by_tenant(
self,
tenant_id: UUID,

View File

@@ -10,7 +10,7 @@ from pydantic import BaseModel, Field, validator
from typing import Generic, TypeVar
from enum import Enum
from app.models.inventory import UnitOfMeasure, IngredientCategory, StockMovementType
from app.models.inventory import UnitOfMeasure, IngredientCategory, StockMovementType, ProductType, ProductCategory
T = TypeVar('T')
@@ -32,11 +32,12 @@ class InventoryBaseSchema(BaseModel):
# ===== INGREDIENT SCHEMAS =====
class IngredientCreate(InventoryBaseSchema):
"""Schema for creating ingredients"""
name: str = Field(..., max_length=255, description="Ingredient name")
"""Schema for creating ingredients and finished products"""
name: str = Field(..., max_length=255, description="Product name")
product_type: ProductType = Field(ProductType.INGREDIENT, description="Type of product (ingredient or finished_product)")
sku: Optional[str] = Field(None, max_length=100, description="SKU code")
barcode: Optional[str] = Field(None, max_length=50, description="Barcode")
category: IngredientCategory = Field(..., description="Ingredient category")
category: Optional[str] = Field(None, description="Product category (ingredient or finished product category)")
subcategory: Optional[str] = Field(None, max_length=100, description="Subcategory")
description: Optional[str] = Field(None, description="Ingredient description")
brand: Optional[str] = Field(None, max_length=100, description="Brand name")
@@ -83,11 +84,12 @@ class IngredientCreate(InventoryBaseSchema):
class IngredientUpdate(InventoryBaseSchema):
"""Schema for updating ingredients"""
name: Optional[str] = Field(None, max_length=255, description="Ingredient name")
"""Schema for updating ingredients and finished products"""
name: Optional[str] = Field(None, max_length=255, description="Product name")
product_type: Optional[ProductType] = Field(None, description="Type of product (ingredient or finished_product)")
sku: Optional[str] = Field(None, max_length=100, description="SKU code")
barcode: Optional[str] = Field(None, max_length=50, description="Barcode")
category: Optional[IngredientCategory] = Field(None, description="Ingredient category")
category: Optional[str] = Field(None, description="Product category")
subcategory: Optional[str] = Field(None, max_length=100, description="Subcategory")
description: Optional[str] = Field(None, description="Ingredient description")
brand: Optional[str] = Field(None, max_length=100, description="Brand name")
@@ -122,13 +124,14 @@ class IngredientUpdate(InventoryBaseSchema):
class IngredientResponse(InventoryBaseSchema):
"""Schema for ingredient API responses"""
"""Schema for ingredient and finished product API responses"""
id: str
tenant_id: str
name: str
product_type: ProductType
sku: Optional[str]
barcode: Optional[str]
category: IngredientCategory
category: Optional[str] # Will be populated from ingredient_category or product_category
subcategory: Optional[str]
description: Optional[str]
brand: Optional[str]

View File

@@ -61,7 +61,15 @@ class InventoryService:
ingredient = await repository.create_ingredient(ingredient_data, tenant_id)
# Convert to response schema
response = IngredientResponse(**ingredient.to_dict())
ingredient_dict = ingredient.to_dict()
# Map category field based on product type
if ingredient.product_type and ingredient.product_type.value == 'finished_product':
ingredient_dict['category'] = ingredient.product_category.value if ingredient.product_category else None
else:
ingredient_dict['category'] = ingredient.ingredient_category.value if ingredient.ingredient_category else None
response = IngredientResponse(**ingredient_dict)
# Add computed fields
response.current_stock = 0.0
@@ -90,7 +98,15 @@ class InventoryService:
stock_totals = await stock_repo.get_total_stock_by_ingredient(tenant_id, ingredient_id)
# Convert to response schema
response = IngredientResponse(**ingredient.to_dict())
ingredient_dict = ingredient.to_dict()
# Map category field based on product type
if ingredient.product_type and ingredient.product_type.value == 'finished_product':
ingredient_dict['category'] = ingredient.product_category.value if ingredient.product_category else None
else:
ingredient_dict['category'] = ingredient.ingredient_category.value if ingredient.ingredient_category else None
response = IngredientResponse(**ingredient_dict)
response.current_stock = stock_totals['total_available']
response.is_low_stock = stock_totals['total_available'] <= ingredient.low_stock_threshold
response.needs_reorder = stock_totals['total_available'] <= ingredient.reorder_point
@@ -138,7 +154,15 @@ class InventoryService:
stock_totals = await stock_repo.get_total_stock_by_ingredient(tenant_id, ingredient_id)
# Convert to response schema
response = IngredientResponse(**updated_ingredient.to_dict())
ingredient_dict = updated_ingredient.to_dict()
# Map category field based on product type
if updated_ingredient.product_type and updated_ingredient.product_type.value == 'finished_product':
ingredient_dict['category'] = updated_ingredient.product_category.value if updated_ingredient.product_category else None
else:
ingredient_dict['category'] = updated_ingredient.ingredient_category.value if updated_ingredient.ingredient_category else None
response = IngredientResponse(**ingredient_dict)
response.current_stock = stock_totals['total_available']
response.is_low_stock = stock_totals['total_available'] <= updated_ingredient.low_stock_threshold
response.needs_reorder = stock_totals['total_available'] <= updated_ingredient.reorder_point
@@ -173,7 +197,15 @@ class InventoryService:
stock_totals = await stock_repo.get_total_stock_by_ingredient(tenant_id, ingredient.id)
# Convert to response schema
response = IngredientResponse(**ingredient.to_dict())
ingredient_dict = ingredient.to_dict()
# Map category field based on product type
if ingredient.product_type and ingredient.product_type.value == 'finished_product':
ingredient_dict['category'] = ingredient.product_category.value if ingredient.product_category else None
else:
ingredient_dict['category'] = ingredient.ingredient_category.value if ingredient.ingredient_category else None
response = IngredientResponse(**ingredient_dict)
response.current_stock = stock_totals['total_available']
response.is_low_stock = stock_totals['total_available'] <= ingredient.low_stock_threshold
response.needs_reorder = stock_totals['total_available'] <= ingredient.reorder_point

View File

@@ -627,13 +627,14 @@ class EnhancedTrainingService:
"status": status or "pending",
"progress": progress or 0,
"current_step": current_step or "initializing",
"start_time": datetime.utcnow()
"start_time": datetime.now(timezone.utc)
}
if error_message:
log_data["error_message"] = error_message
if results:
log_data["results"] = results
# Ensure results are JSON-serializable before storing
log_data["results"] = make_json_serializable(results)
await self.training_log_repo.create_training_log(log_data)
logger.info("Created initial training log", job_id=job_id, tenant_id=tenant_id)
@@ -655,9 +656,10 @@ class EnhancedTrainingService:
if error_message:
update_data["error_message"] = error_message
if results:
update_data["results"] = results
# Ensure results are JSON-serializable before storing
update_data["results"] = make_json_serializable(results)
if status in ["completed", "failed"]:
update_data["end_time"] = datetime.utcnow()
update_data["end_time"] = datetime.now(timezone.utc)
if update_data:
await self.training_log_repo.update(existing_log.id, update_data)