Skip to content

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 customer
    • invoice_date: Invoice date
    • due_date: Payment due date
    • status: DRAFT | OPEN | PARTIAL | PAID | VOID
    • subtotal: Line items total (before tax)
    • tax_amount: Total tax
    • total_amount: Final amount due
    • notes: Invoice notes
  • Relationships:
    • customer(): Customer (via Party)
    • lines(): InvoiceLine records
    • allocations(): ReceiptAllocation records (payments applied)
  • Computed Attributes:
    • amount_paid: Sum of allocations
    • balance_due: total_amount - amount_paid
  • Traits: BelongsToProperty
InvoiceLine (InvoiceLine.php)
  • Invoice line items
  • Table: ar_invoice_lines
  • Key Fields:
    • ar_invoice_id: Parent invoice
    • line_no: Line number
    • item_type: ROOM | HALL | GYM | INVENTORY_ITEM | SERVICE | OTHER
    • description: Line description
    • qty: Quantity
    • unit_price: Unit price
    • line_total: Extended amount (qty × unit_price)
    • revenue_account_id: GL revenue account
  • Relationships:
    • invoice(): Parent invoice
    • revenueAccount(): GL account
  • Tax Integration: Linked to tax_calculations table
Receipt (Receipt.php)
  • Customer payment header
  • Table: ar_receipts
  • Key Fields:
    • receipt_no: Unique receipt number
    • customer_id: Paying customer
    • receipt_date: Payment date
    • total_amount_received: Total payment amount
    • reference_no: External reference (check #, transaction ID)
    • notes: Payment notes
  • Relationships:
    • customer(): Customer
    • payments(): 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 receipt
    • payment_method: CASH | BANK | MOBILE_MONEY | CARD | OTHER
    • amount: Amount for this method
    • cash_account_id: GL cash/bank account
    • reference_no: Method-specific reference
  • Relationships:
    • receipt(): Parent receipt
    • cashAccount(): GL account
ReceiptAllocation (ReceiptAllocation.php)
  • Payment application to invoices
  • Table: ar_receipt_allocations
  • Key Fields:
    • ar_receipt_id: Receipt being applied
    • ar_invoice_id: Invoice being paid
    • amount_applied: Amount allocated
  • Relationships:
    • receipt(): Receipt
    • invoice(): 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

php
$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:

  1. Creates invoice in DRAFT status
  2. Creates invoice lines
  3. Calculates tax for each line via TaxEngine
  4. Stores tax calculations
  5. Updates invoice totals (subtotal + tax = total)
  6. Returns invoice

Tax Calculation:

  • Uses TaxEngine::calculate($itemType, $lineTotal)
  • Tax is calculated but NOT posted until invoice is posted
  • Tax calculations stored in tax_calculations table
postInvoice(Invoice $invoice): void

Posts draft invoice to GL and marks OPEN

php
$invoiceService->postInvoice($invoice);

Process:

  1. Validates invoice is DRAFT
  2. Prepares journal entry lines:
    Debit: Accounts Receivable (Asset)
    Credit: Revenue (by line item)
    Credit: Tax Payable (by tax calculation)
  3. Posts via AccountingPoster (Sales Journal - SJ)
  4. Updates invoice status to OPEN
  5. 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

php
$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:

  1. Creates receipt header
  2. Creates payment method records (split tender)
  3. Creates allocations to invoices
  4. Updates invoice status (PARTIAL or PAID)
  5. Decrements customer current_balance
  6. 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:

php
[
    '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

mermaid
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:

  1. FolioService closes the folio
  2. InvoiceService generates AR invoice from folio charges
  3. Invoice automatically posted to GL
  4. Customer balance updated
php
// 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_calculations table
  • 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

  1. Navigate to AR > Invoices > Create
  2. Select customer
  3. Add line items:
    • Item type
    • Description
    • Quantity
    • Unit price
    • Revenue account
  4. Review calculated totals (with tax)
  5. Save as DRAFT (for review)
  6. Review invoice details
  7. Click Post Invoice
  8. System:
    • Posts to GL
    • Updates customer balance
    • Marks invoice OPEN
    • Sends notification (optional)

2. Record Customer Payment

User: AR Clerk / Front Desk

  1. Navigate to AR > Receipts > Create
  2. Select customer
  3. Enter payment method(s):
    • Method (CASH, CARD, etc.)
    • Amount
    • Bank/Cash account
    • Reference number
  4. Allocate to invoice(s):
    • Select invoice
    • Enter amount to apply
  5. Review total = allocations
  6. Save receipt
  7. System:
    • Records payment
    • Posts to GL
    • Updates invoice status
    • Reduces customer balance
    • Sends SMS confirmation

3. Generate Aging Report

User: AR Manager / Accountant

  1. Navigate to AR > Reports > Aging
  2. Select as-of date
  3. Optional filters:
    • Customer
    • Overdue only
  4. Generate report
  5. Review buckets:
    • Current
    • 31-60 days
    • 61-90 days
    • Over 90 days
  6. Export to Excel
  7. 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:

php
'INV-' . strtoupper(uniqid())  // Invoices
'RCP-' . strtoupper(uniqid())  // Receipts

NOTE

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 AccountingMapService to 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_id parameter.
  • 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() and voidReceipt() 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

  1. Always use InvoiceService: Never create Invoice models directly
  2. Post invoices promptly: Drafts are for review only
  3. Validate allocations: Ensure receipt allocations don't exceed invoice balance
  4. Use transactions: AR operations are multi-step
  5. Configure accounts: Don't hardcode account codes

For Users

  1. Review before posting: Invoices cannot be edited after posting
  2. Apply payments correctly: Allocate to specific invoices
  3. Track overdue: Use aging reports regularly
  4. Document payments: Include reference numbers
  5. 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