Files
orion/docs/implementation/inventory-admin-migration.md
Samir Boulahtit 8d8d41808b feat: add admin inventory management (Phase 1)
- Add admin API endpoints for inventory management
- Add inventory page with vendor selector and filtering
- Add admin schemas for cross-vendor inventory operations
- Support digital products with unlimited inventory
- Add integration tests for admin inventory API
- Add inventory management guide documentation

Mirrors vendor inventory functionality with admin-level access.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-18 21:05:12 +01:00

13 KiB

Admin Inventory Management Migration Plan

Overview

Objective: Add inventory management capabilities to the admin "Vendor Operations" section, allowing administrators to view and manage vendor inventory on their behalf.

Status: Phase 1 Complete


Current State Analysis

What Exists

The inventory system is fully implemented at the vendor API level with comprehensive functionality:

Component Status Location
Database Model Complete models/database/inventory.py
Pydantic Schemas Complete models/schema/inventory.py
Service Layer Complete app/services/inventory_service.py
Vendor API Complete app/api/v1/vendor/inventory.py
Exceptions Complete app/exceptions/inventory.py
Unit Tests Complete tests/unit/services/test_inventory_service.py
Integration Tests Complete tests/integration/api/v1/vendor/test_inventory.py
Vendor UI 🔲 Placeholder app/templates/vendor/inventory.html
Admin API 🔲 Not Started -
Admin UI 🔲 Not Started -
Audit Trail 🔲 Not Started Logs only, no dedicated table

Storage/Location Architecture

The inventory system tracks stock at the storage location level:

Product A
├── 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

Key design decisions:

  • One product can have inventory across multiple locations
  • Unique constraint: (product_id, location) - one entry per product/location
  • Locations are text strings, normalized to UPPERCASE
  • Available quantity = quantity - reserved_quantity

Existing Operations

Operation Description Service Method
Set Replace exact quantity at location set_inventory()
Adjust Add/remove quantity (positive/negative) adjust_inventory()
Reserve Mark items for pending order reserve_inventory()
Release Cancel reservation release_reservation()
Fulfill Complete order (reduces both qty & reserved) fulfill_reservation()
Get Product Summary across all locations get_product_inventory()
Get Vendor List with filters get_vendor_inventory()
Update Partial field update update_inventory()
Delete Remove inventory entry delete_inventory()

Vendor API Endpoints

All endpoints at /api/v1/vendor/inventory/*:

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

Gap Analysis

What's Missing for Admin Vendor Operations

  1. Admin API Endpoints - Implemented in Phase 1
  2. Admin UI Page - No inventory management interface in admin panel
  3. Vendor Selector - Admin needs to select which vendor to manage
  4. Cross-Vendor View - Implemented in Phase 1
  5. Audit Trail - Only application logs, no queryable audit history
  6. Bulk Operations - No bulk adjust/import capabilities
  7. Low Stock Alerts - Basic filter exists, no alert configuration

Digital Products - Infinite Inventory

Implementation Complete

Digital products now have unlimited inventory by default:

# In Product model (models/database/product.py)
UNLIMITED_INVENTORY = 999999

@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 (total - reserved).

    Digital products return unlimited inventory.
    """
    if self.has_unlimited_inventory:
        return self.UNLIMITED_INVENTORY
    return sum(inv.available_quantity for inv in self.inventory_entries)

Behavior:

  • Physical products: Sum of inventory entries (0 if no entries)
  • Digital products: Returns 999999 (unlimited) regardless of entries
  • Orders for digital products never fail due to "insufficient inventory"

Tests: tests/unit/models/database/test_product.py::TestProductInventoryProperties

Documentation: Inventory Management Guide


Migration Plan

Phase 1: Admin API Endpoints

Goal: Expose inventory management to admin users with vendor selection

1.1 New File: app/api/v1/admin/inventory.py

Admin endpoints that mirror vendor functionality with vendor_id as parameter:

Method Endpoint Description
GET /admin/inventory List all inventory (cross-vendor)
GET /admin/inventory/vendors/{vendor_id} Vendor-specific inventory
GET /admin/inventory/products/{product_id} Product inventory summary
POST /admin/inventory/set Set inventory (requires vendor_id)
POST /admin/inventory/adjust Adjust inventory
PUT /admin/inventory/{id} Update inventory entry
DELETE /admin/inventory/{id} Delete inventory entry
GET /admin/inventory/low-stock Low stock report

1.2 Schema Extensions

Add admin-specific request schemas in models/schema/inventory.py:

class AdminInventoryCreate(InventoryCreate):
    """Admin version - requires explicit vendor_id."""
    vendor_id: int = Field(..., description="Target vendor ID")

class AdminInventoryAdjust(InventoryAdjust):
    """Admin version - requires explicit vendor_id."""
    vendor_id: int = Field(..., description="Target vendor ID")

class AdminInventoryListResponse(BaseModel):
    """Cross-vendor inventory list."""
    inventories: list[InventoryResponse]
    total: int
    skip: int
    limit: int
    vendor_filter: int | None = None

1.3 Service Layer Reuse

The existing InventoryService already accepts vendor_id as a parameter - no service changes needed. Admin endpoints simply pass the vendor_id from the request instead of from the JWT token.

Phase 2: Admin UI

Goal: Create admin inventory management page

2.1 New Files

File Description
app/templates/admin/inventory.html Main inventory page
static/admin/js/inventory.js Alpine.js controller

2.2 UI Features

  1. Vendor Selector Dropdown - Filter by vendor (or show all)
  2. Inventory Table - Product, Location, Quantity, Reserved, Available
  3. Search/Filter - By product name, location, low stock
  4. Adjust Modal - Quick add/remove with reason
  5. Pagination - Handle large inventories
  6. Export - CSV download (future)

2.3 Page Layout

┌─────────────────────────────────────────────────────────┐
│ Inventory Management                    [Vendor: All ▼] │
├─────────────────────────────────────────────────────────┤
│ [Search...] [Location ▼] [Low Stock Only ☐]            │
├─────────────────────────────────────────────────────────┤
│ Product      │ Location    │ Qty │ Reserved │ Available │
│──────────────┼─────────────┼─────┼──────────┼───────────│
│ Widget A     │ WAREHOUSE_A │ 100 │ 10       │ 90   [⚙️] │
│ Widget A     │ WAREHOUSE_B │ 50  │ 0        │ 50   [⚙️] │
│ Gadget B     │ WAREHOUSE_A │ 25  │ 5        │ 20   [⚙️] │
├─────────────────────────────────────────────────────────┤
│ Showing 1-20 of 150                    [< 1 2 3 ... >]  │
└─────────────────────────────────────────────────────────┘

2.4 Sidebar Integration

Add to app/templates/admin/partials/sidebar.html:

<!-- Under Vendor Operations section -->
<li class="relative px-6 py-3">
    <a href="/admin/inventory" ...>
        <span class="inline-flex items-center">
            <!-- inventory icon -->
            <span class="ml-4">Inventory</span>
        </span>
    </a>
</li>

Phase 3: Audit Trail (Optional Enhancement)

Goal: Track inventory changes with queryable history

3.1 Database Migration

CREATE TABLE inventory_audit_log (
    id SERIAL PRIMARY KEY,
    inventory_id INTEGER REFERENCES inventory(id) ON DELETE SET NULL,
    product_id INTEGER NOT NULL,
    vendor_id INTEGER NOT NULL,
    location VARCHAR(255) NOT NULL,

    -- Change details
    operation VARCHAR(50) NOT NULL,  -- 'set', 'adjust', 'reserve', 'release', 'fulfill'
    quantity_before INTEGER,
    quantity_after INTEGER,
    reserved_before INTEGER,
    reserved_after INTEGER,
    adjustment_amount INTEGER,

    -- Context
    reason VARCHAR(500),
    performed_by INTEGER REFERENCES users(id),
    performed_by_type VARCHAR(20) NOT NULL,  -- 'vendor', 'admin', 'system'

    created_at TIMESTAMP DEFAULT NOW()
);

CREATE INDEX idx_audit_vendor ON inventory_audit_log(vendor_id);
CREATE INDEX idx_audit_product ON inventory_audit_log(product_id);
CREATE INDEX idx_audit_created ON inventory_audit_log(created_at);

3.2 Service Enhancement

Add audit logging to InventoryService methods:

def _log_audit(
    self,
    db: Session,
    inventory: Inventory,
    operation: str,
    qty_before: int,
    qty_after: int,
    reserved_before: int,
    reserved_after: int,
    user_id: int | None,
    user_type: str,
    reason: str | None = None
) -> None:
    """Record inventory change in audit log."""
    audit = InventoryAuditLog(
        inventory_id=inventory.id,
        product_id=inventory.product_id,
        vendor_id=inventory.vendor_id,
        location=inventory.location,
        operation=operation,
        quantity_before=qty_before,
        quantity_after=qty_after,
        reserved_before=reserved_before,
        reserved_after=reserved_after,
        adjustment_amount=qty_after - qty_before,
        reason=reason,
        performed_by=user_id,
        performed_by_type=user_type,
    )
    db.add(audit)

Implementation Checklist

Phase 1: Admin API

  • Create app/api/v1/admin/inventory.py
  • Add admin inventory schemas to models/schema/inventory.py
  • Register routes in app/api/v1/admin/__init__.py
  • Write integration tests tests/integration/api/v1/admin/test_inventory.py

Phase 2: Admin UI

  • Create app/templates/admin/inventory.html
  • Create static/admin/js/inventory.js
  • Add route in app/routes/admin.py
  • Add sidebar menu item
  • Update static/admin/js/init-alpine.js for page mapping

Phase 3: Audit Trail (Optional)

  • Create Alembic migration for inventory_audit_log table
  • Create models/database/inventory_audit_log.py
  • Update InventoryService with audit logging
  • Add audit history endpoint
  • Add audit history UI component

Testing Strategy

Unit Tests

  • Admin schema validation tests
  • Audit log creation tests (if implemented)

Integration Tests

  • Admin inventory endpoints with authentication
  • Vendor isolation verification (admin can access any vendor)
  • Audit trail creation on operations

Manual Testing

  • Verify vendor selector works correctly
  • Test adjust modal workflow
  • Confirm pagination with large datasets

Rollback Plan

Each phase is independent:

  1. Phase 1 Rollback: Remove admin inventory routes from __init__.py
  2. Phase 2 Rollback: Remove sidebar link, delete template/JS files
  3. Phase 3 Rollback: Run down migration to drop audit table

Dependencies

  • Existing InventoryService (no changes required)
  • Admin authentication (get_current_admin_api)
  • Vendor model for vendor selector dropdown