# Inventory Service ## Overview The **Inventory Service** is the operational backbone of Bakery-IA, managing ingredient tracking, stock levels, expiration dates, and food safety compliance. It implements FIFO (First-In-First-Out) consumption logic, automated low-stock alerts, and HACCP-compliant temperature monitoring. This service is critical for achieving zero food waste, maintaining food safety standards, and ensuring bakeries never run out of essential ingredients. ## Key Features ### Comprehensive Ingredient Management - **Ingredient Catalog** - Complete database of all ingredients with categories - **Stock Tracking** - Real-time stock levels with FIFO consumption - **Batch Tracking** - Lot numbers and traceability for food safety - **Expiration Management** - Automated expiry alerts and FIFO rotation - **Low Stock Alerts** - Configurable threshold notifications - **Barcode Support** - Barcode scanning for quick stock updates - **Multi-Location** - Track inventory across multiple storage locations ### Stock Movement Tracking - **In/Out Transactions** - Complete audit trail of stock movements - **Product Transformations** - Track ingredient consumption in production - **Adjustment Logging** - Record inventory adjustments with reasons - **Historical Analysis** - Analyze consumption patterns over time - **Waste Tracking** - Monitor and categorize waste (expired, damaged, etc.) ### Automatic Stock Updates from Deliveries (🆕) - **Event-Driven Stock Updates** - Automatically updates stock when deliveries are received from procurement - **Batch & Expiry Tracking** - Records batch/lot numbers and expiration dates from delivery receipts - **Audit Trail** - Creates complete stock entry records for each delivery - **Zero Manual Entry** - Eliminates manual stock entry after deliveries - **Real-Time Synchronization** - Stock levels update immediately when deliveries are recorded ### Food Safety Compliance (HACCP) - **Temperature Monitoring** - Critical control point temperature logs - **Food Safety Alerts** - Automated safety notifications - **Compliance Tracking** - HACCP compliance audit trail - **Expiry Management** - Prevent use of expired ingredients - **Lot Traceability** - Complete ingredient traceability - **Safety Checklists** - Digital food safety inspection forms ### Sustainability & Reporting - **Waste Reduction Tracking** - Monitor progress toward zero waste - **Environmental Impact** - Carbon footprint and sustainability metrics - **SDG Compliance** - Sustainable Development Goals reporting - **Grant Reporting** - EU grant compliance reports - **Business Model Detection** - Auto-detect B2B/B2C inventory patterns ### Dashboard & Analytics - **Real-Time KPIs** - Current stock levels, expiring items, low stock warnings - **Consumption Analytics** - Usage patterns and forecasting input - **Valuation Reports** - Current inventory value and cost tracking - **Reorder Recommendations** - Intelligent reorder point suggestions - **Expiry Calendar** - Visual timeline of expiring products ## Business Value ### For Bakery Owners - **Zero Food Waste Goal** - Reduce waste 20-40% through expiry management and FIFO - **Food Safety Compliance** - HACCP compliance built-in, avoid health violations - **Cost Control** - Track inventory value, prevent over-purchasing - **Never Stock Out** - Automated low-stock alerts ensure continuous operations - **Traceability** - Complete ingredient tracking for recalls and audits ### Quantifiable Impact - **Waste Reduction**: 20-40% through FIFO and expiry management - **Cost Savings**: €200-600/month from reduced waste and better purchasing - **Time Savings**: 8-12 hours/week on manual inventory tracking - **Compliance**: 100% HACCP compliance, avoid €5,000+ fines - **Inventory Accuracy**: 95%+ vs. 70-80% with manual tracking ### For Operations Managers - **Multi-Location Visibility** - See all inventory across locations - **Automated Reordering** - System suggests what and when to order - **Waste Analysis** - Identify patterns and reduce waste - **Compliance Reporting** - Generate HACCP reports for inspections ## Technology Stack - **Framework**: FastAPI (Python 3.11+) - Async web framework - **Database**: PostgreSQL 17 - Ingredient and stock data - **Caching**: Redis 7.4 - Dashboard KPI cache - **Messaging**: RabbitMQ 4.1 - Alert publishing - **ORM**: SQLAlchemy 2.0 (async) - Database abstraction - **Logging**: Structlog - Structured JSON logging - **Metrics**: Prometheus Client - Custom metrics ## API Endpoints (Key Routes) ### Ingredient Management - `POST /api/v1/inventory/ingredients` - Create ingredient - `GET /api/v1/inventory/ingredients` - List all ingredients - `GET /api/v1/inventory/ingredients/{ingredient_id}` - Get ingredient details - `PUT /api/v1/inventory/ingredients/{ingredient_id}` - Update ingredient - `DELETE /api/v1/inventory/ingredients/{ingredient_id}` - Delete ingredient ### Stock Management - `GET /api/v1/inventory/stock` - List current stock levels - `GET /api/v1/inventory/stock/{stock_id}` - Get stock item details - `POST /api/v1/inventory/stock/adjustment` - Adjust stock levels - `POST /api/v1/inventory/stock/receive` - Receive new stock - `POST /api/v1/inventory/stock/consume` - Consume stock (production use) - `GET /api/v1/inventory/stock/movements` - Stock movement history ### Alerts & Monitoring - `GET /api/v1/inventory/alerts` - Get active alerts - `GET /api/v1/inventory/alerts/low-stock` - Low stock items - `GET /api/v1/inventory/alerts/expiring` - Items expiring soon - `POST /api/v1/inventory/alerts/configure` - Configure alert thresholds ### Food Safety - `GET /api/v1/inventory/food-safety/compliance` - HACCP compliance status - `POST /api/v1/inventory/food-safety/temperature-log` - Log temperature reading - `GET /api/v1/inventory/food-safety/temperature-logs` - Temperature history - `POST /api/v1/inventory/food-safety/alert` - Report food safety issue ### Analytics & Reporting - `GET /api/v1/inventory/dashboard` - Dashboard KPIs - `GET /api/v1/inventory/analytics/consumption` - Consumption patterns - `GET /api/v1/inventory/analytics/waste` - Waste analysis - `GET /api/v1/inventory/analytics/valuation` - Current inventory value - `GET /api/v1/inventory/reports/haccp` - HACCP compliance report - `GET /api/v1/inventory/reports/sustainability` - Sustainability report ## Database Schema ### Main Tables **ingredients** ```sql CREATE TABLE ingredients ( id UUID PRIMARY KEY, tenant_id UUID NOT NULL, name VARCHAR(255) NOT NULL, category VARCHAR(100), -- flour, sugar, dairy, etc. unit VARCHAR(50) NOT NULL, -- kg, liters, units supplier_id UUID, reorder_point DECIMAL(10, 2), -- Minimum stock level reorder_quantity DECIMAL(10, 2), -- Standard order quantity unit_cost DECIMAL(10, 2), barcode VARCHAR(100), storage_location VARCHAR(255), storage_temperature_min DECIMAL(5, 2), storage_temperature_max DECIMAL(5, 2), shelf_life_days INTEGER, is_active BOOLEAN DEFAULT TRUE, created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW(), UNIQUE(tenant_id, name) ); ``` **stock** ```sql CREATE TABLE stock ( id UUID PRIMARY KEY, tenant_id UUID NOT NULL, ingredient_id UUID REFERENCES ingredients(id), quantity DECIMAL(10, 2) NOT NULL, unit VARCHAR(50) NOT NULL, lot_number VARCHAR(100), received_date DATE NOT NULL, expiry_date DATE, supplier_id UUID, location VARCHAR(255), unit_cost DECIMAL(10, 2), status VARCHAR(50) DEFAULT 'available', -- available, reserved, expired, damaged created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW(), INDEX idx_tenant_ingredient (tenant_id, ingredient_id), INDEX idx_expiry (expiry_date), INDEX idx_status (status) ); ``` **stock_movements** ```sql CREATE TABLE stock_movements ( id UUID PRIMARY KEY, tenant_id UUID NOT NULL, stock_id UUID REFERENCES stock(id), ingredient_id UUID REFERENCES ingredients(id), movement_type VARCHAR(50) NOT NULL, -- in, out, adjustment, waste, production quantity DECIMAL(10, 2) NOT NULL, unit VARCHAR(50) NOT NULL, reference_id UUID, -- production_batch_id, order_id, etc. reference_type VARCHAR(50), -- production, sale, adjustment, waste reason TEXT, performed_by UUID, created_at TIMESTAMP DEFAULT NOW(), INDEX idx_tenant_date (tenant_id, created_at), INDEX idx_ingredient (ingredient_id) ); ``` **stock_alerts** ```sql CREATE TABLE stock_alerts ( id UUID PRIMARY KEY, tenant_id UUID NOT NULL, ingredient_id UUID REFERENCES ingredients(id), alert_type VARCHAR(50) NOT NULL, -- low_stock, expiring_soon, expired, temperature severity VARCHAR(20) NOT NULL, -- low, medium, high, urgent message TEXT NOT NULL, current_quantity DECIMAL(10, 2), threshold_quantity DECIMAL(10, 2), expiry_date DATE, days_until_expiry INTEGER, is_acknowledged BOOLEAN DEFAULT FALSE, acknowledged_at TIMESTAMP, acknowledged_by UUID, created_at TIMESTAMP DEFAULT NOW(), INDEX idx_tenant_active (tenant_id, is_acknowledged) ); ``` **product_transformations** ```sql CREATE TABLE product_transformations ( id UUID PRIMARY KEY, tenant_id UUID NOT NULL, production_batch_id UUID, ingredient_id UUID REFERENCES ingredients(id), quantity_consumed DECIMAL(10, 2) NOT NULL, unit VARCHAR(50) NOT NULL, stock_consumed JSONB, -- Array of stock IDs with quantities (FIFO) created_at TIMESTAMP DEFAULT NOW() ); ``` **food_safety_compliance** ```sql CREATE TABLE food_safety_compliance ( id UUID PRIMARY KEY, tenant_id UUID NOT NULL, compliance_type VARCHAR(100), -- haccp, temperature, expiry, cleaning check_date DATE NOT NULL, status VARCHAR(50) NOT NULL, -- compliant, non_compliant, warning details TEXT, corrective_actions TEXT, verified_by UUID, created_at TIMESTAMP DEFAULT NOW() ); ``` **temperature_logs** ```sql CREATE TABLE temperature_logs ( id UUID PRIMARY KEY, tenant_id UUID NOT NULL, location VARCHAR(255) NOT NULL, -- freezer_1, fridge_2, storage_room temperature DECIMAL(5, 2) NOT NULL, unit VARCHAR(10) DEFAULT 'C', is_within_range BOOLEAN, min_acceptable DECIMAL(5, 2), max_acceptable DECIMAL(5, 2), recorded_by UUID, recorded_at TIMESTAMP DEFAULT NOW(), INDEX idx_tenant_location (tenant_id, location, recorded_at) ); ``` **food_safety_alerts** ```sql CREATE TABLE food_safety_alerts ( id UUID PRIMARY KEY, tenant_id UUID NOT NULL, alert_type VARCHAR(100), -- temperature_violation, expired_used, contamination severity VARCHAR(20) NOT NULL, location VARCHAR(255), ingredient_id UUID, description TEXT, corrective_action_required TEXT, status VARCHAR(50) DEFAULT 'open', -- open, investigating, resolved resolved_at TIMESTAMP, created_at TIMESTAMP DEFAULT NOW() ); ``` ## Events & Messaging ### Published Events (RabbitMQ) **Exchange**: `inventory` **Routing Keys**: `inventory.low_stock`, `inventory.expiring`, `inventory.food_safety` **Low Stock Alert Event** ```json { "event_type": "low_stock_alert", "tenant_id": "uuid", "ingredient_id": "uuid", "ingredient_name": "Harina integral", "current_quantity": 15.5, "unit": "kg", "reorder_point": 50.0, "recommended_order_quantity": 100.0, "days_until_stockout": 3, "severity": "high", "message": "Stock bajo: Harina integral (15.5 kg). Punto de reorden: 50 kg.", "timestamp": "2025-11-06T10:30:00Z" } ``` **Expiring Soon Alert Event** ```json { "event_type": "expiring_soon_alert", "tenant_id": "uuid", "stock_id": "uuid", "ingredient_id": "uuid", "ingredient_name": "Leche fresca", "quantity": 20.0, "unit": "liters", "lot_number": "LOT-2025-1105", "expiry_date": "2025-11-09", "days_until_expiry": 3, "location": "Nevera 1", "severity": "medium", "message": "Leche fresca expira en 3 días (20 litros, LOT-2025-1105)", "recommended_action": "Usar en producción antes del 09/11/2025", "timestamp": "2025-11-06T10:30:00Z" } ``` **Food Safety Alert Event** ```json { "event_type": "food_safety_alert", "tenant_id": "uuid", "alert_type": "temperature_violation", "severity": "urgent", "location": "Congelador 2", "temperature": -12.5, "acceptable_range": { "min": -18.0, "max": -15.0 }, "duration_minutes": 45, "affected_items": ["uuid1", "uuid2"], "message": "Violación de temperatura en Congelador 2: -12.5°C (rango: -18°C a -15°C)", "corrective_action_required": "Revisar congelador inmediatamente y verificar integridad de productos", "timestamp": "2025-11-06T10:30:00Z" } ``` ### Consumed Events **From Procurement Service (🆕)** - **Delivery Received** (`delivery.received`) - Automatically updates stock levels when deliveries are recorded - Creates/updates Stock records for each inventory product - Creates StockEntry audit records with batch/lot numbers and expiry dates - Handles accepted quantities from delivery receipts - Links stock movements to delivery reference IDs for full traceability **From Other Services** - **From Production**: Ingredient consumption in production - **From Sales**: Finished product sales (for inventory valuation) ## Custom Metrics (Prometheus) ```python # Stock level metrics stock_quantity_gauge = Gauge( 'inventory_stock_quantity', 'Current stock quantity', ['tenant_id', 'ingredient_id', 'ingredient_name'] ) low_stock_items_total = Gauge( 'inventory_low_stock_items', 'Number of items below reorder point', ['tenant_id'] ) expiring_items_total = Gauge( 'inventory_expiring_items', 'Number of items expiring within 7 days', ['tenant_id'] ) # Waste metrics waste_quantity = Counter( 'inventory_waste_quantity_total', 'Total waste quantity', ['tenant_id', 'ingredient_category', 'reason'] # expired, damaged, etc. ) waste_value_euros = Counter( 'inventory_waste_value_euros_total', 'Total waste value in euros', ['tenant_id'] ) # Inventory valuation inventory_value_total = Gauge( 'inventory_value_euros', 'Total inventory value', ['tenant_id'] ) # Food safety metrics temperature_violations = Counter( 'inventory_temperature_violations_total', 'Temperature violations detected', ['tenant_id', 'location'] ) food_safety_alerts_total = Counter( 'inventory_food_safety_alerts_total', 'Food safety alerts generated', ['tenant_id', 'alert_type', 'severity'] ) ``` ## Configuration ### Environment Variables **Service Configuration:** - `PORT` - Service port (default: 8005) - `DATABASE_URL` - PostgreSQL connection string - `REDIS_URL` - Redis connection string - `RABBITMQ_URL` - RabbitMQ connection string **Alert Configuration:** - `LOW_STOCK_CHECK_INTERVAL_HOURS` - How often to check (default: 6) - `EXPIRY_WARNING_DAYS` - Days before expiry to alert (default: 7) - `URGENT_EXPIRY_DAYS` - Days for urgent expiry alerts (default: 3) - `ENABLE_AUTO_ALERTS` - Automatic alert generation (default: true) **FIFO Configuration:** - `ENABLE_FIFO_ENFORCEMENT` - Enforce FIFO consumption (default: true) - `FIFO_VIOLATION_ALERT` - Alert on FIFO violations (default: true) **Food Safety Configuration:** - `TEMPERATURE_CHECK_INTERVAL_MINUTES` - Temp log frequency (default: 60) - `TEMPERATURE_VIOLATION_THRESHOLD_MINUTES` - Time before alert (default: 30) - `ENABLE_HACCP_COMPLIANCE` - Enable HACCP tracking (default: true) **Sustainability Configuration:** - `TRACK_CARBON_FOOTPRINT` - Enable carbon tracking (default: true) - `TRACK_SDG_METRICS` - Enable SDG reporting (default: true) ## Development Setup ### Prerequisites - Python 3.11+ - PostgreSQL 17 - Redis 7.4 - RabbitMQ 4.1 (optional) ### Local Development ```bash cd services/inventory python -m venv venv source venv/bin/activate pip install -r requirements.txt export DATABASE_URL=postgresql://user:pass@localhost:5432/inventory export REDIS_URL=redis://localhost:6379/0 export RABBITMQ_URL=amqp://guest:guest@localhost:5672/ alembic upgrade head python main.py ``` ### Testing ```bash # Unit tests pytest tests/unit/ -v # Integration tests pytest tests/integration/ -v # FIFO logic tests pytest tests/test_fifo.py -v # Test with coverage pytest --cov=app tests/ --cov-report=html ``` ## Integration Points ### Dependencies - **Procurement Service** - Receive stock from purchase orders via delivery events (🆕) - **Production Service** - Consume ingredients in production - **Forecasting Service** - Provide consumption data for forecasts - **Suppliers Service** - Supplier information for stock items - **PostgreSQL** - Inventory data storage - **Redis** - Dashboard KPI cache - **RabbitMQ** - Alert publishing and delivery event consumption (🆕) ### Dependents - **Production Service** - Check ingredient availability - **Procurement Service** - Get reorder recommendations - **AI Insights Service** - Analyze inventory patterns - **Frontend Dashboard** - Display inventory status - **Notification Service** - Send inventory alerts ## Delivery Event Processing (🆕) ### Automatic Stock Updates from Deliveries The inventory service automatically updates stock levels when deliveries are recorded in the procurement service through event-driven architecture. **How It Works:** 1. User records delivery receipt in procurement service (frontend `DeliveryReceiptModal`) 2. Procurement service publishes `delivery.received` event to RabbitMQ 3. Inventory service's `DeliveryEventConsumer` listens for and processes the event 4. For each delivered item: - Gets or creates Stock record for the inventory product - Updates `quantity_available` by adding accepted quantity - Creates StockEntry audit record with: - Transaction type: `RESTOCK` - Reference type: `DELIVERY` - Batch/lot number from delivery - Expiration date from delivery - Quantity change and new balance **Benefits:** - **Zero Manual Entry** - Eliminates duplicate data entry in inventory after recording deliveries - **Real-Time Updates** - Stock levels update within seconds of delivery recording - **Complete Traceability** - Every stock change linked to source delivery - **Batch Tracking** - Automatic batch/lot number recording for food safety compliance - **Expiry Management** - Expiration dates automatically captured from delivery receipts ### Delivery Event Consumer Implementation ```python async def process_delivery_stock_update(self, event_data: Dict[str, Any]) -> bool: """ Process delivery event and update stock levels. Listens to: procurement.events exchange, routing key: delivery.received Creates: Stock records and StockEntry audit records """ data = event_data.get('data', {}) tenant_id = uuid.UUID(data.get('tenant_id')) delivery_id = uuid.UUID(data.get('delivery_id')) items = data.get('items', []) async with database_manager.get_session() as session: stock_repo = StockRepository(session) entry_repo = StockEntryRepository(session) for item in items: inventory_product_id = uuid.UUID(item.get('inventory_product_id')) accepted_quantity = Decimal(str(item.get('accepted_quantity', 0))) if accepted_quantity <= 0: continue # Skip rejected items # Get or create stock record stock = await stock_repo.get_stock_by_product( tenant_id=tenant_id, inventory_product_id=inventory_product_id ) if not stock: # Create new stock record stock = await stock_repo.create_stock({ 'tenant_id': tenant_id, 'inventory_product_id': inventory_product_id, 'quantity_available': Decimal('0'), # ... other fields }) # Update stock quantity new_quantity = stock.quantity_available + accepted_quantity await stock_repo.update_stock( stock_id=stock.id, tenant_id=tenant_id, update_data={ 'quantity_available': new_quantity, 'last_restocked_at': datetime.now(timezone.utc) } ) # Create stock entry for audit trail entry = await entry_repo.create_entry({ 'tenant_id': tenant_id, 'stock_id': stock.id, 'transaction_type': 'RESTOCK', 'quantity_change': accepted_quantity, 'quantity_after': new_quantity, 'reference_type': 'DELIVERY', 'reference_id': delivery_id, 'batch_number': item.get('batch_lot_number'), 'expiry_date': item.get('expiry_date'), 'notes': f"Delivery received from PO. Batch: {item.get('batch_lot_number', 'N/A')}" }) await session.commit() return True ``` **Event Schema:** ```json { "event_id": "uuid", "event_type": "delivery.received", "service_name": "procurement", "timestamp": "2025-11-12T10:30:00Z", "data": { "tenant_id": "uuid", "delivery_id": "uuid", "po_id": "uuid", "received_by": "uuid", "received_at": "2025-11-12T10:30:00Z", "items": [ { "inventory_product_id": "uuid", "ordered_quantity": 100.0, "delivered_quantity": 98.0, "accepted_quantity": 95.0, "rejected_quantity": 3.0, "batch_lot_number": "LOT-2025-1112", "expiry_date": "2025-12-12", "rejection_reason": "3 damaged units" } ] } } ``` ## Stock Receipt System The Stock Receipt System provides lot-level tracking for incoming deliveries with complete food safety compliance and traceability. ### Overview When deliveries arrive, users record receipts through the frontend `StockReceiptModal`, capturing: - **Lot-Level Details** - Multiple lots per line item (e.g., 100kg delivered in 4×25kg lots) - **Expiration Dates** - Required for all food ingredients (HACCP compliance) - **Discrepancy Tracking** - Record when actual ≠ expected quantities - **Draft Workflow** - Save progress while completing receipt details ### Database Schema **stock_receipts** ```sql CREATE TABLE stock_receipts ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL, po_id UUID NOT NULL, -- Links to purchase order po_number VARCHAR(50) NOT NULL, -- Receipt details received_at TIMESTAMP NOT NULL, received_by_user_id UUID NOT NULL, status VARCHAR(50) NOT NULL, -- draft, confirmed, cancelled -- Supplier information supplier_id UUID NOT NULL, supplier_name VARCHAR(255) NOT NULL, -- Metadata notes TEXT, has_discrepancies BOOLEAN DEFAULT FALSE, -- Timestamps created_at TIMESTAMP NOT NULL DEFAULT NOW(), updated_at TIMESTAMP NOT NULL DEFAULT NOW(), -- Foreign keys FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE, FOREIGN KEY (po_id) REFERENCES purchase_orders(id) ON DELETE CASCADE ); CREATE INDEX idx_stock_receipts_tenant_po ON stock_receipts(tenant_id, po_id); CREATE INDEX idx_stock_receipts_status ON stock_receipts(status); ``` **stock_receipt_line_items** ```sql CREATE TABLE stock_receipt_line_items ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL, receipt_id UUID NOT NULL, -- Product reference ingredient_id UUID NOT NULL, ingredient_name VARCHAR(255) NOT NULL, po_line_id UUID, -- Links to PO line item -- Quantities expected_quantity DECIMAL(10, 2) NOT NULL, actual_quantity DECIMAL(10, 2) NOT NULL, unit_of_measure VARCHAR(50) NOT NULL, -- Discrepancy tracking has_discrepancy BOOLEAN DEFAULT FALSE, discrepancy_reason TEXT, -- Costing unit_cost DECIMAL(10, 2), total_cost DECIMAL(10, 2), -- Timestamps created_at TIMESTAMP NOT NULL DEFAULT NOW(), updated_at TIMESTAMP NOT NULL DEFAULT NOW(), -- Foreign keys FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE, FOREIGN KEY (receipt_id) REFERENCES stock_receipts(id) ON DELETE CASCADE, FOREIGN KEY (ingredient_id) REFERENCES ingredients(id) ON DELETE CASCADE ); CREATE INDEX idx_receipt_line_items_receipt ON stock_receipt_line_items(receipt_id); ``` **stock_lots** ```sql CREATE TABLE stock_lots ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL, line_item_id UUID NOT NULL, stock_id UUID, -- Links to Stock table after confirmation -- Lot identification lot_number VARCHAR(100), -- Internal lot number supplier_lot_number VARCHAR(100), -- Supplier's lot number -- Quantity quantity DECIMAL(10, 2) NOT NULL, unit_of_measure VARCHAR(50) NOT NULL, -- Food safety (REQUIRED) expiration_date DATE NOT NULL, -- ⭐ REQUIRED for food safety best_before_date DATE, -- Storage warehouse_location VARCHAR(255), storage_zone VARCHAR(100), -- freezer, fridge, dry_storage -- Quality quality_notes TEXT, -- Timestamps created_at TIMESTAMP NOT NULL DEFAULT NOW(), updated_at TIMESTAMP NOT NULL DEFAULT NOW(), -- Foreign keys FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE, FOREIGN KEY (line_item_id) REFERENCES stock_receipt_line_items(id) ON DELETE CASCADE, FOREIGN KEY (stock_id) REFERENCES stock(id) ON DELETE SET NULL, -- Validation: expiration_date is required CONSTRAINT check_expiration_date_not_null CHECK (expiration_date IS NOT NULL) ); CREATE INDEX idx_stock_lots_line_item ON stock_lots(line_item_id); CREATE INDEX idx_stock_lots_expiration ON stock_lots(expiration_date); ``` ### API Endpoints **POST /api/v1/inventory/stock-receipts** Create draft stock receipt ```json { "tenant_id": "uuid", "po_id": "uuid", "received_at": "2025-11-26T10:30:00Z", "received_by_user_id": "uuid", "line_items": [ { "ingredient_id": "uuid", "expected_quantity": 100.0, "actual_quantity": 98.0, "unit_of_measure": "kg", "has_discrepancy": true, "discrepancy_reason": "2kg damaged during transport", "lots": [ { "quantity": 25.0, "expiration_date": "2025-12-26", "lot_number": "LOT-A-001", "supplier_lot_number": "SUP-2025-1126" }, { "quantity": 25.0, "expiration_date": "2025-12-26", "lot_number": "LOT-A-002" }, { "quantity": 25.0, "expiration_date": "2025-12-27", "lot_number": "LOT-B-001" }, { "quantity": 23.0, "expiration_date": "2025-12-27", "lot_number": "LOT-B-002" } ] } ] } ``` **Response**: Created stock receipt with `status=draft` **GET /api/v1/inventory/stock-receipts/{receipt_id}** Retrieve stock receipt details - Returns full receipt with nested line items and lots - Used to resume editing draft receipts **PUT /api/v1/inventory/stock-receipts/{receipt_id}** Update draft stock receipt - Save progress while filling in details - Validates lot quantity sums - Replaces line items (cascade delete-and-recreate) **POST /api/v1/inventory/stock-receipts/{receipt_id}/confirm** Finalize stock receipt (atomic transaction) - Creates Stock records (one per lot) - Creates StockMovement records (type: PURCHASE) - Marks receipt status as `confirmed` - Links lots to stock records via `stock_id` - **Atomic**: All operations succeed or all fail ```json // Request body (minimal) { "confirmed_at": "2025-11-26T10:45:00Z" } ``` **Response**: ```json { "receipt_id": "uuid", "status": "confirmed", "stock_records_created": 4, "total_quantity_added": 98.0, "stock_ids": ["uuid1", "uuid2", "uuid3", "uuid4"] } ``` ### Validation Rules **Lot Quantity Validation**: ```python # Sum of lot quantities must equal actual quantity sum(lot.quantity for lot in line_item.lots) == line_item.actual_quantity ``` **Expiration Date Validation**: ```python # All food ingredients REQUIRE expiration date if ingredient.category in ['perishable', 'dairy', 'meat', 'produce']: if not lot.expiration_date: raise ValidationError("Expiration date required for food safety") ``` **Discrepancy Validation**: ```python # Discrepancy reason required when actual ≠ expected if line_item.actual_quantity != line_item.expected_quantity: line_item.has_discrepancy = True if not line_item.discrepancy_reason: raise ValidationError("Discrepancy reason required") ``` **Tenant Security**: ```python # All operations check tenant_id if receipt.tenant_id != current_user.tenant_id: raise PermissionError("Access denied") ``` ### Integration with Alert System The stock receipt system integrates tightly with the alert system to guide users through the delivery workflow: **DELIVERY_ARRIVING_SOON Alert** (T-2 hours): - Alert triggers StockReceiptModal in `create` mode - User creates draft receipt while delivery is in transit - Pre-fills PO details automatically **DELIVERY_OVERDUE Alert** (T+30 minutes): - Critical priority alert - Primary action: "Contact Supplier" - Secondary action: "Mark as Received" (opens StockReceiptModal) **STOCK_RECEIPT_INCOMPLETE Alert** (Post-window): - Important priority alert - Opens StockReceiptModal in `edit` mode if draft exists - Shows incomplete line items and missing lot details **Workflow**: ``` 1. DELIVERY_ARRIVING_SOON alert appears ↓ 2. User clicks "Mark as Received" ↓ 3. StockReceiptModal opens (create mode) ↓ 4. User enters lot details and expiration dates ↓ 5. Clicks "Save Draft" (can finish later) ↓ 6. Later: Returns and clicks "Complete Stock Receipt" ↓ 7. Reviews, adds missing details ↓ 8. Clicks "Confirm Receipt" ↓ 9. System confirms receipt (atomic transaction) ↓ 10. Triggers delivery.received event ↓ 11. Auto-resolves all delivery alerts for this PO ``` ### Food Safety Compliance **Why Expiration Dates are Required**: - HACCP (Hazard Analysis and Critical Control Points) compliance - Prevents use of expired ingredients - Enables FIFO consumption based on expiration - Required for food safety audits in EU/Spain - Supports lot recall procedures **Lot Traceability**: - Each lot links to supplier lot number - Complete chain: Supplier → Delivery → Stock → Production → Customer - Enables rapid recall if contamination detected - Audit trail for food safety inspectors **Storage Zone Tracking**: - Warehouse location captured per lot - Storage zone (freezer/fridge/dry) enforces temperature requirements - Supports temperature monitoring integration ### Example: Receiving 100kg Flour Delivery **Scenario**: PO for 100kg flour arrives in 4×25kg bags **Step 1**: Create Draft Receipt ```bash POST /api/v1/inventory/stock-receipts { "po_id": "PO-12345", "received_at": "2025-11-26T10:30:00Z", "line_items": [ { "ingredient_id": "flour-00-uuid", "expected_quantity": 100.0, "actual_quantity": 100.0, "unit_of_measure": "kg", "lots": [ { "quantity": 25.0, "expiration_date": "2026-05-26", "supplier_lot_number": "MILL-2025-W47-A", "warehouse_location": "Almacén Principal - Estante 3" }, // ... 3 more lots ] } ] } ``` **Step 2**: Confirm Receipt ```bash POST /api/v1/inventory/stock-receipts/{receipt_id}/confirm { "confirmed_at": "2025-11-26T10:45:00Z" } ``` **Result**: - 4 Stock records created (one per lot) - 4 StockMovement records (type: PURCHASE) - Receipt status: `confirmed` - Inventory available quantity increases by 100kg - FIFO will consume oldest expiration date first ## FIFO Implementation ### FIFO Consumption Logic ```python async def consume_ingredient_fifo( tenant_id: str, ingredient_id: str, quantity_needed: float ) -> list[dict]: """Consume ingredients using FIFO (First-In-First-Out)""" # Get available stock ordered by received_date (oldest first) available_stock = await db.query(Stock).filter( Stock.tenant_id == tenant_id, Stock.ingredient_id == ingredient_id, Stock.status == 'available', Stock.quantity > 0 ).order_by(Stock.received_date.asc()).all() consumed_items = [] remaining_needed = quantity_needed for stock_item in available_stock: if remaining_needed <= 0: break quantity_to_consume = min(stock_item.quantity, remaining_needed) # Update stock quantity stock_item.quantity -= quantity_to_consume if stock_item.quantity == 0: stock_item.status = 'depleted' # Record consumption consumed_items.append({ 'stock_id': stock_item.id, 'lot_number': stock_item.lot_number, 'quantity_consumed': quantity_to_consume, 'received_date': stock_item.received_date, 'expiry_date': stock_item.expiry_date }) remaining_needed -= quantity_to_consume if remaining_needed > 0: raise InsufficientStockError( f"Insufficient stock. Needed: {quantity_needed}, " f"Available: {quantity_needed - remaining_needed}" ) await db.commit() return consumed_items ``` ## Security Measures ### Data Protection - **Tenant Isolation** - All inventory scoped to tenant_id - **Input Validation** - Validate all quantities and dates - **Audit Trail** - Complete history of stock movements - **Access Control** - Role-based permissions ### Food Safety Security - **Temperature Log Integrity** - Tamper-proof temperature records - **Lot Traceability** - Complete ingredient tracking for recalls - **Audit Compliance** - HACCP-compliant record keeping - **Alert Escalation** - Critical food safety alerts escalate automatically ## Troubleshooting ### Common Issues **Issue**: FIFO not working correctly - **Cause**: Stock items missing received_date - **Solution**: Ensure all stock has received_date set **Issue**: Low stock alerts not firing - **Cause**: Reorder points not configured - **Solution**: Set reorder_point for each ingredient **Issue**: Expiry alerts too frequent - **Cause**: `EXPIRY_WARNING_DAYS` set too high - **Solution**: Adjust to 3-5 days instead of 7 **Issue**: Temperature violations not detected - **Cause**: Temperature logs not being recorded - **Solution**: Check temperature monitoring device integration ## Competitive Advantages 1. **FIFO Enforcement** - Automatic expiry prevention 2. **Food Safety Built-In** - HACCP compliance out-of-the-box 3. **Sustainability Tracking** - SDG reporting for EU grants 4. **Barcode Support** - Quick stock updates 5. **Multi-Location** - Track inventory across sites 6. **Spanish Market** - HACCP compliant for Spanish regulations 7. **Zero Waste Focus** - Waste reduction analytics ## Future Enhancements - **IoT Sensor Integration** - Automatic temperature monitoring - **AI-Powered Reorder Points** - Dynamic reorder point calculation - **Image Recognition** - Photo-based stock counting - **Blockchain Traceability** - Immutable ingredient tracking - **Mobile Barcode App** - Smartphone barcode scanning - **Supplier Integration** - Direct supplier ordering - **Predictive Expiry** - Predict expiry based on storage conditions --- **For VUE Madrid Business Plan**: The Inventory Service demonstrates commitment to food safety (HACCP compliance), sustainability (20-40% waste reduction), and operational excellence. The FIFO enforcement and expiry management features directly address EU food waste regulations and support SDG goals, making this ideal for grant applications. The €200-600/month cost savings and compliance benefits provide clear ROI for bakery owners.