Improve the inventory page
This commit is contained in:
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user