# Inventory Management

## Overview

The Orion platform provides comprehensive inventory management with support for:

- **Multi-location tracking** - Track stock across warehouses, stores, and storage bins
- **Reservation system** - Reserve items for pending orders
- **Digital products** - Automatic unlimited inventory for digital goods
- **Admin operations** - Manage inventory on behalf of stores

---

## Key Concepts

### Storage Locations

Inventory is tracked at the **storage location level**. Each product can have stock in multiple locations:

```
Product: "Wireless Headphones"
├── WAREHOUSE_MAIN: 100 units (10 reserved)
├── WAREHOUSE_WEST: 50 units (0 reserved)
└── STORE_FRONT: 25 units (5 reserved)

Total: 175 units | Reserved: 15 | Available: 160
```

**Location naming:** Locations are text strings, normalized to UPPERCASE (e.g., `WAREHOUSE_A`, `STORE_01`).

### Inventory States

| Field | Description |
|-------|-------------|
| `quantity` | Total physical stock at location |
| `reserved_quantity` | Items reserved for pending orders |
| `available_quantity` | `quantity - reserved_quantity` (can be sold) |

### Product Types & Inventory

| Product Type | Inventory Behavior |
|--------------|-------------------|
| **Physical** | Requires inventory tracking, orders check available stock |
| **Digital** | **Unlimited inventory** - no stock constraints |
| **Service** | Treated as digital (unlimited) |
| **Subscription** | Treated as digital (unlimited) |

---

## Digital Products

Digital products have **unlimited inventory** by default. This means:

- Orders for digital products never fail due to "insufficient inventory"
- No need to create inventory entries for digital products
- The `available_inventory` property returns `999999` (effectively unlimited)

### How It Works

```python
# In Product model
@property
def has_unlimited_inventory(self) -> bool:
    """Digital products have unlimited inventory."""
    return self.is_digital

@property
def available_inventory(self) -> int:
    """Calculate available inventory."""
    if self.has_unlimited_inventory:
        return 999999  # Unlimited
    return sum(inv.available_quantity for inv in self.inventory_entries)
```

### Setting a Product as Digital

Digital products are identified by the `is_digital` flag on the `MarketplaceProduct`:

```python
marketplace_product.is_digital = True
marketplace_product.product_type_enum = "digital"
marketplace_product.digital_delivery_method = "license_key"  # or "download", "email"
```

---

## Inventory Operations

### Set Inventory

Replace the exact quantity at a location:

```http
POST /api/v1/store/inventory/set
{
    "product_id": 123,
    "location": "WAREHOUSE_A",
    "quantity": 100
}
```

### Adjust Inventory

Add or remove stock (positive = add, negative = remove):

```http
POST /api/v1/store/inventory/adjust
{
    "product_id": 123,
    "location": "WAREHOUSE_A",
    "quantity": -10  // Remove 10 units
}
```

### Reserve Inventory

Mark items as reserved for an order:

```http
POST /api/v1/store/inventory/reserve
{
    "product_id": 123,
    "location": "WAREHOUSE_A",
    "quantity": 5
}
```

### Release Reservation

Cancel a reservation (order cancelled):

```http
POST /api/v1/store/inventory/release
{
    "product_id": 123,
    "location": "WAREHOUSE_A",
    "quantity": 5
}
```

### Fulfill Reservation

Complete an order (items shipped):

```http
POST /api/v1/store/inventory/fulfill
{
    "product_id": 123,
    "location": "WAREHOUSE_A",
    "quantity": 5
}
```

This decreases both `quantity` and `reserved_quantity`.

---

## Reservation Workflow

```
┌─────────────────┐
│  Order Created  │
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│ Reserve Items   │  reserved_quantity += order_qty
└────────┬────────┘
         │
    ┌────┴────┐
    │         │
    ▼         ▼
┌───────┐  ┌──────────┐
│Cancel │  │  Ship    │
└───┬───┘  └────┬─────┘
    │           │
    ▼           ▼
┌─────────┐  ┌──────────────┐
│ Release │  │   Fulfill    │
│reserved │  │ quantity -=  │
│  -= qty │  │ reserved -=  │
└─────────┘  └──────────────┘
```

---

## Admin Inventory Management

Administrators can manage inventory on behalf of any store through the admin UI at `/admin/inventory` or via the API.

### Admin UI Features

The admin inventory page provides:

- **Overview Statistics** - Total entries, stock quantities, reserved items, and low stock alerts
- **Filtering** - Filter by store, location, and low stock threshold
- **Search** - Search by product title or SKU
- **Stock Adjustment** - Add or remove stock with optional reason tracking
- **Set Quantity** - Set exact stock quantity at any location
- **Delete Entries** - Remove inventory entries

### Admin API Endpoints

### List All Inventory

```http
GET /api/v1/admin/inventory
GET /api/v1/admin/inventory?store_id=1
GET /api/v1/admin/inventory?low_stock=10
```

### Get Inventory Statistics

```http
GET /api/v1/admin/inventory/stats

Response:
{
    "total_entries": 150,
    "total_quantity": 5000,
    "total_reserved": 200,
    "total_available": 4800,
    "low_stock_count": 12,
    "stores_with_inventory": 5,
    "unique_locations": 8
}
```

### Low Stock Alerts

```http
GET /api/v1/admin/inventory/low-stock?threshold=10

Response:
[
    {
        "product_id": 123,
        "store_name": "TechStore",
        "product_title": "USB Cable",
        "location": "WAREHOUSE_A",
        "quantity": 3,
        "available_quantity": 2
    }
]
```

### Set Inventory (Admin)

```http
POST /api/v1/admin/inventory/set
{
    "store_id": 1,
    "product_id": 123,
    "location": "WAREHOUSE_A",
    "quantity": 100
}
```

### Adjust Inventory (Admin)

```http
POST /api/v1/admin/inventory/adjust
{
    "store_id": 1,
    "product_id": 123,
    "location": "WAREHOUSE_A",
    "quantity": 25,
    "reason": "Restocking from supplier"
}
```

---

## Database Schema

### Inventory Table

```sql
CREATE TABLE inventory (
    id SERIAL PRIMARY KEY,
    product_id INTEGER NOT NULL REFERENCES products(id),
    store_id INTEGER NOT NULL REFERENCES stores(id),
    location VARCHAR NOT NULL,
    quantity INTEGER NOT NULL DEFAULT 0,
    reserved_quantity INTEGER DEFAULT 0,
    gtin VARCHAR,
    created_at TIMESTAMP DEFAULT NOW(),
    updated_at TIMESTAMP DEFAULT NOW(),

    UNIQUE(product_id, location)
);

CREATE INDEX idx_inventory_store_product ON inventory(store_id, product_id);
CREATE INDEX idx_inventory_product_location ON inventory(product_id, location);
```

### Constraints

- **Unique constraint:** `(product_id, location)` - One entry per product/location
- **Foreign keys:** References `products` and `stores` tables
- **Non-negative:** `quantity` and `reserved_quantity` must be >= 0

---

## Best Practices

### Physical Products

1. **Create inventory entries** before accepting orders
2. **Use meaningful location names** (e.g., `WAREHOUSE_MAIN`, `STORE_NYC`)
3. **Monitor low stock** using the admin dashboard or API
4. **Reserve on order creation** to prevent overselling

### Digital Products

1. **No inventory setup needed** - unlimited by default
2. **Optional:** Create entries for license key tracking
3. **Focus on fulfillment** - digital delivery mechanism

### Multi-Location

1. **Aggregate queries** use `Product.total_inventory` and `Product.available_inventory`
2. **Location-specific** operations use the Inventory model directly
3. **Transfers** between locations: adjust down at source, adjust up at destination

---

## API Reference

### Store Endpoints

| Method | Endpoint | Description |
|--------|----------|-------------|
| POST | `/api/v1/store/inventory/set` | Set exact quantity |
| POST | `/api/v1/store/inventory/adjust` | Add/remove quantity |
| POST | `/api/v1/store/inventory/reserve` | Reserve for order |
| POST | `/api/v1/store/inventory/release` | Cancel reservation |
| POST | `/api/v1/store/inventory/fulfill` | Complete order |
| GET | `/api/v1/store/inventory/product/{id}` | Product summary |
| GET | `/api/v1/store/inventory` | List with filters |
| PUT | `/api/v1/store/inventory/{id}` | Update entry |
| DELETE | `/api/v1/store/inventory/{id}` | Delete entry |

### Admin Endpoints

| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/api/v1/admin/inventory` | List all (cross-store) |
| GET | `/api/v1/admin/inventory/stats` | Platform statistics |
| GET | `/api/v1/admin/inventory/low-stock` | Low stock alerts |
| GET | `/api/v1/admin/inventory/stores` | Stores with inventory |
| GET | `/api/v1/admin/inventory/locations` | Unique locations |
| GET | `/api/v1/admin/inventory/stores/{id}` | Store inventory |
| GET | `/api/v1/admin/inventory/products/{id}` | Product summary |
| POST | `/api/v1/admin/inventory/set` | Set (requires store_id) |
| POST | `/api/v1/admin/inventory/adjust` | Adjust (requires store_id) |
| PUT | `/api/v1/admin/inventory/{id}` | Update entry |
| DELETE | `/api/v1/admin/inventory/{id}` | Delete entry |

---

## Related Documentation

- [Product Management](../catalog/architecture.md)
- [Admin Inventory Migration Plan](../../implementation/inventory-admin-migration.md)
- [Store Operations Expansion](../../development/migration/store-operations-expansion.md)
