Accounts Receivable (AR) Module Documentation
Overview
The Accounts Receivable (AR) module manages customer invoicing and payment collection processes. It integrates tightly with the Accounting module to ensure all revenue and cash receipts are properly recorded in the general ledger, and with the FrontDesk module for automatic invoice generation from guest folios.
Key Features
- Customer Invoicing: Create and manage customer invoices
- Payment Processing: Record customer payments with split tender support
- Payment Application: Allocate payments to specific invoices
- AR Aging Reports: Track outstanding receivables by age
- Automatic GL Posting: Seamless accounting integration
- SMS Notifications: Automated payment confirmations
- Invoice Status Tracking: DRAFT, OPEN, PARTIAL, PAID, VOID
Architecture
Domain Layer (app/Domain/Ar)
Models (5 Models)
Invoice (Invoice.php)
- Customer invoice header
- Table:
ar_invoices - Key Fields:
invoice_no: Unique invoice number (per property)customer_id: Bill-to customerinvoice_date: Invoice datedue_date: Payment due datestatus: DRAFT | OPEN | PARTIAL | PAID | VOIDsubtotal: Line items total (before tax)tax_amount: Total taxtotal_amount: Final amount duenotes: Invoice notes
- Relationships:
customer(): Customer (via Party)lines(): InvoiceLine recordsallocations(): ReceiptAllocation records (payments applied)
- Computed Attributes:
amount_paid: Sum of allocationsbalance_due: total_amount - amount_paid
- Traits:
BelongsToProperty
InvoiceLine (InvoiceLine.php)
- Invoice line items
- Table:
ar_invoice_lines - Key Fields:
ar_invoice_id: Parent invoiceline_no: Line numberitem_type: ROOM | HALL | GYM | INVENTORY_ITEM | SERVICE | OTHERdescription: Line descriptionqty: Quantityunit_price: Unit priceline_total: Extended amount (qty × unit_price)revenue_account_id: GL revenue account
- Relationships:
invoice(): Parent invoicerevenueAccount(): GL account
- Tax Integration: Linked to tax_calculations table
Receipt (Receipt.php)
- Customer payment header
- Table:
ar_receipts - Key Fields:
receipt_no: Unique receipt numbercustomer_id: Paying customerreceipt_date: Payment datetotal_amount_received: Total payment amountreference_no: External reference (check #, transaction ID)notes: Payment notes
- Relationships:
customer(): Customerpayments(): ReceiptPayment records (split tender)allocations(): ReceiptAllocation records (application to invoices)
- Traits:
BelongsToProperty
ReceiptPayment (ReceiptPayment.php)
- Payment method breakdown (split tender support)
- Table:
ar_receipt_payments - Key Fields:
ar_receipt_id: Parent receiptpayment_method: CASH | BANK | MOBILE_MONEY | CARD | OTHERamount: Amount for this methodcash_account_id: GL cash/bank accountreference_no: Method-specific reference
- Relationships:
receipt(): Parent receiptcashAccount(): GL account
ReceiptAllocation (ReceiptAllocation.php)
- Payment application to invoices
- Table:
ar_receipt_allocations - Key Fields:
ar_receipt_id: Receipt being appliedar_invoice_id: Invoice being paidamount_applied: Amount allocated
- Relationships:
receipt(): Receiptinvoice(): Invoice
- Constraints: Unique (receipt_id, invoice_id) - one allocation per invoice per receipt
Services (3 Services)
InvoiceService (InvoiceService.php)
Purpose: Create and post customer invoices
Dependencies:
- AccountingPoster
- TaxEngine
Key Methods:
createInvoice(Customer $customer, array $lines, ...): Invoice
Creates a new customer invoice
$invoice = $invoiceService->createInvoice(
customer: $customer,
lines: [
[
'item_type' => 'SERVICE',
'description' => 'Consulting Services',
'qty' => 5,
'unit_price' => 100.00,
'revenue_account_id' => $serviceRevenueAccount->id,
],
[
'item_type' => 'ROOM',
'description' => 'Room Charges Deluxe Suite',
'qty' => 3,
'unit_price' => 200.00,
'revenue_account_id' => $roomRevenueAccount->id,
]
],
date: '2026-01-26',
dueDate: '2026-02-25', // 30 days
notes: 'Net 30 payment terms'
);Process:
- Creates invoice in DRAFT status
- Creates invoice lines
- Calculates tax for each line via TaxEngine
- Stores tax calculations
- Updates invoice totals (subtotal + tax = total)
- Returns invoice
Tax Calculation:
- Uses
TaxEngine::calculate($itemType, $lineTotal) - Tax is calculated but NOT posted until invoice is posted
- Tax calculations stored in
tax_calculationstable
postInvoice(Invoice $invoice): void
Posts draft invoice to GL and marks OPEN
$invoiceService->postInvoice($invoice);Process:
- Validates invoice is DRAFT
- Prepares journal entry lines:
Debit: Accounts Receivable (Asset) Credit: Revenue (by line item) Credit: Tax Payable (by tax calculation) - Posts via AccountingPoster (Sales Journal - SJ)
- Updates invoice status to OPEN
- Increments customer current_balance
Accounting Entry Example:
Journal: SJ (Sales Journal)
Reference: Invoice #INV-001
Line 1:
Account: 103 (AR - Guests)
Debit: $1,150.00
Memo: "Invoice #INV-001 - John Doe"
Line 2:
Account: 4010 (Room Revenue)
Credit: $600.00
Memo: "Room Charges Deluxe Suite"
Line 3:
Account: 4020 (Service Revenue)
Credit: $500.00
Memo: "Consulting Services"
Line 4:
Account: 204 (Taxes Payable)
Credit: $50.00
Memo: "Tax: Sales Tax 10%"
Total Debits: $1,150.00 = Total Credits: $1,150.00 ✓ReceiptService (ReceiptService.php)
Purpose: Record customer payments and apply to invoices
Dependencies:
- AccountingPoster
- SmsService
Key Methods:
createReceipt(Customer $customer, array $payments, array $allocations, ...): Receipt
Records a customer payment
$receipt = $receiptService->createReceipt(
customer: $customer,
payments: [
[
'method' => 'CASH',
'amount' => 500.00,
'account_id' => $cashAccount->id,
],
[
'method' => 'CARD',
'amount' => 650.00,
'account_id' => $bankAccount->id,
'reference' => 'AUTH123456',
]
],
allocations: [
[
'invoice_id' => $invoice->id,
'amount' => 1150.00,
]
],
date: '2026-01-26',
reference: 'Payment for Invoice INV-001',
notes: 'Received with thanks'
);Process:
- Creates receipt header
- Creates payment method records (split tender)
- Creates allocations to invoices
- Updates invoice status (PARTIAL or PAID)
- Decrements customer current_balance
- Sends SMS confirmation
Invoice Status Update:
- If
amount_paid >= total_amount: Status = PAID - If
amount_paid > 0 && amount_paid < total_amount: Status = PARTIAL - If
amount_paid = 0: Status = OPEN
postReceipt(Receipt $receipt): void
Posts receipt to GL
Accounting Entry:
Journal: CR (Cash Receipts)
Reference: Receipt #RCP-001
Line 1:
Account: 101 (Cash)
Debit: $500.00
Memo: "Receipt RCP-001 - CASH"
Line 2:
Account: 102 (Bank Checking)
Debit: $650.00
Memo: "Receipt RCP-001 - CARD"
Line 3:
Account: 103 (AR - Guests)
Credit: $1,150.00
Memo: "Payment from John Doe"
Total Debits: $1,150.00 = Total Credits: $1,150.00 ✓ARAgingService (ARAgingService.php)
Purpose: Generate accounts receivable aging reports
Key Methods:
getAgingReport(?int $propertyId = null): Collection
Returns aging report by customer
Aging Buckets:
- Current (0-30 days)
- 31-60 days
- 61-90 days
- Over 90 days
Output Format:
[
'customer_name' => 'John Doe',
'total_outstanding' => 5000.00,
'current' => 2000.00,
'31_60' => 1500.00,
'61_90' => 1000.00,
'over_90' => 500.00,
]Database Schema
Entity Relationship Diagram
erDiagram
INVOICE ||--o{ INVOICE_LINE : contains
INVOICE }o--|| CUSTOMER : "billed to"
INVOICE ||--o{ RECEIPT_ALLOCATION : "paid by"
INVOICE_LINE }o--|| ACCOUNTING_ACCOUNT : "posts to revenue"
RECEIPT }o--|| CUSTOMER : "from"
RECEIPT ||--o{ RECEIPT_PAYMENT : "split tender"
RECEIPT ||--o{ RECEIPT_ALLOCATION : "applied to"
RECEIPT_PAYMENT }o--|| ACCOUNTING_ACCOUNT : "posts to cash"
RECEIPT_ALLOCATION }o--|| INVOICE : "pays"
INVOICE {
bigint id PK
bigint property_id FK
bigint customer_id FK
string invoice_no UK
date invoice_date
date due_date
enum status
decimal subtotal
decimal tax_amount
decimal total_amount
}
INVOICE_LINE {
bigint id PK
bigint ar_invoice_id FK
int line_no
enum item_type
string description
decimal qty
decimal unit_price
decimal line_total
bigint revenue_account_id FK
}
RECEIPT {
bigint id PK
bigint property_id FK
bigint customer_id FK
string receipt_no UK
date receipt_date
decimal total_amount_received
}
RECEIPT_PAYMENT {
bigint id PK
bigint ar_receipt_id FK
enum payment_method
decimal amount
bigint cash_account_id FK
}
RECEIPT_ALLOCATION {
bigint id PK
bigint ar_receipt_id FK
bigint ar_invoice_id FK
decimal amount_applied
}Integration with Other Modules
Accounting Module Integration
Every AR transaction posts to the general ledger:
Invoice Posting:
Debit: AR (Asset increases)
Credit: Revenue (Income increases)
Credit: Tax Payable (Liability increases)Receipt Posting:
Debit: Cash/Bank (Asset increases)
Credit: AR (Asset decreases)FrontDesk Module Integration
Automatic Invoice Generation: When a guest checks out:
- FolioService closes the folio
- InvoiceService generates AR invoice from folio charges
- Invoice automatically posted to GL
- Customer balance updated
// In FrontDesk\BookingService::checkOut()
$invoice = $this->invoiceService->createInvoice(
customer: $booking->customer,
lines: $folio->lines->map(fn($line) => [
'item_type' => $line->type,
'description' => $line->description,
'qty' => 1,
'unit_price' => $line->amount,
'revenue_account_id' => $line->revenue_account_id,
])->toArray(),
date: now(),
notes: "Generated from Folio #{$folio->id}"
);
$this->invoiceService->postInvoice($invoice);Tax Module Integration
Tax Calculation:
- InvoiceService uses
TaxEngine::calculate()for each line - Tax rules applied based on
item_type - Tax amounts stored in
tax_calculationstable - Tax summary included in invoice totals
SMS Module Integration
Automated Notifications:
- Payment Received: Sent when receipt created
- Invoice Generated: Sent when invoice posted (optional)
- Payment Reminder: Scheduled for overdue invoices (future)
Common Workflows
1. Create and Post Customer Invoice
User: AR Clerk / Accountant
- Navigate to AR > Invoices > Create
- Select customer
- Add line items:
- Item type
- Description
- Quantity
- Unit price
- Revenue account
- Review calculated totals (with tax)
- Save as DRAFT (for review)
- Review invoice details
- Click Post Invoice
- System:
- Posts to GL
- Updates customer balance
- Marks invoice OPEN
- Sends notification (optional)
2. Record Customer Payment
User: AR Clerk / Front Desk
- Navigate to AR > Receipts > Create
- Select customer
- Enter payment method(s):
- Method (CASH, CARD, etc.)
- Amount
- Bank/Cash account
- Reference number
- Allocate to invoice(s):
- Select invoice
- Enter amount to apply
- Review total = allocations
- Save receipt
- System:
- Records payment
- Posts to GL
- Updates invoice status
- Reduces customer balance
- Sends SMS confirmation
3. Generate Aging Report
User: AR Manager / Accountant
- Navigate to AR > Reports > Aging
- Select as-of date
- Optional filters:
- Customer
- Overdue only
- Generate report
- Review buckets:
- Current
- 31-60 days
- 61-90 days
- Over 90 days
- Export to Excel
- Follow up on overdue accounts
Configuration
Account Mapping
Required GL accounts for AR:
- 103 - Accounts Receivable (Asset)
- 101 - Cash (Asset)
- 102 - Bank Checking (Asset)
- 204 - Tax Payable (Liability)
- Revenue Accounts - Various (4000 series)
Numbering Sequences
Currently uses:
'INV-' . strtoupper(uniqid()) // Invoices
'RCP-' . strtoupper(uniqid()) // ReceiptsNOTE
Production systems should use sequential numbering with proper locking to prevent duplicates
Known Issues (from Audit)
MAJOR
MAJ-AR-001: Hardcoded Account Lookup
- Location:
InvoiceService::postInvoice() - Status: [FIXED]
- Description: Now uses
AccountingMapServiceto resolve accounts dynamically. - Priority: P1
MAJ-AR-002: Unsafe property_id Handling
- Location:
InvoiceService::createInvoice(),ReceiptService::createReceipt() - Status: [FIXED]
- Description: Methods now require explicit
property_idparameter. - Priority: P1
MAJ-AR-003: Missing Invoice Number Generator
- Location:
InvoiceService::createInvoice() - Description: Uses
uniqid()instead of sequential numbers - Impact: Non-sequential invoice numbers, no gap detection
- Fix: Implement proper sequential number generator with locking
- Priority: P1
MINOR
MIN-AR-001: Receipt Not Auto-Posted
- Description: Receipts are created but not automatically posted to GL
- Impact: Requires manual posting step
- Fix: Auto-post receipts like invoices are auto-posted
- Priority: P2
MIN-AR-002: Missing Void Functionality
- Description: No service method to void invoices or receipts
- Impact: Cannot reverse incorrect transactions
- Fix: Add
voidInvoice()andvoidReceipt()methods - Priority: P2
MIN-AR-003: Tax Account Hardcoded
- Location:
InvoiceService::postInvoice() - Description: Tax payable account code '204' is hardcoded
- Impact: Inflexible, breaks if account code changes
- Fix: Use configuration or tax rule linking
- Priority: P3
Best Practices
For Developers
- Always use InvoiceService: Never create Invoice models directly
- Post invoices promptly: Drafts are for review only
- Validate allocations: Ensure receipt allocations don't exceed invoice balance
- Use transactions: AR operations are multi-step
- Configure accounts: Don't hardcode account codes
For Users
- Review before posting: Invoices cannot be edited after posting
- Apply payments correctly: Allocate to specific invoices
- Track overdue: Use aging reports regularly
- Document payments: Include reference numbers
- Reconcile regularly: Match AR to customer statements
Improvements Planned
ENH-AR-001: Credit Memos
- Add credit memo functionality for returns/adjustments
- Integration with inventory returns
ENH-AR-002: Recurring Invoices
- Support for subscription-based billing
- Automatic invoice generation
ENH-AR-003: Payment Plans
- Enable installment payments
- Automatic payment reminders
ENH-AR-004: Direct Debit Integration
- Automatic payment collection from bank accounts
- Payment gateway integration
Module Version: 1.0 Last Reviewed: January 26, 2026 Status: Production