Fix enum mismatch: Update Python enums and seed data to match database uppercase values
- Fixed ProductType enum values from lowercase to uppercase (INGREDIENT, FINISHED_PRODUCT) - Fixed UnitOfMeasure enum values from lowercase/abbreviated to uppercase (KILOGRAMS, LITERS, etc.) - Fixed IngredientCategory enum values from lowercase to uppercase (FLOUR, YEAST, etc.) - Fixed ProductCategory enum values from lowercase to uppercase (BREAD, CROISSANTS, etc.) - Updated seed data files to use correct uppercase enum values - Fixed hardcoded enum references throughout the codebase - This resolves the InvalidTextRepresentationError when inserting inventory data Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
@@ -26,8 +26,8 @@ class DashboardRepository:
|
||||
query = text("""
|
||||
SELECT
|
||||
COUNT(*) as total_ingredients,
|
||||
COUNT(CASE WHEN product_type::text = 'finished_product' THEN 1 END) as finished_products,
|
||||
COUNT(CASE WHEN product_type::text = 'ingredient' THEN 1 END) as raw_ingredients,
|
||||
COUNT(CASE WHEN product_type::text = 'FINISHED_PRODUCT' THEN 1 END) as finished_products,
|
||||
COUNT(CASE WHEN product_type::text = 'INGREDIENT' THEN 1 END) as raw_ingredients,
|
||||
COUNT(DISTINCT st.supplier_id) as supplier_count,
|
||||
AVG(CASE WHEN s.available_quantity IS NOT NULL THEN s.available_quantity ELSE 0 END) as avg_stock_level
|
||||
FROM ingredients i
|
||||
|
||||
@@ -40,7 +40,7 @@ class IngredientRepository(BaseRepository[Ingredient, IngredientCreate, Ingredie
|
||||
ingredient_name=create_data.get('name'),
|
||||
tenant_id=tenant_id
|
||||
)
|
||||
product_type_value = 'ingredient'
|
||||
product_type_value = 'INGREDIENT'
|
||||
|
||||
if 'product_type' in create_data:
|
||||
from app.models.inventory import ProductType
|
||||
@@ -73,7 +73,7 @@ class IngredientRepository(BaseRepository[Ingredient, IngredientCreate, Ingredie
|
||||
if 'category' in create_data:
|
||||
category_value = create_data.pop('category')
|
||||
|
||||
if product_type_value == 'finished_product' or product_type_value == 'FINISHED_PRODUCT':
|
||||
if product_type_value == 'FINISHED_PRODUCT':
|
||||
# Map to product_category for finished products
|
||||
from app.models.inventory import ProductCategory
|
||||
if category_value:
|
||||
@@ -166,15 +166,15 @@ class IngredientRepository(BaseRepository[Ingredient, IngredientCreate, Ingredie
|
||||
# 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')
|
||||
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'
|
||||
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':
|
||||
if product_type_value == 'FINISHED_PRODUCT':
|
||||
# Map to product_category for finished products
|
||||
from app.models.inventory import ProductCategory
|
||||
if category_value:
|
||||
@@ -559,4 +559,110 @@ class IngredientRepository(BaseRepository[Ingredient, IngredientCreate, Ingredie
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Failed to get active tenants from ingredients", error=str(e))
|
||||
return []
|
||||
return []
|
||||
|
||||
async def get_critical_stock_shortages(self) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Get critical stock shortages across all tenants using CTE analysis.
|
||||
Returns ingredients that are critically low on stock.
|
||||
"""
|
||||
try:
|
||||
from sqlalchemy import text
|
||||
query = text("""
|
||||
WITH stock_analysis AS (
|
||||
SELECT
|
||||
i.id as ingredient_id,
|
||||
i.name as ingredient_name,
|
||||
i.tenant_id,
|
||||
i.reorder_point,
|
||||
COALESCE(SUM(s.current_quantity), 0) as current_quantity,
|
||||
i.low_stock_threshold,
|
||||
GREATEST(0, i.low_stock_threshold - COALESCE(SUM(s.current_quantity), 0)) as shortage_amount,
|
||||
CASE
|
||||
WHEN COALESCE(SUM(s.current_quantity), 0) < i.low_stock_threshold THEN 'critical'
|
||||
WHEN COALESCE(SUM(s.current_quantity), 0) < i.low_stock_threshold * 1.2 THEN 'low'
|
||||
ELSE 'normal'
|
||||
END as status
|
||||
FROM ingredients i
|
||||
LEFT JOIN stock s ON s.ingredient_id = i.id AND s.is_available = true
|
||||
WHERE i.is_active = true
|
||||
GROUP BY i.id, i.name, i.tenant_id, i.reorder_point, i.low_stock_threshold
|
||||
)
|
||||
SELECT
|
||||
ingredient_id,
|
||||
ingredient_name,
|
||||
tenant_id,
|
||||
current_quantity,
|
||||
reorder_point,
|
||||
shortage_amount
|
||||
FROM stock_analysis
|
||||
WHERE status = 'critical'
|
||||
ORDER BY shortage_amount DESC
|
||||
""")
|
||||
|
||||
result = await self.session.execute(query)
|
||||
rows = result.fetchall()
|
||||
|
||||
shortages = []
|
||||
for row in rows:
|
||||
shortages.append({
|
||||
'ingredient_id': row.ingredient_id,
|
||||
'ingredient_name': row.ingredient_name,
|
||||
'tenant_id': row.tenant_id,
|
||||
'current_quantity': float(row.current_quantity) if row.current_quantity else 0,
|
||||
'required_quantity': float(row.reorder_point) if row.reorder_point else 0,
|
||||
'shortage_amount': float(row.shortage_amount) if row.shortage_amount else 0
|
||||
})
|
||||
|
||||
return shortages
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Failed to get critical stock shortages", error=str(e))
|
||||
raise
|
||||
|
||||
async def get_stock_issues(self, tenant_id: UUID) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Get stock level issues with CTE analysis for a specific tenant
|
||||
Returns list of critical, low, and overstock situations
|
||||
"""
|
||||
try:
|
||||
from sqlalchemy import text
|
||||
query = text("""
|
||||
WITH stock_analysis AS (
|
||||
SELECT
|
||||
i.id, i.name, i.tenant_id,
|
||||
COALESCE(SUM(s.current_quantity), 0) as current_stock,
|
||||
i.low_stock_threshold as minimum_stock,
|
||||
i.max_stock_level as maximum_stock,
|
||||
i.reorder_point,
|
||||
0 as tomorrow_needed,
|
||||
0 as avg_daily_usage,
|
||||
7 as lead_time_days,
|
||||
CASE
|
||||
WHEN COALESCE(SUM(s.current_quantity), 0) < i.low_stock_threshold THEN 'critical'
|
||||
WHEN COALESCE(SUM(s.current_quantity), 0) < i.low_stock_threshold * 1.2 THEN 'low'
|
||||
WHEN i.max_stock_level IS NOT NULL AND COALESCE(SUM(s.current_quantity), 0) > i.max_stock_level THEN 'overstock'
|
||||
ELSE 'normal'
|
||||
END as status,
|
||||
GREATEST(0, i.low_stock_threshold - COALESCE(SUM(s.current_quantity), 0)) as shortage_amount
|
||||
FROM ingredients i
|
||||
LEFT JOIN stock s ON s.ingredient_id = i.id AND s.is_available = true
|
||||
WHERE i.tenant_id = :tenant_id AND i.is_active = true
|
||||
GROUP BY i.id, i.name, i.tenant_id, i.low_stock_threshold, i.max_stock_level, i.reorder_point
|
||||
)
|
||||
SELECT * FROM stock_analysis WHERE status != 'normal'
|
||||
ORDER BY
|
||||
CASE status
|
||||
WHEN 'critical' THEN 1
|
||||
WHEN 'low' THEN 2
|
||||
WHEN 'overstock' THEN 3
|
||||
END,
|
||||
shortage_amount DESC
|
||||
""")
|
||||
|
||||
result = await self.session.execute(query, {"tenant_id": tenant_id})
|
||||
return [dict(row._mapping) for row in result.fetchall()]
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Failed to get stock issues", error=str(e), tenant_id=str(tenant_id))
|
||||
raise
|
||||
Reference in New Issue
Block a user