Accounting Integration Improvement Plan
Date: January 2026 Status: COMPLETED (Jan 27, 2026)
Implementation Summary (Completed)
We have successfully implemented the AccountingMapService to abstract General Ledger Account Codes.
Achievements:
AccountingKeyEnum: Defined logical keys for all financial touchpoints.accounting_mappingsTable: Created property-scoped mapping table.- Service Refactoring: Updated
PayrollService,WalletService,InvoiceService, andBillServiceto use dynamic mapping. - Seeder Update: Updated
AccountingSeederto populate default mappings for new properties.
1. The Problem
Currently, multiple modules interact with the Accounting System by hardcoding General Ledger (GL) Account Codes.
Examples:
PayrollService: Hardcodes'501'(Salaries Expense),'203'(Salaries Payable).WalletService: Hardcodes'102-SX'(Wallet Clearing).InvoiceService: Hardcodes'103'(Accounts Receivable).
Risks:
- Fragility: If the Finance Manager changes the Chart of Accounts (e.g., renames '103' to '1200'), the application breaks.
- Rigidity: Different properties (Tenants) might want to map "Room Revenue" to different accounts, but the code forces a single global value.
- Testing: Unit tests rely on these specific database records existing.
2. Proposed Solution: AccountingMapService
We will introduce a central service to resolve Logical Keys to Account IDs.
A. The "Logical Keys" Enum
We will define a set of constants representing every financial touchpoint in the system.
php
enum AccountingKey: string {
case REVENUE_ROOM = 'revenue.room';
case REVENUE_FOOD = 'revenue.food';
case ASSET_INVENTORY = 'asset.inventory';
case LIABILITY_TAX_PAYE = 'liability.tax.paye';
case LIABILITY_WALLET = 'liability.wallet';
// ...
}B. The Mapping Table accounting_mappings
A new table to store the configuration per property.
| Column | Type | Description |
|---|---|---|
| id | bigint | PK |
| property_id | bigint | Tenant Scope |
| key | string | Enum Value (e.g. 'revenue.room') |
| account_id | bigint | FK to accounting_accounts |
C. The Resolver Service
php
class AccountingMapService {
public function getAccount(AccountingKey $key, int $propertyId): Account {
// 1. Try DB Mapping
$mapping = AccountingMapping::where('property_id', $propertyId)
->where('key', $key->value)
->first();
if ($mapping) return $mapping->account;
// 2. Fallback to "Default" config (optional)
// 3. Throw Exception "Accounting Configuration Missing for {$key}"
}
}3. Implementation Steps
Phase 1: Preparation (The Schema)
- Create
AccountingKeyEnum. - Create migration for
accounting_mappingstable. - Seed default mappings for the Demo Property based on current hardcoded values.
Phase 2: Refactoring (The Code)
Iterate through each module and replace hardcoded lookups.
Before (InvoiceService):
php
$arAccount = Account::where('code', '103')->first();After (InvoiceService):
php
$arAccount = $this->accountingMap->getAccount(AccountingKey::ASSET_AR_GUEST, $propertyId);Phase 3: The UI (The Interface)
Create a new Settings page Finance > Accounting Setup.
- List all Logical Keys (grouped by Module).
- Dropdown to select an active GL Account for each key.
4. Affected Modules
- HR/Payroll: 6 Keys (Salary Exp, Payable, PAYE, NSSF, Loan, Advance).
- Wallet: 2 Keys (Liability, Clearing Asset).
- Inventory: 2 Keys (Asset, COGS - per Category ideally).
- FrontDesk: 2 Keys (Cash, Room Revenue).
- Gym: 2 Keys (Membership Revenue, Tax).
- AP/AR: 2 Keys (AP Control, AR Control).
- Restaurant: 2 Keys (Food Revenue, COGS).
- Laundry: 1 Key (Laundry Revenue).