OLOW Hotel ERP — Domain-by-Domain Deep Audit v4
Date: February 11, 2026 Scope: All 21 domains — models, services, Livewire, views, migrations, accounting integration Status: Complete
Critical Cross-Domain Issue: Hardcoded Account Codes
The AccountingMapService was created to abstract hardcoded GL codes, but 6 of 21 domains still bypass it with direct Account::where('code', ...) lookups. This is the single biggest systemic risk — if a property has different account codes, these services will post to wrong accounts or fail silently.
Services Still Hardcoding Accounts
| Domain | Service | Hardcoded Codes | Has AccountingMapService? |
|---|---|---|---|
| Hall | HallBookingService::postToAccounting() | 103, 450, 412, 415, 416, 417, 204 | ✅ Injected but not used |
| Wallet | WalletService::deduct(), ::refund() | 205, 103 | ✅ Used in deposit() only |
| HR | StaffCreditService (3 private methods) | 110, 101, 502 | ✅ Injected but helpers bypass it |
| Inventory | InventoryService (all methods) | 104, 604 via config() | ❌ Not injected |
| Expense | ExpenseService::postExpenseToAccounting() | 1010, 1020, 1030 via config() | ❌ Not injected |
| Gym | GymMembershipService::postMembershipRevenue() | 204, 450 | ❌ Not injected |
Services Using AccountingMapService Correctly ✅
| Domain | Service | Notes |
|---|---|---|
| Gym | GymDayPassService | Uses it but falls back to hardcoded in catch blocks |
| Wallet | WalletService::deposit() | Only the deposit() method is migrated |
| HR | StaffCreditService::approveCredit() | Uses it for REVENUE_LAUNDRY key only |
| HR | PayrollService | Fully migrated |
| FrontDesk | FolioService, BookingService, etc. | Fully migrated |
| AP | BillService | Fully migrated |
| AR | InvoiceService | Fully migrated |
Domain-by-Domain Findings
1. Restaurant Domain
Files: 2 models, 2 services, 3 Livewire, 3 views, 1 migration
Issues Found
| # | Severity | Issue | Location |
|---|---|---|---|
| R-01 | 🔴 HIGH | admin_id defaults to 1 — should use auth()->id() | RestaurantService::createOrder() L43 |
| R-02 | 🟡 MEDIUM | No standalone accounting entry — relies entirely on FolioService::addCharge() | RestaurantService::chargeFolio() |
| R-03 | 🟡 MEDIUM | No void/cancel order functionality | RestaurantService — missing voidOrder() |
| R-04 | 🟡 MEDIUM | No walk-in customer support — only booking guests can order | RestaurantService::createOrder() |
| R-05 | 🟢 LOW | RestaurantOrderItem model has no BelongsToProperty trait | RestaurantOrderItem.php |
| R-06 | 🟢 LOW | No order editing after creation | RestaurantService |
Improvements
- Walk-in POS: Add
customer_idpolymorphic field (like Laundry) for walk-in restaurant orders - Direct accounting: For walk-in orders (no folio), post directly: Dr Cash, Cr Restaurant Revenue
- Order lifecycle: Add
PENDING,IN_PROGRESS,COMPLETED,CANCELLEDstatuses with transitions - Kitchen display: Add a kitchen-facing order queue view with status updates
- Menu integration: Link to
InventoryItemfor COGS tracking and stock deductions on each order
2. Laundry Domain
Files: 4 models, 3 Livewire dirs, 8 views, well-structured
Issues Found
| # | Severity | Issue | Location |
|---|---|---|---|
| L-01 | 🟡 MEDIUM | LaundryOrderLine and LaundryPriceList — no SoftDeletes | Models |
| L-02 | 🟡 MEDIUM | LaundryItem — no SoftDeletes | LaundryItem.php |
| L-03 | 🟢 LOW | scopePending() and scopeNeedsApproval() have identical implementations | LaundryOrder.php L221, L229 |
Strengths
- ✅ Polymorphic
billable(Booking, Customer, Staff) — excellent architecture - ✅
SoftDeletesonLaundryOrder - ✅ Proper status enum with transition validation
- ✅ Folio integration for booking orders
Improvements
- Accounting for non-booking orders: Walk-in customer orders have no accounting entries. Should post: Dr Cash/AR, Cr Laundry Revenue
- Staff laundry deduction: Staff orders with
SALARY_DEDUCTIONpayment method need payroll integration to auto-deduct - Pickup/delivery tracking: Add timestamps for each status transition, not just key milestones
3. Hall / Events Domain
Files: 6 models, 1 service (915 lines), 7 Livewire, multiple views
Issues Found
| # | Severity | Issue | Location |
|---|---|---|---|
| H-01 | 🔴 CRITICAL | postToAccounting() has 10 hardcoded Account::where('code', ...) lookups | HallBookingService.php L630-751 |
| H-02 | 🔴 HIGH | AR account fallback: $arAccountId = $arAccount ? $arAccount->id : 1 — falls back to ID 1 which is wrong | L640 |
| H-03 | 🟡 MEDIUM | No SoftDeletes on HallBooking, HallBookingService, HallBookingCatering, HallBookingAdjustment | All Hall models |
| H-04 | 🟡 MEDIUM | HallEventType has no SoftDeletes — deleting removes from reporting | Model |
| H-05 | 🟡 MEDIUM | calculateTotal() does N+1-like querying (3 separate aggregate queries) | L583-628 |
| H-06 | 🟢 LOW | AccountingMapService is injected but never used — postToAccounting() entirely bypasses it | Constructor vs L630-751 |
Improvements
- Migrate
postToAccounting(): Replace allAccount::where('code', ...)with$this->accountingMap->getAccountId(AccountingKey::...)— the service already hasAccountingMapServiceinjected - Add
AccountingKeyenum entries:REVENUE_HALL_RENTAL,REVENUE_HALL_CATERING,REVENUE_HALL_SERVICES,REVENUE_HALL_MISC - Split payment support: Accept partial payments via folio and direct cash (currently folio-only)
- Calendar view: Visual hall availability calendar for quick booking
- Attendee management: Add attendee name lists for event coordination
4. Maintenance Domain
Files: 1 model, 0 services, 2 Livewire, minimal views
Issues Found
| # | Severity | Issue | Location |
|---|---|---|---|
| M-01 | 🟡 MEDIUM | No service layer — all logic in Livewire component | MaintenanceTicket has assignedTo() but no assignment workflow |
| M-02 | 🟡 MEDIUM | Uses string constants (CATEGORY_PLUMBING, etc.) instead of PHP 8.1 enums | MaintenanceTicket.php |
| M-03 | 🟡 MEDIUM | No cost tracking — can't track maintenance expenses | Model has no estimated_cost, actual_cost fields |
| M-04 | 🟡 MEDIUM | No SoftDeletes — historical tickets lost on delete | MaintenanceTicket.php |
| M-05 | 🟢 LOW | searchGlobal() searches description field directly (potential SQL injection via LIKE) | L21 |
| M-06 | 🟢 LOW | photos stored as JSON array — no file upload/validation | Model casts |
Improvements
- Cost tracking with accounting: Add
estimated_cost,actual_cost,expense_idfields. When resolved, optionally create anExpenserecord that integrates with accounting - Vendor assignment: Link to
Vendormodel for external contractors (plumbing company, electrician) - Part requisition: Link to
InventoryItemfor parts used in repairs (auto-deduct from inventory) - Recurring issues: Track repeat tickets for the same room → flag for major repair/renovation
- SLA dashboard: Visual SLA compliance dashboard using existing
getIsOverdueAttribute()
5. Wallet Domain
Files: 3 models, 1 service, merchant model
Issues Found
| # | Severity | Issue | Location |
|---|---|---|---|
| W-01 | 🔴 HIGH | deduct() and refund() hardcode Account::where('code', '205') and '103' — but deposit() correctly uses AccountingMapService | WalletService.php L195, L204, L283-284 |
| W-02 | 🔴 HIGH | deduct() only checks if ($this->accountingPoster) — doesn't check $this->accountingMap like deposit() does | L193 |
| W-03 | 🟡 MEDIUM | Constructor does manual class_exists() + app() resolution instead of constructor injection | WalletService.php L21-30 |
| W-04 | 🟢 LOW | WalletTransaction has no SoftDeletes | Model |
Improvements
- Complete AccountingMapService migration:
deduct()andrefund()should use$this->accountingMap->getAccountId(AccountingKey::LIABILITY_WALLET, ...)andAccountingKey::ASSET_ARjust likedeposit()does - Constructor injection: Replace manual resolution with proper DI:
__construct(AccountingPoster $poster, AccountingMapService $map) - Withdrawal support: Allow wallet-to-bank withdrawals (currently deposit-only)
- Balance alerts: Notify guests when balance drops below configurable threshold
6. HR / StaffCredit Domain
Files: 7 models, 3 services (Payroll, StaffCredit, PayrollCalculation)
Issues Found
| # | Severity | Issue | Location |
|---|---|---|---|
| SC-01 | 🔴 HIGH | 3 private methods hardcode account codes ('110', '101', '502') despite having AccountingMapService injected | StaffCreditService.php L320-356 |
| SC-02 | 🟡 MEDIUM | StaffCreditRepayment has no SoftDeletes | Model |
| SC-03 | 🟡 MEDIUM | PayrollRunLine, PayrollRunLineDeduction, PayrollRunLineEarning have no SoftDeletes | Models |
Improvements
- Migrate helper methods: Replace
getEmployeeAdvancesAccountId(),getCashAccountId(),getEmployeeBenefitsExpenseAccountId()with$this->accountingMap->getAccountId(AccountingKey::ASSET_EMPLOYEE_ADVANCES, ...)etc. - Add missing
AccountingKeyentries:ASSET_EMPLOYEE_ADVANCES,EXPENSE_EMPLOYEE_BENEFITS
7. Inventory Domain
Files: 7 models, 1 service (large), 13 Livewire, multiple views
Issues Found
| # | Severity | Issue | Location |
|---|---|---|---|
| I-01 | 🔴 HIGH | 8+ hardcoded account lookups via config('inventory.default_...') fallback to '104', '604' | InventoryService.php L98, L193-194, L318-319, L442-443 |
| I-02 | 🔴 HIGH | InventoryItemImportService hardcodes Account::where('code', '301') for opening balance account | InventoryItemImportService.php L135-136 |
| I-03 | 🟡 MEDIUM | No AccountingMapService injected anywhere in Inventory domain | All services |
| I-04 | 🟡 MEDIUM | InventoryTransaction, InventoryLot, InventoryLotConsumption, InventoryLocationStock — no SoftDeletes | Models |
| I-05 | 🟡 MEDIUM | InventoryCategory — no SoftDeletes (deleting category orphans items) | Model |
Improvements
- Inject
AccountingMapService: AddAccountingKey::ASSET_INVENTORY,AccountingKey::EXPENSE_COGS,AccountingKey::EQUITY_OPENING_BALANCEand use them - Low stock alerts: Configurable per-item minimum levels with SMS/notification alerts
- Auto-reorder: When stock drops below min, auto-create a Purchase Requisition linked to AP
- Expiry tracking: For perishable items (food, cleaning chemicals), add
expiry_datetoInventoryLot
8. Expense Domain
Files: 5+ models, 1 service, 14 Livewire, extensive views
Issues Found
| # | Severity | Issue | Location |
|---|---|---|---|
| E-01 | 🔴 HIGH | getCashAccountCodeByMethod() hardcodes '1010', '1020', '1030' via config() fallback | ExpenseService.php L332-344 |
| E-02 | 🔴 HIGH | No AccountingMapService injected — only AccountingPoster | ExpenseService.php L15 |
| E-03 | 🟡 MEDIUM | config('accounting.accounts.cash_in_hand', '1010') — this config key likely doesn't exist, so always falls back to hardcoded value | L337 |
| E-04 | 🟡 MEDIUM | reverseAccountingEntry() is a stub — only logs but doesn't actually reverse | L212-222 |
Improvements
- Inject
AccountingMapService: UseAccountingKey::ASSET_CASH,AccountingKey::ASSET_BANK,AccountingKey::ASSET_MOBILE_MONEY,AccountingKey::ASSET_PETTY_CASH - Receipt scanning: Add file upload for expense receipts with preview
- Recurring expenses: Support for recurring expenses (rent, utilities) auto-submitted monthly
- Multi-currency: For international hotels, support expense in foreign currency with exchange rate
9. Gym Domain
Files: 5 models, 5 services
Issues Found
| # | Severity | Issue | Location |
|---|---|---|---|
| G-01 | 🔴 HIGH | GymMembershipService has NO AccountingMapService — hardcodes '204' (tax) and '450' (discount) | GymMembershipService.php L158, L177 |
| G-02 | 🟡 MEDIUM | GymDayPassService has AccountingMapService but falls back to hardcoded '204' and '450' in catch blocks | GymDayPassService.php L105, L127 |
| G-03 | 🟡 MEDIUM | No SoftDeletes on any Gym model — GymMembership, GymDayPass, GymMember, GymCheckIn | All models |
| G-04 | 🟢 LOW | GymPlan has no soft delete — deleting a plan orphans active memberships | Model |
Improvements
- Add
AccountingMapServicetoGymMembershipService: Inject and useAccountingKey::LIABILITY_TAX_SALESandAccountingKey::REVENUE_DISCOUNT - Remove catch fallbacks in
GymDayPassService: Let the exceptions propagate — hiding failures leads to wrong account postings - Class schedules: Add group fitness class booking (yoga, aerobics) with capacity management
- Personal trainer booking: Link staff (trainers) to time-slot bookings
10. Housekeeping Domain
Files: 4 models, 2 services, 6 Livewire
Issues Found
| # | Severity | Issue | Location |
|---|---|---|---|
| HK-01 | 🟡 MEDIUM | supplyRoom() method uses InventoryService for stock deduction and accounting — but the accounting integration is inherited from InventoryService's hardcoded accounts | HousekeepingService.php L233-301 |
| HK-02 | 🟡 MEDIUM | HousekeepingChecklistItem — no SoftDeletes | Model |
| HK-03 | 🟢 LOW | RoomAssignmentService searches for department by hardcoded 'HOUSEKEEPING' string | RoomAssignmentService.php L151 |
Strengths
- ✅ Well-structured task lifecycle (create → start → complete → inspect → approve/reject)
- ✅ Inventory supply tracking with accounting via InventoryService
- ✅ Maintenance issue reporting from within housekeeping tasks
11. Accounting Domain (Core)
Files: 11+ models, services, Livewire
Strengths
- ✅
AccountingMapServiceandAccountingKeyenum are well-designed - ✅
AccountingPosterhandles journal entry creation with validation - ✅ Property-scoped chart of accounts
Issues Found
| # | Severity | Issue | Location |
|---|---|---|---|
| A-01 | 🟡 MEDIUM | AccountingKey enum is incomplete — missing keys for Hall, Maintenance, Inventory, Employee Advances | AccountingKey.php |
| A-02 | 🟡 MEDIUM | No automated trial balance validation / period closing | Missing feature |
12. FrontDesk Domain (Core)
Files: 56 files — the largest domain
Strengths
- ✅ Fully migrated to
AccountingMapService - ✅
Bookingmodel hasSoftDeletes - ✅ Comprehensive folio system with charge types, void, and transfers
- ✅ Revenue management with pricing rules
Issues Found
| # | Severity | Issue | Location |
|---|---|---|---|
| FD-01 | 🟢 LOW | Some Livewire components load full model relationships — potential N+1 on large datasets | Various |
13. Party (Customer / Vendor) Domain
Files: 3 models
Strengths
- ✅
BelongsToPropertyon all models - ✅ Customer has
credit_limit,vip_status
Issues Found
| # | Severity | Issue | Location |
|---|---|---|---|
| P-01 | 🟢 LOW | Party model has no SoftDeletes — deleting a party cascades to customer/vendor | Model |
14-21. Remaining Domains (AP, AR, Tax, SMS, Staff, Property, Import, Finance)
| Domain | Status | Key Finding |
|---|---|---|
| AP | ✅ Good | Bill model has SoftDeletes, uses AccountingMapService |
| AR | ✅ Good | Invoice/Receipt have SoftDeletes, uses AccountingMapService |
| Tax | ✅ Good | TaxEngine properly scoped, no hardcoded accounts |
| SMS | ✅ Good | Well-structured with templates, channels, logging |
| Staff | ✅ Good | All models have BelongsToProperty, performance reviews |
| Property | ✅ Good | PropertySeederService uses BelongsToProperty |
| Import | 🟡 Medium | InventoryItemImportService hardcodes Account::where('code', '301') |
| Finance | 🟡 Medium | Minimal — only 2 files, seems under-developed |
Summary: Missing SoftDeletes
The following key models should have SoftDeletes added (financial/audit trail importance):
| Priority | Model | Reason |
|---|---|---|
| 🔴 | HallBooking | Financial record with accounting entries |
| 🔴 | WalletTransaction | Audit trail for payment disputes |
| 🔴 | Expense | Financial record — should never be permanently deleted |
| 🔴 | MaintenanceTicket | Historical tracking for repeat issues |
| 🟡 | GymMembership, GymDayPass | Revenue records |
| 🟡 | GymMember | Customer data protection |
| 🟡 | StaffCreditRepayment | Financial audit trail |
| 🟡 | InventoryTransaction | Stock audit trail |
| 🟡 | PayrollRunLine, child models | Payroll audit trail |
| 🟡 | LaundryOrderLine, LaundryItem, LaundryPriceList | Service data preservation |
| 🟢 | HallEventType, GymPlan, Party | Config/reference data protection |
Priority Improvement Roadmap
P0: Accounting Consistency (1-2 days)
- Add missing
AccountingKeyenum entries:ASSET_EMPLOYEE_ADVANCES,EXPENSE_EMPLOYEE_BENEFITS,REVENUE_HALL_RENTAL,REVENUE_HALL_CATERING,REVENUE_HALL_SERVICES,REVENUE_HALL_MISC,ASSET_INVENTORY,EXPENSE_COGS,ASSET_PETTY_CASH,ASSET_MOBILE_MONEY - Migrate
HallBookingService::postToAccounting(): Replace all 10 hardcoded lookups withAccountingMapService - Migrate
WalletService::deduct()andrefund(): Matchdeposit()pattern - Migrate
StaffCreditServiceprivate helpers: Use existing injectedAccountingMapService - Inject
AccountingMapServiceintoExpenseService: ReplacegetCashAccountCodeByMethod()config fallbacks - Inject
AccountingMapServiceintoGymMembershipService: Replace hardcoded'204'and'450' - Inject
AccountingMapServiceintoInventoryService: Replace config-based fallbacks
P1: Data Protection (1 day)
- Add
SoftDeletesto all financial models listed above - Add migrations for
deleted_atcolumns - Update any
->delete()calls to respect soft delete behavior
P2: Feature Enhancements (ongoing)
- Restaurant: Walk-in POS, void orders
- Maintenance: Cost tracking, vendor assignment, part requisition from inventory
- Laundry: Accounting for non-booking orders, staff payroll deduction integration
- Gym: Class schedules, trainer booking