docs: Add Stock Receipt System documentation to inventory service

This commit is contained in:
Urtzi Alfaro
2025-11-26 07:00:44 +01:00
parent 9a7f4343f1
commit 21651b396e

View File

@@ -624,6 +624,362 @@ async def process_delivery_stock_update(self, event_data: Dict[str, Any]) -> boo
} }
``` ```
## 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 Implementation
### FIFO Consumption Logic ### FIFO Consumption Logic