# Desktop ERP — Accounting Module

This document explains how the **Accounting & Finance** module works in the Saimpex desktop backend: what each screen shows, where data comes from, and how debits/credits are calculated.

---

## 1. Big picture

The accounting module does **not** copy sales or purchases into a separate accounting database. Instead, it uses a **virtual accounting layer**:

```
┌─────────────────────────────────────────────────────────────────┐
│                     Operational tables                          │
│  orders │ purchase_orders │ purchase_order_invoice │ cash_...   │
└───────────────────────────────┬─────────────────────────────────┘
                                │
                                ▼
                   AccountingService (PHP)
                   - reads source rows
                   - maps each event to debit/credit lines
                   - groups, filters, calculates balances
                                │
                                ▼
┌─────────────────────────────────────────────────────────────────┐
│              Four UI views (API responses)                      │
│  Chart of Accounts │ Journal Entries │ Ledger │ Trial Balance   │
└─────────────────────────────────────────────────────────────────┘
```

**Only manual adjustments** are stored in dedicated tables:

| Table | Purpose |
|-------|---------|
| `journal_entries` | Header: date, description, draft/posted status |
| `journal_entry_lines` | Lines: account code, debit, credit |

Everything else is **computed at request time** from existing POS/ERP data.

---

## 2. Accounting basics (quick reference)

### Double-entry bookkeeping

Every financial event affects **at least two accounts**. Total debits must equal total credits.

| Term | Meaning |
|------|---------|
| **Debit (DR)** | Left side of an entry |
| **Credit (CR)** | Right side of an entry |
| **Journal entry** | One business transaction (e.g. one sale, one payment) |
| **Journal entry line** | One account line inside that transaction |

### Chart of Accounts (COA)

A **Chart of Accounts** is the master list of accounts the business uses. Ours is predefined in `AccountingService::CHART_OF_ACCOUNTS`:

| Code | Account | Type | Normal balance |
|------|---------|------|----------------|
| 1000 | Cash | Asset | Debit (+) |
| 1100 | Accounts Receivable | Asset | Debit (+) |
| 2000 | Accounts Payable | Liability | Credit (− in our display) |
| 3000 | Sales Revenue | Revenue | Credit (− in our display) |
| 4000 | Cost of Goods Sold | Expense | Debit (+) |
| 5000 | Operating Expenses | Expense | Debit (+) |

### What is a **Ledger**?

A **ledger** is the **detailed transaction history for accounts**, line by line, in date order.

- Each row = one debit or credit to one account
- The **running balance** column shows the account balance after that row
- You can filter by account (e.g. only Cash `1000`) or show all accounts

Think of it as: *“Show me every movement that hit Cash, and what my Cash balance was after each movement.”*

### What is a **Journal Entry** list?

The **Journal Entries** screen groups ledger lines into **whole transactions**.

Example — one POS sale of MRU 1,000:

| Entry | Account | Debit | Credit |
|-------|---------|------:|-------:|
| JE-SALE-ORD-POS-000001 | Cash (1000) | 1,000 | 0 |
| JE-SALE-ORD-POS-000001 | Sales Revenue (3000) | 0 | 1,000 |

The UI shows **one row**: date, entry number, description, debit 1,000, credit 1,000, status Posted.

### What is a **Trial Balance**?

A **trial balance** is a snapshot **as of a date** that lists each account’s total debits and credits. If bookkeeping is correct:

```
Sum of all debit columns = Sum of all credit columns
```

It is used to verify that the books balance before preparing reports.

### Balance display rule

For every account:

```
balance = total_debit − total_credit
```

| Sign | Typical meaning |
|------|-----------------|
| **Positive** | Net debit (Cash, AR, COGS, Expenses) |
| **Negative** | Net credit (AP, Revenue) |

This matches the UI: Cash MRU 50,000 (green), Sales Revenue −MRU 200,000 (red).

---

## 3. Core engine: `AccountingService`

**File:** `app/Services/desktop/AccountingService.php`

### Step 1 — Collect ledger lines

`collectLedgerLines($vendorId, $fromDate, $toDate, $postedOnly)` merges lines from six sources:

| # | Source method | Database table(s) | `source_type` |
|---|---------------|-------------------|---------------|
| 1 | `linesFromSales` | `orders` | `sale` |
| 2 | `linesFromPurchaseAccruals` | `purchase_orders` | `purchase` |
| 3 | `linesFromPurchasePayments` | `purchase_order_invoice` | `purchase_payment` |
| 4 | `linesFromCashTransactions` | `cash_transactions` | `cash_transaction` |
| 5 | `linesFromCustomerPayments` | `customer_balance_transactions` | `customer_payment` |
| 6 | `linesFromManualEntries` | `journal_entries` + `journal_entry_lines` | `manual` |

Each line has this shape:

```json
{
  "date": "2026-01-13",
  "entry_number": "JE-SALE-ORD-POS-000001",
  "account_code": "1000",
  "account_name": "Cash",
  "description": "Sales receipt",
  "reference": "ORD-POS-000001",
  "debit": 1000.00,
  "credit": 0.00,
  "source_type": "sale",
  "source_id": 42,
  "status": 2
}
```

Lines are sorted by **date**, then **entry_number**.

### Step 2 — Build views

| Method | Used by screen | What it does |
|--------|----------------|--------------|
| `buildChartOfAccounts()` | Chart of Accounts | Sum debits/credits per account → show balance |
| `buildJournalEntriesList()` | Journal Entries | Group lines by `entry_number` into one row per transaction |
| `buildRunningLedger()` | Ledger | Add running balance after each line |
| `buildTrialBalance()` | Trial Balance | Per-account debit/credit totals + grand totals |

---

## 4. Data sources in detail

All queries are **scoped to the logged-in vendor** (`vendor_id`).

### 4.1 POS sales → `orders`

**Filter:**
- `vendor_id` = current vendor
- `source = 2` (POS only, not delivery app orders)
- `status != 10` (not cancelled)

**Date field:** `COALESCE(placed_at, created_at)`

**Amount:** `orders.total`

**Accounting logic:**

| Condition | Debit | Credit |
|-----------|-------|--------|
| Cash/online paid sale (`is_credit_sale = false` and `payment_status != 1`) | 1000 Cash | 3000 Sales Revenue |
| Credit sale (`is_credit_sale = true` OR `payment_status = 1`) | 1100 Accounts Receivable | 3000 Sales Revenue |

**Entry number:** `JE-SALE-{order_code}`  
**Reference:** `order_code` (e.g. `ORD-POS-000001`)

**Example — MRU 1,000 cash sale:**

```
DR  Cash (1000)           1,000
CR  Sales Revenue (3000)          1,000
```

---

### 4.2 Purchase accrual → `purchase_orders`

When a PO is created, we record the **obligation** to the supplier (you received goods/expense, you owe money).

**Filter:** `purchase_orders.vendor_id`  
**Date field:** `order_date` (fallback: `created_at`)  
**Amount:** `purchase_orders.total`

**Accounting logic:**

```
DR  Cost of Goods Sold (4000)    {total}
CR  Accounts Payable (2000)               {total}
```

**Entry number:** `JE-PO-{po_number}`  
**Reference:** `po_number`

---

### 4.3 Purchase payments → `purchase_order_invoice`

When the vendor pays a supplier (initial payment on PO create or `purchaseOrderRepayment`).

**Filter:**
- `invoice_total > 0`
- PO belongs to vendor

**Date field:** `invoice_date`  
**Amount:** `invoice_total` (this payment amount)

**Accounting logic:**

```
DR  Accounts Payable (2000)    {payment}
CR  Cash (1000)                        {payment}
```

**Entry number:** `JE-PAY-{invoice_number}`  
**Reference:** `invoice_number`

> **Note:** Accrual (4.2) increases AP; each payment (4.3) reduces AP and reduces Cash. Net AP on the Chart of Accounts ≈ outstanding supplier balance.

---

### 4.4 Cash transactions → `cash_transactions`

Petty cash / manual cash in and out from the POS cash module.

**Filter:** `cash_transactions.vendor_id`  
**Date field:** `COALESCE(date, created_at)`  
**Amount:** `amount`

| `type` | Meaning | Debit | Credit |
|--------|---------|-------|--------|
| `1` | Cash In | 1000 Cash | 5000 Operating Expenses |
| `2` | Cash Out | 5000 Operating Expenses | 1000 Cash |

**Entry number:** `JE-CASH-{id}`  
**Reference:** `CASH-{id}`

---

### 4.5 Customer balance payments → `customer_balance_transactions`

When a customer pays down their on-account balance.

**Filter:**
- `vendor_id`
- `type = 2` (Payment)

**Date field:** `created_at`  
**Amount:** `abs(amount)`

**Accounting logic:**

```
DR  Cash (1000)                    {amount}
CR  Accounts Receivable (1100)            {amount}
```

**Entry number:** `JE-CBT-{id}`  
**Reference:** `CBT-{id}`

> Credit sales (4.1) increase AR. Customer payments (4.5) reduce AR and increase Cash.

---

### 4.6 Manual journal entries → `journal_entries`

Created via **New Journal Entry** in the UI.

| Status | Value | Included in COA / Ledger / Trial Balance? |
|--------|-------|----------------------------------------|
| Draft | `1` | Shown in Journal Entries list only |
| Posted | `2` | Included in all reports |

**Entry number format:** `JE-{YEAR}-{seq}` (e.g. `JE-2026-001`)

Rules:
- At least 2 lines
- Each line: debit **or** credit (not both)
- Total debits must equal total credits
- Post via `postJournalEntry` to include in financial reports

---

## 5. End-to-end flow examples

### Example A — Cash sale MRU 1,000

```
1. Cashier completes POS sale → row in `orders`
2. User opens Journal Entries → backend reads `orders`
3. AccountingService creates 2 virtual lines (Cash DR, Revenue CR)
4. User opens Ledger → same lines with running Cash balance
5. Chart of Accounts → Cash and Revenue balances updated
```

### Example B — Purchase PO MRU 5,000, pay MRU 2,000 now

```
1. createPurchaseOrder → purchase_orders.total = 5,000
   → Virtual entry: DR COGS 5,000 / CR AP 5,000

2. Initial invoice payment 2,000 → purchase_order_invoice
   → Virtual entry: DR AP 2,000 / CR Cash 2,000

3. Chart of Accounts:
   - AP balance ≈ −3,000 (still owe 3,000)
   - Cash reduced by 2,000
   - COGS increased by 5,000
```

### Example C — Trial balance on 2026-05-26

```
1. API: GET trialBalance?as_of_date=2026-05-26
2. collectLedgerLines(vendorId, null, '2026-05-26')
3. Only transactions on or before that date are included
4. buildTrialBalance() sums debits/credits per account
5. Response includes is_balanced: true/false
```

---

## 6. API endpoints

Base path: `GET/POST api/desktop/erp/` (auth + vendor middleware)

### 6.1 Four main list screens

| UI tab | Endpoint | Key parameters |
|--------|----------|----------------|
| Chart of Accounts | `GET chartOfAccounts` | `limit`, `page`, `keyword`, `type` |
| Journal Entries | `GET journalEntries` | `limit`, `page`, `keyword`, `from_date`, `to_date`, `status` |
| Ledger | `GET ledger` | `limit`, `page`, `account_code`, `from_date`, `to_date` |
| Trial Balance | `GET trialBalance` | `as_of_date` **(required)** |

### 6.2 Supporting endpoints

| Endpoint | Purpose |
|----------|---------|
| `GET getAccounts` | Account dropdown for manual entry form |
| `POST createJournalEntry` | Create manual entry (draft or post immediately) |
| `GET postJournalEntry?journal_entry_id=` | Post a draft manual entry |
| `GET journalEntryDetails?entry_number=` | View full entry with all lines |

### 6.3 Manual journal entry body

```json
{
  "entry_date": "2026-05-26",
  "description": "Office supplies adjustment",
  "post_now": false,
  "lines": [
    { "account_code": "5000", "debit": 200, "credit": 0 },
    { "account_code": "1000", "debit": 0, "credit": 200 }
  ]
}
```

---

## 7. Architecture diagram

```mermaid
flowchart TB
    subgraph Sources["Operational data (existing tables)"]
        O[orders - POS sales]
        PO[purchase_orders]
        POI[purchase_order_invoice]
        CT[cash_transactions]
        CBT[customer_balance_transactions]
        JE[journal_entries - manual only]
    end

    subgraph Service["AccountingService"]
        CL[collectLedgerLines]
        COA[buildChartOfAccounts]
        JEL[buildJournalEntriesList]
        RL[buildRunningLedger]
        TB[buildTrialBalance]
    end

    subgraph API["AccountsController"]
        A1[chartOfAccounts]
        A2[journalEntries]
        A3[ledger]
        A4[trialBalance]
    end

    O --> CL
    PO --> CL
    POI --> CL
    CT --> CL
    CBT --> CL
    JE --> CL

    CL --> COA --> A1
    CL --> JEL --> A2
    CL --> RL --> A3
    CL --> TB --> A4
```

---

## 8. What is stored vs computed

| Data | Stored in DB? | How |
|------|---------------|-----|
| POS sale | Yes | `orders` + `order_items` |
| Purchase order | Yes | `purchase_orders` |
| Supplier payment | Yes | `purchase_order_invoice` |
| Cash in/out | Yes | `cash_transactions` |
| Customer on-account payment | Yes | `customer_balance_transactions` |
| Manual journal entry | Yes | `journal_entries` + `journal_entry_lines` |
| Chart of accounts list | No | Fixed config in `AccountingService` |
| Account balances | No | Calculated from ledger lines |
| Journal entry list (auto) | No | Grouped from ledger lines |
| Ledger running balance | No | Calculated on the fly |
| Trial balance | No | Calculated on the fly |

---

## 9. Status codes

### Journal entry status (`journal_entries.status`)

| Value | Label | Meaning |
|-------|-------|---------|
| `1` | Draft | Manual entry not yet posted |
| `2` | Posted | Included in reports |

Auto-generated entries (sales, purchases, etc.) are always treated as **Posted** (`2`).

### Auto entry `source_type` values

| Value | Origin |
|-------|--------|
| `sale` | POS order |
| `purchase` | Purchase order accrual |
| `purchase_payment` | PO invoice payment |
| `cash_transaction` | Cash in/out |
| `customer_payment` | Customer balance payment |
| `manual` | User-created journal entry |

---

## 10. Files reference

| File | Role |
|------|------|
| `app/Services/desktop/AccountingService.php` | Core logic: COA, line collection, balances |
| `app/Http/Controllers/desktop/erp/AccountsController.php` | HTTP API |
| `app/Models/desktop/JournalEntry.php` | Manual entry header model |
| `app/Models/desktop/JournalEntryLine.php` | Manual entry line model |
| `database/migrations/2026_05_26_100000_create_journal_entries_table.php` | Manual entries table |
| `database/migrations/2026_05_26_100001_create_journal_entry_lines_table.php` | Manual entry lines table |
| `routes/desktop.php` | Route registration under `erp/` prefix |

---

## 11. FAQ

### Why don’t we duplicate sales into an accounting table?

- Single source of truth — POS/ERP data stays authoritative
- No sync issues — accounting always reflects live operations
- Less storage and simpler maintenance

### Do delivery app orders appear in accounting?

No. Only `orders.source = 2` (POS) is included. App/delivery orders are excluded unless extended later.

### Why does Sales Revenue show a negative balance?

Revenue accounts normally have a **credit** balance. Our formula `debit − credit` is negative when credits dominate. The UI shows this as −MRU (red).

### Can I add more accounts?

Today the COA is fixed in `AccountingService::CHART_OF_ACCOUNTS`. To make it vendor-configurable, add an `accounting_accounts` table and load it instead of the constant.

### What is not included yet?

- Employee expense approvals → Operating Expenses
- Payroll payments
- Delivery/app sales
- Refunds / returns as reversing entries
- Export to CSV/PDF

These can be added by new `linesFrom*` methods in `AccountingService`.

---

## 12. Quick mental model

```
Business event happens (sale, PO, payment, cash, manual JE)
        ↓
Stored in its normal operational table
        ↓
Accounting API called
        ↓
AccountingService reads rows → converts to debit/credit lines
        ↓
Same lines power all 4 screens:
  • COA      → balances per account
  • Journal  → one row per transaction
  • Ledger   → line detail + running balance
  • Trial    → debit/credit totals as of date
```

**Ledger** = the detailed story of every account movement.  
**Journal** = those movements grouped into business transactions.  
**Chart of Accounts** = account list + current balance.  
**Trial Balance** = proof that debits equal credits at a point in time.
