# Task 21 — AR/AP Aging & Reconciliation Reports

## Goal
Build accounts receivable aging, accounts payable aging, and customer/supplier statement reports.

---

## AR Aging Report (Customer-side)

Already designed in Task 18. This task adds the Finance-integrated version that ties to the journal entries.

### AR Reconciliation Check
```
For each customer:
  Ledger balance (from journal entries, AR account)
  vs.
  ERP balance (invoices.total_amount - payments.amount + opening_balance)
  Difference should be zero.
```

Query for AR balance from Finance:
```php
JournalEntryLine::join('accounts', 'accounts.id', '=', 'journal_entry_lines.account_id')
    ->join('journal_entries', 'journal_entries.id', '=', 'journal_entry_lines.journal_entry_id')
    ->where('journal_entries.status', 'posted')
    ->where('journal_entry_lines.account_id', $arAccountId)
    ->selectRaw('SUM(debit) - SUM(credit) as balance')
    ->value('balance')
```

---

## AP Aging Report (Supplier-side)

When purchase receipt is posted → journal entry creates AP credit.
When supplier payment is made (future task) → AP debited.

**AP Balance per Supplier:**
```
AP Balance = opening_balance + sum(posted receipts) - sum(posted supplier payments)
```

**AP Aging buckets:** same as AR (Current / 1-30 / 31-60 / 61-90 / >90 days from receipt date)

---

## Customer Statement

A printable document sent to the customer showing:
```
Opening Balance
+ Invoices in period
- Payments in period
= Closing Balance

Detail:
Date | Reference | Description | Debit | Credit | Balance
```

**Route:** `GET /sales/customers/{customer}/statement?from=&to=`

---

## Supplier Statement

Same as customer statement but for purchase receipts and supplier payments.

---

## Service: `app/Services/Finance/ARAPService.php`

```php
public function getCustomerStatement(Customer $customer, string $from, string $to): array
{
    $openingBalance = $this->getCustomerOpeningBalance($customer, $from);
    $transactions   = $this->getCustomerTransactions($customer, $from, $to);
    $closingBalance = $openingBalance;
    foreach ($transactions as &$tx) {
        $closingBalance += $tx['debit'] - $tx['credit'];
        $tx['running_balance'] = $closingBalance;
    }
    return compact('openingBalance', 'transactions', 'closingBalance');
}

public function getARAgingReport(array $filters = []): array
{
    // Grouped by customer, bucketed by days overdue
    // Returns: customer totals per bucket + grand total row
}

public function getAPAgingReport(array $filters = []): array
{
    // Grouped by supplier
}
```

---

## Permissions
```
sales.reports.customer_statement.view
inventory.reports.supplier_statement.view
finance.reports.ar_aging.view
finance.reports.ap_aging.view
```

---

## Routes
```php
// Customer Statement
Route::get('/sales/customers/{customer}/statement', [ARAPController::class, 'customerStatement'])
    ->name('sales.customers.statement');

// AR/AP Reports
Route::prefix('finance/reports')->as('finance.reports.')->group(function () {
    Route::get('/ar-aging', [ARAPController::class, 'arAging'])->name('ar-aging');
    Route::get('/ap-aging', [ARAPController::class, 'apAging'])->name('ap-aging');
});
```
