# Accounting Module — Architecture Map

> **Last updated:** 2026-06-10
> **Status:** Living document — incrementally updated as new information is discovered

---

## 1. Purpose & Responsibilities

The Accounting module is the **full double-entry General Ledger** for the ERP. It provides:

- Chart of Accounts (Egyptian Standard, 5-level hierarchy, 8-digit codes)
- Journal entries (manual + auto-generated from all modules)
- Fiscal year management (auto-create, close, period locking)
- Financial reporting (Trial Balance, Income Statement, Balance Sheet, GL, AR, AP, Member Statement)
- Auto-posting from events (20+ event listeners across 10+ modules)
- Accounts Receivable tracking (fines, installments, subscriptions)
- Accounts Payable tracking (vendor invoices, payments)
- Bank accounts and reconciliation
- Negotiable instruments (checks, promissory notes)
- Cost centers and budgets
- Accounting dimensions (multi-dimensional analysis)
- Period closing (monthly lock)
- Opening entries and historical balances
- Bank loans
- Cross-entity settlements
- Documentary credits and letters of guarantee
- Account statements (customer/supplier/daily cash)
- Daily cash movement tracking

It does **NOT**:
- Process payments (Payments module)
- Manage payment queue (Cashier module)
- Handle physical treasury operations (Treasury module)
- Generate sales invoices (Sales module)
- Process payroll (HR module)

---

## 2. Directory & File Structure

```
app/Modules/Accounting/
├── bootstrap.php                          # 50+ permissions, menu, auto-fiscal-year, 20+ event listeners
├── Routes.php                             # Web routes (89 routes)
├── AccountCodes.php                       # Centralized Egyptian Standard account code constants
├── JournalServiceAdapter.php              # Interface adapter for cross-module use
├── Controllers/
│   ├── ReportController.php              # Dashboard + 12 financial reports + GL sync
│   ├── ChartOfAccountsController.php     # CoA CRUD + search
│   ├── JournalEntryController.php        # Journal CRUD + post + reverse
│   ├── JournalTypeController.php         # Journal type management
│   ├── FiscalYearController.php          # Fiscal year CRUD + close
│   ├── CostCenterController.php          # Cost center CRUD
│   ├── BudgetController.php              # Budget management + variance report
│   ├── BankAccountController.php         # Bank account CRUD
│   ├── NegotiableInstrumentController.php # Instruments CRUD + status changes
│   ├── InstrumentPortfolioController.php  # Portfolio grouping for bank submission
│   ├── BankReconciliationController.php  # Bank reconciliation sessions
│   ├── DimensionController.php           # Accounting dimensions + values
│   ├── PeriodClosingController.php       # Monthly close/reopen
│   ├── DailyTransactionController.php    # Bulk daily entry + consolidation
│   ├── OpeningEntryController.php        # Opening balances + snapshots
│   ├── AccountStatementController.php    # Customer/supplier/daily cash statements
│   ├── BankLoanController.php            # Bank loan management
│   ├── SettlementController.php          # Cross-entity settlements
│   ├── DocumentaryCreditController.php   # LC management
│   └── LetterOfGuaranteeController.php   # LG management
├── Models/
│   ├── Account.php                       # Chart of accounts model
│   ├── AccountBalance.php                # Period balances model
│   ├── AccountBudget.php                 # Account-level budget
│   ├── AccountPayable.php                # AP records
│   ├── AccountReceivable.php             # AR records (findByDocument)
│   ├── AccountingDimension.php           # Custom dimensions
│   ├── AccountingDimensionValue.php      # Dimension value options
│   ├── BankAccount.php                   # Bank account model
│   ├── BankReconciliation.php            # Reconciliation session
│   ├── BankReconciliationItem.php        # Individual reconciliation items
│   ├── CostCenter.php                    # Cost center model
│   ├── CostCenterBudget.php             # Cost center budget
│   ├── DailyTransaction.php              # Daily bulk transaction
│   ├── FiscalYear.php                    # Fiscal year model (findByDate, isOpen)
│   ├── HistoricalBalance.php             # Snapshot balances
│   ├── InstrumentPortfolio.php           # Instrument portfolio
│   ├── JournalEntry.php                  # Journal entry model (findByReference, generateEntryNumber)
│   ├── JournalEntryLine.php              # Journal line items
│   ├── JournalType.php                   # Journal type config
│   ├── NegotiableInstrument.php          # Checks/notes model
│   └── PeriodClosing.php                 # Period lock model (isPeriodClosed)
├── Services/
│   ├── AccountingIntegrationService.php  # THE auto-posting hub (20+ methods)
│   ├── JournalService.php                # Core: createEntry, postEntry, reverseEntry
│   ├── LedgerService.php                 # GL query/aggregation
│   ├── FinancialReportService.php        # Trial balance, income stmt, balance sheet
│   ├── AccountStatementService.php       # Customer/supplier statements
│   ├── StatementIntegrationService.php   # Auto-records to customer/supplier statements
│   ├── DailyCashMovementService.php      # Daily cash in/out tracking
│   ├── DailyTransactionService.php       # Bulk daily entry service
│   ├── GLSyncService.php                 # GL balance rebuild from journal entries
│   ├── BudgetService.php                 # Budget allocation + variance
│   ├── BankReconciliationService.php     # Reconciliation logic
│   ├── BankLoanService.php               # Loan amortization
│   ├── CheckLifecycleService.php         # Check status tracking
│   ├── CrossEntitySettlementService.php  # AR/AP netting
│   ├── DocumentaryCreditService.php      # LC lifecycle
│   ├── LetterOfGuaranteeService.php      # LG lifecycle
│   ├── MultiCurrencyService.php          # FX rate management
│   ├── PeriodClosingService.php          # Period lock/unlock logic
│   └── Reports/
│       ├── RevenueAnalysisService.php    # Revenue breakdown by type/period
│       └── TreasuryReportService.php     # Treasury & payments report
└── Views/
    ├── dashboard/index.php               # Main accounting dashboard
    ├── chart_of_accounts/                 # CoA index, create, edit
    ├── journal_entries/                   # Journal index, create, show
    ├── journal_types/                     # Journal type index, edit
    ├── fiscal_years/                      # FY index, create, show
    ├── cost_centers/                      # CC index, create, edit
    ├── budgets/                           # Budget index, cost centers, variance
    ├── bank_accounts/                     # Bank account index, create, edit
    ├── bank_reconciliation/               # Recon index, create, show
    ├── instruments/                       # Instrument index, show, due_soon
    ├── portfolios/                        # Portfolio index, create, show
    ├── dimensions/                        # Dimension index, show
    ├── period_closing/                    # Period closing index
    ├── daily_transactions/                # Daily TX index
    ├── opening_entries/                   # Opening entry index, create
    ├── loans/                             # Loan index, form, show
    ├── settlements/                       # Settlement index, form
    ├── documentary_credits/               # LC index, form
    ├── guarantees/                        # LG index, form
    ├── reports/                           # 12 report views
    │   ├── trial_balance.php
    │   ├── general_ledger.php
    │   ├── income_statement.php
    │   ├── balance_sheet.php
    │   ├── consolidated_balance_sheet.php
    │   ├── accounts_receivable.php
    │   ├── accounts_payable.php
    │   ├── member_statement.php
    │   ├── treasury.php
    │   └── revenue_analysis.php
    └── statements/                        # Account statements
        ├── customer.php
        ├── supplier.php
        └── daily_cash.php
```

---

## 3. Database Schema (Production — Source of Truth)

### 3.1 `chart_of_accounts` Table (710 rows as of 2026-06-10)

| Column | Type | Nullable | Key | Notes |
|--------|------|----------|-----|-------|
| id | bigint unsigned | NO | PRI | auto_increment |
| account_code | varchar(20) | NO | UNI | 1-8 digit Egyptian Standard code |
| name_ar | varchar(200) | NO | | Arabic name |
| name_en | varchar(200) | NO | | English name |
| account_type | enum | NO | MUL | asset/liability/equity/revenue/expense |
| account_nature | enum | NO | | debit/credit |
| parent_id | bigint unsigned | YES | MUL | Hierarchical parent |
| level | tinyint unsigned | NO | MUL | 1-5 (L1=general, L5=detail) |
| level_name | varchar(20) | YES | | عام/مساعد/فرعي/جزئي |
| is_header | tinyint(1) | NO | MUL | Header accounts cannot receive postings |
| is_active | tinyint(1) | NO | MUL | Default: 1 |
| is_system | tinyint(1) | NO | | System accounts cannot be deleted |
| description_ar | text | YES | | |
| description_en | text | YES | | |
| opening_balance | decimal(18,2) | NO | | Beginning of fiscal year |
| current_balance | decimal(18,2) | NO | | Running balance (updated on post) |
| currency | varchar(3) | NO | | Default: 'EGP' |
| cost_center_id | bigint unsigned | YES | | Default cost center for this account |
| is_bank_account | tinyint(1) | NO | | Links to bank_accounts |
| bank_account_id | bigint unsigned | YES | | FK to bank_accounts |
| is_archived | tinyint(1) | NO | | Soft delete |
| archived_at | datetime | YES | | |
| archived_by | bigint unsigned | YES | | |
| created_at | datetime | NO | | |
| updated_at | datetime | NO | | |
| created_by | bigint unsigned | YES | MUL | |
| updated_by | bigint unsigned | YES | MUL | |

### 3.2 `journal_entries` Table (40 rows as of 2026-06-10)

| Column | Type | Nullable | Key | Notes |
|--------|------|----------|-----|-------|
| id | bigint unsigned | NO | PRI | auto_increment |
| entry_number | varchar(30) | NO | UNI | Sequential: JE-YYYY-NNNNNN |
| fiscal_year_id | bigint unsigned | NO | MUL | FK to fiscal_years |
| entry_date | date | NO | MUL | |
| reference_type | varchar(50) | YES | MUL | payment/sale_cogs/fine/installment_plan/reversal/etc. |
| reference_id | bigint unsigned | YES | | ID of the source document |
| reference_number | varchar(50) | YES | | Receipt/invoice number |
| description_ar | varchar(500) | NO | | |
| description_en | varchar(500) | YES | | |
| total_debit | decimal(18,2) | NO | | Must equal total_credit |
| total_credit | decimal(18,2) | NO | | Must equal total_debit |
| currency | varchar(3) | NO | | Default: 'EGP' |
| status | enum | NO | MUL | draft/posted/reversed |
| posted_at | datetime | YES | | When posted to GL |
| posted_by | bigint unsigned | YES | MUL | |
| reversed_entry_id | bigint unsigned | YES | | ID of the reversal entry |
| reversal_of_id | bigint unsigned | YES | | ID of entry being reversed |
| is_auto_generated | tinyint(1) | NO | | 1 = created by event listener |
| source_module | varchar(50) | YES | MUL | payments/sales/hr/fines/installments/etc. |
| branch_id | bigint unsigned | YES | MUL | |
| cost_center_id | bigint unsigned | YES | MUL | |
| period_closed | tinyint(1) | NO | | Flags entries in closed periods |
| attachments | json | YES | | |
| notes | text | YES | | |
| is_archived | tinyint(1) | NO | | Soft delete |
| created_at | datetime | NO | | |
| updated_at | datetime | NO | | |
| created_by | bigint unsigned | YES | MUL | |
| updated_by | bigint unsigned | YES | MUL | |

### 3.3 `journal_entry_lines` Table

| Column | Type | Nullable | Key | Notes |
|--------|------|----------|-----|-------|
| id | bigint unsigned | NO | PRI | auto_increment |
| journal_entry_id | bigint unsigned | NO | MUL | FK to journal_entries |
| line_number | smallint unsigned | NO | | Sequential within entry |
| account_id | bigint unsigned | NO | MUL | FK to chart_of_accounts |
| description_ar | varchar(300) | YES | | |
| description_en | varchar(300) | YES | | |
| debit | decimal(18,2) | NO | | 0.00 if credit line |
| credit | decimal(18,2) | NO | | 0.00 if debit line |
| currency | varchar(3) | NO | | Default: 'EGP' |
| cost_center_id | bigint unsigned | YES | MUL | Optional dimensional analysis |
| dimensions | json | YES | | Custom dimensions |
| branch_id | bigint unsigned | YES | MUL | |
| member_id | bigint unsigned | YES | MUL | For member-level analysis |
| employee_id | bigint unsigned | YES | MUL | For HR entries |
| supplier_id | bigint unsigned | YES | MUL | For AP entries |
| reference_type | varchar(50) | YES | | Line-level reference |
| reference_id | bigint unsigned | YES | | |
| notes | varchar(300) | YES | | |
| created_at | datetime | NO | | |

### 3.4 `fiscal_years` Table

| Column | Type | Nullable | Key | Notes |
|--------|------|----------|-----|-------|
| id | bigint unsigned | NO | PRI | auto_increment |
| name_ar | varchar(100) | NO | | |
| name_en | varchar(100) | NO | | |
| start_date | date | NO | MUL | |
| end_date | date | NO | | |
| status | enum | NO | MUL | open/closing/closed |
| is_current | tinyint(1) | NO | MUL | |
| closed_at | datetime | YES | | |
| closed_by | bigint unsigned | YES | MUL | |
| notes | text | YES | | |
| is_archived | tinyint(1) | NO | | |

### 3.5 `account_balances` Table

| Column | Type | Nullable | Key | Notes |
|--------|------|----------|-----|-------|
| id | bigint unsigned | NO | PRI | auto_increment |
| account_id | bigint unsigned | NO | MUL | FK to chart_of_accounts |
| fiscal_year_id | bigint unsigned | NO | MUL | FK to fiscal_years |
| period | varchar(7) | NO | MUL | YYYY-MM format |
| opening_debit | decimal(18,2) | NO | | |
| opening_credit | decimal(18,2) | NO | | |
| period_debit | decimal(18,2) | NO | | Debits in this period |
| period_credit | decimal(18,2) | NO | | Credits in this period |
| closing_debit | decimal(18,2) | NO | | opening + period |
| closing_credit | decimal(18,2) | NO | | opening + period |
| cost_center_id | bigint unsigned | YES | MUL | Dimensional breakdown |
| branch_id | bigint unsigned | YES | MUL | |
| is_closed | tinyint(1) | NO | | Period lock flag |

### 3.6 `accounts_receivable` Table

| Column | Type | Nullable | Key | Notes |
|--------|------|----------|-----|-------|
| id | bigint unsigned | NO | PRI | auto_increment |
| member_id | bigint unsigned | NO | MUL | FK to members |
| document_type | varchar(50) | NO | MUL | fine/installment/subscription |
| document_id | bigint unsigned | YES | | FK to source document |
| document_number | varchar(50) | YES | | |
| document_date | date | NO | | |
| due_date | date | NO | MUL | |
| description_ar | varchar(300) | NO | | |
| total_amount | decimal(18,2) | NO | | |
| paid_amount | decimal(18,2) | NO | | Default: 0.00 |
| balance | decimal(18,2) | NO | | total - paid |
| currency | varchar(3) | NO | | Default: 'EGP' |
| status | enum | NO | MUL | pending/partial/paid/overdue/written_off/cancelled |
| journal_entry_id | bigint unsigned | YES | MUL | Original accrual entry |
| payment_entry_id | bigint unsigned | YES | MUL | Last payment entry |
| cost_center_id | bigint unsigned | YES | MUL | |
| branch_id | bigint unsigned | YES | MUL | |

### 3.7 `accounts_payable` Table

| Column | Type | Nullable | Key | Notes |
|--------|------|----------|-----|-------|
| id | bigint unsigned | NO | PRI | auto_increment |
| supplier_id | bigint unsigned | NO | MUL | FK to suppliers |
| invoice_number | varchar(50) | NO | | |
| invoice_date | date | NO | MUL | |
| due_date | date | NO | MUL | |
| description_ar | varchar(300) | NO | | |
| total_amount | decimal(18,2) | NO | | |
| paid_amount | decimal(18,2) | NO | | Default: 0.00 |
| balance | decimal(18,2) | NO | | total - paid |
| currency | varchar(3) | NO | | Default: 'EGP' |
| status | enum | NO | MUL | pending/partial/paid/overdue/cancelled |
| journal_entry_id | bigint unsigned | YES | MUL | |
| payment_entry_id | bigint unsigned | YES | MUL | |
| purchase_order_id | bigint unsigned | YES | MUL | |

### 3.8 `bank_accounts` Table

| Column | Type | Nullable | Key | Notes |
|--------|------|----------|-----|-------|
| id | bigint unsigned | NO | PRI | auto_increment |
| account_name_ar | varchar(200) | NO | | |
| account_name_en | varchar(200) | NO | | |
| bank_name_ar | varchar(200) | NO | | |
| account_number | varchar(50) | NO | UNI | |
| iban | varchar(34) | YES | | |
| swift_code | varchar(11) | YES | | |
| branch_name | varchar(150) | YES | | |
| currency | varchar(3) | NO | | Default: 'EGP' |
| gl_account_id | bigint unsigned | YES | MUL | FK to chart_of_accounts |
| opening_balance | decimal(18,2) | NO | | |
| current_balance | decimal(18,2) | NO | | |
| is_default | tinyint(1) | NO | | |
| is_active | tinyint(1) | NO | MUL | |

---

## 4. Chart of Accounts Hierarchy (Egyptian Standard)

```
Level 1 (1 digit):    1=Assets, 2=Equity+Liabilities, 3=Expenses, 4=Revenue
Level 2 (2 digits):   11=Fixed Assets, 12=Current Assets, 21=Equity, 22=LT Liabilities, 23=ST Liabilities, 31-34=Expenses, 41-42=Revenue
Level 3 (4 digits):   1101, 1202, 1206, 2101, 2306, 3101, 4101...
Level 4 (6 digits):   110101, 120601, 230601, 310101...
Level 5 (8 digits):   11010101, 12060101, 23060101...
```

**Key Account Codes (from AccountCodes.php):**

| Code | Name | Type |
|------|------|------|
| 12060101 | Cash on Hand (Main Treasury) | Asset |
| 12060102 | Sub-Treasury Cash (Sports) | Asset |
| 12060201 | Cash at Bank (Main) | Asset |
| 120301 | Accounts Receivable | Asset |
| 120209 | Inventory | Asset |
| 230601 | Accounts Payable | Liability |
| 230804 | Tax Payable | Liability |
| 23080601 | Insurance Payable | Liability |
| 230809 | Deferred Revenue | Liability |
| 310101 | Salary Expense | Expense |
| 3172 | COGS | Expense |
| 410101 | Form Fee Revenue | Revenue |
| 410102 | Membership Value Revenue | Revenue |
| 410103 | Dependent Addition Revenue | Revenue |
| 410201 | Annual Subscription Revenue | Revenue |
| 410301-410305 | Transfer/Case Fee Revenue | Revenue |
| 4106 | Fine Revenue | Revenue |
| 4107 | Sales Revenue | Revenue |
| 410901 | Down Payment Revenue (to AR) | Revenue |
| 410902 | Installment Revenue (to AR) | Revenue |

---

## 5. Core Business Flows

### 5.1 Auto-Posting from Payment Events

Every `payment.completed` event triggers `AccountingIntegrationService::onPaymentCompleted()`:
```
1. Determine debit account:
   - cash → 12060101 (Main Cash)
   - check/visa/bank_transfer → 12060201 (Bank)
   - Sub-treasury cash → 12060102 or treasury-specific code
   
2. Determine credit account (by payment type):
   - form_fee → 410101 (Form Fee Revenue)
   - membership_fee → 410102 (Membership Value Revenue)
   - addition_fee → 410103 (Dependent Addition Revenue)
   - down_payment/installment → 120301 (AR — reduces receivable)
   - fine → 4106 (Fine Revenue)
   - annual_subscription → 410201 (Subscription Revenue)
   - [and 10+ more mappings in AccountCodes::creditAccountForPaymentType()]

3. Create journal entry via JournalService::createEntry()
   - Validates fiscal year is open
   - Validates period is not closed
   - Validates double-entry balance (debit = credit)
   - Validates accounts exist and are postable (not header, is_active)
   - Auto-posts immediately (status='posted')
   - Updates chart_of_accounts.current_balance for each account
   - Upserts account_balances for the period
```

### 5.2 Journal Entry Reversal (on payment void)

```
1. payment.voided event → AccountingIntegrationService::onPaymentVoided()
2. Find original journal entry by reference_type='payment', reference_id=$paymentId
3. JournalService::reverseEntry():
   - Creates new entry with swapped debit/credit lines
   - Auto-posts the reversal
   - Marks original as status='reversed'
   - Links both entries via reversed_entry_id / reversal_of_id
```

### 5.3 Fiscal Year Auto-Creation (Bootstrap)

On every page load, the bootstrap IIFE checks if a fiscal year exists for the current calendar year. If not, it creates one:
```
- name: "السنة المالية YYYY"
- start_date: YYYY-01-01
- end_date: YYYY-12-31
- status: 'open'
- is_current: 1
```
This prevents ALL auto-posted journal entries from silently failing (no open fiscal year = entry rejected).

### 5.4 Period Closing

Monthly periods can be closed to prevent backdated entries:
```
POST /accounting/period-closing/close-month
  → PeriodClosingService: validates all entries are posted, sets is_closed=1
  → After closing: no new entries can be created for that period

POST /accounting/period-closing/reopen-month
  → Removes the lock (requires accounting.period.reopen permission)
```

### 5.5 GL Sync (Balance Rebuild)

`POST /accounting/sync-gl` — Super admin only. Rebuilds ALL account balances from posted journal entries:
```
1. Reset chart_of_accounts.current_balance to opening_balance for all accounts
2. Delete all account_balances records
3. Re-process all posted journal entries in date order
4. Recalculate period totals
```
Used when balances drift due to bugs or manual DB edits.

---

## 6. Events Consumed (20+ listeners in bootstrap.php)

| Event | Source Module | Action |
|-------|-------------|--------|
| `payment.completed` | Payments | Dr. Cash/Bank, Cr. Revenue (type-dependent) |
| `payment.voided` | Payments | Reverse the journal entry |
| `sale.completed` | Sales | Post COGS entry (Dr. COGS, Cr. Inventory) |
| `sale.voided` | Sales | Reverse COGS entry |
| `sale.refunded` | Sales | Dr. Sales Revenue, Cr. Cash |
| `hr.payroll.paid` | HR | Dr. Salary+Insurance, Cr. Bank+Tax+Insurance Payable |
| `subscription.paid` | Subscriptions | Update AR record (reduce balance) |
| `fine.imposed` | Fines | Create AR + journal (Dr. AR, Cr. Fine Revenue) |
| `fine.paid` | Fines | Update AR record (reduce balance) |
| `installment.plan_created` | Payments/Installments | Create AR (Dr. AR, Cr. Installment Revenue) |
| `installment.paid` | Installments | Update AR record (reduce balance) |
| `procurement.invoice_approved` | Procurement | Dr. Inventory+Tax, Cr. AP + create AP record |
| `procurement.payment_completed` | Procurement | Dr. AP, Cr. Cash/Bank + update AP |
| `procurement.payment_voided` | Procurement | Reverse journal + revert AP |
| `procurement.rtv_completed` | Procurement | Dr. AP, Cr. Inventory + reduce AP |
| `rental.deposit_collected` | Rentals | Reclassify revenue to deposit liability |
| `rental.deposit_refunded` | Rentals | Reverse deposit reclassification |
| `facility.entry_recorded` | Facilities | Dr. Cash, Cr. Facility Revenue |
| `carnet.guest_entry_recorded` | Carnets | Dr. Cash, Cr. Guest Entry Revenue |
| `tournament.fee_collected` | Tournaments | Dr. Cash, Cr. Tournament Revenue |
| `transfer.completed` | Transfers | Dr. AR, Cr. Transfer Revenue |
| `waiver.completed` | Waivers | Dr. AR, Cr. Waiver Revenue |
| `member.dropped` | Members | Bad debt write-off (Dr. Misc, Cr. AR) |
| `treasury.settlement.received` | Treasury/Cashier | Dr. Main Cash, Cr. Sub Cash |
| `treasury.deposit.confirmed` | Cashier | Dr. Bank, Cr. Main Cash |

**Additional listeners via StatementIntegrationService:**
- sale.completed, sale.refunded, payment.completed, procurement.invoice_approved, procurement.payment_completed, procurement.rtv_completed, fine.imposed, subscription.paid

**Additional listeners via DailyCashMovementService:**
- sale.completed, payment.completed, procurement.payment_completed

---

## 7. Events Dispatched

| Event | Payload | Dispatched From |
|-------|---------|-----------------|
| `accounting.journal_entry.created` | `{journal_entry_id, entry_number, total_debit, source_module, auto_posted}` | JournalService::createEntry |
| `accounting.journal_entry.posted` | `{journal_entry_id, entry_number}` | JournalService::postEntry |
| `accounting.journal_entry.reversed` | `{original_entry_id, reversal_entry_id, reason}` | JournalService::reverseEntry |

---

## 8. Cross-Module Dependencies

### 8.1 Accounting module IMPORTS FROM:

| Module | What it uses |
|--------|-------------|
| Payments | PaymentService (getPaymentTypeLabel in ReportController) |
| Inventory | CreditLimitService (updateCustomerBalance in StatementIntegrationService) |

### 8.2 Other modules that IMPORT FROM Accounting:

| Module | What it imports |
|--------|----------------|
| Sales | JournalServiceAdapter (direct journal posting for complex sale scenarios) |
| Inventory | MovementEngineService (journal posting for stock adjustments) |

### 8.3 Interface Contract

Accounting exposes `JournalServiceInterface` via `JournalServiceAdapter`:
- `createEntry(array $header, array $lines, bool $autoPost): array`
- `postEntry(int $entryId): array`
- `reverseEntry(int $entryId, string $reason, ?string $reversalDate): array`

### 8.4 Indirect Coupling (via Events)

Nearly every module is indirectly coupled to Accounting via events. The modules dispatch events; Accounting's bootstrap.php listens and auto-posts. This is a loose coupling — if Accounting is disabled, payments still work but no GL entries are created.

---

## 9. Routes (89 total)

### Dashboard & Reports (14)
| Method | Path | Action | Permission |
|--------|------|--------|------------|
| GET | /accounting | dashboard | accounting.reports.view |
| GET | /accounting/reports/trial-balance | trialBalance | accounting.reports.trial_balance |
| GET | /accounting/reports/trial-balance/export-csv | CSV export | accounting.reports.trial_balance |
| GET | /accounting/reports/general-ledger | generalLedger | accounting.reports.general_ledger |
| GET | /accounting/reports/income-statement | incomeStatement | accounting.reports.income_statement |
| GET | /accounting/reports/balance-sheet | balanceSheet | accounting.reports.balance_sheet |
| GET | /accounting/reports/balance-sheet/export-csv | CSV export | accounting.reports.balance_sheet |
| GET | /accounting/reports/consolidated-balance-sheet | multi-branch | accounting.reports.consolidated |
| GET | /accounting/reports/accounts-receivable | AR aging | accounting.reports.ar |
| GET | /accounting/reports/accounts-payable | AP aging | accounting.reports.ap |
| GET | /accounting/reports/member-statement | member account | accounting.reports.member_statement |
| GET | /accounting/reports/treasury | treasury report | accounting.reports.treasury |
| GET | /accounting/reports/revenue-analysis | revenue breakdown | accounting.reports.revenue_analysis |
| POST | /accounting/sync-gl | rebuild GL | accounting.fiscal_year.manage |

### Core Accounting (75)
- Fiscal Years: 5 routes (CRUD + close)
- Chart of Accounts: 6 routes (CRUD + search)
- Journal Types: 5 routes (CRUD + toggle)
- Cost Centers: 5 routes (CRUD)
- Budgets: 5 routes (CRUD + variance)
- Bank Accounts: 5 routes (CRUD)
- Journal Entries: 7 routes (CRUD + post + reverse + search)
- Instruments: 5 routes (CRUD + status + due-soon)
- Portfolios: 5 routes (CRUD + confirm)
- Bank Reconciliation: 6 routes (CRUD + add-item + complete)
- Dimensions: 5 routes (CRUD + values + toggle)
- Period Closing: 3 routes (index + close + reopen)
- Daily Transactions: 3 routes (index + store + consolidate)
- Opening Entries: 4 routes (index + create + store + snapshot)
- Statements: 3 routes (customer + supplier + daily-cash)
- Bank Loans: 5 routes (CRUD + payment)
- Settlements: 4 routes (CRUD + approve)
- Documentary Credits: 5 routes (CRUD + status)
- Letters of Guarantee: 5 routes (CRUD + status)

---

## 10. Permissions (50+)

| Category | Permissions |
|----------|-------------|
| Reports | accounting.reports.view, .trial_balance, .general_ledger, .income_statement, .balance_sheet, .consolidated, .ar, .ap, .member_statement, .treasury, .revenue_analysis |
| Fiscal Year | accounting.fiscal_year.view, .manage, .close |
| Chart of Accounts | accounting.coa.view, .manage |
| Journal Types | accounting.journal_type.view, .manage |
| Cost Centers | accounting.cost_center.view, .manage |
| Budgets | accounting.budget.view, .manage |
| Bank Accounts | accounting.bank_account.view, .manage |
| Instruments | accounting.instruments.view, .manage |
| Journal Entries | accounting.journal.view, .create, .post, .reverse |
| Dimensions | accounting.dimensions.view, .manage |
| Bank Reconciliation | accounting.bank_recon.view, .manage |
| Daily Transactions | accounting.daily_tx.view, .manage |
| Opening Entries | accounting.opening_entry.view, .manage |
| Period Closing | accounting.period.view, .close, .reopen |
| Statements | accounting.statements.view |
| Cash | accounting.cash.view |
| Loans | accounting.loans.view, .manage |
| Settlements | accounting.settlements.view, .manage, .approve |
| Documentary Credits | accounting.lc.view, .lc.manage |
| Letters of Guarantee | accounting.guarantee.view, .guarantee.manage |

---

## 11. High-Risk Areas

### 11.1 AccountingIntegrationService (CRITICAL)
- **Single point of failure** for ALL financial records
- If account codes don't exist in chart_of_accounts, auto-posting silently fails (logged but not raised)
- If fiscal year is not open, ALL auto-posts fail silently
- Each listener is wrapped in try/catch — never blocks the originating operation
- If modified incorrectly: financial records diverge from reality

### 11.2 JournalService::createEntry (CRITICAL)
- Enforces double-entry balance (debit = credit)
- Enforces fiscal year boundaries
- Enforces period lock
- Updates running balances on `chart_of_accounts.current_balance` and `account_balances`
- If balance update fails mid-transaction: GL out of sync (requires GL Sync to fix)

### 11.3 Fiscal Year Auto-Creation (Bootstrap IIFE)
- Runs on EVERY page load
- If the fiscal_years table doesn't exist yet (initial deployment): silently caught
- Creates calendar-year fiscal years (Jan-Dec), but the club operates July-June fiscal years for subscriptions
- This mismatch is intentional — GL uses calendar year, subscriptions use club year

### 11.4 Running Balance Drift
- `chart_of_accounts.current_balance` is updated incrementally on every post
- If a journal entry is created via direct DB manipulation (bypassing JournalService), balances drift
- GL Sync (`/accounting/sync-gl`) is the safety net — rebuilds from scratch
- No automatic drift detection (relies on trial balance check)

### 11.5 Period Closing Without Validation
- Closing a period locks new entries but does NOT validate that all entries are posted
- Draft entries in a closed period become permanently stuck (can't post, can't delete)
- Reopen permission is the escape hatch

### 11.6 Event Listener Priority
- All Accounting listeners run at priority 50
- Payments module's own listener runs at priority 100 (higher = earlier)
- This means: Payments processes its workflow transition BEFORE Accounting posts the journal
- If Payments' listener fails: Accounting still posts (correct behavior)
- If Accounting's listener fails: Payment is still recorded (correct behavior — fail-open for GL)

---

## 12. Known Patterns & Gotchas

1. **Fail-open for auto-posting**: All event listeners are wrapped in try/catch. If the journal entry fails (account missing, fiscal year closed, etc.), the originating operation STILL succeeds. Financial records can lag behind operational records.
2. **No reversal for AR updates**: When `onSubscriptionPaid` or `onFineCollected` updates an AR record, there is no corresponding reversal if the payment is later voided. The payment void reverses the journal entry, but the AR paid_amount is NOT decremented.
3. **Account lookup by code**: `getAccountByCode()` does a DB query every time. No caching. Under high volume, this can be a performance issue.
4. **Journal entry numbers are global sequential**: Not per-type or per-module. All entries share one sequence.
5. **member_id on journal lines**: When present, enables per-member GL analysis (member statement report). Not all auto-posted entries include this.
6. **Balance adjustment hack**: In `onPayrollPaid`, if debits don't equal credits due to rounding, the bank line's credit is silently adjusted to balance. This can create tiny discrepancies in bank reconciliation.
7. **Down payment and installment credit to AR (not revenue)**: Unlike other payment types that credit revenue accounts, down_payment and installment credit AccountCodes::ACCOUNTS_RECEIVABLE — reducing the member's receivable balance.
8. **710 accounts in CoA**: The chart is comprehensive but many accounts may have zero activity. The `is_header` flag prevents posting to parent/summary accounts.

---

## 13. Configuration Dependencies

| Dependency | Purpose |
|------------|---------|
| fiscal_years table | Must have an open year for current date |
| chart_of_accounts | Must have all codes referenced in AccountCodes.php |
| account_balances | Period tracking (auto-created on first posting) |

---

## 14. Sidebar Menu Position

The Accounting menu is at order 330 (after Cashier at 300, Payments at 320), with 30 child items covering all sub-features. Icon: calculator.
