# TANRAIL ONE — Phase 4.0 Financial Integration Specification

## 1. General Ledger Posting Rules

Phase 4.0 introduces 4 mandatory double-entry financial posting patterns. All postings execute through `FinancialService::postJournal()` and resolve ledger accounts through `AccountResolver`.

---

### 1.1 Customer Invoice Issuance (Revenue Recognition)
- **Trigger**: `Invoice` transitions to `ISSUED`.
- **Event**: `App\Domains\Core\Events\InvoiceIssued`
- **Journal Type**: `JournalType::OPERATIONAL`
- **Posting Rule**:
  - `DEBIT` Accounts Receivable Account (`default_accounts_receivable_account_id`) = `Invoice.total`
  - `CREDIT` Sales Revenue Account (`default_sales_revenue_account_id`) = `Invoice.subtotal`
  - `CREDIT` Tax Liability Account (from `TaxCategory.liability_account_id` or `default_tax_liability_account_id`) = `Invoice.tax_amount`

```text
Debit:  Accounts Receivable (1200)   $118.00
Credit: Sales Revenue (4000)                   $100.00
Credit: Tax Liability (2200)                    $18.00
```

---

### 1.2 Customer Payment & Allocation (AR Settlement)
- **Trigger**: `PaymentAllocation` recorded against an `Invoice`.
- **Event**: `App\Domains\Core\Events\PaymentAllocated`
- **Journal Type**: `JournalType::OPERATIONAL`
- **Posting Rule**:
  - `DEBIT` Asset Account (Cash / Bank / Mobile Money / Card based on `Payment.method`) = `Allocation.amount`
  - `CREDIT` Accounts Receivable Account (`default_accounts_receivable_account_id`) = `Allocation.amount`

```text
Debit:  Cash / Bank Account (1000)   $118.00
Credit: Accounts Receivable (1200)             $118.00
```

---

### 1.3 Supplier Invoice Approval (Vendor Bill Recognition)
- **Trigger**: `SupplierInvoice` transitions to `APPROVED`.
- **Event**: `App\Domains\Core\Events\SupplierInvoiceApproved`
- **Journal Type**: `JournalType::OPERATIONAL`
- **Posting Rule**:
  - `DEBIT` Inventory / Expense Account = `SupplierInvoice.subtotal`
  - `DEBIT` Input Tax Recoverable / Tax Account = `SupplierInvoice.tax_amount`
  - `CREDIT` Accounts Payable Account (`default_accounts_payable_account_id`) = `SupplierInvoice.total`

```text
Debit:  Inventory / Expense Account (1500) $500.00
Debit:  Input Tax Recoverable (2210)       $ 90.00
Credit: Accounts Payable (2000)                    $590.00
```

---

### 1.4 Supplier Payment Allocation (AP Settlement)
- **Trigger**: `PaymentAllocation` recorded against a `SupplierInvoice`.
- **Event**: `App\Domains\Core\Events\SupplierPaymentAllocated`
- **Journal Type**: `JournalType::OPERATIONAL`
- **Posting Rule**:
  - `DEBIT` Accounts Payable Account (`default_accounts_payable_account_id`) = `Allocation.amount`
  - `CREDIT` Asset Account (Bank / Cash based on `SupplierPayment.method`) = `Allocation.amount`

```text
Debit:  Accounts Payable (2000)            $590.00
Credit: Bank Account (1010)                        $590.00
```

---

### 1.5 Credit Note Issuance (Revenue & AR Reversal)
- **Trigger**: `CreditNote` transitions to `ISSUED`.
- **Event**: `App\Domains\Core\Events\CreditNoteIssued`
- **Journal Type**: `JournalType::OPERATIONAL`
- **Posting Rule**:
  - `DEBIT` Sales Revenue Account (`default_sales_revenue_account_id`) = `CreditNote.subtotal`
  - `DEBIT` Tax Liability Account = `CreditNote.tax_amount`
  - `CREDIT` Accounts Receivable Account (`default_accounts_receivable_account_id`) = `CreditNote.total`

---

## 2. Dynamic Account Resolution Mapping

All accounts are resolved via `AccountResolver` using deterministic fallback:

| Financial Role | Setting Key |
|---|---|
| Accounts Receivable | `default_accounts_receivable_account_id` |
| Accounts Payable | `default_accounts_payable_account_id` |
| Sales Revenue | `default_sales_revenue_account_id` |
| Tax Liability | `TaxCategory.liability_account_id` → `default_tax_liability_account_id` |
| Cash Payment | `default_cash_account_id` |
| Bank Transfer | `default_bank_account_id` |
| Mobile Money | `default_mobile_money_account_id` |
| Card Payment | `default_card_account_id` |

---

## 3. Idempotency & Replay Protection

All financial integration listeners wrapper logic in `ProcessedEvent`:

```php
ProcessedEvent::process(
    eventClass: get_class($event),
    eventId: (string) $event->document->id,
    callback: function () use ($event) {
        // Execute GL Posting
    }
);
```

### Guarantees:
- Re-dispatching `InvoiceIssued` will **NEVER** duplicate revenue or AR journals.
- Re-dispatching `SupplierInvoiceApproved` will **NEVER** duplicate AP or expense journals.
- Re-dispatching payment allocations will **NEVER** duplicate cash postings.

---

## 4. Fiscal Period Controls & Closed Period Reversals

1. **Active Period Verification**: All postings validate that the document's posting date falls within an open `FiscalPeriod`.
2. **Closed Period Protection**: If an invoice date or credit note date falls in a closed period, `FinancialService` raises `FiscalPeriodClosedException`, rolling back the transaction.
3. **Subsequent Period Reversals**: If an invoice issued in Period 1 (now closed) requires a credit note in Period 2, the credit note posts into Period 2 (open) using current date, preserving historical closed GL periods.
