diff --git a/services/inventory/app/services/inventory_service.py b/services/inventory/app/services/inventory_service.py index 6b18a0cc..b70fc143 100644 --- a/services/inventory/app/services/inventory_service.py +++ b/services/inventory/app/services/inventory_service.py @@ -7,6 +7,7 @@ from typing import List, Optional, Dict, Any, Tuple from uuid import UUID from datetime import datetime, timedelta import structlog +import uuid from app.models.inventory import Ingredient, Stock, StockMovement, StockAlert, StockMovementType from app.repositories.ingredient_repository import IngredientRepository @@ -46,13 +47,18 @@ class InventoryService: async with get_db_transaction() as db: repository = IngredientRepository(db) - + + # Auto-generate SKU if not provided + if not ingredient_data.sku: + ingredient_data.sku = await self._generate_sku(db, tenant_id, ingredient_data.name) + logger.info("Auto-generated SKU", sku=ingredient_data.sku, name=ingredient_data.name) + # Check for duplicates if ingredient_data.sku: existing = await repository.get_by_sku(tenant_id, ingredient_data.sku) if existing: raise ValueError(f"Ingredient with SKU '{ingredient_data.sku}' already exists") - + if ingredient_data.barcode: existing = await repository.get_by_barcode(tenant_id, ingredient_data.barcode) if existing: @@ -1060,6 +1066,43 @@ class InventoryService: # ===== PRIVATE HELPER METHODS ===== + async def _generate_sku(self, db, tenant_id: UUID, product_name: str) -> str: + """ + Generate unique SKU for inventory item + Format: SKU-{PREFIX}-{SEQUENCE} + Example: SKU-FLO-0001 for Flour, SKU-BRE-0023 for Bread + + Following the same pattern as order number generation in orders service + """ + try: + from sqlalchemy import select, func + + # Extract prefix from product name (first 3 chars, uppercase) + prefix = product_name[:3].upper() if product_name and len(product_name) >= 3 else "ITM" + + # Count existing items with this SKU prefix for this tenant + # This ensures sequential numbering per prefix per tenant + stmt = select(func.count(Ingredient.id)).where( + Ingredient.tenant_id == tenant_id, + Ingredient.sku.like(f"SKU-{prefix}-%") + ) + result = await db.execute(stmt) + count = result.scalar() or 0 + + # Generate sequential number + sequence = count + 1 + sku = f"SKU-{prefix}-{sequence:04d}" + + logger.info("Generated SKU", sku=sku, prefix=prefix, sequence=sequence, tenant_id=tenant_id) + return sku + + except Exception as e: + logger.error("Error generating SKU, using fallback", error=str(e), product_name=product_name) + # Fallback to UUID-based SKU to ensure uniqueness + fallback_sku = f"SKU-{uuid.uuid4().hex[:8].upper()}" + logger.warning("Using fallback SKU", sku=fallback_sku) + return fallback_sku + async def _validate_ingredient_data(self, ingredient_data: IngredientCreate, tenant_id: UUID): """Validate ingredient data for business rules""" # Only validate reorder_point if both values are provided