Improve the inventory page

This commit is contained in:
Urtzi Alfaro
2025-09-17 16:06:30 +02:00
parent 7aa26d51d3
commit dcb3ce441b
39 changed files with 5852 additions and 1762 deletions

View File

@@ -535,6 +535,185 @@ class InventoryService:
logger.error("Failed to get inventory summary", error=str(e), tenant_id=tenant_id)
raise
async def get_stock(
self,
tenant_id: UUID,
skip: int = 0,
limit: int = 100,
ingredient_id: Optional[UUID] = None,
available_only: bool = True
) -> List[StockResponse]:
"""Get stock entries with filtering"""
try:
async with get_db_transaction() as db:
stock_repo = StockRepository(db)
ingredient_repo = IngredientRepository(db)
# Get stock entries
stock_entries = await stock_repo.get_stock_entries(
tenant_id, skip, limit, ingredient_id, available_only
)
responses = []
for stock in stock_entries:
# Get ingredient information
ingredient = await ingredient_repo.get_by_id(stock.ingredient_id)
response = StockResponse(**stock.to_dict())
if ingredient:
response.ingredient = IngredientResponse(**ingredient.to_dict())
responses.append(response)
return responses
except Exception as e:
logger.error("Failed to get stock entries", error=str(e), tenant_id=tenant_id)
raise
# ===== DELETION METHODS =====
async def hard_delete_ingredient(
self,
ingredient_id: UUID,
tenant_id: UUID,
user_id: Optional[UUID] = None
) -> Dict[str, Any]:
"""
Completely delete an ingredient and all associated data.
This includes:
- All stock entries
- All stock movements
- All stock alerts
- The ingredient record itself
Returns a summary of what was deleted.
"""
try:
deletion_summary = {
"ingredient_id": str(ingredient_id),
"deleted_stock_entries": 0,
"deleted_stock_movements": 0,
"deleted_stock_alerts": 0,
"ingredient_name": None,
"success": False
}
async with get_db_transaction() as db:
ingredient_repo = IngredientRepository(db)
stock_repo = StockRepository(db)
movement_repo = StockMovementRepository(db)
# 1. Verify ingredient exists and belongs to tenant
ingredient = await ingredient_repo.get_by_id(ingredient_id)
if not ingredient or ingredient.tenant_id != tenant_id:
raise ValueError(f"Ingredient {ingredient_id} not found or access denied")
deletion_summary["ingredient_name"] = ingredient.name
logger.info(
"Starting hard deletion of ingredient",
ingredient_id=str(ingredient_id),
ingredient_name=ingredient.name,
tenant_id=str(tenant_id)
)
# 2. Delete all stock movements first (due to foreign key constraints)
try:
deleted_movements = await movement_repo.delete_by_ingredient(ingredient_id, tenant_id)
deletion_summary["deleted_stock_movements"] = deleted_movements
logger.info(f"Deleted {deleted_movements} stock movements")
except Exception as e:
logger.warning(f"Error deleting stock movements: {str(e)}")
# Continue with deletion even if this fails
# 3. Delete all stock entries
try:
deleted_stock = await stock_repo.delete_by_ingredient(ingredient_id, tenant_id)
deletion_summary["deleted_stock_entries"] = deleted_stock
logger.info(f"Deleted {deleted_stock} stock entries")
except Exception as e:
logger.warning(f"Error deleting stock entries: {str(e)}")
# Continue with deletion even if this fails
# 4. Delete stock alerts if they exist
try:
# Note: StockAlert deletion would go here if that table exists
# For now, we'll assume this is handled by cascading deletes or doesn't exist
deletion_summary["deleted_stock_alerts"] = 0
except Exception as e:
logger.warning(f"Error deleting stock alerts: {str(e)}")
# 5. Finally, delete the ingredient itself
deleted_ingredient = await ingredient_repo.delete_by_id(ingredient_id, tenant_id)
if not deleted_ingredient:
raise ValueError("Failed to delete ingredient record")
deletion_summary["success"] = True
logger.info(
"Successfully completed hard deletion of ingredient",
ingredient_id=str(ingredient_id),
ingredient_name=ingredient.name,
summary=deletion_summary
)
return deletion_summary
except ValueError:
# Re-raise validation errors
raise
except Exception as e:
logger.error(
"Failed to hard delete ingredient",
ingredient_id=str(ingredient_id),
tenant_id=str(tenant_id),
error=str(e)
)
raise
async def soft_delete_ingredient(
self,
ingredient_id: UUID,
tenant_id: UUID,
user_id: Optional[UUID] = None
) -> IngredientResponse:
"""
Soft delete an ingredient (mark as inactive).
This preserves all associated data for reporting and audit purposes.
"""
try:
async with get_db_transaction() as db:
ingredient_repo = IngredientRepository(db)
# Verify ingredient exists and belongs to tenant
ingredient = await ingredient_repo.get_by_id(ingredient_id)
if not ingredient or ingredient.tenant_id != tenant_id:
raise ValueError(f"Ingredient {ingredient_id} not found or access denied")
# Mark as inactive
update_data = IngredientUpdate(is_active=False)
updated_ingredient = await ingredient_repo.update_ingredient(ingredient_id, update_data)
logger.info(
"Soft deleted ingredient",
ingredient_id=str(ingredient_id),
ingredient_name=ingredient.name,
tenant_id=str(tenant_id)
)
return IngredientResponse(**updated_ingredient.to_dict())
except ValueError:
raise
except Exception as e:
logger.error(
"Failed to soft delete ingredient",
ingredient_id=str(ingredient_id),
tenant_id=str(tenant_id),
error=str(e)
)
raise
# ===== PRIVATE HELPER METHODS =====
async def _validate_ingredient_data(self, ingredient_data: IngredientCreate, tenant_id: UUID):