# Indirect Goods Module — Implementation Plan

## Context

The current KCG Inventory system treats all items as "Direct Goods" — materials stored in inventory, issued to projects, and returnable from site. The business needs a separate **Indirect Goods** module for consumable items (safety gear, fuel, small tools) that follow a different lifecycle: received → stored → issued → **consumed** (no returns).

This module will be built as a **completely separate entity** — new tables, new controllers, new views — without touching the existing stable Direct Goods system. The existing "Items" menu label will be renamed to "Direct Goods" (UI only, no code/route changes).

### Key Differences: Direct Goods vs Indirect Goods

| Behavior | Direct Goods (Current) | Indirect Goods (New) |
|----------|----------------------|---------------------|
| Stored in inventory | Yes, always | Yes |
| Goods receipt | Full inspection + LPO flow | Simplified receipt with invoice tracking |
| Issue to project | Approval → dispatch | Configurable approval → issue |
| Returnable from site | Yes (unused items) | **No — consumed/written off only** |
| Tracking method | By batch, division, expiry | **By invoice number + batch** |
| End of lifecycle | Return to warehouse or FOC | **Consumption record (write-off)** |

---

## Phase 1: Database Schema (9 New Tables)

### Table 1: `indirect_goods` (Item Catalog)

| Column | Type | Description |
|--------|------|-------------|
| `id` | bigint PK | Auto-increment |
| `ig_code` | string(50), unique | Auto-generated: `IG-{CategoryCode}{0001}` |
| `ig_description` | string | Item name/description |
| `category_id` | FK → `item_categories` | **Shared** with Direct Goods |
| `unit_id` | FK → `units`, nullable | **Shared** with Direct Goods |
| `unit_of_measure` | string(50), nullable | Fallback text field |
| `min_stock_level` | decimal(10,2) | Minimum threshold |
| `max_stock_level` | decimal(10,2) | Maximum threshold |
| `reorder_point` | decimal(10,2) | Reorder alert level |
| `status` | enum: active/inactive | Default: active |
| `notes` | text, nullable | Additional notes |
| `timestamps` | | created_at, updated_at |

**Indexes:** ig_code, category_id, status

---

### Table 2: `indirect_goods_inventory` (Warehouse Stock)

| Column | Type | Description |
|--------|------|-------------|
| `id` | bigint PK | Auto-increment |
| `indirect_good_id` | FK → `indirect_goods` | Which item |
| `division_id` | FK → `project_divisions`, nullable | Which division |
| `supplier_id` | FK → `suppliers`, nullable | Which supplier |
| `quantity_available` | decimal(10,2) | Current stock |
| `unit_price` | decimal(10,2) | Price per unit |
| `total_value` | decimal(15,2) | quantity × price |
| `location` | string(100), nullable | Warehouse location |
| `batch_number` | string(50), nullable | Batch reference |
| **`invoice_number`** | **string(100), nullable** | **Invoice tracking** |
| **`invoice_date`** | **date, nullable** | **Invoice date** |
| `production_date` | date, nullable | Manufacturing date |
| `expiry_date` | date, nullable | Expiry date |
| `status` | enum: in_stock/out_of_stock/low_stock | Auto-calculated |
| `timestamps` | | created_at, updated_at |

**Indexes:** [indirect_good_id, division_id], batch_number, invoice_number, status

---

### Table 3: `indirect_goods_project_inventory` (Project Stock by Invoice)

| Column | Type | Description |
|--------|------|-------------|
| `id` | bigint PK | Auto-increment |
| `project_id` | FK → `projects` | Which project |
| `indirect_good_id` | FK → `indirect_goods` | Which item |
| **`invoice_number`** | **string(100), nullable** | **Track by invoice** |
| `batch_number` | string(50), nullable | Track by batch |
| `quantity_available` | decimal(10,2) | Current stock at project |
| **`quantity_consumed`** | **decimal(10,2)** | **Write-off tracking** |
| `unit_price` | decimal(10,2), nullable | Unit price |
| `total_value` | decimal(15,2) | Total value |
| `timestamps` | | created_at, updated_at |

**Note:** Unlike Direct Goods `project_inventory` (unique per project+item), this allows **multiple rows per project+item** because each invoice/batch is tracked separately.

**Indexes:** [project_id, indirect_good_id], invoice_number, quantity_available

---

### Table 4: `indirect_goods_incoming` (Incoming Receipts)

| Column | Type | Description |
|--------|------|-------------|
| `id` | bigint PK | Auto-increment |
| `receipt_number` | string, unique | Auto: `IGR-YYYYMMDD-NNNN` |
| `receipt_date` | date | Date of receipt |
| `status` | enum: draft/approved/completed | Workflow status |
| `supplier_id` | FK → `suppliers`, nullable | Supplier |
| **`invoice_number`** | **string(100), nullable** | **Supplier invoice** |
| **`invoice_date`** | **date, nullable** | **Invoice date** |
| `delivery_note_number` | string, nullable | DN reference |
| `project_id` | FK → `projects`, nullable | If direct to project |
| `division_id` | FK → `project_divisions`, nullable | Division |
| `total_amount` | decimal(15,2) | Total value |
| `vat_amount` | decimal(15,2) | VAT amount |
| `vat_rate` | decimal(5,2), default 5.00 | VAT percentage |
| `currency` | enum: AED/USD/EUR | Currency |
| `delivery_file` | string, nullable | Attached file |
| `attached_documents` | json, nullable | Additional documents |
| `received_by` | FK → `users` | Who received |
| `approved_by` | FK → `users`, nullable | Who approved |
| `approved_at` | datetime, nullable | Approval timestamp |
| `notes` | text, nullable | Notes |
| `timestamps` | | created_at, updated_at |
| `deleted_at` | | Soft deletes |

**Indexes:** [status, receipt_date], invoice_number, supplier_id

---

### Table 5: `indirect_goods_incoming_items` (Receipt Line Items)

| Column | Type | Description |
|--------|------|-------------|
| `id` | bigint PK | Auto-increment |
| `indirect_goods_incoming_id` | FK → `indirect_goods_incoming` | Parent receipt |
| `indirect_good_id` | FK → `indirect_goods` | Which item |
| `quantity_ordered` | decimal(10,2), nullable | Ordered quantity |
| `quantity_delivered` | decimal(10,2) | Delivered quantity |
| `quantity_accepted` | decimal(10,2) | Accepted quantity |
| `unit_of_measure` | string, nullable | UOM |
| `unit_price` | decimal(10,2) | Price per unit |
| `total_price` | decimal(15,2) | Total line value |
| `batch_number` | string, nullable | Batch reference |
| `expiry_date` | date, nullable | Expiry |
| `notes` | text, nullable | Notes |
| `timestamps` | | created_at, updated_at |

---

### Table 6: `indirect_goods_requests` (Material Requests)

| Column | Type | Description |
|--------|------|-------------|
| `id` | bigint PK | Auto-increment |
| `request_number` | string, unique | Auto: `IGMR-YYYYMMDD-NNNN` |
| `request_date` | date | Request date |
| `project_id` | FK → `projects` | Requesting project |
| `division_id` | FK → `project_divisions`, nullable | Division |
| `status` | enum | draft/pending_approval/approved/rejected/issued/partially_issued/cancelled |
| **`requires_approval`** | **boolean, default true** | **Configurable per request** |
| `requested_by` | FK → `users` | Requester |
| `approved_by` | FK → `users`, nullable | Approver |
| `approved_at` | datetime, nullable | Approval time |
| `issued_by` | FK → `users`, nullable | Who issued |
| `issued_at` | datetime, nullable | Issue time |
| `receiver_name` | string, nullable | Person receiving |
| `notes` | text, nullable | Notes |
| `rejection_reason` | text, nullable | If rejected |
| `request_file` | string, nullable | Attached file |
| `timestamps` | | created_at, updated_at |
| `deleted_at` | | Soft deletes |

**Configurable Approval:** When `requires_approval = false`, the request skips from `draft` directly to `approved` status. A warehouse setting `indirect_goods_require_approval` controls the default value.

**Indexes:** [status, request_date], project_id

---

### Table 7: `indirect_goods_request_items` (Request Line Items)

| Column | Type | Description |
|--------|------|-------------|
| `id` | bigint PK | Auto-increment |
| `indirect_goods_request_id` | FK → `indirect_goods_requests` | Parent request |
| `indirect_good_id` | FK → `indirect_goods` | Which item |
| `invoice_number` | string(100), nullable | Which invoice/batch to draw from |
| `quantity_requested` | decimal(10,2) | Requested qty |
| `quantity_released` | decimal(10,2), default 0 | Released qty |
| `quantity_balance` | decimal(10,2), default 0 | Remaining to fulfill |
| `unit_price` | decimal(10,2) | Price per unit |
| `total_price` | decimal(15,2) | Total line value |
| `notes` | text, nullable | Notes |
| `timestamps` | | created_at, updated_at |

---

### Table 8: `indirect_goods_stock_movements` (Audit Trail)

| Column | Type | Description |
|--------|------|-------------|
| `id` | bigint PK | Auto-increment |
| `indirect_good_id` | FK → `indirect_goods` | Which item |
| `movement_type` | enum: **in/out/adjustment/consumption** | Movement type |
| `reference_type` | string | 'incoming', 'request', 'consumption', 'adjustment' |
| `reference_id` | unsignedBigInteger | Related record ID |
| `quantity_before` | decimal(10,2) | Stock before |
| `quantity_moved` | decimal(10,2) | Quantity changed |
| `quantity_after` | decimal(10,2) | Stock after |
| `invoice_number` | string(100), nullable | Invoice reference |
| `division_id` | FK → `project_divisions`, nullable | Division |
| `user_id` | FK → `users`, nullable | Who made the change |
| `notes` | text, nullable | Notes |
| `timestamps` | | created_at, updated_at |

**Indexes:** [indirect_good_id, movement_type], invoice_number, created_at

---

### Table 9: `indirect_goods_consumption` (Consumption / Write-Off Records)

| Column | Type | Description |
|--------|------|-------------|
| `id` | bigint PK | Auto-increment |
| `consumption_number` | string, unique | Auto: `IGC-YYYYMMDD-NNNN` |
| `consumption_date` | date | When consumed |
| `project_id` | FK → `projects` | Which project |
| `indirect_good_id` | FK → `indirect_goods` | Which item |
| `invoice_number` | string(100), nullable | Which batch consumed |
| `quantity_consumed` | decimal(10,2) | How much |
| `consumption_type` | enum: **consumed/written_off/damaged/expired** | Type |
| `reason` | text, nullable | Reason for write-off |
| `recorded_by` | FK → `users` | Who recorded |
| `approved_by` | FK → `users`, nullable | Approval if needed |
| `timestamps` | | created_at, updated_at |

**Indexes:** [project_id, indirect_good_id], consumption_date, invoice_number

---

## Phase 2: Models (9 New Files)

All in `app/Models/Warehouse/IndirectGoods/` subdirectory.

| # | Model | Key Logic |
|---|-------|-----------|
| 1 | `IndirectGood.php` | Auto-generate `ig_code` in boot. Shares `ItemCategory` and `Unit` relationships. Scopes: active, search, needsReorder. Computed: total_quantity, average_unit_price |
| 2 | `IndirectGoodsInventory.php` | Auto-calc total_value and status. Method: `updateQuantity()` with weighted average pricing |
| 3 | `IndirectGoodsProjectInventory.php` | Keyed by project + item + invoice_number. Method: `recordConsumption()` |
| 4 | `IndirectGoodsIncoming.php` | Auto-generate receipt_number. Method: `complete()` → `processToInventory()`. SoftDeletes |
| 5 | `IndirectGoodsIncomingItem.php` | Auto-calc total_price. Belongs to incoming and indirect good |
| 6 | `IndirectGoodsRequest.php` | Auto-generate request_number. Methods: `approve()`, `reject()`, `issue()`. SoftDeletes |
| 7 | `IndirectGoodsRequestItem.php` | Auto-set quantity_balance = quantity_requested on create |
| 8 | `IndirectGoodsStockMovement.php` | Movement types: in, out, adjustment, consumption |
| 9 | `IndirectGoodsConsumption.php` | Auto-generate consumption_number. Types: consumed, written_off, damaged, expired |

---

## Phase 3: Controllers & Routes

### 5 New Controllers in `app/Http/Controllers/Warehouse/IndirectGoods/`

#### 1. `IndirectGoodController.php` (Catalog)
| Method | Route | Description |
|--------|-------|-------------|
| `index()` | GET `/indirect-goods/catalog` | DataTable list with category/status filters |
| `store()` | POST `/indirect-goods/catalog` | Create item with auto-code |
| `show($id)` | GET `/indirect-goods/catalog/{id}` | Detail view |
| `edit($id)` | GET `/indirect-goods/catalog/{id}/edit` | Edit form |
| `update($id)` | PUT `/indirect-goods/catalog/{id}` | Update item |
| `destroy($id)` | DELETE `/indirect-goods/catalog/{id}` | Delete item |
| `getDropdown()` | GET `/indirect-goods/catalog/dropdown` | AJAX dropdown |
| `import()` | POST `/indirect-goods/catalog/import` | Excel import |
| `export()` | GET `/indirect-goods/catalog/export` | Excel export |
| `downloadTemplate()` | GET `/indirect-goods/catalog/import-template` | Template download |

#### 2. `IndirectGoodsIncomingController.php` (Receipts)
| Method | Route | Description |
|--------|-------|-------------|
| `index()` | GET `/indirect-goods/incoming` | Receipt list with filters |
| `create()` | GET `/indirect-goods/incoming/create` | Create form (supplier, invoice, items) |
| `store()` | POST `/indirect-goods/incoming` | Save draft receipt |
| `show($receipt)` | GET `/indirect-goods/incoming/{receipt}` | Receipt detail |
| `edit($receipt)` | GET `/indirect-goods/incoming/{receipt}/edit` | Edit receipt |
| `update($receipt)` | PUT `/indirect-goods/incoming/{receipt}` | Update receipt |
| `destroy($receipt)` | DELETE `/indirect-goods/incoming/{receipt}` | Delete receipt |
| `approve($receipt)` | POST `/indirect-goods/incoming/{receipt}/approve` | Approve receipt |
| `complete($receipt)` | POST `/indirect-goods/incoming/{receipt}/complete` | Complete → push to inventory |
| `print($receipt)` | GET `/indirect-goods/incoming/{receipt}/print` | Print receipt |

**`complete()` logic (processToInventory):**
```
For each incoming item:
    1. Upsert indirect_goods_inventory (keyed on indirect_good_id + batch + division)
       → Weighted average pricing if adding to existing batch
    2. If project_id set: create/update indirect_goods_project_inventory (keyed by invoice)
    3. Create indirect_goods_stock_movements (type: 'in')
```

#### 3. `IndirectGoodsRequestController.php` (Material Requests)
| Method | Route | Description |
|--------|-------|-------------|
| `index()` | GET `/indirect-goods/requests` | Request list with status tabs |
| `create()` | GET `/indirect-goods/requests/create` | Create form with approval toggle |
| `store()` | POST `/indirect-goods/requests` | Save request (auto-approve if no approval needed) |
| `show($request)` | GET `/indirect-goods/requests/{request}` | Detail + issue action |
| `pendingApproval()` | GET `/indirect-goods/requests/pending-approval` | Approval queue |
| `approve($request)` | POST `/indirect-goods/requests/{request}/approve` | Approve request |
| `reject($request)` | POST `/indirect-goods/requests/{request}/reject` | Reject with reason |
| `issue($request)` | POST `/indirect-goods/requests/{request}/issue` | Issue materials |
| `print($request)` | GET `/indirect-goods/requests/{request}/print` | Print request |

**`issue()` logic:**
```
For each request item:
    1. Deduct from indirect_goods_inventory (FIFO by expiry date)
    2. Add to indirect_goods_project_inventory (with invoice reference from source batch)
    3. Update quantity_released and quantity_balance on request item
    4. Create indirect_goods_stock_movements (type: 'out')
Update request status to 'issued' (or 'partially_issued')
```

**Configurable approval:**
```
On store():
    If requires_approval = false → status = 'approved' (skip pending_approval)
    If requires_approval = true  → status = 'pending_approval'
Default controlled by warehouse setting: indirect_goods_require_approval
```

#### 4. `IndirectGoodsConsumptionController.php` (Write-Off)
| Method | Route | Description |
|--------|-------|-------------|
| `index()` | GET `/indirect-goods/consumption` | Consumption records list |
| `create()` | GET `/indirect-goods/consumption/create` | Form: project, item, invoice, qty, type, reason |
| `store()` | POST `/indirect-goods/consumption` | Record consumption |
| `show($consumption)` | GET `/indirect-goods/consumption/{consumption}` | Detail view |

**`store()` logic:**
```
1. Validate project has sufficient indirect_goods_project_inventory
2. Deduct from indirect_goods_project_inventory (by invoice_number)
3. Increment quantity_consumed on project inventory record
4. Create indirect_goods_consumption record
5. Create indirect_goods_stock_movements (type: 'consumption')
```

#### 5. `IndirectGoodsInventoryController.php` (Inventory Views)
| Method | Route | Description |
|--------|-------|-------------|
| `index()` | GET `/indirect-goods/inventory` | Warehouse stock DataTable |
| `projectInventory()` | GET `/indirect-goods/inventory/project-inventory` | Project stock by invoice |
| `lowStock()` | GET `/indirect-goods/inventory/low-stock` | Below reorder point |
| `adjustments()` | GET `/indirect-goods/inventory/adjustments` | View adjustments |
| `storeAdjustment()` | POST `/indirect-goods/inventory/adjustments` | Process adjustment |
| `stockMovements($id)` | GET `/indirect-goods/inventory/{id}/stock-movements` | Movement history |

---

## Phase 4: Views

### Directory Structure: `resources/views/warehouse/indirect-goods/`

```
catalog/
    index.blade.php              — DataTable + create modal (mirrors items/index)
    show.blade.php               — Detail view with stock info
    edit.blade.php               — Edit form

incoming/
    index.blade.php              — Receipt list with status filters
    create.blade.php             — Create form (invoice fields prominent)
    show.blade.php               — Receipt detail + approve/complete buttons
    print.blade.php              — Print layout

requests/
    index.blade.php              — Request list with status tabs
    create.blade.php             — Create form with approval toggle checkbox
    show.blade.php               — Detail view + issue action button
    pending-approval.blade.php   — Approval queue
    print.blade.php              — Print layout

consumption/
    index.blade.php              — Write-off records list
    create.blade.php             — Record consumption form
    show.blade.php               — Detail view

inventory/
    index.blade.php              — Warehouse inventory DataTable
    project-inventory.blade.php  — Project stock grouped by invoice
    low-stock.blade.php          — Low stock alerts
    adjustments.blade.php        — Manual adjustment form
    stock-movements.blade.php    — Movement audit trail
```

**Total: 20 new view files**

All views extend `layouts.admin-simple` and use existing components (DataTables, Select2, Material Icons, card-based layout).

---

## Phase 5: Sidebar & UI Changes

### Sidebar Changes (`resources/views/components/warehouse/sidebar.blade.php`)

**1. Rename existing "Items" section:**
- "Items" → **"Direct Goods"**
- "All Items" → **"All Direct Goods"**
- "Add Item" → **"Add Direct Good"**

**2. Add new "INDIRECT GOODS" section:**
```
INDIRECT GOODS (section header — orange accent color)
└── Indirect Goods (expandable menu)
    ├── Catalog                    → /warehouse/indirect-goods/catalog
    ├── Inventory                  → /warehouse/indirect-goods/inventory
    ├── Incoming Receipts          → /warehouse/indirect-goods/incoming
    ├── Material Requests          → /warehouse/indirect-goods/requests
    └── Consumption / Write-Off    → /warehouse/indirect-goods/consumption
```

### Items Index Page Changes (`resources/views/warehouse/items/index.blade.php`)
- Page title: "Items" → **"Direct Goods"**
- Breadcrumb: "Items" → **"Direct Goods"**

**No changes to:** routes, controllers, table names, model names, permission names

---

## Phase 6: Permissions

### 16 New Permissions

| Group | Permissions |
|-------|------------|
| `warehouse_indirect_goods` | `warehouse.indirect-goods.view`, `.create`, `.edit`, `.delete` |
| `warehouse_indirect_incoming` | `warehouse.indirect-incoming.view`, `.create`, `.edit`, `.process` |
| `warehouse_indirect_requests` | `warehouse.indirect-requests.view`, `.create`, `.approve`, `.issue` |
| `warehouse_indirect_inventory` | `warehouse.indirect-inventory.view`, `.adjust` |
| `warehouse_indirect_consumption` | `warehouse.indirect-consumption.view`, `.create` |

### Warehouse Setting
- `indirect_goods_require_approval` — boolean, default: `true`

---

## Document Numbering Summary

| Document | Format | Example |
|----------|--------|---------|
| Indirect Good Code | `IG-{CategoryCode}{0001}` | `IG-CON0001` |
| Incoming Receipt | `IGR-YYYYMMDD-NNNN` | `IGR-20260415-0001` |
| Material Request | `IGMR-YYYYMMDD-NNNN` | `IGMR-20260415-0001` |
| Consumption Record | `IGC-YYYYMMDD-NNNN` | `IGC-20260415-0001` |

---

## Business Flow Summary

### Complete Indirect Goods Lifecycle

```
┌─────────────────────────────────────────────────────────────────┐
│                     INDIRECT GOODS LIFECYCLE                     │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  1. REGISTER          Register indirect good in catalog          │
│     IG-CON0001        (safety gloves, fuel, tools, etc.)         │
│         │                                                        │
│         ▼                                                        │
│  2. RECEIVE           Create incoming receipt with invoice       │
│     IGR-20260415-0001 Supplier + Invoice# + Items + Quantities   │
│         │             Approve → Complete → Push to inventory     │
│         ▼                                                        │
│  3. IN STOCK          Warehouse inventory updated                │
│     Invoice tracked   Quantity, price, batch, invoice all stored │
│         │                                                        │
│         ▼                                                        │
│  4. REQUEST           Project requests indirect goods            │
│     IGMR-20260415-0001 Configurable: with or without approval    │
│         │                                                        │
│         ▼                                                        │
│  5. ISSUE             Deduct from warehouse inventory (FIFO)     │
│     To project        Add to project inventory (by invoice)      │
│         │                                                        │
│         ▼                                                        │
│  6. AT PROJECT        Tracked in project inventory by invoice    │
│     Invoice tracked   quantity_available + quantity_consumed      │
│         │                                                        │
│         ▼                                                        │
│  7. CONSUME           Record consumption / write-off             │
│     IGC-20260415-0001 Type: consumed / written_off / damaged     │
│                       Deducts from project inventory             │
│                       ❌ NO RETURN TO WAREHOUSE                  │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘
```

---

## Implementation Order

| Step | What | Files Created/Modified |
|------|------|----------------------|
| 1 | Database migrations (9 tables) | `database/migrations/2026_04_15_*` (9 files) |
| 2 | Models (9 models) | `app/Models/Warehouse/IndirectGoods/` (9 files) |
| 3 | Permissions seeder | `database/seeders/IndirectGoodsPermissionsSeeder.php` |
| 4 | Sidebar rename + new section | `resources/views/components/warehouse/sidebar.blade.php` |
| 5 | Items index page rename | `resources/views/warehouse/items/index.blade.php` |
| 6 | Routes | `routes/warehouse.php` (append new route group) |
| 7 | Catalog controller + views | 1 controller + 3 views |
| 8 | Incoming controller + views | 1 controller + 4 views |
| 9 | Request controller + views | 1 controller + 5 views |
| 10 | Consumption controller + views | 1 controller + 3 views |
| 11 | Inventory controller + views | 1 controller + 5 views |
| 12 | Import/export, print templates | Utility features |

**Total new files: ~40** (9 migrations + 9 models + 5 controllers + 1 seeder + 20 views + route additions)

---

## Key Reference Files (Patterns to Follow)

| Pattern | Reference File |
|---------|---------------|
| Item registration | `app/Http/Controllers/Warehouse/ItemController.php` |
| Incoming receipt + processToInventory | `app/Models/Warehouse/IncomingOperation.php` |
| Material request + approval + dispatch | `app/Models/Warehouse/OutgoingTransaction.php` |
| DataTable AJAX pattern | `resources/views/warehouse/items/index.blade.php` |
| Sidebar structure | `resources/views/components/warehouse/sidebar.blade.php` |
| Route groups | `routes/warehouse.php` |
| Permission seeder | `database/seeders/WarehouseRolesAndPermissionsSeeder.php` |
