Skip to content

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

DomainServiceHardcoded CodesHas AccountingMapService?
HallHallBookingService::postToAccounting()103, 450, 412, 415, 416, 417, 204✅ Injected but not used
WalletWalletService::deduct(), ::refund()205, 103✅ Used in deposit() only
HRStaffCreditService (3 private methods)110, 101, 502✅ Injected but helpers bypass it
InventoryInventoryService (all methods)104, 604 via config()❌ Not injected
ExpenseExpenseService::postExpenseToAccounting()1010, 1020, 1030 via config()❌ Not injected
GymGymMembershipService::postMembershipRevenue()204, 450❌ Not injected

Services Using AccountingMapService Correctly ✅

DomainServiceNotes
GymGymDayPassServiceUses it but falls back to hardcoded in catch blocks
WalletWalletService::deposit()Only the deposit() method is migrated
HRStaffCreditService::approveCredit()Uses it for REVENUE_LAUNDRY key only
HRPayrollServiceFully migrated
FrontDeskFolioService, BookingService, etc.Fully migrated
APBillServiceFully migrated
ARInvoiceServiceFully migrated

Domain-by-Domain Findings


1. Restaurant Domain

Files: 2 models, 2 services, 3 Livewire, 3 views, 1 migration

Issues Found

#SeverityIssueLocation
R-01🔴 HIGHadmin_id defaults to 1 — should use auth()->id()RestaurantService::createOrder() L43
R-02🟡 MEDIUMNo standalone accounting entry — relies entirely on FolioService::addCharge()RestaurantService::chargeFolio()
R-03🟡 MEDIUMNo void/cancel order functionalityRestaurantService — missing voidOrder()
R-04🟡 MEDIUMNo walk-in customer support — only booking guests can orderRestaurantService::createOrder()
R-05🟢 LOWRestaurantOrderItem model has no BelongsToProperty traitRestaurantOrderItem.php
R-06🟢 LOWNo order editing after creationRestaurantService

Improvements

  • Walk-in POS: Add customer_id polymorphic 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, CANCELLED statuses with transitions
  • Kitchen display: Add a kitchen-facing order queue view with status updates
  • Menu integration: Link to InventoryItem for COGS tracking and stock deductions on each order

2. Laundry Domain

Files: 4 models, 3 Livewire dirs, 8 views, well-structured

Issues Found

#SeverityIssueLocation
L-01🟡 MEDIUMLaundryOrderLine and LaundryPriceList — no SoftDeletesModels
L-02🟡 MEDIUMLaundryItem — no SoftDeletesLaundryItem.php
L-03🟢 LOWscopePending() and scopeNeedsApproval() have identical implementationsLaundryOrder.php L221, L229

Strengths

  • ✅ Polymorphic billable (Booking, Customer, Staff) — excellent architecture
  • SoftDeletes on LaundryOrder
  • ✅ 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_DEDUCTION payment 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

#SeverityIssueLocation
H-01🔴 CRITICALpostToAccounting() has 10 hardcoded Account::where('code', ...) lookupsHallBookingService.php L630-751
H-02🔴 HIGHAR account fallback: $arAccountId = $arAccount ? $arAccount->id : 1 — falls back to ID 1 which is wrongL640
H-03🟡 MEDIUMNo SoftDeletes on HallBooking, HallBookingService, HallBookingCatering, HallBookingAdjustmentAll Hall models
H-04🟡 MEDIUMHallEventType has no SoftDeletes — deleting removes from reportingModel
H-05🟡 MEDIUMcalculateTotal() does N+1-like querying (3 separate aggregate queries)L583-628
H-06🟢 LOWAccountingMapService is injected but never usedpostToAccounting() entirely bypasses itConstructor vs L630-751

Improvements

  • Migrate postToAccounting(): Replace all Account::where('code', ...) with $this->accountingMap->getAccountId(AccountingKey::...) — the service already has AccountingMapService injected
  • Add AccountingKey enum 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

#SeverityIssueLocation
M-01🟡 MEDIUMNo service layer — all logic in Livewire componentMaintenanceTicket has assignedTo() but no assignment workflow
M-02🟡 MEDIUMUses string constants (CATEGORY_PLUMBING, etc.) instead of PHP 8.1 enumsMaintenanceTicket.php
M-03🟡 MEDIUMNo cost tracking — can't track maintenance expensesModel has no estimated_cost, actual_cost fields
M-04🟡 MEDIUMNo SoftDeletes — historical tickets lost on deleteMaintenanceTicket.php
M-05🟢 LOWsearchGlobal() searches description field directly (potential SQL injection via LIKE)L21
M-06🟢 LOWphotos stored as JSON array — no file upload/validationModel casts

Improvements

  • Cost tracking with accounting: Add estimated_cost, actual_cost, expense_id fields. When resolved, optionally create an Expense record that integrates with accounting
  • Vendor assignment: Link to Vendor model for external contractors (plumbing company, electrician)
  • Part requisition: Link to InventoryItem for 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

#SeverityIssueLocation
W-01🔴 HIGHdeduct() and refund() hardcode Account::where('code', '205') and '103' — but deposit() correctly uses AccountingMapServiceWalletService.php L195, L204, L283-284
W-02🔴 HIGHdeduct() only checks if ($this->accountingPoster) — doesn't check $this->accountingMap like deposit() doesL193
W-03🟡 MEDIUMConstructor does manual class_exists() + app() resolution instead of constructor injectionWalletService.php L21-30
W-04🟢 LOWWalletTransaction has no SoftDeletesModel

Improvements

  • Complete AccountingMapService migration: deduct() and refund() should use $this->accountingMap->getAccountId(AccountingKey::LIABILITY_WALLET, ...) and AccountingKey::ASSET_AR just like deposit() 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

#SeverityIssueLocation
SC-01🔴 HIGH3 private methods hardcode account codes ('110', '101', '502') despite having AccountingMapService injectedStaffCreditService.php L320-356
SC-02🟡 MEDIUMStaffCreditRepayment has no SoftDeletesModel
SC-03🟡 MEDIUMPayrollRunLine, PayrollRunLineDeduction, PayrollRunLineEarning have no SoftDeletesModels

Improvements

  • Migrate helper methods: Replace getEmployeeAdvancesAccountId(), getCashAccountId(), getEmployeeBenefitsExpenseAccountId() with $this->accountingMap->getAccountId(AccountingKey::ASSET_EMPLOYEE_ADVANCES, ...) etc.
  • Add missing AccountingKey entries: ASSET_EMPLOYEE_ADVANCES, EXPENSE_EMPLOYEE_BENEFITS

7. Inventory Domain

Files: 7 models, 1 service (large), 13 Livewire, multiple views

Issues Found

#SeverityIssueLocation
I-01🔴 HIGH8+ hardcoded account lookups via config('inventory.default_...') fallback to '104', '604'InventoryService.php L98, L193-194, L318-319, L442-443
I-02🔴 HIGHInventoryItemImportService hardcodes Account::where('code', '301') for opening balance accountInventoryItemImportService.php L135-136
I-03🟡 MEDIUMNo AccountingMapService injected anywhere in Inventory domainAll services
I-04🟡 MEDIUMInventoryTransaction, InventoryLot, InventoryLotConsumption, InventoryLocationStock — no SoftDeletesModels
I-05🟡 MEDIUMInventoryCategory — no SoftDeletes (deleting category orphans items)Model

Improvements

  • Inject AccountingMapService: Add AccountingKey::ASSET_INVENTORY, AccountingKey::EXPENSE_COGS, AccountingKey::EQUITY_OPENING_BALANCE and 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_date to InventoryLot

8. Expense Domain

Files: 5+ models, 1 service, 14 Livewire, extensive views

Issues Found

#SeverityIssueLocation
E-01🔴 HIGHgetCashAccountCodeByMethod() hardcodes '1010', '1020', '1030' via config() fallbackExpenseService.php L332-344
E-02🔴 HIGHNo AccountingMapService injected — only AccountingPosterExpenseService.php L15
E-03🟡 MEDIUMconfig('accounting.accounts.cash_in_hand', '1010') — this config key likely doesn't exist, so always falls back to hardcoded valueL337
E-04🟡 MEDIUMreverseAccountingEntry() is a stub — only logs but doesn't actually reverseL212-222

Improvements

  • Inject AccountingMapService: Use AccountingKey::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

#SeverityIssueLocation
G-01🔴 HIGHGymMembershipService has NO AccountingMapService — hardcodes '204' (tax) and '450' (discount)GymMembershipService.php L158, L177
G-02🟡 MEDIUMGymDayPassService has AccountingMapService but falls back to hardcoded '204' and '450' in catch blocksGymDayPassService.php L105, L127
G-03🟡 MEDIUMNo SoftDeletes on any Gym model — GymMembership, GymDayPass, GymMember, GymCheckInAll models
G-04🟢 LOWGymPlan has no soft delete — deleting a plan orphans active membershipsModel

Improvements

  • Add AccountingMapService to GymMembershipService: Inject and use AccountingKey::LIABILITY_TAX_SALES and AccountingKey::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

#SeverityIssueLocation
HK-01🟡 MEDIUMsupplyRoom() method uses InventoryService for stock deduction and accounting — but the accounting integration is inherited from InventoryService's hardcoded accountsHousekeepingService.php L233-301
HK-02🟡 MEDIUMHousekeepingChecklistItem — no SoftDeletesModel
HK-03🟢 LOWRoomAssignmentService searches for department by hardcoded 'HOUSEKEEPING' stringRoomAssignmentService.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

  • AccountingMapService and AccountingKey enum are well-designed
  • AccountingPoster handles journal entry creation with validation
  • ✅ Property-scoped chart of accounts

Issues Found

#SeverityIssueLocation
A-01🟡 MEDIUMAccountingKey enum is incomplete — missing keys for Hall, Maintenance, Inventory, Employee AdvancesAccountingKey.php
A-02🟡 MEDIUMNo automated trial balance validation / period closingMissing feature

12. FrontDesk Domain (Core)

Files: 56 files — the largest domain

Strengths

  • ✅ Fully migrated to AccountingMapService
  • Booking model has SoftDeletes
  • ✅ Comprehensive folio system with charge types, void, and transfers
  • ✅ Revenue management with pricing rules

Issues Found

#SeverityIssueLocation
FD-01🟢 LOWSome Livewire components load full model relationships — potential N+1 on large datasetsVarious

13. Party (Customer / Vendor) Domain

Files: 3 models

Strengths

  • BelongsToProperty on all models
  • ✅ Customer has credit_limit, vip_status

Issues Found

#SeverityIssueLocation
P-01🟢 LOWParty model has no SoftDeletes — deleting a party cascades to customer/vendorModel

14-21. Remaining Domains (AP, AR, Tax, SMS, Staff, Property, Import, Finance)

DomainStatusKey Finding
AP✅ GoodBill model has SoftDeletes, uses AccountingMapService
AR✅ GoodInvoice/Receipt have SoftDeletes, uses AccountingMapService
Tax✅ GoodTaxEngine properly scoped, no hardcoded accounts
SMS✅ GoodWell-structured with templates, channels, logging
Staff✅ GoodAll models have BelongsToProperty, performance reviews
Property✅ GoodPropertySeederService uses BelongsToProperty
Import🟡 MediumInventoryItemImportService hardcodes Account::where('code', '301')
Finance🟡 MediumMinimal — only 2 files, seems under-developed

Summary: Missing SoftDeletes

The following key models should have SoftDeletes added (financial/audit trail importance):

PriorityModelReason
🔴HallBookingFinancial record with accounting entries
🔴WalletTransactionAudit trail for payment disputes
🔴ExpenseFinancial record — should never be permanently deleted
🔴MaintenanceTicketHistorical tracking for repeat issues
🟡GymMembership, GymDayPassRevenue records
🟡GymMemberCustomer data protection
🟡StaffCreditRepaymentFinancial audit trail
🟡InventoryTransactionStock audit trail
🟡PayrollRunLine, child modelsPayroll audit trail
🟡LaundryOrderLine, LaundryItem, LaundryPriceListService data preservation
🟢HallEventType, GymPlan, PartyConfig/reference data protection

Priority Improvement Roadmap

P0: Accounting Consistency (1-2 days)

  1. Add missing AccountingKey enum 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
  2. Migrate HallBookingService::postToAccounting(): Replace all 10 hardcoded lookups with AccountingMapService
  3. Migrate WalletService::deduct() and refund(): Match deposit() pattern
  4. Migrate StaffCreditService private helpers: Use existing injected AccountingMapService
  5. Inject AccountingMapService into ExpenseService: Replace getCashAccountCodeByMethod() config fallbacks
  6. Inject AccountingMapService into GymMembershipService: Replace hardcoded '204' and '450'
  7. Inject AccountingMapService into InventoryService: Replace config-based fallbacks

P1: Data Protection (1 day)

  1. Add SoftDeletes to all financial models listed above
  2. Add migrations for deleted_at columns
  3. Update any ->delete() calls to respect soft delete behavior

P2: Feature Enhancements (ongoing)

  1. Restaurant: Walk-in POS, void orders
  2. Maintenance: Cost tracking, vendor assignment, part requisition from inventory
  3. Laundry: Accounting for non-booking orders, staff payroll deduction integration
  4. Gym: Class schedules, trainer booking