Expense Module Documentation
Overview
The Expense Module is a critical component for managing operational cash flow. It handles the full lifecycle of business expenditures—from submission (by staff) to approval (by managers) and final settlement (by finance). It is tightly integrated with Budgeting to track spending in real-time and Accounting for automated General Ledger postings.
Key Features
- Petty Cash & Billable Expenses: Unified flow for small cash claims and larger vendor bills.
- Budget Control: Real-time tracking of spend against monthly category limits.
- Approval Chains: Configurable rules (e.g., "Auto-approve if < $50", "Require Manager if > $500").
- Financial Posting: Automated double-entry bookkeeping upon payment.
- Reversal Logic: Robust "Void" function that reverses both the GL entries and the budget utilization.
Architecture
Domain Layer (app/Domain/Expense)
Models (5 Models)
Expense (Expense.php)
- Table:
expenses - Description: The header record for a claim.
- Key Fields:
expense_no: Sequential ID (e.g., EXP-202601-0012).status: DRAFT | PENDING_APPROVAL | APPROVED | REJECTED | PAID | VOID.total_amount: Sum of all lines + tax.payment_method: CASH | BANK | MOBILE_MONEY.expense_category_id: Primary category for routing approvals.department_id: Cost center.
- Relationships:
lines(): HasMany ExpenseLine.journalEntry(): MorphOne JournalEntry.
ExpenseLine (ExpenseLine.php)
- Table:
expense_lines - Description: Granular detail allowing split-coding (e.g., one receipt split between "Food" and "Cleaning").
- Key Fields:
expense_account_id: The GL Account to debit.amount: Line item cost.tax_amount: VAT portion.
ExpenseBudget (ExpenseBudget.php)
- Table:
expense_budgets - Description: Monthly spending limit.
- Key Fields:
year,month: Fiscal period.limit_amount: Allowed spend.spent_amount: Actual utilization.is_hard_limit: Boolean (Stop vs Warn).
Services
ExpenseService (ExpenseService.php)
Purpose: Orchestrates the expense lifecycle, ensuring financial and budgetary consistency.
Key Methods:
createExpense(data, lines)
Initializes a DRAFT.
- Logic:
- Generates
expense_no(Prefix + Sequence). - Calculates
subtotal,tax,total. - Persists Header and Lines.
- Generates
submitForApproval(expense)
Triggers the workflow.
- Logic:
- Status -> PENDING_APPROVAL.
- Auto-Approval Check: Calls
isWithinAutoApprovalLimit(). If Category allows it and Amount < Limit, auto-transitions to APPROVED.
approveExpense(expense, approver)
Authorizes the spend.
- Logic:
- Status -> APPROVED.
- Budget Impact: Calls
updateBudgetSpent(..., 'add'). Incrementsexpense_budgets.spent_amount.
payExpense(expense, method)
Finalizes the financial transaction.
- Logic:
- Status -> PAID.
- Accounting: Calls
postExpenseToAccounting(Dr Expense, Cr Cash).
voidExpense(expense, reason)
Safety mechanism for errors.
- Logic:
- Reversal: If previously PAID, calls
AccountingPoster::voidEntry()to reverse GL. - Budget: If previously APPROVED, decrements
expense_budgets.spent_amount. - Status -> VOID.
- Reversal: If previously PAID, calls
Accounting Integration
The module is tightly coupled with AccountingPoster.
Account Resolution
Credit accounts (Source of Funds) are resolved dynamically based on the selected Payment Method using config() lookups.
| Payment Method | Config Key | Default Code |
|---|---|---|
| CASH | accounting.accounts.cash_in_hand | 1010 |
| BANK | accounting.accounts.bank | 1020 |
| MOBILE_MONEY | accounting.accounts.mobile_money | 1030 |
| PETTY_CASH | accounting.accounts.petty_cash | 1010 |
Journal Entries
1. Expense Payment
| Account | Dr/Cr | Amount | Memo |
|---|---|---|---|
| Expense Account (from Line) | Debit | Line Total | Line Description |
| Cash/Bank (1010/1020) | Credit | Expense Total | Payment for EXP-001 |
Audit Findings & Improvements
Strengths
- Reversal Logic: The
voidExpensemethod is excellent—it handles both the Financial Reversal (viavoidEntry) and the Budget Restoration (viadecrementSpent) atomically. This ensures reports are always accurate. - Budget Tracking: Real-time updates to budget utilization happen during the approval phase, preventing "surprise" overages at month-end.
- Granular Lines: Supporting multiple lines per expense allows for proper Cost Center accounting (e.g., a single supermarket receipt containing both "Kitchen Supplies" and "Office Supplies").
Issues Identified
Major
- Race Condition in Numbering:
generateExpenseNumberquerys for the "last number" and then increments it. Concurrent requests could generate duplicate Expense Numbers.- Fix: Use a database sequence or atomic lock.
- Hardcoded Config Dependency: The service relies on config keys like
accounting.accounts.cash_in_hand. If these keys are missing inconfig/accounting.php, it falls back to '1010', which might be incorrect for a specific tenant's Chart of Accounts.
Minor
- Soft Budget Limits: Currently, the system only tracks the budget (
updateBudgetSpent). It does not explicitly block approval if the budget is exceeded. It relies on the human approver checking the dashboard.
Configuration
Config File: config/expense.php (Virtual)
return [
'auto_approval_limit' => 50.00, // USD
'budget_enforcement' => 'soft', // 'soft' (warn) or 'hard' (block)
];Module Version: 1.0 Status: Production Ready