fix inventory
This commit is contained in:
@@ -0,0 +1,162 @@
|
||||
# Inventory System Improvement — Implementation Summary
|
||||
|
||||
## Overview
|
||||
|
||||
This implementation applies the first three sprints of the inventory improvement plan:
|
||||
|
||||
1. **Stabilize Inventory Data** — Make movement ledger the source of truth
|
||||
2. **Movement and Audit Cleanup** — Append-only ledger with void/correction support
|
||||
3. **Procurement Integration** — Link purchase orders to inventory movements
|
||||
4. **Low-Stock and Reorder Workflow** — New endpoints and features
|
||||
|
||||
---
|
||||
|
||||
## Changes Made
|
||||
|
||||
### Phase 1: Stabilize Inventory Data
|
||||
|
||||
#### 1. Quantity removed from item updates
|
||||
|
||||
- **`app/Http/Requests/Inventory/InventoryItemUpdateRequest.php`**: Removed `quantity` from validatable fields. Stock changes must go through movements (adjust, audit, etc.).
|
||||
- **`app/Services/Inventory/InventoryItemService.php`**: `filterItemData()` now only includes `quantity` during creation (`$lockedType === null`), not during updates.
|
||||
- **`app/Http/Requests/Inventory/InventoryItemStoreRequest.php`**: Strengthened validation — `type` uses `in:classroom,book,office,kitchen`, `condition` uses `in:good,needs_repair,need_replace,cannot_find`, `category_id` checks `exists:inventory_categories,id`.
|
||||
|
||||
#### 2. Append-only movement ledger with void/correction
|
||||
|
||||
New migration: **`2026_06_11_074503_add_inventory_movement_audit_fields.php`**
|
||||
|
||||
Adds to `inventory_movements`:
|
||||
- `voided_at` (timestamp) — when the movement was voided
|
||||
- `voided_by` (bigint) — user who voided it
|
||||
- `void_reason` (varchar 255) — reason for voiding
|
||||
- `corrects_movement_id` (bigint) — links a correction to the original movement
|
||||
- `source_type` / `source_id` (varchar/bigint) — polymorphic source reference
|
||||
|
||||
**`app/Models/InventoryMovement.php`**: Added fillable fields, casts, scopes (`notVoided`, `voided`), and relationships (`voidedBy`, `correctsMovement`, `corrections`).
|
||||
|
||||
**`app/Services/Inventory/InventoryMovementService.php`**:
|
||||
|
||||
| Method | Status | Notes |
|
||||
|--------|--------|-------|
|
||||
| `create()` | ⚠️ Deprecated | Still works, but marked `@deprecated`. Use `recordMovement()` instead. |
|
||||
| `update()` | ⚠️ Deprecated | Still works. Prevents editing voided movements. |
|
||||
| `delete()` | ⚠️ Deprecated | Prevents deleting movements that have corrections. |
|
||||
| `bulkDelete()` | ⚠️ Deprecated | Same prevention for corrections. |
|
||||
| `recordMovement()` | ✅ Primary method | Append-only, creates `initial` movement automatically. |
|
||||
| `voidMovement()` | ✅ NEW | Marks original as voided + creates reverse correction. |
|
||||
| `correctMovement()` | ✅ NEW | Adds an adjusting entry linked to the original. |
|
||||
| `recalcQuantity()` | ✅ Updated | Excludes voided movements from sum calculations. |
|
||||
| `reconcile()` | ✅ NEW | Compares stored quantity vs movement sum, reports discrepancies. |
|
||||
|
||||
#### 3. Reconciliation command
|
||||
|
||||
**`app/Console/Commands/InventoryReconcile.php`**: `php artisan inventory:reconcile` with `--school-year` and `--fix` options.
|
||||
|
||||
### Phase 2: Movement and Audit Cleanup
|
||||
|
||||
#### Void and correction API endpoints
|
||||
|
||||
**`app/Http/Controllers/Api/Inventory/InventoryMovementController.php`**:
|
||||
- `POST movements/{id}/void` — requires `reason`
|
||||
- `POST movements/{id}/correct` — requires `qty_change` and `reason`
|
||||
|
||||
Both require authentication and return appropriate success/error responses.
|
||||
|
||||
### Phase 3: Procurement Integration
|
||||
|
||||
#### 1. inventory_item_id on purchase_order_items
|
||||
|
||||
New migration: **`2026_06_11_074642_add_inventory_item_id_to_purchase_order_items.php`**
|
||||
|
||||
Adds `inventory_item_id` (unsigned int, nullable) to `purchase_order_items` with an index. This allows linking PO items to inventory items.
|
||||
|
||||
**`app/Models/PurchaseOrderItem.php`**: Added `inventory_item_id` to `$fillable`.
|
||||
|
||||
#### 2. PurchaseOrderReceiveService links to inventory
|
||||
|
||||
**`app/Services/PurchaseOrders/PurchaseOrderReceiveService.php`**:
|
||||
- Injects `InventoryMovementService`
|
||||
- On receiving PO items with `inventory_item_id` set, creates an inventory movement via `recordMovement()`
|
||||
- Continues to update legacy `supplies.qty_on_hand` for backward compatibility
|
||||
|
||||
### Phase 4: Low-Stock and Reorder Workflow
|
||||
|
||||
#### New database fields
|
||||
|
||||
New migration: **`2026_06_11_074621_add_inventory_item_reorder_fields.php`**
|
||||
|
||||
Adds to `inventory_items`:
|
||||
- `preferred_supplier_id`, `last_purchase_price`, `average_unit_cost`
|
||||
- `reorder_point`, `reorder_quantity`, `lead_time_days`
|
||||
- `barcode`, `qr_code`, `asset_tag`
|
||||
|
||||
**`app/Models/InventoryItem.php`**: Added fillable fields, casts, relationships (`preferredSupplier`), and scopes (`lowStock`, `outOfStock`).
|
||||
|
||||
**`app/Http/Resources/Inventory/InventoryItemResource.php`**: Exposes all new fields.
|
||||
|
||||
#### New API endpoints
|
||||
|
||||
**`app/Http/Controllers/Api/Inventory/InventoryController.php`**:
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| `lowStock()` | `GET inventory/low-stock` | Lists items where `quantity <= reorder_point` |
|
||||
| `reorderSuggestions()` | `GET inventory/reorder-suggestions` | Calculates suggested order quantities |
|
||||
| `reorderRequest()` | `POST inventory/items/{id}/reorder-request` | Single item reorder info |
|
||||
| `stockStatus()` | `GET inventory/stock-status` | All items with status: ok/low_stock/out_of_stock |
|
||||
| `dashboard()` | `GET inventory/dashboard` | Summary counts by type, low stock, out of stock, etc. |
|
||||
|
||||
All routes are registered under `api/v1/inventory/` in `routes/api.php`.
|
||||
|
||||
---
|
||||
|
||||
## Database Migrations
|
||||
|
||||
| Migration | Description |
|
||||
|-----------|-------------|
|
||||
| `2026_06_11_074503_add_inventory_movement_audit_fields` | Adds void/correction fields to movements |
|
||||
| `2026_06_11_074621_add_inventory_item_reorder_fields` | Adds reorder/supplier/barcode fields to items |
|
||||
| `2026_06_11_074642_add_inventory_item_id_to_purchase_order_items` | Links PO items to inventory items |
|
||||
|
||||
---
|
||||
|
||||
## Key Backward Compatibility Notes
|
||||
|
||||
1. **Existing endpoints remain unchanged.** `GET/POST/PATCH/DELETE inventory/items`, `inventory/movements`, and all summary/distribution endpoints continue to work with the same payloads.
|
||||
|
||||
2. **Old `create/update/delete` movement methods** work as before, but are marked `@deprecated`. They still enforce new guards (e.g., cannot edit voided movements, cannot delete movements with corrections).
|
||||
|
||||
3. **Purchase order receiving** continues to update `supplies.qty_on_hand` for legacy support. The new inventory movement is added alongside, not instead.
|
||||
|
||||
4. **Voided movements** are excluded from quantity calculations by default. Existing reports that call `recalcQuantity()` automatically benefit.
|
||||
|
||||
---
|
||||
|
||||
## Console Commands
|
||||
|
||||
```bash
|
||||
# Check for discrepancies between stored quantity and movement sum
|
||||
php artisan inventory:reconcile
|
||||
|
||||
# With --fix to auto-correct discrepancies
|
||||
php artisan inventory:reconcile --fix
|
||||
|
||||
# For a specific school year
|
||||
php artisan inventory:reconcile --school-year=2025-2026
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API Reference
|
||||
|
||||
### New Endpoints
|
||||
|
||||
```
|
||||
GET /api/v1/inventory/low-stock
|
||||
GET /api/v1/inventory/reorder-suggestions
|
||||
POST /api/v1/inventory/items/{id}/reorder-request
|
||||
GET /api/v1/inventory/stock-status?type=book
|
||||
GET /api/v1/inventory/dashboard?school_year=2025-2026
|
||||
POST /api/v1/inventory/movements/{id}/void { reason: "..." }
|
||||
POST /api/v1/inventory/movements/{id}/correct { qty_change: -5, reason: "..." }
|
||||
```
|
||||
Reference in New Issue
Block a user