# Task 11: Stock Management Admin Screen

## Goal
Dedicated admin interface to:
1. View real-time stock for any product (calculated from log)
2. Add new stock (restock)
3. Record a manual adjustment (+/−)
4. Record a stock return linked to an order
5. View the full movement history of a product

This is the ONLY place stock numbers change after a product is created.
Checkout writes `sale` entries automatically. Everything else goes through this screen.

## Database
- `product_stock_logs` (read + append)
- `products` (read `low_stock_threshold`; update it here)

## Relations
- ProductStockLog `belongsTo` Product, Order, User (admin)

## Files
- `app/Services/Ecommerce/StockService.php`                          ← core service
- `app/Http/Controllers/Admin/Ecommerce/StockController.php`
- `app/Http/Requests/Ecommerce/RestockRequest.php`
- `app/Http/Requests/Ecommerce/AdjustmentRequest.php`
- `app/Http/Requests/Ecommerce/ReturnStockRequest.php`
- `app/Http/Resources/Ecommerce/StockLogResource.php`
- `resources/views/dashbord/admin/ecommerce/stock/index.blade.php`            ← product stock dashboard
- `resources/views/dashbord/admin/ecommerce/stock/show.blade.php`             ← per-product stock detail + history
- `resources/views/dashbord/admin/ecommerce/stock/restock.blade.php`          ← add new stock form
- `resources/views/dashbord/admin/ecommerce/stock/adjust.blade.php`           ← adjustment form
- `resources/views/dashbord/admin/ecommerce/stock/return.blade.php`           ← return form

## Permissions
- Admin only

## Admin Screen Layout

### Stock Dashboard (index) — all products with stock tracking
```
┌──────────────────────────────────────────────────────────────┐
│  Stock Management                              [Search...]    │
├────────────────┬──────────┬────────┬──────────┬─────────────┤
│ Product        │ Received │  Sold  │ Returned │ Current     │
├────────────────┼──────────┼────────┼──────────┼─────────────┤
│ Product A      │   100    │   97   │    0     │  3  ⚠ LOW   │
│ Product B      │   200    │   50   │    5     │  155  ✓     │
│ Product C      │    50    │   50   │    0     │  0  ✗ OUT   │
├────────────────┴──────────┴────────┴──────────┴─────────────┤
│ [Add Stock] [Adjust] [View History]  per row                 │
└──────────────────────────────────────────────────────────────┘
```

### Per-Product Stock Detail (show)
```
┌─────────────────────────────────────────────────────────────┐
│  Product A — Stock Detail                                    │
│  ─────────────────────────────────────────────────          │
│  Total Received:   100     Total Sold:      97               │
│  Total Returned:     0     Total Adj Out:    0               │
│  ────────────────────────────────────────────               │
│  Current Stock:      3   ⚠ Low Stock (threshold: 5)         │
│  ─────────────────────────────────────────────────          │
│  [+ Add Stock]  [± Adjust]  [↩ Record Return]               │
│                                                              │
│  Movement History                                            │
│  ──────────────────────────────────────────────────────     │
│  Date          │ Type       │ Qty │ Dir │ Note │ Order      │
│  2025-01-01    │ initial    │ 100 │  +  │  —   │  —         │
│  2025-03-15    │ sale       │  20 │  −  │  —   │ #12        │
│  2025-03-20    │ sale       │  50 │  −  │  —   │ #18        │
│  2025-03-22    │ sale       │  27 │  −  │  —   │ #24        │
│  ──────────────────────────────────────────────────────     │
└─────────────────────────────────────────────────────────────┘
```

### Add Stock / Restock Form
```
Product: Product A       Current Stock: 3
─────────────────────────────────────────
Quantity to Add:  [____]   (required, min: 1)
Note:             [____]   (optional, e.g. "Received from supplier")
─────────────────────────────────────────
              [Cancel]  [Add Stock]
```

### Adjustment Form
```
Product: Product A       Current Stock: 3
─────────────────────────────────────────
Direction:  (●) Add (+)   ( ) Remove (−)
Quantity:   [____]   (required, min: 1)
Note:       [____]   (required — must explain reason)
─────────────────────────────────────────
              [Cancel]  [Apply Adjustment]
```

### Return Form
```
Product: Product A       Current Stock: 3
─────────────────────────────────────────
Order:      [Select order that had this product]
Quantity:   [____]  (max: qty in that order)
Note:       [____]  (optional)
─────────────────────────────────────────
              [Cancel]  [Record Return]
```

## StockService — full method list

```php
class StockService
{
    /**
     * Write the first log entry when a product is created with opening stock.
     * Called by ProductService::store() if stock_quantity is set.
     */
    public function recordInitial(Product $product, int $quantity, User $admin): void
    {
        $product->stockLogs()->create([
            'movement_type' => StockMovement::Initial,
            'quantity'      => $quantity,
            'direction'     => StockDirection::In,
            'note'          => 'Opening stock',
            'admin_id'      => $admin->id,
            'created_at'    => now(),
        ]);
    }

    /**
     * Add new stock (restock). Called from Stock Admin Screen.
     */
    public function recordRestock(Product $product, int $quantity, ?string $note, User $admin): void
    {
        $product->stockLogs()->create([
            'movement_type' => StockMovement::Restock,
            'quantity'      => $quantity,
            'direction'     => StockDirection::In,
            'note'          => $note,
            'admin_id'      => $admin->id,
            'created_at'    => now(),
        ]);
    }

    /**
     * Record a sale. Called automatically by CheckoutService per physical order item.
     * Never called manually from the admin screen.
     */
    public function recordSale(Product $product, int $quantity, Order $order): void
    {
        $product->stockLogs()->create([
            'movement_type' => StockMovement::Sale,
            'quantity'      => $quantity,
            'direction'     => StockDirection::Out,
            'order_id'      => $order->id,
            'created_at'    => now(),
        ]);
    }

    /**
     * Record a stock return (reversed sale). Called from Stock Admin Screen.
     * Requires a valid order_id that contains this product.
     */
    public function recordReturn(Product $product, int $quantity, Order $order, ?string $note, User $admin): void
    {
        // Validate: order must contain this product as a physical item
        $orderItem = $order->items()
            ->where('product_id', $product->id)
            ->where('type', 'physical')
            ->firstOrFail();

        // Validate: return quantity cannot exceed what was sold in this order
        if ($quantity > $orderItem->quantity) {
            throw new \InvalidArgumentException('Return quantity exceeds ordered quantity.');
        }

        $product->stockLogs()->create([
            'movement_type' => StockMovement::Return,
            'quantity'      => $quantity,
            'direction'     => StockDirection::In,
            'note'          => $note,
            'order_id'      => $order->id,
            'admin_id'      => $admin->id,
            'created_at'    => now(),
        ]);
    }

    /**
     * Record a manual adjustment. Called from Stock Admin Screen.
     * Note is REQUIRED for adjustments (audit trail).
     */
    public function recordAdjustment(Product $product, int $quantity, string $direction, string $note, User $admin): void
    {
        if (empty($note)) {
            throw new \InvalidArgumentException('Note is required for adjustments.');
        }

        $product->stockLogs()->create([
            'movement_type' => StockMovement::Adjustment,
            'quantity'      => $quantity,
            'direction'     => $direction === '+' ? StockDirection::In : StockDirection::Out,
            'note'          => $note,
            'admin_id'      => $admin->id,
            'created_at'    => now(),
        ]);
    }

    /**
     * Returns paginated movement history for a product (newest first).
     */
    public function getHistory(Product $product, int $perPage = 20): LengthAwarePaginator
    {
        return $product->stockLogs()
            ->with(['order', 'admin'])
            ->latest('created_at')
            ->paginate($perPage);
    }
}
```

## Implementation Steps

1. Implement `StockService` with all five methods above
2. Create `RestockRequest`:
   - `quantity` → required, integer, min: 1
   - `note` → nullable string
3. Create `AdjustmentRequest`:
   - `quantity` → required, integer, min: 1
   - `direction` → required, in: `+`, `−`
   - `note` → required string (mandatory for audit)
4. Create `ReturnStockRequest`:
   - `order_id` → required, exists in orders
   - `quantity` → required, integer, min: 1
   - `note` → nullable string
5. Implement `StockController`:
   - `index()` → all products with stock tracking; show computed columns
   - `show(Product $product)` → stock detail + paginated history
   - `restock(RestockRequest $request, Product $product)` → call `StockService::recordRestock()`
   - `adjust(AdjustmentRequest $request, Product $product)` → call `StockService::recordAdjustment()`
   - `recordReturn(ReturnStockRequest $request, Product $product)` → call `StockService::recordReturn()`
6. Create `StockLogResource`:
   - Fields: `id`, `movement_type`, `direction`, `quantity`, `note`, `created_at`
   - Include: `order.id` (if set), `admin.name` (if set)
7. Build all Blade views per the wireframes above
8. Register routes under admin middleware:
   - `GET    /admin/stock` → `index`
   - `GET    /admin/stock/{product}` → `show`
   - `POST   /admin/stock/{product}/restock` → `restock`
   - `POST   /admin/stock/{product}/adjust` → `adjust`
   - `POST   /admin/stock/{product}/return` → `recordReturn`
9. Write unit tests:
   - Record initial 100 → `currentStock()` = 100
   - Record sale 30 → `currentStock()` = 70
   - Record return 5 → `currentStock()` = 75
   - Record adjustment `−` 10 → `currentStock()` = 65
   - Record restock 50 → `currentStock()` = 115
   - Confirm `stockLabel()` output at each threshold boundary
   - Confirm return validation rejects qty > order qty
