# Full ERP Features Execution Plan
## Every Missing Feature — Detailed Implementation Blueprint

**Total Scope:** 70+ features across 8 phases
**Estimated Timeline:** 30-39 weeks
**Migration Phase Numbering:** Starting at Phase_56 (after existing Phase_55)

---

# ═══════════════════════════════════════════════════════════════════════
# PHASE 1: QUICK WINS (Week 1-3)
# ═══════════════════════════════════════════════════════════════════════

## 1.1 — Customer & Supplier Credit Limits

### Database Changes
```
Migration: Phase_56_001_add_credit_limits.php

ALTER TABLE members ADD COLUMN credit_limit DECIMAL(15,2) NOT NULL DEFAULT 0.00 AFTER membership_status;
ALTER TABLE members ADD COLUMN credit_balance DECIMAL(15,2) NOT NULL DEFAULT 0.00 AFTER credit_limit;

ALTER TABLE suppliers ADD COLUMN credit_limit DECIMAL(15,2) NOT NULL DEFAULT 0.00 AFTER email;
ALTER TABLE suppliers ADD COLUMN credit_balance DECIMAL(15,2) NOT NULL DEFAULT 0.00 AFTER credit_limit;
```

### Files to Create/Modify
```
MODIFY: app/Modules/Members/Controllers/MemberController.php
  - Add credit_limit to store/update validation and saving
  - Add credit_balance display on show view

MODIFY: app/Modules/Members/Views/show.php
  - Add credit limit display section
  - Show: حد الائتمان | التحصيلات النقدية | الحد المسموح به

MODIFY: app/Modules/Inventory/Controllers/SupplierController.php
  - Add credit_limit field to store/update

MODIFY: app/Modules/Inventory/Views/suppliers/show.php
  - Display credit information

CREATE: app/Modules/Members/Services/CreditLimitService.php
  Methods:
  - checkMemberCredit(int $memberId, float $amount): bool
  - getMemberAvailableCredit(int $memberId): float
  - updateMemberCreditBalance(int $memberId): void
  - checkSupplierCredit(int $supplierId, float $amount): bool
  - getSupplierAvailableCredit(int $supplierId): float

MODIFY: app/Modules/Sales/Controllers/SaleController.php
  - In store(): call CreditLimitService::checkMemberCredit() before processing
  - If exceeds: flash error "تم تجاوز حد الائتمان المسموح به" and redirect back

MODIFY: app/Modules/Procurement/Controllers/VendorInvoiceController.php
  - In store(): call CreditLimitService::checkSupplierCredit()
```

### Event Integration
```
MODIFY: app/Modules/Accounting/bootstrap.php
  - Listen to 'payment.completed' → recalculate member credit_balance
  - Listen to 'vendor_payment.completed' → recalculate supplier credit_balance
```

---

## 1.2 — Purchase Order Delivery Tracking

### Database Changes
```
Migration: Phase_56_002_add_po_delivery_tracking.php

ALTER TABLE purchase_order_items ADD COLUMN expected_delivery_date DATE NULL AFTER quantity;
ALTER TABLE purchase_order_items ADD COLUMN quantity_received DECIMAL(15,3) NOT NULL DEFAULT 0 AFTER expected_delivery_date;
ALTER TABLE purchase_order_items ADD COLUMN delivery_percentage DECIMAL(5,2) GENERATED ALWAYS AS (
  CASE WHEN quantity > 0 THEN ROUND((quantity_received / quantity) * 100, 2) ELSE 0 END
) STORED;
```

### Files to Create/Modify
```
MODIFY: app/Modules/Inventory/Controllers/PurchaseOrderController.php
  - Add expected_delivery_date to store/receiveForm
  - Update quantity_received on receive action

MODIFY: app/Modules/Inventory/Views/purchase-orders/create.php
  - Add date picker per line item for expected delivery

MODIFY: app/Modules/Procurement/Controllers/ProcurementReportController.php
  - Add method: overdueDeliveries()

CREATE: app/Modules/Procurement/Views/reports/overdue-deliveries.php
  - Table: PO#, Supplier, Item, Expected Date, Days Overdue, Qty Ordered, Qty Received, %

MODIFY: app/Modules/Procurement/Routes.php
  - Add: ['GET', '/procurement/reports/overdue-deliveries', 'Procurement\Controllers\ProcurementReportController@overdueDeliveries', ['auth'], 'procurement.report']
```

---

## 1.3 — PO Balance Report (Ordered vs Received vs Remaining)

### Files to Create/Modify
```
MODIFY: app/Modules/Procurement/Controllers/ProcurementReportController.php
  - Add method: poBalance()
  - Query: SELECT po.*, poi.*, s.name as supplier_name, ii.name_ar as item_name
    FROM purchase_orders po
    JOIN purchase_order_items poi ON po.id = poi.purchase_order_id
    JOIN suppliers s ON po.supplier_id = s.id
    JOIN inventory_items ii ON poi.item_id = ii.id
    WHERE poi.quantity > poi.quantity_received
    GROUP BY po.id

CREATE: app/Modules/Procurement/Views/reports/po-balance.php
  - Filters: supplier, date range, item group
  - Table: PO#, Date, Item, Qty Ordered, Qty Received, Remaining, Delivery %
  - Summary row with totals

MODIFY: app/Modules/Procurement/Routes.php
  - Add route for PO balance report
```

---

## 1.4 — Customer Account Statement

### Files to Create/Modify
```
CREATE: app/Modules/Members/Controllers/MemberStatementController.php
  namespace App\Modules\Members\Controllers;
  
  Methods:
  - statement(Request $request, int $id)
    → Fetches all financial movements for member:
      - Payments (from payments table)
      - Sales (from sales table)
      - Fines (from fines table)
      - Installments (from installment_schedule)
      - Subscriptions (from subscriptions)
    → Merges, sorts by date
    → Calculates running balance (debit/credit)
    → Returns detailed or summary view based on ?type=detailed|summary

CREATE: app/Modules/Members/Views/statement.php
  - Header: Member name, code, date range filter
  - Table (detailed): التاريخ | نوع المستند | رقم المستند | البيان | مدين | دائن | الرصيد
  - Table (summary): نوع المستند | عدد | إجمالي مدين | إجمالي دائن
  - Footer: الإجمالي

MODIFY: app/Modules/Members/Routes.php
  - Add: ['GET', '/members/{id:\d+}/statement', 'Members\Controllers\MemberStatementController@statement', ['auth'], 'member.view']

MODIFY: app/Modules/Members/bootstrap.php
  - Add permission: 'member.statement' => ['ar' => 'كشف حساب العضو', 'en' => 'Member Statement']
```

---

## 1.5 — Supplier Account Statement

### Files to Create/Modify
```
CREATE: app/Modules/Inventory/Controllers/SupplierStatementController.php
  namespace App\Modules\Inventory\Controllers;
  
  Methods:
  - statement(Request $request, int $id)
    → Fetches all movements for supplier:
      - Purchase Orders (from purchase_orders)
      - GRNs (from goods_received_notes)
      - Vendor Invoices (from vendor_invoices)
      - Vendor Payments (from vendor_payments)
      - Returns to Vendor (from returns_to_vendor)
    → Running balance calculation
    → Detailed and summary modes

CREATE: app/Modules/Inventory/Views/suppliers/statement.php
  - Same format as member statement but for suppliers
  - التاريخ | نوع المستند | رقم المستند | البيان | مدين | دائن | الرصيد

MODIFY: app/Modules/Inventory/Routes.php
  - Add: ['GET', '/inventory/suppliers/{id:\d+}/statement', 'Inventory\Controllers\SupplierStatementController@statement', ['auth'], 'supplier.view']
```

---

## 1.6 — Sales Returns with Automatic Warehouse Update

### Files to Create/Modify
```
MODIFY: app/Modules/Sales/Controllers/SaleController.php
  - In refund() method: after creating sale_refund record, call StockService to add items back

MODIFY: app/Modules/Sales/Controllers/SaleController.php::refund()
  Add after refund record creation:
  ```php
  // Return items to stock
  foreach ($refundItems as $item) {
      StockService::adjustStock(
          itemId: $item['item_id'],
          warehouseId: $sale['warehouse_id'],
          quantity: $item['quantity'],
          type: 'in',
          reference: 'sale_refund',
          referenceId: $refundId,
          notes: "مرتجع مبيعات - فاتورة #{$sale['invoice_number']}"
      );
  }
  EventBus::dispatch('sale.refunded', ['sale_id' => $saleId, 'refund_id' => $refundId, 'items' => $refundItems]);
  ```

MODIFY: app/Modules/Inventory/Services/StockService.php
  - Ensure adjustStock method handles 'sale_refund' reference type
```

---

## 1.7 — Inventory Opening Balances

### Files to Create/Modify
```
CREATE: app/Modules/Inventory/Controllers/OpeningBalanceController.php
  namespace App\Modules\Inventory\Controllers;
  
  Methods:
  - index(Request $request)
    → Show form: select warehouse, list all items with quantity input
  - store(Request $request)
    → For each item with quantity > 0:
      - Create stock movement (type: 'opening_balance')
      - Update item_warehouse_stocks
    → Flash success

CREATE: app/Modules/Inventory/Views/opening-balance/index.php
  - Warehouse selector dropdown
  - Items table: كود الصنف | اسم الصنف | الوحدة | الرصيد الحالي | رصيد أول المدة (input)
  - Submit button

MODIFY: app/Modules/Inventory/Routes.php
  - ['GET', '/inventory/opening-balance', ...]
  - ['POST', '/inventory/opening-balance', ...]

MODIFY: app/Modules/Inventory/bootstrap.php
  - Permission: 'inventory.opening_balance' => ['ar' => 'أرصدة أول المدة', 'en' => 'Opening Balances']
```

---

## 1.8 — Asset Custody & Location Tracking

### Database Changes
```
Migration: Phase_56_003_add_asset_custody_tracking.php

ALTER TABLE asset_registers ADD COLUMN custodian_employee_id INT UNSIGNED NULL AFTER depreciation_start;
ALTER TABLE asset_registers ADD COLUMN site_location VARCHAR(255) NULL AFTER custodian_employee_id;

CREATE TABLE asset_custody_history (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    asset_id INT UNSIGNED NOT NULL,
    from_employee_id INT UNSIGNED NULL,
    to_employee_id INT UNSIGNED NULL,
    from_location VARCHAR(255) NULL,
    to_location VARCHAR(255) NULL,
    transfer_date DATE NOT NULL,
    notes TEXT NULL,
    created_by INT UNSIGNED NOT NULL,
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    KEY idx_asset (asset_id),
    KEY idx_employee (to_employee_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
```

### Files to Create/Modify
```
MODIFY: app/Modules/Inventory/Controllers/AssetController.php
  - Add method: transferCustody(Request $request, int $id)
  - Add method: custodyHistory(Request $request, int $id)

CREATE: app/Modules/Inventory/Views/assets/transfer-custody.php
  - Form: from employee (auto-filled), to employee (dropdown), location, date, notes

CREATE: app/Modules/Inventory/Views/assets/custody-history.php
  - Table: Date | From | To | Location | Notes | Done By

MODIFY: app/Modules/Inventory/Routes.php
  - ['GET', '/inventory/assets/{id:\d+}/transfer-custody', ...]
  - ['POST', '/inventory/assets/{id:\d+}/transfer-custody', ...]
  - ['GET', '/inventory/assets/{id:\d+}/custody-history', ...]
```

---

# ═══════════════════════════════════════════════════════════════════════
# PHASE 2: FINANCIAL & BANKING ENHANCEMENTS (Week 4-7)
# ═══════════════════════════════════════════════════════════════════════

## 2.1 — Check Lifecycle Status Tracking

### Database Changes
```
Migration: Phase_57_001_check_lifecycle_tracking.php

CREATE TABLE instrument_status_history (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    instrument_id INT UNSIGNED NOT NULL,
    from_status VARCHAR(50) NULL,
    to_status VARCHAR(50) NOT NULL,
    transition_date DATE NOT NULL,
    notes TEXT NULL,
    created_by INT UNSIGNED NOT NULL,
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    KEY idx_instrument (instrument_id),
    KEY idx_date (transition_date)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

ALTER TABLE negotiable_instruments ADD COLUMN deposit_date DATE NULL AFTER maturity_date;
ALTER TABLE negotiable_instruments ADD COLUMN collection_date DATE NULL AFTER deposit_date;
ALTER TABLE negotiable_instruments ADD COLUMN bounce_date DATE NULL AFTER collection_date;
ALTER TABLE negotiable_instruments ADD COLUMN endorsed_to VARCHAR(255) NULL AFTER bounce_date;
ALTER TABLE negotiable_instruments ADD COLUMN endorsement_date DATE NULL AFTER endorsed_to;
```

### Status Workflow
```
ISSUED → DEPOSITED → COLLECTED (success path)
ISSUED → DEPOSITED → BOUNCED (failure path)
ISSUED → ENDORSED (transfer to another party)
DEPOSITED → RETURNED (bank returns before maturity)
```

### Files to Create/Modify
```
CREATE: app/Modules/Accounting/Services/InstrumentLifecycleService.php
  namespace App\Modules\Accounting\Services;
  
  Methods:
  - deposit(int $instrumentId, string $depositDate, ?string $notes): void
    → Validate status = 'issued' or 'active'
    → Update status to 'deposited', set deposit_date
    → Insert status_history record
    → Dispatch 'instrument.deposited' event
    
  - collect(int $instrumentId, string $collectionDate, ?string $notes): void
    → Validate status = 'deposited'
    → Update status to 'collected', set collection_date
    → Insert status_history
    → Auto-post journal entry (DR Bank, CR Instruments Receivable)
    → Dispatch 'instrument.collected'
    
  - bounce(int $instrumentId, string $bounceDate, ?string $notes): void
    → Validate status = 'deposited'
    → Update status to 'bounced', set bounce_date
    → Insert status_history
    → Auto-post reversal journal entry
    → Dispatch 'instrument.bounced'
    
  - endorse(int $instrumentId, string $endorsedTo, string $endorsementDate, ?string $notes): void
    → Validate status = 'issued' or 'active'
    → Update status to 'endorsed', set endorsed_to, endorsement_date
    → Insert status_history
    → Auto-post journal entry (DR AP/new party, CR Instruments)
    → Dispatch 'instrument.endorsed'
    
  - getStatusHistory(int $instrumentId): array
  - getValidTransitions(string $currentStatus): array

MODIFY: app/Modules/Accounting/Controllers/NegotiableInstrumentController.php
  - Add method: deposit(Request $request, int $id)
  - Add method: collect(Request $request, int $id)
  - Add method: bounce(Request $request, int $id)
  - Add method: endorse(Request $request, int $id)
  - Add method: statusHistory(Request $request, int $id)

CREATE: app/Modules/Accounting/Views/instruments/status-actions.php
  - Show current status with colored badge
  - Show available actions as buttons based on current status
  - Action modals with date/notes inputs

CREATE: app/Modules/Accounting/Views/instruments/status-history.php
  - Timeline view of all status changes

MODIFY: app/Modules/Accounting/Routes.php
  - ['POST', '/accounting/instruments/{id:\d+}/deposit', ...]
  - ['POST', '/accounting/instruments/{id:\d+}/collect', ...]
  - ['POST', '/accounting/instruments/{id:\d+}/bounce', ...]
  - ['POST', '/accounting/instruments/{id:\d+}/endorse', ...]
  - ['GET', '/accounting/instruments/{id:\d+}/history', ...]
```

---

## 2.2 — Check Endorsement (تظهير)

### Already covered in 2.1 endorse() method above
### Additional UI:
```
CREATE: app/Modules/Accounting/Views/instruments/endorse-form.php
  - Endorsed to (text field — person/company name)
  - Endorsement date
  - Reason/notes
  - Original check details displayed for reference
```

---

## 2.3 — Payment Note Portfolio Batch Processing

### Files to Create/Modify
```
MODIFY: app/Modules/Accounting/Controllers/InstrumentPortfolioController.php
  - Add method: batchUpdateStatus(Request $request, int $id)
    → Accept array of instrument IDs + target status
    → Call InstrumentLifecycleService for each
    → Return summary of successes/failures

CREATE: app/Modules/Accounting/Views/portfolios/batch-update.php
  - Portfolio header info
  - Checkbox list of all instruments in portfolio
  - "Select All" / "Deselect All" buttons
  - Target status dropdown (filtered to valid transitions)
  - Date field
  - Notes field
  - Submit button

MODIFY: app/Modules/Accounting/Routes.php
  - ['POST', '/accounting/portfolios/{id:\d+}/batch-update', ...]
```

---

## 2.4 — Paper/Check Multi-Criteria Inquiry

### Files to Create/Modify
```
MODIFY: app/Modules/Accounting/Controllers/NegotiableInstrumentController.php
  - Add method: advancedSearch(Request $request)
    → Accept filters: serial, check_number, bank_id, status, type (check/note),
      date_from, date_to, date_type (issue/maturity), party_name, 
      amount_from, amount_to, direction (receivable/payable)
    → Build dynamic WHERE clause
    → Return paginated results

CREATE: app/Modules/Accounting/Views/instruments/advanced-search.php
  - Multi-field filter form (collapsible)
  - Results grid: مسلسل | رقم الشيك | البنك | الجهة | القيمة | تاريخ الإصدار | الاستحقاق | الحالة | النوع
  - Export to Excel button

MODIFY: app/Modules/Accounting/Routes.php
  - ['GET', '/accounting/instruments/search', ...]
```

---

## 2.5 — Daily Cash Movement Report (Safe/Treasury)

### Files to Create/Modify
```
MODIFY: app/Modules/Accounting/Controllers/ReportController.php
  - Add method: dailyCashMovement(Request $request)
    → Accept: date, safe_id/bank_account_id
    → Calculate: opening balance (sum of all prior movements)
    → List all movements for the day with description
    → Calculate: closing = opening + inflows - outflows

CREATE: app/Modules/Accounting/Views/reports/daily-cash-movement.php
  - Header: Safe/Bank name, Date
  - Opening balance
  - Inflows table: مسلسل | البيان | المبلغ | المرجع
  - Outflows table: same structure
  - Summary: Opening | + Inflows | - Outflows | = Closing

MODIFY: app/Modules/Accounting/Routes.php
  - ['GET', '/accounting/reports/daily-cash', ...]
```

---

## 2.6 — Cross-Entity Settlements

### Database Changes
```
Migration: Phase_57_002_cross_entity_settlements.php

CREATE TABLE cross_entity_settlements (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    settlement_number VARCHAR(20) NOT NULL,
    settlement_date DATE NOT NULL,
    purpose ENUM('payment','advance','adjustment') NOT NULL,
    
    -- Source party
    source_type ENUM('supplier','customer','bank','safe','member') NOT NULL,
    source_id INT UNSIGNED NOT NULL,
    source_name VARCHAR(255) NOT NULL,
    
    -- Destination party
    dest_type ENUM('supplier','customer','bank','safe','member') NOT NULL,
    dest_id INT UNSIGNED NOT NULL,
    dest_name VARCHAR(255) NOT NULL,
    
    amount DECIMAL(15,2) NOT NULL,
    notes TEXT NULL,
    
    journal_entry_id INT UNSIGNED NULL,
    status ENUM('draft','posted','reversed') NOT NULL DEFAULT 'draft',
    
    created_by INT UNSIGNED NOT NULL,
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME NULL ON UPDATE CURRENT_TIMESTAMP,
    
    UNIQUE KEY uk_number (settlement_number),
    KEY idx_source (source_type, source_id),
    KEY idx_dest (dest_type, dest_id),
    KEY idx_date (settlement_date)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
```

### Files to Create/Modify
```
CREATE: app/Modules/Accounting/Controllers/SettlementController.php
  Methods:
  - index(Request $request) — list all settlements
  - create(Request $request) — show form
  - store(Request $request) — validate & save
  - show(Request $request, int $id) — view details
  - post(Request $request, int $id) — post to GL
  - reverse(Request $request, int $id) — reverse

CREATE: app/Modules/Accounting/Services/SettlementService.php
  Methods:
  - createSettlement(array $data): int
  - postSettlement(int $id): void
    → Create journal entry: DR source account, CR destination account
    → Update related entity balances
  - reverseSettlement(int $id): void
  - getNextNumber(): string

CREATE: app/Modules/Accounting/Models/CrossEntitySettlement.php
  protected static string $table = 'cross_entity_settlements';

CREATE: app/Modules/Accounting/Views/settlements/index.php
CREATE: app/Modules/Accounting/Views/settlements/create.php
  - Purpose dropdown: سداد | دفعة مقدمة | تسوية
  - Source party: type dropdown + entity picker
  - Destination party: type dropdown + entity picker
  - Amount, Date, Notes
CREATE: app/Modules/Accounting/Views/settlements/show.php

MODIFY: app/Modules/Accounting/Routes.php
  - Full CRUD + post + reverse routes for settlements

MODIFY: app/Modules/Accounting/bootstrap.php
  - Permissions: 'accounting.settlement.view', 'accounting.settlement.create', 'accounting.settlement.post'
  - Menu item under Accounting
```

---

## 2.7 — Bank Loan Management

### Database Changes
```
Migration: Phase_57_003_bank_loans.php

CREATE TABLE bank_loans (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    loan_number VARCHAR(30) NOT NULL,
    bank_account_id INT UNSIGNED NOT NULL,
    loan_type ENUM('short_term','long_term','revolving') NOT NULL,
    principal_amount DECIMAL(15,2) NOT NULL,
    interest_rate DECIMAL(5,4) NOT NULL,
    term_months INT UNSIGNED NOT NULL,
    start_date DATE NOT NULL,
    end_date DATE NOT NULL,
    payment_frequency ENUM('monthly','quarterly','semi_annual','annual') NOT NULL DEFAULT 'monthly',
    
    total_interest DECIMAL(15,2) NOT NULL DEFAULT 0,
    total_paid_principal DECIMAL(15,2) NOT NULL DEFAULT 0,
    total_paid_interest DECIMAL(15,2) NOT NULL DEFAULT 0,
    outstanding_balance DECIMAL(15,2) NOT NULL DEFAULT 0,
    
    gl_account_id INT UNSIGNED NULL COMMENT 'Loan liability account',
    interest_expense_account_id INT UNSIGNED NULL,
    
    status ENUM('active','completed','defaulted') NOT NULL DEFAULT 'active',
    notes TEXT NULL,
    
    created_by INT UNSIGNED NOT NULL,
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME NULL ON UPDATE CURRENT_TIMESTAMP,
    
    UNIQUE KEY uk_number (loan_number),
    KEY idx_bank (bank_account_id),
    KEY idx_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE bank_loan_schedule (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    loan_id INT UNSIGNED NOT NULL,
    installment_number INT UNSIGNED NOT NULL,
    due_date DATE NOT NULL,
    principal_amount DECIMAL(15,2) NOT NULL,
    interest_amount DECIMAL(15,2) NOT NULL,
    total_amount DECIMAL(15,2) NOT NULL,
    paid_amount DECIMAL(15,2) NOT NULL DEFAULT 0,
    paid_date DATE NULL,
    status ENUM('pending','paid','overdue','partial') NOT NULL DEFAULT 'pending',
    journal_entry_id INT UNSIGNED NULL,
    
    KEY idx_loan (loan_id),
    KEY idx_due (due_date),
    KEY idx_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
```

### Files to Create/Modify
```
CREATE: app/Modules/Accounting/Controllers/BankLoanController.php
  Methods:
  - index — list loans with status filter
  - create — new loan form
  - store — validate, save, generate amortization schedule
  - show — loan details with schedule
  - payInstallment — record payment against schedule
  - scheduleReport — amortization schedule view

CREATE: app/Modules/Accounting/Services/BankLoanService.php
  Methods:
  - createLoan(array $data): int
  - generateAmortizationSchedule(int $loanId): void
    → Calculate PMT formula: M = P * [r(1+r)^n] / [(1+r)^n - 1]
    → Split each payment into principal + interest
    → Insert schedule records
  - recordPayment(int $scheduleItemId, float $amount, string $date): void
    → Post journal: DR Loan Payable + DR Interest Expense, CR Bank
    → Update outstanding_balance
  - checkOverdueInstallments(): array
  - getOutstandingBalance(int $loanId): float

CREATE: app/Modules/Accounting/Models/BankLoan.php
CREATE: app/Modules/Accounting/Models/BankLoanSchedule.php

CREATE: app/Modules/Accounting/Views/loans/index.php
CREATE: app/Modules/Accounting/Views/loans/create.php
  - Bank account (dropdown)
  - Loan type, amount, rate, term, start date, frequency
  - GL accounts mapping
CREATE: app/Modules/Accounting/Views/loans/show.php
  - Loan summary card
  - Amortization schedule table: # | Due Date | Principal | Interest | Total | Paid | Status
  - "Pay" button for pending installments

MODIFY: app/Modules/Accounting/Routes.php
  - CRUD + payInstallment routes

MODIFY: app/Modules/Accounting/bootstrap.php
  - Permissions: loan.view, loan.create, loan.pay
  - Menu: "القروض البنكية" under Accounting
```

---

# ═══════════════════════════════════════════════════════════════════════
# PHASE 3: PROCUREMENT CYCLE ENHANCEMENT (Week 8-11)
# ═══════════════════════════════════════════════════════════════════════

## 3.1 — Supplier Price Quote Management

### Database Changes
```
Migration: Phase_58_001_supplier_price_quotes.php

CREATE TABLE supplier_price_quotes (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    quote_number VARCHAR(20) NOT NULL,
    requisition_id INT UNSIGNED NULL COMMENT 'Links to purchase_requisitions',
    supplier_id INT UNSIGNED NOT NULL,
    quote_date DATE NOT NULL,
    valid_until DATE NULL,
    delivery_days INT UNSIGNED NULL,
    payment_terms VARCHAR(255) NULL,
    currency VARCHAR(3) NOT NULL DEFAULT 'EGP',
    total_amount DECIMAL(15,2) NOT NULL DEFAULT 0,
    notes TEXT NULL,
    status ENUM('draft','submitted','evaluated','accepted','rejected','expired') NOT NULL DEFAULT 'draft',
    
    created_by INT UNSIGNED NOT NULL,
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME NULL ON UPDATE CURRENT_TIMESTAMP,
    
    UNIQUE KEY uk_number (quote_number),
    KEY idx_requisition (requisition_id),
    KEY idx_supplier (supplier_id),
    KEY idx_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE supplier_quote_items (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    quote_id INT UNSIGNED NOT NULL,
    item_id INT UNSIGNED NOT NULL,
    quantity DECIMAL(15,3) NOT NULL,
    unit_price DECIMAL(15,4) NOT NULL,
    total_price DECIMAL(15,2) NOT NULL,
    delivery_days INT UNSIGNED NULL,
    notes VARCHAR(500) NULL,
    
    KEY idx_quote (quote_id),
    KEY idx_item (item_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
```

### Files to Create/Modify
```
CREATE: app/Modules/Procurement/Controllers/SupplierQuoteController.php
  Methods:
  - index(Request $request) — list with filters (supplier, status, date range)
  - create(Request $request) — form, optionally pre-fill from requisition
  - store(Request $request) — validate, save quote + items
  - show(Request $request, int $id) — view details
  - edit(Request $request, int $id)
  - update(Request $request, int $id)
  - submit(Request $request, int $id) — mark as submitted
  - createFromRequisition(Request $request, int $requisitionId) — pre-fill from PR

CREATE: app/Modules/Procurement/Models/SupplierPriceQuote.php
CREATE: app/Modules/Procurement/Models/SupplierQuoteItem.php

CREATE: app/Modules/Procurement/Views/quotes/index.php
CREATE: app/Modules/Procurement/Views/quotes/create.php
  - Supplier selector (with lookup helper)
  - Link to requisition (optional)
  - Items grid: Item | Qty | Unit Price | Total | Delivery Days | Notes
  - Validity date, payment terms, total
CREATE: app/Modules/Procurement/Views/quotes/show.php

MODIFY: app/Modules/Procurement/Routes.php
  - Full CRUD + submit + createFromRequisition

MODIFY: app/Modules/Procurement/bootstrap.php
  - Permissions: procurement.quote.view, procurement.quote.create, procurement.quote.manage
  - Menu: 'عروض أسعار الموردين' in Procurement submenu
```

---

## 3.2 — Quote Evaluation & Side-by-Side Comparison

### Database Changes
```
Migration: Phase_58_002_quote_evaluations.php

CREATE TABLE quote_evaluations (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    evaluation_number VARCHAR(20) NOT NULL,
    requisition_id INT UNSIGNED NOT NULL,
    evaluation_date DATE NOT NULL,
    
    winning_quote_id INT UNSIGNED NULL,
    winning_supplier_id INT UNSIGNED NULL,
    justification TEXT NULL,
    
    purchase_order_id INT UNSIGNED NULL COMMENT 'PO created from this evaluation',
    status ENUM('in_progress','completed','cancelled') NOT NULL DEFAULT 'in_progress',
    
    evaluated_by INT UNSIGNED NOT NULL,
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME NULL ON UPDATE CURRENT_TIMESTAMP,
    
    UNIQUE KEY uk_number (evaluation_number),
    KEY idx_requisition (requisition_id),
    KEY idx_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE quote_evaluation_criteria (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    evaluation_id INT UNSIGNED NOT NULL,
    quote_id INT UNSIGNED NOT NULL,
    price_score DECIMAL(5,2) NULL,
    delivery_score DECIMAL(5,2) NULL,
    quality_score DECIMAL(5,2) NULL,
    terms_score DECIMAL(5,2) NULL,
    total_score DECIMAL(5,2) NULL,
    notes TEXT NULL,
    
    KEY idx_evaluation (evaluation_id),
    KEY idx_quote (quote_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
```

### Files to Create/Modify
```
CREATE: app/Modules/Procurement/Controllers/QuoteEvaluationController.php
  Methods:
  - index(Request $request) — list evaluations
  - create(Request $request) — start new evaluation (select PR)
  - store(Request $request) — create evaluation record
  - show(Request $request, int $id) — THE BIG ONE: side-by-side comparison view
  - score(Request $request, int $id) — save scoring criteria
  - selectWinner(Request $request, int $id) — mark winning quote
  - generatePO(Request $request, int $id) — create PO from winner

CREATE: app/Modules/Procurement/Services/QuoteEvaluationService.php
  Methods:
  - getQuotesForRequisition(int $requisitionId): array
    → Returns all quotes linked to this PR with their items
  - buildComparisonMatrix(int $requisitionId): array
    → Returns structure:
      [
        'items' => [
          ['item_id' => 1, 'name' => '...', 'qty' => 100, 'quotes' => [
            ['quote_id' => 1, 'supplier' => '...', 'unit_price' => 50, 'total' => 5000, 'delivery' => 7],
            ['quote_id' => 2, 'supplier' => '...', 'unit_price' => 48, 'total' => 4800, 'delivery' => 14],
          ]],
        ],
        'totals' => ['quote_1' => 50000, 'quote_2' => 48000],
        'best_price_per_item' => [1 => 'quote_2', 2 => 'quote_1', ...],
      ]
  - createPOFromEvaluation(int $evaluationId): int
    → Get winning quote
    → Copy items to new purchase_order + purchase_order_items
    → Link evaluation to PO
    → Update statuses

CREATE: app/Modules/Procurement/Views/evaluations/index.php
CREATE: app/Modules/Procurement/Views/evaluations/show.php
  THIS IS THE KEY VIEW — Side-by-side comparison:
  ┌─────────────────────────────────────────────────────────────────────┐
  │ تقييم عروض الأسعار - طلب شراء رقم: PR-0042                          │
  ├──────────┬──────────┬──────────┬──────────┬──────────┬──────────────┤
  │ الصنف    │ الكمية   │ مورد 1   │ مورد 2   │ مورد 3   │ أقل سعر     │
  │          │          │ سعر|إجم  │ سعر|إجم  │ سعر|إجم  │             │
  ├──────────┼──────────┼──────────┼──────────┼──────────┼──────────────┤
  │ صنف أ    │ 100      │ 50|5000  │ 48|4800✓ │ 52|5200  │ مورد 2      │
  │ صنف ب    │ 200      │ 30|6000✓ │ 35|7000  │ 32|6400  │ مورد 1      │
  ├──────────┼──────────┼──────────┼──────────┼──────────┼──────────────┤
  │ الإجمالي │          │ 11,000   │ 11,800   │ 11,600   │             │
  │ التوصيل  │          │ 7 أيام   │ 14 يوم   │ 10 أيام  │             │
  └──────────┴──────────┴──────────┴──────────┴──────────┴──────────────┘
  
  Buttons: [اختيار الفائز] [إصدار أمر شراء]

MODIFY: app/Modules/Procurement/Routes.php
  - CRUD + score + selectWinner + generatePO routes

MODIFY: app/Modules/Procurement/bootstrap.php
  - Permissions: procurement.evaluation.view, procurement.evaluation.create, procurement.evaluation.decide
  - Menu: 'تقييم عروض الأسعار'
```

---

## 3.3 — Supplier Payment Scheduling

### Database Changes
```
Migration: Phase_58_003_supplier_payment_scheduling.php

CREATE TABLE supplier_payment_schedules (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    schedule_number VARCHAR(20) NOT NULL,
    supplier_id INT UNSIGNED NOT NULL,
    vendor_invoice_id INT UNSIGNED NULL,
    total_amount DECIMAL(15,2) NOT NULL,
    installments_count INT UNSIGNED NOT NULL,
    start_date DATE NOT NULL,
    frequency ENUM('weekly','biweekly','monthly') NOT NULL DEFAULT 'monthly',
    notes TEXT NULL,
    status ENUM('active','completed','cancelled') NOT NULL DEFAULT 'active',
    
    created_by INT UNSIGNED NOT NULL,
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    
    UNIQUE KEY uk_number (schedule_number),
    KEY idx_supplier (supplier_id),
    KEY idx_invoice (vendor_invoice_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE supplier_payment_schedule_items (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    schedule_id INT UNSIGNED NOT NULL,
    installment_number INT UNSIGNED NOT NULL,
    due_date DATE NOT NULL,
    amount DECIMAL(15,2) NOT NULL,
    paid_amount DECIMAL(15,2) NOT NULL DEFAULT 0,
    payment_method ENUM('cash','check','transfer') NULL,
    paid_date DATE NULL,
    vendor_payment_id INT UNSIGNED NULL,
    status ENUM('pending','paid','overdue','partial') NOT NULL DEFAULT 'pending',
    
    KEY idx_schedule (schedule_id),
    KEY idx_due (due_date)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
```

### Files to Create/Modify
```
CREATE: app/Modules/Procurement/Controllers/SupplierPaymentScheduleController.php
  Methods:
  - index — list schedules
  - create — form (select supplier, invoice, amounts)
  - store — generate schedule items
  - show — view schedule with items
  - payItem — record payment for one installment

CREATE: app/Modules/Procurement/Services/SupplierPaymentScheduleService.php
  - createSchedule(array $data): int
  - generateInstallments(int $scheduleId, float $total, int $count, string $startDate, string $frequency): void
  - recordPayment(int $itemId, float $amount, string $method, string $date): void
  - getOverdueItems(): array
  - getUpcomingItems(int $days = 7): array

CREATE: Views for schedule CRUD
MODIFY: Routes.php, bootstrap.php with permissions and menu
```

---

## 3.4 — Item Price Deviation Monitoring

### Database Changes
```
Migration: Phase_58_004_item_price_tracking.php

CREATE TABLE item_price_history (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    item_id INT UNSIGNED NOT NULL,
    supplier_id INT UNSIGNED NULL,
    price_type ENUM('purchase','sale') NOT NULL,
    unit_price DECIMAL(15,4) NOT NULL,
    quantity DECIMAL(15,3) NOT NULL,
    reference_type VARCHAR(50) NOT NULL COMMENT 'vendor_invoice, purchase_order, sale',
    reference_id INT UNSIGNED NOT NULL,
    recorded_date DATE NOT NULL,
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    
    KEY idx_item (item_id),
    KEY idx_supplier (supplier_id),
    KEY idx_date (recorded_date)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

ALTER TABLE inventory_items ADD COLUMN standard_cost DECIMAL(15,4) NULL AFTER cost_price;
ALTER TABLE inventory_items ADD COLUMN price_deviation_threshold DECIMAL(5,2) NOT NULL DEFAULT 10.00 COMMENT 'Percentage' AFTER standard_cost;
```

### Files to Create/Modify
```
CREATE: app/Modules/Inventory/Services/PriceMonitoringService.php
  Methods:
  - recordPrice(int $itemId, ?int $supplierId, string $type, float $price, float $qty, string $refType, int $refId): void
  - checkDeviation(int $itemId, float $newPrice): ?array
    → Compare vs standard_cost
    → If deviation > threshold: return ['item_id', 'standard', 'actual', 'deviation_pct', 'alert']
  - getItemPriceHistory(int $itemId, ?int $supplierId, ?string $dateFrom, ?string $dateTo): array
  - getPriceDeviationReport(float $thresholdPct): array

MODIFY: app/Modules/Procurement/Services/VendorInvoiceService.php
  - After invoice approval: call PriceMonitoringService::recordPrice() for each line item
  - If deviation detected: flash warning (don't block, just warn)

CREATE: app/Modules/Procurement/Views/reports/price-deviation.php
  - Table: Item | Standard Price | Actual Price | Deviation % | Supplier | Invoice# | Date
  - Color-code: green (<5%), yellow (5-10%), red (>10%)
```

---

## 3.5 — Purchase vs Budget Comparison

### Files to Create/Modify
```
MODIFY: app/Modules/Procurement/Controllers/ProcurementReportController.php
  - Add method: budgetComparison(Request $request)
    → Query actual purchases grouped by cost_center / budget account
    → Join with account_budgets or cost_center_budgets
    → Calculate: budget | actual | variance | variance %

CREATE: app/Modules/Procurement/Views/reports/budget-comparison.php
  - Filters: fiscal year, cost center, account
  - Table: Account/Center | Budget | Actual YTD | Variance | % Used
  - Progress bars for visual representation
  - Red highlight for over-budget items
```

---

# ═══════════════════════════════════════════════════════════════════════
# PHASE 4: HR & PAYROLL ENHANCEMENTS (Week 12-16)
# ═══════════════════════════════════════════════════════════════════════

## 4.1 — Overtime System

### Database Changes
```
Migration: Phase_59_001_overtime_system.php

CREATE TABLE hr_overtime_types (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    name_ar VARCHAR(100) NOT NULL,
    name_en VARCHAR(100) NULL,
    rate_multiplier DECIMAL(4,2) NOT NULL DEFAULT 1.50 COMMENT '1.5x, 2x, etc',
    max_hours_per_day DECIMAL(4,2) NULL,
    max_hours_per_month DECIMAL(5,2) NULL,
    requires_approval TINYINT(1) NOT NULL DEFAULT 1,
    is_active TINYINT(1) NOT NULL DEFAULT 1,
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE hr_overtime_requests (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    employee_id INT UNSIGNED NOT NULL,
    overtime_type_id INT UNSIGNED NOT NULL,
    request_date DATE NOT NULL,
    start_time TIME NOT NULL,
    end_time TIME NOT NULL,
    hours DECIMAL(4,2) NOT NULL,
    reason TEXT NULL,
    status ENUM('pending','approved','rejected','cancelled') NOT NULL DEFAULT 'pending',
    approved_by INT UNSIGNED NULL,
    approved_at DATETIME NULL,
    rejection_reason VARCHAR(500) NULL,
    payroll_run_id INT UNSIGNED NULL COMMENT 'Linked when processed in payroll',
    calculated_amount DECIMAL(10,2) NULL,
    
    created_by INT UNSIGNED NOT NULL,
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    
    KEY idx_employee (employee_id),
    KEY idx_date (request_date),
    KEY idx_status (status),
    KEY idx_payroll (payroll_run_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Seed default overtime types
INSERT INTO hr_overtime_types (name_ar, name_en, rate_multiplier) VALUES
('عمل إضافي عادي', 'Regular Overtime', 1.50),
('عمل إضافي ليلي', 'Night Overtime', 2.00),
('عمل إضافي أجازات', 'Holiday Overtime', 2.50);
```

### Files to Create/Modify
```
CREATE: app/Modules/HR/Controllers/OvertimeController.php
  Methods:
  - types() — manage overtime types
  - storeType() / updateType()
  - index() — list overtime requests (filterable)
  - request() — employee submits request
  - storeRequest()
  - approve(int $id)
  - reject(int $id)
  - monthly(Request $request) — monthly overtime summary

CREATE: app/Modules/HR/Services/OvertimeService.php
  Methods:
  - calculateOvertimeAmount(int $employeeId, float $hours, int $typeId): float
    → Get employee hourly rate = monthly_salary / (working_days * hours_per_day)
    → Multiply by rate_multiplier from overtime_type
    → Return amount
  - getMonthlyOvertimeForPayroll(int $employeeId, string $month): array
    → Sum all approved overtime for the month
    → Return ['total_hours' => x, 'total_amount' => y, 'breakdown' => [...]]

MODIFY: app/Modules/HR/Controllers/PayrollController.php
  - In calculate(): include overtime from OvertimeService::getMonthlyOvertimeForPayroll()
  - Add overtime as a payroll component

CREATE: Views for overtime CRUD + request + approval
MODIFY: Routes, bootstrap with permissions
```

---

## 4.2 — Attendance Violation Detection

### Database Changes
```
Migration: Phase_59_002_attendance_violations.php

CREATE TABLE hr_attendance_violations (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    employee_id INT UNSIGNED NOT NULL,
    violation_date DATE NOT NULL,
    violation_type ENUM('late_arrival','early_departure','absence','unauthorized_absence','incomplete_day') NOT NULL,
    scheduled_time TIME NULL COMMENT 'When they should have been',
    actual_time TIME NULL COMMENT 'When they actually were',
    difference_minutes INT NULL COMMENT 'How many minutes off',
    auto_detected TINYINT(1) NOT NULL DEFAULT 1,
    penalty_applied TINYINT(1) NOT NULL DEFAULT 0,
    penalty_amount DECIMAL(10,2) NULL,
    notes VARCHAR(500) NULL,
    acknowledged TINYINT(1) NOT NULL DEFAULT 0,
    
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    
    KEY idx_employee (employee_id),
    KEY idx_date (violation_date),
    KEY idx_type (violation_type)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE hr_attendance_rules (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    rule_name_ar VARCHAR(100) NOT NULL,
    rule_type ENUM('grace_period','late_penalty','absence_penalty','early_leave_penalty') NOT NULL,
    threshold_minutes INT UNSIGNED NOT NULL DEFAULT 0 COMMENT 'Minutes before this rule triggers',
    penalty_type ENUM('none','warning','deduction_fixed','deduction_percentage','deduction_hours') NOT NULL DEFAULT 'warning',
    penalty_value DECIMAL(10,2) NOT NULL DEFAULT 0,
    max_occurrences_per_month INT UNSIGNED NULL COMMENT 'After this many, escalate',
    escalation_penalty_type ENUM('none','deduction_day','deduction_percentage') NULL,
    escalation_penalty_value DECIMAL(10,2) NULL,
    is_active TINYINT(1) NOT NULL DEFAULT 1,
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Default rules
INSERT INTO hr_attendance_rules (rule_name_ar, rule_type, threshold_minutes, penalty_type, penalty_value) VALUES
('فترة سماح', 'grace_period', 15, 'none', 0),
('تأخير بسيط (15-30 دقيقة)', 'late_penalty', 30, 'warning', 0),
('تأخير متوسط (30-60 دقيقة)', 'late_penalty', 60, 'deduction_hours', 1),
('تأخير كبير (أكثر من ساعة)', 'late_penalty', 120, 'deduction_hours', 2),
('غياب بدون إذن', 'absence_penalty', 0, 'deduction_day', 1);
```

### Files to Create/Modify
```
CREATE: app/Modules/HR/Services/AttendanceViolationService.php
  Methods:
  - detectViolations(string $date): array
    → For each employee with attendance on $date:
      - Compare check_in time vs scheduled start (from work_schedule or shift)
      - If late beyond grace_period → create violation
      - Compare check_out time vs scheduled end
      - If early → create violation
    → For employees with NO attendance record and no leave:
      - Create 'absence' or 'unauthorized_absence' violation
    → Return array of detected violations
    
  - applyPenalties(string $month): array
    → For each employee, count violations in month
    → Apply rules based on threshold and occurrence count
    → Return penalties to include in payroll

  - getViolationSummary(int $employeeId, string $month): array
  - acknowledgeViolation(int $violationId): void

CREATE: app/Modules/HR/Controllers/AttendanceViolationController.php
  Methods:
  - index() — list violations with filters
  - detect() — trigger detection for a date
  - rules() — manage rules
  - storeRule() / updateRule()
  - summary() — monthly summary per employee

MODIFY: app/Modules/HR/Controllers/PayrollController.php
  - In calculate(): call AttendanceViolationService::applyPenalties() to include deductions

CREATE: Views for violations list, rules config, summary
MODIFY: Routes, bootstrap
```

---

## 4.3 — Permission Requests (Hourly Leaves)

### Database Changes
```
Migration: Phase_59_003_permission_requests.php

CREATE TABLE hr_permission_requests (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    employee_id INT UNSIGNED NOT NULL,
    permission_date DATE NOT NULL,
    start_time TIME NOT NULL,
    end_time TIME NOT NULL,
    duration_hours DECIMAL(4,2) NOT NULL,
    reason VARCHAR(500) NOT NULL,
    status ENUM('pending','approved','rejected','cancelled') NOT NULL DEFAULT 'pending',
    approved_by INT UNSIGNED NULL,
    approved_at DATETIME NULL,
    
    created_by INT UNSIGNED NOT NULL,
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    
    KEY idx_employee (employee_id),
    KEY idx_date (permission_date),
    KEY idx_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
```

### Files to Create/Modify
```
CREATE: app/Modules/HR/Controllers/PermissionRequestController.php
  Methods: index, request, store, approve, reject, monthly

CREATE: app/Modules/HR/Views/permissions/index.php
CREATE: app/Modules/HR/Views/permissions/request.php
  - Date, start time, end time (auto-calc duration), reason
  
MODIFY: Routes, bootstrap with 'hr.permission.view', 'hr.permission.request', 'hr.permission.approve'
```

---

## 4.4 — Social Insurance Auto-Calculation

### Database Changes
```
Migration: Phase_59_004_insurance_configuration.php

CREATE TABLE hr_insurance_config (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    effective_date DATE NOT NULL,
    basic_salary_cap DECIMAL(12,2) NOT NULL COMMENT 'Max basic insurable salary',
    variable_salary_cap DECIMAL(12,2) NOT NULL COMMENT 'Max variable insurable salary',
    employer_basic_rate DECIMAL(5,4) NOT NULL COMMENT 'e.g. 0.1850 = 18.5%',
    employer_variable_rate DECIMAL(5,4) NOT NULL,
    employee_basic_rate DECIMAL(5,4) NOT NULL COMMENT 'e.g. 0.1100 = 11%',
    employee_variable_rate DECIMAL(5,4) NOT NULL,
    min_basic_salary DECIMAL(12,2) NOT NULL DEFAULT 0 COMMENT 'Floor for insurance calc',
    is_active TINYINT(1) NOT NULL DEFAULT 1,
    
    KEY idx_effective (effective_date)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Egyptian 2024/2025 rates (example)
INSERT INTO hr_insurance_config (effective_date, basic_salary_cap, variable_salary_cap, 
  employer_basic_rate, employer_variable_rate, employee_basic_rate, employee_variable_rate, min_basic_salary)
VALUES ('2024-01-01', 12600.00, 10900.00, 0.1850, 0.1150, 0.1100, 0.1100, 2000.00);
```

### Files to Create/Modify
```
CREATE: app/Modules/HR/Services/InsuranceCalculationService.php
  Methods:
  - calculate(int $employeeId, string $month): array
    → Get employee salary (basic + variable)
    → Get active insurance config
    → Apply caps: min(basic_salary, basic_salary_cap)
    → Calculate:
      - employee_basic_share = capped_basic * employee_basic_rate
      - employee_variable_share = capped_variable * employee_variable_rate  
      - employer_basic_share = capped_basic * employer_basic_rate
      - employer_variable_share = capped_variable * employer_variable_rate
    → Return ['employee_total', 'employer_total', 'breakdown' => [...]]
    
  - calculateBulk(string $month): array — for all active employees
  - getConfig(?string $date = null): array — get applicable config

CREATE: app/Modules/HR/Controllers/InsuranceConfigController.php
  - CRUD for insurance configuration

MODIFY: app/Modules/HR/Controllers/PayrollController.php
  - In calculate(): auto-include insurance deduction from InsuranceCalculationService

CREATE: app/Modules/HR/Views/insurance/config.php
  - Configuration form for rates and caps
```

---

## 4.5 — Progressive Tax Bracket Calculation

### Database Changes
```
Migration: Phase_59_005_tax_brackets.php

CREATE TABLE hr_tax_brackets (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    effective_year INT UNSIGNED NOT NULL,
    bracket_from DECIMAL(12,2) NOT NULL,
    bracket_to DECIMAL(12,2) NOT NULL,
    rate DECIMAL(5,4) NOT NULL COMMENT '0.0000 to 1.0000',
    is_exempt TINYINT(1) NOT NULL DEFAULT 0,
    description_ar VARCHAR(255) NULL,
    
    KEY idx_year (effective_year)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE hr_tax_exemptions (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    effective_year INT UNSIGNED NOT NULL,
    exemption_type VARCHAR(50) NOT NULL COMMENT 'personal, insurance, etc',
    description_ar VARCHAR(255) NOT NULL,
    amount DECIMAL(12,2) NOT NULL,
    is_percentage TINYINT(1) NOT NULL DEFAULT 0,
    
    KEY idx_year (effective_year)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Egyptian 2024 tax brackets (example)
INSERT INTO hr_tax_brackets (effective_year, bracket_from, bracket_to, rate, is_exempt, description_ar) VALUES
(2024, 0, 40000, 0.0000, 1, 'شريحة معفاة'),
(2024, 40000, 55000, 0.1000, 0, '10%'),
(2024, 55000, 70000, 0.1500, 0, '15%'),
(2024, 70000, 200000, 0.2000, 0, '20%'),
(2024, 200000, 400000, 0.2250, 0, '22.5%'),
(2024, 400000, 999999999, 0.2500, 0, '25%');

INSERT INTO hr_tax_exemptions (effective_year, exemption_type, description_ar, amount) VALUES
(2024, 'personal', 'إعفاء شخصي', 20000),
(2024, 'insurance', 'حصة التأمينات (خصم)', 0);
```

### Files to Create/Modify
```
CREATE: app/Modules/HR/Services/TaxCalculationService.php
  Methods:
  - calculateAnnualTax(float $annualTaxableIncome, int $year): float
    → Get brackets for year
    → For each bracket: tax += (min(income, bracket_to) - bracket_from) * rate
    → Apply exemptions
    → Return annual tax
    
  - calculateMonthlyTax(int $employeeId, string $month): float
    → Get annual gross salary projection
    → Subtract exemptions (personal + insurance contributions)
    → Calculate annual tax
    → Divide by 12 for monthly
    → Adjust for YTD actual vs expected (reconciliation)
    
  - yearEndReconciliation(int $employeeId, int $year): array
    → Compare actual annual income vs projected
    → Calculate actual tax owed vs tax already withheld
    → Return adjustment amount

CREATE: app/Modules/HR/Controllers/TaxConfigController.php
  - CRUD for brackets and exemptions

MODIFY: app/Modules/HR/Controllers/PayrollController.php
  - In calculate(): use TaxCalculationService for tax deduction
```

---

## 4.6 — Shift Management

### Database Changes
```
Migration: Phase_59_006_shift_management.php

CREATE TABLE hr_shifts (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    name_ar VARCHAR(100) NOT NULL,
    name_en VARCHAR(100) NULL,
    start_time TIME NOT NULL,
    end_time TIME NOT NULL,
    break_start TIME NULL,
    break_end TIME NULL,
    break_duration_minutes INT UNSIGNED NOT NULL DEFAULT 0,
    working_hours DECIMAL(4,2) NOT NULL,
    is_night_shift TINYINT(1) NOT NULL DEFAULT 0,
    grace_minutes INT UNSIGNED NOT NULL DEFAULT 15,
    overtime_after_minutes INT UNSIGNED NULL COMMENT 'Auto-overtime after this many extra minutes',
    color_code VARCHAR(7) NULL COMMENT 'For calendar display',
    is_active TINYINT(1) NOT NULL DEFAULT 1,
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE hr_shift_assignments (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    employee_id INT UNSIGNED NOT NULL,
    shift_id INT UNSIGNED NOT NULL,
    start_date DATE NOT NULL,
    end_date DATE NULL COMMENT 'NULL = ongoing',
    rotation_pattern ENUM('fixed','weekly','biweekly','monthly') NOT NULL DEFAULT 'fixed',
    notes VARCHAR(255) NULL,
    
    KEY idx_employee (employee_id),
    KEY idx_shift (shift_id),
    KEY idx_dates (start_date, end_date)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
```

### Files to Create/Modify
```
CREATE: app/Modules/HR/Controllers/ShiftController.php
  Methods: index, create, store, edit, update, assign, calendar

CREATE: app/Modules/HR/Views/shifts/index.php — list shifts
CREATE: app/Modules/HR/Views/shifts/create.php — shift form
CREATE: app/Modules/HR/Views/shifts/assign.php — assign employees to shifts
CREATE: app/Modules/HR/Views/shifts/calendar.php — visual shift calendar

MODIFY: app/Modules/HR/Services/AttendanceViolationService.php
  - Use shift assignment to determine expected times (not just work schedule)
```

---

# ═══════════════════════════════════════════════════════════════════════
# PHASE 5: ADVANCED INVENTORY & SALES (Week 17-22)
# ═══════════════════════════════════════════════════════════════════════

## 5.1 — Bill of Materials (BOM)

### Database Changes
```
Migration: Phase_60_001_bill_of_materials.php

CREATE TABLE bill_of_materials (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    parent_item_id INT UNSIGNED NOT NULL COMMENT 'The composite/finished item',
    version INT UNSIGNED NOT NULL DEFAULT 1,
    effective_date DATE NOT NULL,
    end_date DATE NULL,
    yield_quantity DECIMAL(15,3) NOT NULL DEFAULT 1 COMMENT 'How many parent items this BOM produces',
    notes TEXT NULL,
    is_active TINYINT(1) NOT NULL DEFAULT 1,
    
    created_by INT UNSIGNED NOT NULL,
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME NULL ON UPDATE CURRENT_TIMESTAMP,
    
    KEY idx_parent (parent_item_id),
    UNIQUE KEY uk_item_version (parent_item_id, version)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE bom_components (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    bom_id INT UNSIGNED NOT NULL,
    component_item_id INT UNSIGNED NOT NULL,
    quantity DECIMAL(15,6) NOT NULL,
    unit_of_measure VARCHAR(50) NULL,
    waste_percentage DECIMAL(5,2) NOT NULL DEFAULT 0 COMMENT 'Expected waste/scrap %',
    is_optional TINYINT(1) NOT NULL DEFAULT 0,
    sort_order INT UNSIGNED NOT NULL DEFAULT 0,
    notes VARCHAR(255) NULL,
    
    KEY idx_bom (bom_id),
    KEY idx_component (component_item_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

ALTER TABLE inventory_items ADD COLUMN has_bom TINYINT(1) NOT NULL DEFAULT 0 AFTER is_active;
ALTER TABLE inventory_items ADD COLUMN bom_cost DECIMAL(15,4) NULL COMMENT 'Rolled-up component cost' AFTER has_bom;
```

### Files to Create/Modify
```
CREATE: app/Modules/Inventory/Controllers/BOMController.php
  Methods:
  - index() — list all items with BOMs
  - show(int $itemId) — show BOM for item
  - create(int $itemId) — create/edit BOM
  - store(Request $request) — save BOM + components
  - rollupCost(int $bomId) — recalculate BOM cost from components
  - explode(int $itemId, float $quantity) — show all components needed for N units

CREATE: app/Modules/Inventory/Services/BOMService.php
  Methods:
  - getActiveBOM(int $itemId): ?array
  - explodeBOM(int $itemId, float $quantity): array
    → Recursively expand components (BOM within BOM)
    → Apply waste percentages
    → Return flat list: [['item_id' => x, 'quantity_needed' => y], ...]
  - deductComponents(int $itemId, float $quantity, int $warehouseId, string $reference, int $referenceId): void
    → Get BOM → explode → for each component: deduct stock
  - rollupCost(int $bomId): float
    → Sum(component.cost_price * component.quantity * (1 + waste%))
  - checkComponentAvailability(int $itemId, float $quantity, int $warehouseId): array
    → Return which components have insufficient stock

MODIFY: app/Modules/Sales/Controllers/SaleController.php
  - In store(): for items with has_bom=1, call BOMService::deductComponents() instead of simple stock deduction

CREATE: app/Modules/Inventory/Views/bom/show.php
  - Parent item header
  - Components table: # | Component Item | Qty per Unit | Waste % | Effective Qty | Unit Cost | Total Cost
  - Total BOM cost
  - "Check Availability" button

CREATE: app/Modules/Inventory/Views/bom/create.php
  - Parent item (fixed)
  - Dynamic rows for components: item picker + qty + waste% + optional flag
  - Add/remove row buttons

MODIFY: app/Modules/Inventory/Routes.php
MODIFY: app/Modules/Inventory/bootstrap.php
  - Permissions: 'inventory.bom.view', 'inventory.bom.manage'
  - Menu: 'قوائم المواد (BOM)' under Inventory
```

---

## 5.2 — Multiple Units per Item with Conversion

### Database Changes
```
Migration: Phase_60_002_item_units.php

CREATE TABLE item_units (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    item_id INT UNSIGNED NOT NULL,
    unit_name_ar VARCHAR(50) NOT NULL,
    unit_name_en VARCHAR(50) NULL,
    conversion_factor DECIMAL(15,6) NOT NULL COMMENT 'How many base units in this unit',
    is_base_unit TINYINT(1) NOT NULL DEFAULT 0,
    barcode VARCHAR(50) NULL COMMENT 'Different barcode per unit pack',
    purchase_default TINYINT(1) NOT NULL DEFAULT 0,
    sale_default TINYINT(1) NOT NULL DEFAULT 0,
    sort_order INT UNSIGNED NOT NULL DEFAULT 0,
    
    KEY idx_item (item_id),
    UNIQUE KEY uk_item_unit (item_id, unit_name_ar)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
```

### Files to Create/Modify
```
CREATE: app/Modules/Inventory/Services/UnitConversionService.php
  Methods:
  - convertToBaseUnit(int $itemId, float $quantity, int $unitId): float
    → quantity * conversion_factor
  - convertFromBaseUnit(int $itemId, float $baseQuantity, int $unitId): float
    → baseQuantity / conversion_factor
  - getItemUnits(int $itemId): array
  - getPurchaseDefaultUnit(int $itemId): ?array
  - getSaleDefaultUnit(int $itemId): ?array

MODIFY: app/Modules/Inventory/Controllers/ItemController.php
  - In create/edit: add unit management section
  - Store/update: save item_units

MODIFY: app/Modules/Inventory/Views/items/create.php
  - Add "Units" section: dynamic rows for unit_name, conversion_factor, is_base, purchase_default, sale_default

MODIFY: app/Modules/Sales/Controllers/SaleController.php
  - When adding item to sale: show unit dropdown, convert quantity to base for stock deduction

MODIFY: app/Modules/Procurement/Controllers/VendorInvoiceController.php
  - Allow selecting purchase unit, convert to base for stock-in
```

---

## 5.3 — Sales Representative & Commission

### Database Changes
```
Migration: Phase_60_003_sales_reps_commissions.php

CREATE TABLE sales_representatives (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    employee_id INT UNSIGNED NULL COMMENT 'If linked to an employee',
    code VARCHAR(20) NOT NULL,
    name_ar VARCHAR(100) NOT NULL,
    phone VARCHAR(20) NULL,
    commission_type ENUM('flat','percentage','tiered') NOT NULL DEFAULT 'percentage',
    commission_rate DECIMAL(5,4) NULL COMMENT 'For percentage type',
    commission_flat DECIMAL(10,2) NULL COMMENT 'For flat type',
    target_amount DECIMAL(15,2) NULL COMMENT 'Monthly sales target',
    is_active TINYINT(1) NOT NULL DEFAULT 1,
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    
    UNIQUE KEY uk_code (code),
    KEY idx_employee (employee_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE sales_commission_tiers (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    representative_id INT UNSIGNED NOT NULL,
    tier_from DECIMAL(15,2) NOT NULL,
    tier_to DECIMAL(15,2) NOT NULL,
    rate DECIMAL(5,4) NOT NULL,
    
    KEY idx_rep (representative_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE sales_commissions (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    representative_id INT UNSIGNED NOT NULL,
    sale_id INT UNSIGNED NOT NULL,
    sale_amount DECIMAL(15,2) NOT NULL,
    commission_amount DECIMAL(10,2) NOT NULL,
    period VARCHAR(7) NOT NULL COMMENT 'YYYY-MM',
    is_paid TINYINT(1) NOT NULL DEFAULT 0,
    paid_date DATE NULL,
    
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    
    KEY idx_rep (representative_id),
    KEY idx_sale (sale_id),
    KEY idx_period (period)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

ALTER TABLE sales ADD COLUMN representative_id INT UNSIGNED NULL AFTER member_id;
```

### Files to Create/Modify
```
CREATE: app/Modules/Sales/Controllers/RepresentativeController.php
  Methods: index, create, store, show, edit, update, commissionReport, payCommissions

CREATE: app/Modules/Sales/Services/CommissionService.php
  Methods:
  - calculateCommission(int $repId, float $saleAmount): float
    → Based on commission_type: flat, percentage, or tiered calculation
  - recordCommission(int $repId, int $saleId, float $saleAmount): void
  - getMonthlyCommissions(int $repId, string $month): array
  - getCommissionReport(string $month): array — all reps

MODIFY: app/Modules/Sales/Controllers/SaleController.php
  - Add representative_id field to sale form
  - After sale completion: call CommissionService::recordCommission()

CREATE: Views for rep CRUD + commission reports
MODIFY: Routes, bootstrap
```

---

## 5.4 — Per-Customer Pricing & Discounts

### Database Changes
```
Migration: Phase_60_004_customer_pricing.php

CREATE TABLE customer_price_lists (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    name_ar VARCHAR(100) NOT NULL,
    name_en VARCHAR(100) NULL,
    discount_percentage DECIMAL(5,2) NOT NULL DEFAULT 0,
    is_default TINYINT(1) NOT NULL DEFAULT 0,
    is_active TINYINT(1) NOT NULL DEFAULT 1,
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE customer_item_prices (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    member_id INT UNSIGNED NULL COMMENT 'Specific member price',
    price_list_id INT UNSIGNED NULL COMMENT 'Or via price list',
    item_id INT UNSIGNED NOT NULL,
    special_price DECIMAL(15,4) NULL COMMENT 'Override price',
    discount_percentage DECIMAL(5,2) NULL COMMENT 'Or just a discount',
    effective_from DATE NULL,
    effective_to DATE NULL,
    
    KEY idx_member (member_id),
    KEY idx_list (price_list_id),
    KEY idx_item (item_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

ALTER TABLE members ADD COLUMN price_list_id INT UNSIGNED NULL AFTER credit_balance;
```

### Files to Create/Modify
```
CREATE: app/Modules/Sales/Services/PricingService.php
  Methods:
  - getItemPriceForMember(int $itemId, ?int $memberId): float
    → Priority: member-specific price > price list price > default price
    → Check effective dates
  - getMemberDiscount(int $memberId, int $itemId): float

MODIFY: app/Modules/Sales/Controllers/SaleController.php
  - In searchItems(): use PricingService to return member-specific prices

CREATE: app/Modules/Sales/Controllers/PriceListController.php
  - CRUD for price lists and item prices
```

---

# ═══════════════════════════════════════════════════════════════════════
# PHASE 6: CONFIGURABLE MOVEMENT ENGINE (Week 23-30)
# ═══════════════════════════════════════════════════════════════════════

## 6.1 — Movement Specifications Engine

### Database Changes
```
Migration: Phase_61_001_movement_specifications_engine.php

CREATE TABLE movement_types (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    module ENUM('sales','inventory','procurement') NOT NULL,
    code VARCHAR(50) NOT NULL COMMENT 'machine-readable identifier',
    name_ar VARCHAR(100) NOT NULL,
    name_en VARCHAR(100) NULL,
    description_ar VARCHAR(255) NULL,
    direction ENUM('in','out','neutral','both') NOT NULL,
    is_system TINYINT(1) NOT NULL DEFAULT 0 COMMENT 'Cannot be deleted',
    is_active TINYINT(1) NOT NULL DEFAULT 1,
    sort_order INT UNSIGNED NOT NULL DEFAULT 0,
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    
    UNIQUE KEY uk_code (code),
    KEY idx_module (module)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE movement_specifications (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    movement_type_id INT UNSIGNED NOT NULL,
    spec_key VARCHAR(50) NOT NULL,
    spec_value TEXT NULL,
    
    UNIQUE KEY uk_type_key (movement_type_id, spec_key),
    KEY idx_type (movement_type_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Specification keys include:
-- auto_number_enabled (bool)
-- auto_number_prefix (string)
-- auto_number_start (int)
-- auto_number_digits (int)
-- affects_stock (bool)
-- stock_direction (in/out)
-- posts_to_gl (bool)
-- gl_debit_account_id (int)
-- gl_credit_account_id (int)
-- requires_approval (bool)
-- approval_levels (int)
-- creates_ar (bool)
-- creates_ap (bool)
-- allows_tax (bool)
-- default_tax_rate (decimal)
-- allows_discount (bool)
-- max_discount_percentage (decimal)
-- party_type (customer/supplier/warehouse/any)
-- requires_reference_document (bool)
-- reference_document_type (string)
-- print_template (string)
-- allows_partial (bool)
-- currency_editable (bool)
-- default_warehouse_id (int)
-- cost_center_required (bool)
-- notes_required (bool)

-- Seed default movement types
INSERT INTO movement_types (module, code, name_ar, name_en, direction, is_system) VALUES
('sales', 'sales_invoice', 'فاتورة بيع', 'Sales Invoice', 'out', 1),
('sales', 'sales_return', 'مرتجع بيع', 'Sales Return', 'in', 1),
('sales', 'sales_quotation', 'عرض أسعار', 'Sales Quotation', 'neutral', 0),
('inventory', 'stock_in', 'إذن إضافة', 'Stock In', 'in', 1),
('inventory', 'stock_out', 'إذن صرف', 'Stock Out', 'out', 1),
('inventory', 'stock_transfer', 'تحويل مخزني', 'Stock Transfer', 'both', 1),
('inventory', 'stock_adjustment', 'تسوية مخزنية', 'Stock Adjustment', 'both', 1),
('procurement', 'purchase_order', 'أمر شراء', 'Purchase Order', 'neutral', 1),
('procurement', 'goods_receipt', 'إذن استلام', 'Goods Receipt', 'in', 1),
('procurement', 'purchase_return', 'مرتجع مشتريات', 'Purchase Return', 'out', 1),
('procurement', 'purchase_invoice', 'فاتورة مورد', 'Supplier Invoice', 'in', 1);
```

### Files to Create/Modify
```
CREATE: app/Core/Services/MovementSpecificationEngine.php
  namespace App\Core\Services;
  
  THE HEART OF THE SYSTEM — This class controls all movement behavior:
  
  Methods:
  - getMovementType(string $code): array
  - getSpecifications(string $movementTypeCode): array
  - getSpec(string $movementTypeCode, string $specKey, $default = null): mixed
  - getNextNumber(string $movementTypeCode): string
    → Read auto_number_prefix + auto_number_digits
    → Query MAX existing number for this type
    → Return next formatted number
  - shouldAffectStock(string $code): bool
  - shouldPostToGL(string $code): bool
  - getGLAccounts(string $code): array ['debit' => int, 'credit' => int]
  - requiresApproval(string $code): bool
  - getValidTransitions(string $code, string $currentStatus): array
  - validateMovement(string $code, array $data): array (errors)
  - getActiveMovementTypes(string $module): array
  - getAllSpecs(int $movementTypeId): array

CREATE: app/Modules/Settings/Controllers/MovementSpecController.php
  Methods:
  - index() — list all movement types grouped by module
  - edit(int $typeId) — show all specs for a type
  - update(Request $request, int $typeId) — save specs
  - createType() — create new movement type
  - storeType() — save new type
  - toggleActive(int $typeId) — enable/disable

CREATE: app/Modules/Settings/Views/movement-specs/index.php
  - Tabs: المبيعات | المخازن | المشتريات
  - Per tab: list of movement types with edit buttons

CREATE: app/Modules/Settings/Views/movement-specs/edit.php
  - Movement type name/description
  - Grouped specification fields:
    - الترقيم التلقائي (auto numbering section)
    - التأثير على المخزون (stock impact section)
    - الترحيل المحاسبي (GL posting section)
    - الاعتمادات (approval section)
    - الأطراف (party configuration section)
    - الضرائب والخصومات (tax/discount section)
    - الطباعة (printing section)

MODIFY: app/Modules/Sales/Controllers/SaleController.php
  - Use MovementSpecificationEngine::getNextNumber('sales_invoice') for invoice numbering
  - Check shouldAffectStock() before stock deduction
  - Check shouldPostToGL() before GL posting

MODIFY: app/Modules/Inventory/Controllers/StockMovementController.php
  - Use engine for movement type selection and behavior

MODIFY: app/Modules/Procurement/* controllers
  - Use engine for all procurement movement types

MODIFY: Routes, bootstrap with permissions for movement spec management
```

---

## 6.2 — Multi-Currency Support

### Database Changes
```
Migration: Phase_61_002_multi_currency.php

CREATE TABLE currencies (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    code VARCHAR(3) NOT NULL COMMENT 'ISO 4217',
    name_ar VARCHAR(50) NOT NULL,
    name_en VARCHAR(50) NOT NULL,
    symbol VARCHAR(5) NOT NULL,
    decimal_places INT UNSIGNED NOT NULL DEFAULT 2,
    is_base TINYINT(1) NOT NULL DEFAULT 0 COMMENT 'System base currency',
    is_active TINYINT(1) NOT NULL DEFAULT 1,
    
    UNIQUE KEY uk_code (code)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE exchange_rates (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    currency_code VARCHAR(3) NOT NULL,
    rate_date DATE NOT NULL,
    buy_rate DECIMAL(15,6) NOT NULL COMMENT 'How many base units to buy 1 foreign',
    sell_rate DECIMAL(15,6) NOT NULL,
    mid_rate DECIMAL(15,6) NOT NULL,
    source VARCHAR(50) NULL COMMENT 'CBE, manual, etc',
    
    UNIQUE KEY uk_currency_date (currency_code, rate_date),
    KEY idx_date (rate_date)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Add currency columns to transaction tables
ALTER TABLE journal_entries ADD COLUMN currency_code VARCHAR(3) NOT NULL DEFAULT 'EGP' AFTER description;
ALTER TABLE journal_entries ADD COLUMN exchange_rate DECIMAL(15,6) NOT NULL DEFAULT 1.000000 AFTER currency_code;

ALTER TABLE vendor_invoices ADD COLUMN currency_code VARCHAR(3) NOT NULL DEFAULT 'EGP' AFTER total_amount;
ALTER TABLE vendor_invoices ADD COLUMN exchange_rate DECIMAL(15,6) NOT NULL DEFAULT 1.000000 AFTER currency_code;
ALTER TABLE vendor_invoices ADD COLUMN base_currency_amount DECIMAL(15,2) NULL AFTER exchange_rate;

ALTER TABLE sales ADD COLUMN currency_code VARCHAR(3) NOT NULL DEFAULT 'EGP' AFTER total;
ALTER TABLE sales ADD COLUMN exchange_rate DECIMAL(15,6) NOT NULL DEFAULT 1.000000 AFTER currency_code;

-- Seed base currency
INSERT INTO currencies (code, name_ar, name_en, symbol, is_base) VALUES
('EGP', 'جنيه مصري', 'Egyptian Pound', 'ج.م', 1),
('USD', 'دولار أمريكي', 'US Dollar', '$', 0),
('EUR', 'يورو', 'Euro', '€', 0),
('SAR', 'ريال سعودي', 'Saudi Riyal', 'ر.س', 0);
```

### Files to Create/Modify
```
CREATE: app/Core/Services/CurrencyService.php
  Methods:
  - getBaseCurrency(): string
  - getRate(string $currencyCode, ?string $date = null): array
  - convertToBase(float $amount, string $fromCurrency, ?string $date = null): float
  - convertFromBase(float $baseAmount, string $toCurrency, ?string $date = null): float
  - getExchangeGainLoss(float $originalAmount, string $currency, string $originalDate, string $currentDate): float

CREATE: app/Modules/Settings/Controllers/CurrencyController.php
  Methods: index, store, rates, storeRate

CREATE: app/Modules/Settings/Views/currencies/index.php
CREATE: app/Modules/Settings/Views/currencies/rates.php
```

---

# ═══════════════════════════════════════════════════════════════════════
# PHASE 7: ADVANCED FINANCIAL (Week 31-35)
# ═══════════════════════════════════════════════════════════════════════

## 7.1 — Documentary Credits (LC)

### Database Changes
```
Migration: Phase_62_001_documentary_credits.php

CREATE TABLE documentary_credits (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    lc_number VARCHAR(30) NOT NULL,
    bank_account_id INT UNSIGNED NOT NULL,
    supplier_id INT UNSIGNED NULL,
    
    lc_type ENUM('import','export','local') NOT NULL DEFAULT 'import',
    currency_code VARCHAR(3) NOT NULL DEFAULT 'USD',
    amount DECIMAL(15,2) NOT NULL,
    margin_percentage DECIMAL(5,2) NOT NULL DEFAULT 0,
    margin_amount DECIMAL(15,2) NOT NULL DEFAULT 0,
    
    issue_date DATE NOT NULL,
    expiry_date DATE NOT NULL,
    shipment_deadline DATE NULL,
    
    beneficiary_name VARCHAR(255) NOT NULL,
    beneficiary_bank VARCHAR(255) NULL,
    
    terms TEXT NULL,
    goods_description TEXT NULL,
    
    -- Expenses
    bank_charges DECIMAL(10,2) NOT NULL DEFAULT 0,
    insurance_cost DECIMAL(10,2) NOT NULL DEFAULT 0,
    freight_cost DECIMAL(10,2) NOT NULL DEFAULT 0,
    customs_duty DECIMAL(10,2) NOT NULL DEFAULT 0,
    other_expenses DECIMAL(10,2) NOT NULL DEFAULT 0,
    total_landed_cost DECIMAL(15,2) NOT NULL DEFAULT 0,
    
    -- GL
    margin_account_id INT UNSIGNED NULL,
    expense_account_id INT UNSIGNED NULL,
    
    status ENUM('draft','opened','shipped','documents_presented','paid','closed','cancelled') NOT NULL DEFAULT 'draft',
    
    purchase_order_id INT UNSIGNED NULL,
    
    created_by INT UNSIGNED NOT NULL,
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME NULL ON UPDATE CURRENT_TIMESTAMP,
    
    UNIQUE KEY uk_number (lc_number),
    KEY idx_bank (bank_account_id),
    KEY idx_supplier (supplier_id),
    KEY idx_status (status),
    KEY idx_expiry (expiry_date)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE lc_documents (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    lc_id INT UNSIGNED NOT NULL,
    document_type VARCHAR(50) NOT NULL COMMENT 'bill_of_lading, commercial_invoice, packing_list, certificate_origin, insurance_cert',
    document_number VARCHAR(100) NULL,
    received_date DATE NULL,
    notes VARCHAR(255) NULL,
    file_path VARCHAR(255) NULL,
    
    KEY idx_lc (lc_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
```

### Files to Create/Modify
```
CREATE: app/Modules/Accounting/Controllers/DocumentaryCreditController.php
  Methods: index, create, store, show, edit, update, open, ship, presentDocs, pay, close, cancel

CREATE: app/Modules/Accounting/Services/DocumentaryCreditService.php
  Methods:
  - createLC(array $data): int
  - openLC(int $id): void — posts margin to GL
  - recordShipment(int $id, array $docs): void
  - presentDocuments(int $id): void
  - settleLC(int $id): void — final payment, release margin, post expenses
  - calculateLandedCost(int $id): float
  - getLCsByStatus(string $status): array
  - getExpiringLCs(int $daysAhead): array

CREATE: Views for full LC lifecycle management
MODIFY: Routes, bootstrap
```

---

## 7.2 — Letter of Guarantee

### Database Changes
```
Migration: Phase_62_002_letters_of_guarantee.php

CREATE TABLE letters_of_guarantee (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    guarantee_number VARCHAR(30) NOT NULL,
    bank_account_id INT UNSIGNED NOT NULL,
    
    guarantee_type ENUM('tender','performance','advance_payment','maintenance','customs') NOT NULL,
    beneficiary_name VARCHAR(255) NOT NULL,
    beneficiary_type ENUM('government','private','other') NOT NULL DEFAULT 'private',
    
    currency_code VARCHAR(3) NOT NULL DEFAULT 'EGP',
    amount DECIMAL(15,2) NOT NULL,
    margin_percentage DECIMAL(5,2) NOT NULL DEFAULT 0,
    margin_amount DECIMAL(15,2) NOT NULL DEFAULT 0,
    
    issue_date DATE NOT NULL,
    expiry_date DATE NOT NULL,
    
    commission_rate DECIMAL(5,4) NULL,
    commission_amount DECIMAL(10,2) NULL,
    commission_frequency ENUM('quarterly','semi_annual','annual','one_time') NOT NULL DEFAULT 'quarterly',
    
    project_name VARCHAR(255) NULL,
    tender_number VARCHAR(100) NULL,
    
    margin_account_id INT UNSIGNED NULL,
    commission_account_id INT UNSIGNED NULL,
    
    status ENUM('requested','issued','active','renewed','released','called','expired') NOT NULL DEFAULT 'requested',
    
    created_by INT UNSIGNED NOT NULL,
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME NULL ON UPDATE CURRENT_TIMESTAMP,
    
    UNIQUE KEY uk_number (guarantee_number),
    KEY idx_bank (bank_account_id),
    KEY idx_status (status),
    KEY idx_expiry (expiry_date)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
```

### Files to Create/Modify
```
CREATE: app/Modules/Accounting/Controllers/GuaranteeController.php
  Methods: index, create, store, show, issue, renew, release, call, commissionReport

CREATE: app/Modules/Accounting/Services/GuaranteeService.php
  - createGuarantee, issueGuarantee, renewGuarantee, releaseGuarantee
  - calculateCommission, recordCommissionPayment
  - getExpiringGuarantees, getActiveGuarantees

CREATE: Views for guarantee lifecycle
MODIFY: Routes, bootstrap
```

---

## 7.3 — Fixed Asset Enhancements

### Database Changes
```
Migration: Phase_62_003_enhanced_fixed_assets.php

CREATE TABLE asset_categories (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    name_ar VARCHAR(100) NOT NULL,
    name_en VARCHAR(100) NULL,
    depreciation_method ENUM('straight_line','declining_balance','sum_of_years','units_of_production') NOT NULL DEFAULT 'straight_line',
    useful_life_years INT UNSIGNED NOT NULL DEFAULT 5,
    salvage_value_percentage DECIMAL(5,2) NOT NULL DEFAULT 0,
    asset_account_id INT UNSIGNED NULL,
    depreciation_account_id INT UNSIGNED NULL,
    accumulated_depreciation_account_id INT UNSIGNED NULL,
    is_active TINYINT(1) NOT NULL DEFAULT 1,
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE asset_revaluations (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    asset_id INT UNSIGNED NOT NULL,
    revaluation_date DATE NOT NULL,
    old_value DECIMAL(15,2) NOT NULL,
    new_value DECIMAL(15,2) NOT NULL,
    difference DECIMAL(15,2) NOT NULL,
    reason TEXT NULL,
    journal_entry_id INT UNSIGNED NULL,
    created_by INT UNSIGNED NOT NULL,
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    
    KEY idx_asset (asset_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE asset_improvements (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    asset_id INT UNSIGNED NOT NULL,
    improvement_date DATE NOT NULL,
    description_ar VARCHAR(255) NOT NULL,
    amount DECIMAL(15,2) NOT NULL,
    extends_life_months INT UNSIGNED NOT NULL DEFAULT 0,
    journal_entry_id INT UNSIGNED NULL,
    created_by INT UNSIGNED NOT NULL,
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    
    KEY idx_asset (asset_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

ALTER TABLE asset_registers ADD COLUMN category_id INT UNSIGNED NULL AFTER id;
ALTER TABLE asset_registers ADD COLUMN insurance_policy_number VARCHAR(50) NULL;
ALTER TABLE asset_registers ADD COLUMN insurance_company VARCHAR(100) NULL;
ALTER TABLE asset_registers ADD COLUMN insurance_expiry DATE NULL;
ALTER TABLE asset_registers ADD COLUMN insurance_value DECIMAL(15,2) NULL;
```

### Files to Create/Modify
```
CREATE: app/Modules/Inventory/Controllers/AssetCategoryController.php
CREATE: app/Modules/Inventory/Services/DepreciationCalculatorService.php
  Methods:
  - straightLine(float $cost, float $salvage, int $lifeMonths, int $elapsedMonths): float
  - decliningBalance(float $bookValue, float $rate): float
  - sumOfYears(float $depreciableAmount, int $lifeYears, int $currentYear): float
  - unitsOfProduction(float $depreciableAmount, int $totalUnits, int $periodUnits): float
  - calculateForAsset(int $assetId): float — uses category method

MODIFY: app/Modules/Inventory/Controllers/AssetController.php
  - Add: revalue(), improve(), addInsurance()

CREATE: Views for categories, revaluation form, improvement form
```

---

# ═══════════════════════════════════════════════════════════════════════
# PHASE 8: REPORTING & ANALYTICS (Week 36-39)
# ═══════════════════════════════════════════════════════════════════════

## 8.1 — Universal Report Export Engine

### Files to Create/Modify
```
CREATE: app/Core/Services/ReportExportService.php
  namespace App\Core\Services;
  
  Methods:
  - exportToExcel(array $headers, array $data, string $filename): Response
    → Uses PhpSpreadsheet-compatible plain PHP (no composer)
    → Generate CSV with BOM for Arabic Excel compatibility
  - exportToPDF(string $html, string $filename): Response
    → Use DomPDF or wkhtmltopdf (server binary)
  - exportToCSV(array $headers, array $data, string $filename): Response
  - exportToHTML(string $title, array $headers, array $data): string

CREATE: app/Core/Traits/ExportableTrait.php
  - Mixin for report controllers
  - Adds: handleExport(Request $request, array $headers, array $data, string $filename)
    → Check $request->query('export') value
    → Call appropriate export method
    → Return download response
```

### Apply to ALL existing report controllers:
```
MODIFY: app/Modules/Accounting/Controllers/ReportController.php — add ExportableTrait
MODIFY: app/Modules/HR/Controllers/HrReportController.php — add ExportableTrait
MODIFY: app/Modules/Inventory/Controllers/InventoryReportController.php — add ExportableTrait
MODIFY: app/Modules/Procurement/Controllers/ProcurementReportController.php — add ExportableTrait
MODIFY: app/Modules/Sales/Controllers/SaleReportController.php — add ExportableTrait

All report views get an export dropdown button:
[تصدير ▼] → Excel | PDF | CSV
```

---

## 8.2 — Executive Dashboard Enhancements

### Files to Create/Modify
```
MODIFY: app/Modules/Dashboard/Controllers/DashboardController.php
  Add method: executive(Request $request)
  
  Widgets:
  - Revenue this month vs last month (% change)
  - Expenses this month vs last month
  - Net cash position (total across all safes + banks)
  - AR aging summary (0-30, 31-60, 61-90, 90+ days)
  - AP aging summary
  - Top 5 overdue supplier payments
  - Procurement: pending approvals count
  - HR: attendance % today, leaves pending count
  - Inventory: low stock items count, expiring items count
  - Budget utilization gauge (% of annual budget consumed)

CREATE: app/Modules/Dashboard/Views/executive.php
  - 4-column grid of metric cards
  - Mini charts (can use Chart.js or plain SVG)
```

---

## 8.3 — Enhanced Purchasing Reports Suite

### Files to Create/Modify
```
MODIFY: app/Modules/Procurement/Controllers/ProcurementReportController.php
  Add methods:
  - supplierDebtAging() — aging buckets per supplier
  - dailyPurchaseAnalysis() — purchases per day with trends
  - itemSupplierCrossRef() — which items from which suppliers
  - purchaseRequestFollowup() — status of all PRs
  - costCenterExpenseAnalysis() — expenses by cost center from procurement

CREATE views for each report
MODIFY routes to add each endpoint
```

---

## 8.4 — Enhanced Sales Reports

### Files to Create/Modify
```
MODIFY: app/Modules/Sales/Controllers/SaleReportController.php
  Add methods:
  - customerPosition() — all customers/members with balances snapshot
  - customerMovements() — movements for specific customer in period
  - commissionSummary() — sales by rep with commission totals
  - itemMargins() — cost vs sell price, margin analysis
  - topCustomers() — top N by revenue

CREATE views for each
MODIFY routes
```

---

# ═══════════════════════════════════════════════════════════════════════
# CROSS-CUTTING CONCERNS
# ═══════════════════════════════════════════════════════════════════════

## Event Bus Registrations (new events to add across phases)

```
// Phase 1
'credit_limit.exceeded' — log attempts
'sale.refunded' — enhanced with stock return

// Phase 2
'instrument.deposited'
'instrument.collected'
'instrument.bounced'
'instrument.endorsed'
'settlement.posted'
'loan.installment_paid'

// Phase 3
'supplier_quote.submitted'
'quote_evaluation.completed'
'purchase_order.created_from_evaluation'
'price_deviation.detected'

// Phase 4
'overtime.approved'
'attendance_violation.detected'
'insurance.calculated'
'tax.calculated'

// Phase 5
'bom.components_deducted'
'commission.recorded'

// Phase 6
'movement.processed' — generic event from engine
'exchange_rate.updated'

// Phase 7
'lc.opened'
'lc.settled'
'guarantee.issued'
'guarantee.released'
'asset.revalued'
```

---

## Permission Registration Summary (new permissions per phase)

### Phase 1 (add to existing modules' bootstrap.php):
- member.statement
- supplier.statement  
- inventory.opening_balance
- asset.custody

### Phase 2 (Accounting module):
- accounting.settlement.view/create/post
- accounting.loan.view/create/pay
- accounting.instrument.deposit/collect/bounce/endorse

### Phase 3 (Procurement module):
- procurement.quote.view/create/manage
- procurement.evaluation.view/create/decide
- procurement.payment_schedule.view/create/pay

### Phase 4 (HR module):
- hr.overtime.view/request/approve/manage_types
- hr.violation.view/detect/rules
- hr.permission.view/request/approve
- hr.shift.view/manage/assign
- hr.insurance_config
- hr.tax_config

### Phase 5 (Inventory + Sales):
- inventory.bom.view/manage
- inventory.units.manage
- sales.representative.view/manage
- sales.commission.view/pay
- sales.pricing.view/manage

### Phase 6 (Settings):
- settings.movement_specs.view/manage
- settings.currency.view/manage

### Phase 7 (Accounting):
- accounting.lc.view/create/manage
- accounting.guarantee.view/create/manage
- accounting.asset_category.manage
- accounting.asset.revalue/improve

---

## Menu Structure (sidebar additions)

```
المحاسبة (existing)
  └── + التسويات (settlements)
  └── + القروض البنكية (bank loans)
  └── + الاعتمادات المستندية (documentary credits)
  └── + خطابات الضمان (letters of guarantee)
  └── + البحث المتقدم عن الأوراق (advanced paper search)

المشتريات (existing)
  └── + عروض أسعار الموردين (supplier quotes)
  └── + تقييم عروض الأسعار (quote evaluation)
  └── + جدولة مدفوعات الموردين (supplier payment schedules)

الموارد البشرية (existing)
  └── + العمل الإضافي (overtime)
  └── + مخالفات الحضور (attendance violations)
  └── + طلبات الإذن (permission requests)
  └── + الورديات (shifts)

المخازن (existing)
  └── + قوائم المواد BOM (bill of materials)
  └── + أرصدة أول المدة (opening balances)
  └── + عهدة الأصول (asset custody)

المبيعات (existing)
  └── + مندوبي المبيعات (sales representatives)
  └── + قوائم الأسعار (price lists)

الإعدادات (existing or new)
  └── + مواصفات الحركات (movement specifications)
  └── + العملات وأسعار الصرف (currencies & exchange rates)
```

---

# ═══════════════════════════════════════════════════════════════════════
# EXECUTION CHECKLIST PER FEATURE
# ═══════════════════════════════════════════════════════════════════════

For EVERY feature above, the implementation steps are:

```
□ 1. Write migration file (Phase_XX_YYY_description.php)
□ 2. Run migration: php cli.php migrate
□ 3. Create Model class(es) with $table, $fillable, $timestamps
□ 4. Create Service class with business logic
□ 5. Create Controller with action methods
□ 6. Create Views (Arabic UI, RTL)
□ 7. Add Routes to module's Routes.php
□ 8. Register Permissions in bootstrap.php
□ 9. Register Menu Items in bootstrap.php
□ 10. Register Event Listeners if needed
□ 11. Test the happy path manually
□ 12. Test edge cases (empty data, invalid input, permission denied)
□ 13. Verify GL posting if applicable
□ 14. Check Arabic translation completeness in views
```

---

# STATUS TRACKING

| Phase | Feature | Status |
|-------|---------|--------|
| 1.1 | Credit Limits | ⬜ TODO |
| 1.2 | PO Delivery Tracking | ⬜ TODO |
| 1.3 | PO Balance Report | ⬜ TODO |
| 1.4 | Customer Account Statement | ⬜ TODO |
| 1.5 | Supplier Account Statement | ⬜ TODO |
| 1.6 | Sales Returns + Stock Update | ⬜ TODO |
| 1.7 | Inventory Opening Balances | ⬜ TODO |
| 1.8 | Asset Custody & Location | ⬜ TODO |
| 2.1 | Check Lifecycle Status | ⬜ TODO |
| 2.2 | Check Endorsement | ⬜ TODO |
| 2.3 | Portfolio Batch Processing | ⬜ TODO |
| 2.4 | Paper Multi-Criteria Inquiry | ⬜ TODO |
| 2.5 | Daily Cash Movement Report | ⬜ TODO |
| 2.6 | Cross-Entity Settlements | ⬜ TODO |
| 2.7 | Bank Loan Management | ⬜ TODO |
| 3.1 | Supplier Price Quotes | ⬜ TODO |
| 3.2 | Quote Evaluation & Comparison | ⬜ TODO |
| 3.3 | Supplier Payment Scheduling | ⬜ TODO |
| 3.4 | Item Price Deviation | ⬜ TODO |
| 3.5 | Purchase vs Budget | ⬜ TODO |
| 4.1 | Overtime System | ⬜ TODO |
| 4.2 | Attendance Violations | ⬜ TODO |
| 4.3 | Permission Requests | ⬜ TODO |
| 4.4 | Insurance Auto-Calc | ⬜ TODO |
| 4.5 | Tax Bracket Calc | ⬜ TODO |
| 4.6 | Shift Management | ⬜ TODO |
| 5.1 | Bill of Materials | ⬜ TODO |
| 5.2 | Multiple Units/Item | ⬜ TODO |
| 5.3 | Sales Reps & Commission | ⬜ TODO |
| 5.4 | Per-Customer Pricing | ⬜ TODO |
| 6.1 | Movement Spec Engine | ⬜ TODO |
| 6.2 | Multi-Currency | ⬜ TODO |
| 7.1 | Documentary Credits | ⬜ TODO |
| 7.2 | Letters of Guarantee | ⬜ TODO |
| 7.3 | Fixed Asset Enhancements | ⬜ TODO |
| 8.1 | Report Export Engine | ⬜ TODO |
| 8.2 | Executive Dashboard | ⬜ TODO |
| 8.3 | Purchasing Reports Suite | ⬜ TODO |
| 8.4 | Sales Reports Suite | ⬜ TODO |
