# Habib Cables ERP — Project Context

> **Purpose**: This document is a living reference for all future module and feature development. It captures the architecture, conventions, data model, and workflow patterns established in the codebase. Every new developer or AI session should read this before writing code.

---

## 1. Project Overview

| Field | Value |
|---|---|
| **Project Name** | Habib Cables ERP |
| **Framework** | Laravel 12 (PHP 8.3) |
| **Database** | MySQL 8.0 (`habib_cables`) |
| **Frontend** | TailwindCSS v3, Alpine.js, Vite |
| **Auth** | Laravel Breeze + Spatie Laravel Permission + Laravel Sanctum (API) |
| **Containerization** | Docker Compose (app, web/Nginx, node, redis, db) |
| **Queue Driver** | `database` |
| **Session Driver** | `database` |
| **Cache Driver** | `database` |
| **PDF Generation** | `barryvdh/laravel-dompdf` |
| **Data Tables** | `yajra/laravel-datatables` |
| **Activity Logging** | `spatie/laravel-activitylog` |
| **RBAC** | `spatie/laravel-permission` |
| **Desktop App Sync** | Laravel Sanctum (Push/Pull REST API) |

---

## 2. Technology Stack

### Backend (PHP)
```
PHP        8.3 (FPM)
Laravel    ^12.0
Sanctum    ^4.2
Tinker     ^2.10
```

### Key Composer Packages
| Package | Purpose |
|---|---|
| `barryvdh/laravel-dompdf` | PDF export for reports, invoices, BOMs |
| `spatie/laravel-activitylog` | Audit trail on all major models |
| `spatie/laravel-permission` | Role-based access control (RBAC) |
| `yajra/laravel-datatables` | Server-side DataTables via AJAX |
| `barryvdh/laravel-debugbar` | Dev only — debug toolbar |
| `pestphp/pest` | Testing framework |

### Frontend (JS/CSS)
```
TailwindCSS    ^3.x  (with @tailwindcss/forms)
Alpine.js      ^3.4
Axios          ^1.11
Vite           ^7.x  (laravel-vite-plugin)
```

---

## 3. Infrastructure & Deployment

### Docker Compose Services
| Service | Image | Port | Role |
|---|---|---|---|
| `app` | `php:8.3-fpm` (custom Dockerfile) | 9000 | PHP-FPM application |
| `web` | `nginx:stable-alpine` | 8000→80 | Reverse proxy / static files |
| `node` | `node:20` | 5173 | Vite HMR dev server |
| `redis` | `redis:alpine` | 6379 | Cache / Queue backend |
| `db` | `mysql:8.0` | 3306 | Primary database |

### PHP Extensions Installed in Container
`pdo_mysql`, `pdo_sqlite`, `gd`, `intl`, `zip`, `bcmath`, `redis` (PECL)

### Persistent Volume
`mysql_data` — MySQL data survives container restarts.

### Local Development
```bash
composer run dev   # Starts: php artisan serve + queue:listen + pail + npm run dev (concurrently)
```

---

## 4. Application Architecture

### Directory Layout
```
app/
├── Console/          # Artisan commands
├── Events/           # Domain events
├── Helpers/          # Additional helper classes
├── Http/
│   ├── Controllers/  # Web + API controllers
│   │   └── Api/      # JSON API controllers (Desktop sync, QC, Contacts, Shop, Payment)
│   ├── Middleware/   # CheckActiveUser, CheckRole, RequestLogging, VerifyCsrf
│   └── Requests/     # Form Request validation classes (per module)
├── Listeners/        # Event listeners
├── Mail/             # Mailable classes
├── Models/           # Eloquent models (89 files)
├── Observers/        # Model observers
├── Providers/        # Service providers
├── Services/         # Business logic services (14 files)
├── Traits/           # Reusable traits (e.g., HasStatusManagement)
├── View/             # View composers
└── helpers.php       # Global helper functions (autoloaded)
```

### Key Architectural Patterns

#### Services Layer
Heavy business logic lives in `app/Services/`. Controllers stay thin and delegate to services.

| Service | Responsibility |
|---|---|
| `GrnService` | GRN creation, stock posting, IGP workflow |
| `ManufacturingService` | Run lifecycle, material issue, FG receipt |
| `ManufacturingBOMService` | BOM management, cost data |
| `InventoryService` | Stock balance updates, movements |
| `QcService` | Quality check workflow, stock addition |
| `PurchaseOrderService` | PO creation from PR, status workflow |
| `PurchaseRequestService` | PR lifecycle, approval flow |
| `ContactService` | Contact + Account unified creation |
| `WarehouseService` | WMS operations, bin management |
| `StockAccountingService` | Stock ledger, weighted average price |
| `NumberGenerationService` | Auto-numbered document codes (PR-, PO-, GRN-, etc.) |
| `GatePassService` | Inward/outward gate pass processing |
| `ChartOfAccountService` | COA hierarchy management |
| `CostCenterService` | Cost center history and assignments |

#### Traits
- **`HasStatusManagement`** — Applied to transactional models. Provides standardized `canEdit()`, `canDelete()`, `canCancel()`, `canConfirm()`, and `canComplete()` methods based on status constants defined per model.

#### Global Helpers (`app/helpers.php`)
Contains shared formatting functions used across views and controllers:
- `format_currency()` — Consistent currency display
- `get_stock_status_badge()` — Returns badge HTML based on stock vs reorder level

---

## 5. Database & Data Model

### Core Entity: `Account` (Unified Entity Model)
The `Account` model is the **central polymorphic entity** of this ERP. A single `accounts` table stores multiple entity types distinguished by `model_type`.

| `model_type` Value | Entity Represented |
|---|---|
| `Item` | Inventory item / product |
| `customer` | Customer account |
| `supplier` | Supplier account |
| `employee` | Employee |
| `shop` | Retail/distribution shop |
| `contact` | Generic contact |
| `user` | System user |

**Key relationships on `Account`:**
- `belongsTo` `ChartOfAccount` (via `chart_of_account_id` and `sub_chart_of_account_id`)
- `belongsTo` `Uom` — Unit of Measure
- `hasMany` `AccountTransaction` — Debit/credit ledger entries
- `hasMany` `WeightedAveragePriceHistory` — WAP history for items
- `hasMany` `ShopProduct` — Shop item listings

**Account uses `SoftDeletes` and `LogsActivity`.**

### Accounting Structure
```
ChartOfAccount (parent_id self-referential — hierarchical)
    └── type: asset | liability | equity | income | expense
    └── Account (many per COA node)
        └── AccountTransaction (debit | credit entries)
```
Balance calculation respects accounting rules: assets/expenses increase on debit; liabilities/equity/income increase on credit.

### Item System (Legacy — partially superseded by Account)
The `Item` model (`items` table) still exists for the procurement and manufacturing modules. Items map to `Account` entries via `model_type = 'Item'`. The `accounts` table has item-specific columns (`sku`, `barcode`, `reorder_level`, `weighted_average_price`).

**Item status lifecycle:** `draft` → `active` → `inactive` / `discontinued`

### Procurement Document Flow
```
Purchase Request (PR)
    └── Purchase Order (PO)
        └── Inward Gate Pass (IGP / Gatepass)
            └── QC (Quality Control)
                └── GRN (Goods Received Note)
                    └── Stock Posted to Warehouse
```

### Stock System
| Table | Purpose |
|---|---|
| `stocks` | Current stock summary per item/warehouse |
| `stock_ledgers` | Full chronological stock movement ledger |
| `stock_balances` | Balance per item+warehouse+bin |
| `stock_pieces` | Individual piece-level tracking |
| `warehouse_stock_ledger` | WMS-level ledger |
| `warehouse_current_stock` | WMS current stock snapshot |
| `stock_transfers` / `stock_transfer_items` | Inter-warehouse transfers |

### Manufacturing Data Flow
```
ManufacturingBOM (Bill of Materials)
    ├── ManufacturingBOMStep (process steps)
    ├── ManufacturingBOMIngredient (raw material inputs)
    └── ManufacturingRun (production execution)
        ├── Status: planned → received and manufacturing → completed
        ├── ManufacturingBOMHistory (snapshot of BOM at run time)
        ├── BOMPickList (warehouse material issue)
        ├── ManufacturingRunBOMDetail (actual vs planned per ingredient)
        ├── ManufacturingScrapBreakdown
        └── StockPiece / WarehousePiece (piece-tracked output)
```

### Shop Module Data Flow
```
Shop (Account with model_type='shop')
├── ShopProduct → ShopProductStock, ShopProductPrice
├── ShopContact → ShopContactAccountTransaction
├── ShopInvoice → ShopInvoiceItem
├── ShopInvoiceReturn → ShopInvoiceReturnItem
├── ShopGoodsReceipt → ShopGoodsReceiptItem
└── ShopStock
```

### Outward Logistics
```
FactoryInvoice (Factory POS)
    └── OutwardGatePass
        └── DeliveryReceipt → DeliveryReceiptItem
```

---

## 6. Module Inventory

| Module | Controller(s) | Views | Key Models |
|---|---|---|---|
| **Items** | `ItemController`, `ItemVariationController`, `ItemCategoryController`, `ItemSupplierController` | `items/`, `item_variations/`, `item_categories/`, `item_suppliers/` | `Item`, `ItemVariation`, `ItemCategory`, `ItemSupplier`, `Uom` |
| **Contacts** | `ContactController` | `contacts/` | `Contact` |
| **Customers** | `CustomerController` | `customers/` | `Account` (model_type=customer) |
| **Suppliers** | `SupplierController` | `suppliers/` | `Account` (model_type=supplier) |
| **Purchase Requests** | `PurchaseRequestController` | `purchase_requests/` | `PurchaseRequest`, `PurchaseRequestItem` |
| **Purchase Orders** | `PurchaseOrderController` | `purchase_orders/` | `PurchaseOrder`, `PurchaseOrderItem` |
| **Inward Gate Passes** | `GatepassController` | `gatepasses/` | `Grn` (IGP), `GrnItem` |
| **GRNs** | `GrnController` | `grns/` | `Grn`, `GrnItem` |
| **Quality Control** | `QcController` | `qcs/` | `Qc`, `QcItem` |
| **Inspection Requests** | `InspectionRequestController` | — | `InspectionRequest`, `InspectionResult` |
| **Warehouses / WMS** | `WarehouseController`, `WmsController`, `WarehouseBinController`, `WarehousePieceController` | `warehouses/`, `warehouse_bins/`, `warehouse-pieces/`, `wms/` | `Warehouse`, `WarehouseBin`, `WarehouseCurrentStock`, `WarehouseStockLedger`, `WarehousePiece` |
| **Stock** | `StockController`, `StockLedgerController`, `StockTransferController` | `stocks/`, `stock-ledgers/`, `stock_transfers/` | `Stock`, `StockLedger`, `StockBalance`, `StockTransfer` |
| **Manufacturing BOMs** | `ManufacturingBOMController` | `manufacturing/` (boms) | `ManufacturingBOM`, `ManufacturingBOMStep`, `ManufacturingBOMIngredient` |
| **Manufacturing Runs** | `ManufacturingController` | `manufacturing/` | `ManufacturingRun`, `BOMPickList`, `CostOfProduction` |
| **Cost Centers** | `CostCenterController` (API) | — | `CostCenter`, `CostCenterHistory`, `CostCenterHistoryDetail` |
| **Factory POS / Outward** | `FactoryPOSController`, `OutwardGatePassController` | `factory-pos/`, `outward-gate-passes/` | `FactoryInvoice`, `FactoryInvoiceItem`, `GatePass`, `GatePassItem` |
| **Delivery Receipts** | `DeliveryReceiptController` | `delivery-receipts/` | `DeliveryReceipt`, `DeliveryReceiptItem` |
| **Shops** | `ShopController`, `ShopProductController`, `ShopContactController`, `ShopTransactionController` | `shops/`, `shop/` | `Shop`, `ShopProduct`, `ShopInvoice`, `ShopContact` |
| **Accounting** | `ChartOfAccountController`, `AccountController`, `AccountTransactionController`, `ManualTransactionController`, `AccountingReportController` | `chart_of_accounts/`, `accounts/`, `account_transactions/`, `accounting/` | `ChartOfAccount`, `Account`, `AccountTransaction` |
| **Reports** | `ReportController`, `AccountingReportController` | `reports/`, `accounting/` | Various |
| **Employees** | `EmployeeController` | `employees/` | `Account` (model_type=employee) |
| **Machines** | `MachineController` | `machines/` | `Machine` |
| **UOMs** | `UomController` | `uoms/` | `Uom` |
| **Locations** | `LocationController` | `locations/` | `Location` |
| **Logging** | `LoggingController` | `logging/` | `activity_log` table |
| **Desktop Sync API** | `Api/Desktop/*` | — | Multiple (Push/Pull) |

---

## 7. Routing Conventions

All web routes are in `routes/web.php`. Routes follow these patterns:

### Authentication Guard
```php
// All authenticated routes use this middleware stack:
Route::middleware(['auth', 'verified', 'request.logging', 'check.active.user'])->group(...)
```

### Resource Routes + Custom Actions
Standard pattern: `Route::resource(...)` extended with custom action routes defined **before** the resource registration to avoid conflicts.

```php
// Custom actions first
Route::get('purchase-requests/approval', [..., 'approvalIndex'])->name('purchase-requests.approval');
Route::post('purchase-requests/{purchaseRequest}/approve', [..., 'approve'])->name('purchase-requests.approve');
// Resource last
Route::resource('purchase-requests', PurchaseRequestController::class);
```

### AJAX / API Routes (Web-side)
Many controllers expose data-fetch endpoints for DataTables and Select2:
- `GET /items/search` — Select2 search
- `GET /stocks/get-data` — DataTables server-side data
- `GET /warehouses/current-stock/data` — DataTables

### Desktop Sync API
Prefix: `/api/desktop/`  
Auth: `auth:sanctum` (Sanctum token-based)  
Pattern: Push (local→server) and Pull (server→local) operations.

```
POST /api/desktop/authenticate    — get Sanctum token
GET  /api/desktop/sync/pull/*     — pull master data
POST /api/desktop/sync/push/*     — push local changes
```

---

## 8. Middleware

| Middleware | Class | Purpose |
|---|---|---|
| `check.active.user` | `CheckActiveUser` | Blocks login for deactivated users |
| `check.role` | `CheckRole` | Role-based route guard (Spatie) |
| `request.logging` | `RequestLoggingMiddleware` | Logs all authenticated requests |
| `VerifyCsrfToken` | `VerifyCsrfToken` | Standard CSRF (with exclusions for API routes) |

---

## 9. Number Generation

Document reference numbers are auto-generated by `NumberGenerationService`. Follow the established prefix pattern when adding new document types:

| Document | Example Code |
|---|---|
| Purchase Request | `PR-YYYY-XXXXX` |
| Purchase Order | `PO-YYYY-XXXXX` |
| GRN | `GRN-YYYY-XXXXX` |
| Manufacturing Run | `M-XXXXX` |
| Factory Invoice | Prefix defined in service |

---

## 10. Status Workflow Pattern

All transactional models use the `HasStatusManagement` trait. Define status constants as class constants and override the following protected methods:

```php
const STATUS_DRAFT     = 'draft';
const STATUS_SUBMITTED = 'submitted';
const STATUS_APPROVED  = 'approved';
const STATUS_CANCELLED = 'cancelled';

protected function getEditableStatuses(): array    { return [self::STATUS_DRAFT]; }
protected function getDeletableStatuses(): array   { return [self::STATUS_DRAFT]; }
protected function getCancellableStatuses(): array { return [self::STATUS_DRAFT, self::STATUS_SUBMITTED]; }
protected function getConfirmedStatuses(): array   { return [self::STATUS_APPROVED]; }
```

Status transitions are enforced in the controller before persisting — never transition silently.

---

## 11. Activity Logging Convention

Every major model uses `spatie/laravel-activitylog`. Standard implementation:

```php
use Spatie\Activitylog\Traits\LogsActivity;
use Spatie\Activitylog\LogOptions;

class MyModel extends Model
{
    use LogsActivity;

    public function getActivitylogOptions(): LogOptions
    {
        return LogOptions::defaults()
            ->useLogName('my_model')  // snake_case model name
            ->logFillable()
            ->logOnlyDirty()
            ->dontSubmitEmptyLogs();
    }
}
```

---

## 12. Frontend Conventions

### Layout
Views extend a common layout. Check `resources/views/layouts/` for the base template.

### Alpine.js
Used for reactive UI behavior without a full SPA framework. Components are declared inline with `x-data`.

### DataTables (Yajra)
Server-side DataTables are the standard for index pages. Pattern:
1. Controller exposes a `getData()` method returning `DataTables::of(query)->...->make(true)`.
2. Route named `{resource}.get-data` or `{resource}.data`.
3. View initializes `$('#table').DataTable({ ajax: '{{ route(...) }}', ... })`.

### Select2
Used for all searchable dropdowns. Always backed by a `search` controller action returning `{ id, text }` JSON.

### PDF Export
Use `barryvdh/laravel-dompdf`. PDF views live in `resources/views/pdf/`. Controllers return:
```php
$pdf = Pdf::loadView('pdf.my-document', $data);
return $pdf->download('filename.pdf');
// or ->stream() for browser preview
```

---

## 13. API Layer

### Internal Web API (within authenticated session)
Prefix `/api/` within `auth` middleware group. Used by:
- QC module (IGP search, bin lookups)
- Contacts (by type, search)
- Chart of Accounts (hierarchy)
- Cost Centers (assignments)
- Payment processing

### Desktop Sync API (`/api/desktop/`)
Sanctum token auth. Used by an offline desktop application to sync:
- **Push**: invoices, customers, stock, contacts, goods receipts
- **Pull**: products, stock, customer info, invoices

### Shop API (`/api/shop/`)
Returns contact info, invoice PDFs, transaction data for shop-facing interfaces.

---

## 14. Key Conventions to Follow

### When Adding a New Module

1. **Migration** — Create in `database/migrations/` with timestamp prefix. Follow existing naming: `YYYY_MM_DD_NNNNNN_create_{table}_table.php`.
2. **Model** — In `app/Models/`. Add `LogsActivity` and `HasStatusManagement` traits where applicable. Define `$fillable`, `$casts`, relationships, scopes, and `getActivitylogOptions()`.
3. **Service** — In `app/Services/`. Inject via constructor in controller.
4. **Form Request** — In `app/Http/Requests/{Module}/`. One request class per action (Store/Update).
5. **Controller** — In `app/Http/Controllers/`. Keep thin; call the service. Always add `getData()` for DataTables and `search()` for Select2.
6. **Routes** — Add to `routes/web.php` inside the auth middleware group. Custom actions before `Route::resource()`.
7. **Views** — In `resources/views/{module_name}/` — create `index.blade.php`, `create.blade.php`, `edit.blade.php`, `show.blade.php`.
8. **Number Generation** — Add a new prefix to `NumberGenerationService` if the document needs a reference code.

### Naming Conventions
| Thing | Convention | Example |
|---|---|---|
| Table | `snake_case`, plural | `purchase_orders` |
| Model | `PascalCase`, singular | `PurchaseOrder` |
| Controller | `PascalCase` + `Controller` | `PurchaseOrderController` |
| Route name | `kebab-case`, resource-style | `purchase-orders.create` |
| Service | `PascalCase` + `Service` | `PurchaseOrderService` |
| Blade view | `snake_case` | `purchase_orders/create.blade.php` |
| Migration | timestamp + description | `2026_05_14_000001_create_...` |
| Log name | `snake_case` model | `manufacturing_run` |

### Account Model Usage
When adding a new entity type (e.g., a new party type), consider extending `Account` via a new `model_type` value rather than creating a standalone model. This keeps it integrated with the accounting ledger automatically.

### Soft Deletes
Use `SoftDeletes` on any model where data must be preserved for audit purposes (transactions, accounts, important masters). Check existing models before deciding.

---

## 15. Known Patterns & Gotchas

- **Duplicate/backup controllers** — Several controllers have `copy.php` and `_backup.php` variants (e.g., `DeliveryReceiptController copy.php`). These are safe to ignore; the primary file (without suffix) is active.
- **Gate Pass consolidation** — There are multiple gate pass view folders (`gate_passes/`, `gatepasses/`, `gatepasses1/`). The active module uses `gatepasses/` with `GatepassController`. Outward uses `outward-gate-passes/`.
- **IGP vs GRN** — "IGP" (Inward Gate Pass) and "GRN" share some model overlap. IGP is the physical arrival document; GRN is created after QC and triggers stock posting.
- **Account model balance** — `getBalanceAttribute()` is a computed attribute summing transactions, not a stored column. Avoid using `->balance` in large list queries without eager loading transactions — it will N+1.
- **`model_type` casing** — `Item` is capitalized (`'Item'`), while others are lowercase (`'customer'`, `'supplier'`). Maintain this inconsistency as-is or risk breaking existing queries.
- **Queue** — Uses `database` driver. Always run `php artisan queue:listen` in development (handled by `composer run dev`).

---

## 16. Testing

- Framework: **PestPHP** with `pestphp/pest-plugin-laravel`
- Tests live in `tests/`
- Run: `composer test` (clears config first, then runs Pest)
- Browser tests: `laravel/dusk` is installed

---

*Last updated: 2026-05-13 | Generated from codebase analysis*
