> ## Documentation Index
> Fetch the complete documentation index at: https://docs.quinnsambal.com/llms.txt
> Use this file to discover all available pages before exploring further.

# General ledger

<style>
  {`
    [class*="max-w-none"][class*="table"] {
      display: block !important;
      overflow-x: auto !important;
      max-width: 100% !important;
      width: 100% !important;
      flex-grow: 0 !important;
    }
    [class*="max-w-none"][class*="table"] > table {
      width: 100% !important;
      max-width: 100% !important;
      table-layout: fixed !important;
    }
    .mermaid {
      max-width: 100% !important;
      overflow-x: auto !important;
    }
    article svg[role="img"] {
      max-width: 100% !important;
      height: auto !important;
    }
    article img, .prose img {
      max-width: 100% !important;
      height: auto !important;
    }
    img[src*="LOGO"], img[src*="logo"] {
      max-width: 120px !important;
      max-height: 40px !important;
      width: auto !important;
      height: auto !important;
      object-fit: contain !important;
    }
    `}
</style>

***

title: "General Ledger"
description: "Chart of Accounts hierarkis, jurnal double-entry, posting lifecycle, buku besar per akun, trial balance, dan auto-journaling dari lintas modul di SNISHOP ERP."
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------

# General Ledger

<img src="https://mintcdn.com/quinnofspicy/e4f_upKhVWcjsUmM/docs/mintlify/screenshots/finance/general-ledger.png?fit=max&auto=format&n=e4f_upKhVWcjsUmM&q=85&s=78740761cf47ac7c5ba5b60bc9e8e0b6" alt="General Ledger" width="1920" height="1080" data-path="docs/mintlify/screenshots/finance/general-ledger.png" />

General Ledger adalah pusat akuntansi SNISHOP ERP. Setiap transaksi keuangan — baik dari input manual maupun otomatis dari modul POS, Manufacturing, Expense, Invoice, atau Payroll — tercatat di jurnal umum menggunakan prinsip **double-entry bookkeeping**. Sistem ini mengelola **Chart of Accounts (COA) hierarkis 3 level** dengan 5 tipe akun, memvalidasi keseimbangan debit-kredit hingga toleransi Rp 0.01, dan mendukung lifecycle jurnal dari draft hingga reversal.

## Arsitektur General Ledger

```mermaid theme={null}
graph TB
    subgraph COA["Chart of Accounts (COA)"]
        L0["Level 0: Header<br/>Aset / Liabilitas / Ekuitas / Revenue / Expense"]
        L1["Level 1: Main Account<br/>Kas & Bank, Piutang, dll"]
        L2["Level 2: Sub Account<br/>BCA, Mandiri, Kas IDR, dll"]
        L0 --> L1 --> L2
    end

    subgraph JOURNAL["Journal Entry Lifecycle"]
        D["Draft"] --> P["Posted"]
        P --> R["Reversed"]
        D -->|Delete| X["Deleted"]
    end

    subgraph SOURCES["Sumber Jurnal"]
        MANUAL["Manual Entry"]
        EXP["Expense Approval"]
        INV["Invoice Payment"]
        POS["POS Transaction"]
        MFG["Production Batch"]
        XFER["Transfer"]
    end

    subgraph OUTPUT["Output"]
        LEDGER["Buku Besar<br/>Per Akun"]
        TB["Trial Balance<br/>Debit = Kredit"]
        REPORT["Laporan Keuangan<br/>P&L, Neraca"]
    end

    MANUAL --> D
    EXP --> D
    INV --> D
    POS --> D
    MFG --> D
    XFER --> D

    D --> P
    P --> LEDGER
    LEDGER --> TB
    TB --> REPORT
```

## Entity Relationship Diagram (ERD)

Diagram berikut menunjukkan relasi antar entitas inti dalam modul General Ledger beserta entitas pendukung dari modul keuangan lainnya. Relasi ini menggambarkan bagaimana data mengalir dari rekening kas (`Account`), pencatatan transaksi (`FinancialRecord`), chart of accounts (`GLAccount`), hingga ke jurnal umum (`GLJournalEntry`).

```mermaid theme={null}
erDiagram
    GLJournalEntry ||--o{ GLAccount : "line_items[].account_id"
    GLAccount ||--o| GLAccount : "parent_account_id (self-referential)"
    GLAccount }o--|| Company : "company_id"
    GLJournalEntry }o--|| Company : "company_id"

    FinancialRecord }o--o| GLAccount : "account_id (mapping ke COA)"
    FinancialRecord }o--o| Account : "account_id (sumber dana)"
    FinancialRecord }o--o| Account : "transfer_to_account_id (tujuan)"
    FinancialRecord }o--|| Company : "company_id"
    FinancialRecord }o--|| User : "user_id"

    Account }o--|| Company : "company_id"
    Account }o--|| User : "user_id"

    GLJournalEntry {
        string id PK
        string company_id FK
        date entry_date
        string reference_number
        string reference_type "enum: manual, invoice, purchase_order, transfer, expense"
        string reference_id FK
        string description
        array line_items "[{account_id, account_code, debit, credit, description}]"
        number total_debit
        number total_credit
        boolean is_balanced
        string status "enum: draft, posted, reversed"
        date posted_date
        string posted_by
        string notes
    }

    GLAccount {
        string id PK
        string company_id FK
        string account_code "unique, misal: 1000, 2100, 4001"
        string account_name
        string account_type "enum: asset, liability, equity, revenue, expense"
        string category "misal: Current Asset, Fixed Asset"
        string sub_category "sub-kategori akun"
        string parent_account_id FK "self-referential ke GLAccount"
        string normal_balance "enum: debit, credit"
        boolean is_active
        string description
        number level "0=header, 1=main, 2=sub"
    }

    Account {
        string id PK
        string user_id FK
        string company_id FK
        string name "Nama rekening/kantong"
        string type "enum: cash, bank, e-wallet, other"
        string account_number
        string bank_name
        number initial_balance
        number current_balance
        string currency "default: IDR"
        string icon
        string color
        boolean is_active
        boolean is_default_pos
        string notes
        string mode "enum: personal, business"
    }

    FinancialRecord {
        string id PK
        string user_id FK
        string company_id FK
        string account_id FK
        string type "enum: income, expense, transfer"
        number amount
        string category
        string description
        datetime date
        string attachment_url
        string source "enum: manual, ai_text, ai_scan, pos, manufacturing, distribution, recall, stock_opname"
        string mode "enum: personal, business"
        string transfer_to_account_id FK
        number transfer_fee
        string reference_id FK
        string reference_type "enum: invoice_payment, pos_transaction, expense, manual, transfer, production_order, distribution_shipment, distribution_return, batch_recall, stock_opname"
        string idempotency_key
        boolean is_inventory_material
        number channel_fee
        number cogs_amount
        number tax_amount
    }
```

### Penjelasan Relasi

* **GLJournalEntry → GLAccount**: Satu jurnal memiliki banyak line items, masing-masing line item mereferensikan satu `GLAccount` melalui `account_id`. Ini adalah inti dari double-entry bookkeeping — setiap jurnal minimal memiliki 2 line items (satu debit, satu kredit).
* **GLAccount → GLAccount (self-referential)**: COA bersifat hierarkis. Field `parent_account_id` memungkinkan akun anak merujuk ke akun parent-nya. Contoh: "BCA Rupiah" (level 2) → parent → "Kas & Bank" (level 1) → parent → "Aset Lancar" (level 0).
* **FinancialRecord → Account**: Setiap pencatatan transaksi keuangan berasal dari suatu rekening/kantong (`Account`). Untuk transfer, ada rekening tujuan (`transfer_to_account_id`).
* **FinancialRecord → GLAccount**: Pada konteks bisnis, `FinancialRecord` dapat dipetakan ke akun COA tertentu untuk keperluan konsolidasi laporan keuangan.
* **GLJournalEntry ↔ FinancialRecord**: Keduanya terhubung melalui `reference_id` dan `reference_type`. POS transaction misalnya, membuat `FinancialRecord` dengan `source='pos'` dan secara paralel dapat menghasilkan `GLJournalEntry` dengan `reference_type='manual'` atau melalui proses rekonsiliasi.

## Chart of Accounts (COA)

### 5 Tipe Akun

| Tipe | Saldo Normal | Kode COA | Contoh |
| - | - | - | - |
| **Asset** | Debit | 1-xxxx | Kas, Bank, Piutang, Persediaan, Aset Tetap |
| **Liability** | Kredit | 2-xxxx | Hutang Usaha, Hutang Pajak, Hutang Bank |
| **Equity** | Kredit | 3-xxxx | Modal Saham, Laba Ditahan, Prive |
| **Revenue** | Kredit | 4-xxxx | Penjualan, Pendapatan Jasa, Pendapatan Bunga |
| **Expense** | Debit | 5-xxxx | HPP, Gaji, Sewa, Listrik, Marketing |

### 3 Level Hierarki

| Level | Fungsi | Contoh |
| - | - | - |
| **0 — Header** | Kelompok besar | Aset Lancar, Aset Tetap, Liabilitas Jangka Pendek |
| **1 — Main Account** | Akun utama | Kas & Bank, Piutang Usaha, Persediaan |
| **2 — Sub Account** | Akun detail | BCA Rupiah, Mandiri Rupiah, Kas IDR |

### Diagram Hierarki COA Lengkap

Berikut adalah visualisasi pohon COA yang menunjukkan 5 tipe akun beserta contoh 3 level hierarki untuk masing-masing tipe. Struktur ini digunakan SNISHOP ERP untuk mengklasifikasikan seluruh transaksi keuangan PT Selera Pedas Nusantara.

```mermaid theme={null}
graph TD
    ROOT["COA<br/>Chart of Accounts"]

    subgraph ASSET["1-xxxx ASSET (Saldo Normal: Debit)"]
        A0["1000<br/>Aset (Header L0)"]
        A1A["1100<br/>Aset Lancar (L1)"]
        A1B["1200<br/>Aset Tetap (L1)"]
        A2A["1101<br/>Kas & Bank (L2)"]
        A2B["1102<br/>Piutang Usaha (L2)"]
        A2C["1103<br/>Persediaan (L2)"]
        A2D["1201<br/>Peralatan (L2)"]
        A2E["1202<br/>Kendaraan (L2)"]
        A0 --> A1A --> A2A
        A1A --> A2B
        A1A --> A2C
        A0 --> A1B --> A2D
        A1B --> A2E
    end

    subgraph LIABILITY["2-xxxx LIABILITY (Saldo Normal: Kredit)"]
        L0H["2000<br/>Liabilitas (Header L0)"]
        L1A["2100<br/>Liabilitas Jangka Pendek (L1)"]
        L1B["2200<br/>Liabilitas Jangka Panjang (L1)"]
        L2A["2101<br/>Hutang Usaha (L2)"]
        L2B["2102<br/>Hutang Pajak (L2)"]
        L2C["2201<br/>Hutang Bank (L2)"]
        L0H --> L1A --> L2A
        L1A --> L2B
        L0H --> L1B --> L2C
    end

    subgraph EQUITY["3-xxxx EQUITY (Saldo Normal: Kredit)"]
        E0["3000<br/>Ekuitas (Header L0)"]
        E1A["3100<br/>Modal (L1)"]
        E1B["3200<br/>Laba (L1)"]
        E2A["3101<br/>Modal Saham (L2)"]
        E2B["3201<br/>Laba Ditahan (L2)"]
        E2C["3202<br/>Prive (L2)"]
        E0 --> E1A --> E2A
        E0 --> E1B --> E2B
        E1B --> E2C
    end

    subgraph REVENUE["4-xxxx REVENUE (Saldo Normal: Kredit)"]
        R0["4000<br/>Pendapatan (Header L0)"]
        R1A["4100<br/>Pendapatan Usaha (L1)"]
        R1B["4200<br/>Pendapatan Lain (L1)"]
        R2A["4101<br/>Penjualan (L2)"]
        R2B["4102<br/>Pendapatan Jasa (L2)"]
        R2C["4201<br/>Pendapatan Bunga (L2)"]
        R0 --> R1A --> R2A
        R1A --> R2B
        R0 --> R1B --> R2C
    end

    subgraph EXPENSE["5-xxxx EXPENSE (Saldo Normal: Debit)"]
        X0["5000<br/>Beban (Header L0)"]
        X1A["5100<br/>Beban Pokok (L1)"]
        X1B["5200<br/>Beban Operasional (L1)"]
        X2A["5101<br/>HPP (L2)"]
        X2B["5201<br/>Gaji (L2)"]
        X2C["5202<br/>Sewa (L2)"]
        X2D["5203<br/>Listrik (L2)"]
        X2E["5204<br/>Marketing (L2)"]
        X0 --> X1A --> X2A
        X0 --> X1B --> X2B
        X1B --> X2C
        X1B --> X2D
        X1B --> X2E
    end

    ROOT --> A0
    ROOT --> L0H
    ROOT --> E0
    ROOT --> R0
    ROOT --> X0
```

### Field GLAccount — Schema Lengkap

Schema berikut diambil langsung dari definisi entitas `GLAccount` di sistem SNISHOP ERP.

| Field | Tipe | Required | Default | Deskripsi |
| - | - | - | - | - |
| `id` | string (UUID) | ya | auto-generated | Primary key |
| `company_id` | string (UUID) | **ya** | — | ID perusahaan; multi-tenant isolation |
| `account_code` | string | **ya** | — | Kode akun unik (misalnya: `1000`, `2100`, `4001`). Format: digit pertama = tipe akun (1=Asset, 2=Liability, 3=Equity, 4=Revenue, 5=Expense) |
| `account_name` | string | **ya** | — | Nama akun dalam Bahasa Indonesia |
| `account_type` | enum | **ya** | — | Tipe akun: `asset`, `liability`, `equity`, `revenue`, `expense` |
| `category` | string | tidak | — | Kategori akun (misalnya: `Current Asset`, `Fixed Asset`, `Receivable`) |
| `sub_category` | string | tidak | — | Sub-kategori akun untuk klasifikasi lebih detail |
| `parent_account_id` | string (UUID) | tidak | — | ID akun parent untuk hierarki COA (self-referential ke `GLAccount.id`) |
| `normal_balance` | enum | **ya** | — | Saldo normal akun: `debit` atau `credit` |
| `is_active` | boolean | tidak | `true` | Status aktif/nonaktif. Akun nonaktif tidak bisa digunakan untuk jurnal baru |
| `description` | string | tidak | — | Deskripsi tambahan tentang akun |
| `level` | number | tidak | `0` | Level hierarki: `0` (header), `1` (main account), `2` (sub account) |

**RLS (Row-Level Security)**: Create, Read, Update, Delete — semua terbatas pada pengguna yang terautentikasi dengan akses ke `company_id` yang sesuai.

## Jurnal Umum (GLJournalEntry)

### Field Jurnal — Schema Lengkap

Schema berikut diambil langsung dari definisi entitas `GLJournalEntry` di sistem SNISHOP ERP.

| Field | Tipe | Required | Default | Deskripsi |
| - | - | - | - | - |
| `id` | string (UUID) | ya | auto-generated | Primary key |
| `company_id` | string (UUID) | **ya** | — | ID perusahaan; multi-tenant isolation |
| `entry_date` | date (YYYY-MM-DD) | **ya** | — | Tanggal entri jurnal |
| `reference_number` | string | tidak | — | Nomor referensi eksternal (misalnya: `TRX-001`, `INV-001`, `EXP-2026-042`) |
| `reference_type` | enum | tidak | — | Tipe referensi sumber entri: `manual`, `invoice`, `purchase_order`, `transfer`, `expense` |
| `reference_id` | string (UUID) | tidak | — | ID dokumen sumber (Invoice ID, PO ID, Expense ID, dll) |
| `description` | string | **ya** | — | Deskripsi jurnal — keterangan ringkas transaksi |
| `line_items` | array of objects | **ya** | — | Baris jurnal double-entry. Setiap item: `{account_id, account_code, debit, credit, description}` |
| `total_debit` | number | tidak | `0` | Total akumulasi debit seluruh line items |
| `total_credit` | number | tidak | `0` | Total akumulasi kredit seluruh line items |
| `is_balanced` | boolean | tidak | `false` | Apakah `total_debit = total_credit`. Harus `true` sebelum bisa di-post |
| `status` | enum | tidak | `draft` | Status jurnal: `draft`, `posted`, `reversed` |
| `posted_date` | date | tidak | — | Tanggal saat jurnal diposting ke buku besar |
| `posted_by` | string (UUID) | tidak | — | ID user yang melakukan posting |
| `notes` | string | tidak | — | Catatan tambahan (misalnya: alasan reversal) |

**RLS (Row-Level Security)**: Create, Read, Update, Delete — semua terbatas pada pengguna yang terautentikasi dengan akses ke `company_id` yang sesuai.

### Line Item Structure (Sub-Schema)

Setiap elemen dalam array `line_items` memiliki struktur sebagai berikut:

| Field | Tipe | Default | Deskripsi |
| - | - | - | - |
| `account_id` | string (UUID) | — | ID `GLAccount` yang menjadi akun target. Harus merujuk ke akun yang `is_active = true` |
| `account_code` | string | — | Kode akun (di-copy dari `GLAccount.account_code` untuk kemudahan baca) |
| `debit` | number | `0` | Jumlah debit. Tidak boleh ada nilai di `debit` dan `credit` secara bersamaan |
| `credit` | number | `0` | Jumlah kredit. Tidak boleh ada nilai di `debit` dan `credit` secara bersamaan |
| `description` | string | — | Keterangan untuk baris jurnal ini |

Contoh line items untuk transaksi pembayaran invoice:

```json theme={null}
[
  {
    "account_id": "uuid-of-bca-account",
    "account_code": "1101-001",
    "debit": 5000000,
    "credit": 0,
    "description": "Pembayaran invoice INV-2026-001"
  },
  {
    "account_id": "uuid-of-piutang-account",
    "account_code": "1102-001",
    "debit": 0,
    "credit": 5000000,
    "description": "Pembayaran invoice INV-2026-001"
  }
]
```

**Validasi**: `Math.abs(totalDebit - totalCredit) < 0.01`

## Journal Entry Lifecycle

```mermaid theme={null}
stateDiagram-v2
    [*] --> Draft: Create
    Draft --> Draft: Edit
    Draft --> Posted: Post (validate balanced)
    Draft --> Deleted: Delete
    Posted --> Reversed: Reverse (create new entry with swapped debits/credits)
    Reversed --> [*]
    Posted --> [*]

    state Draft {
        [*] --> CanEdit
        CanEdit --> CanPost: Validate balanced
    }

    state Posted {
        [*] --> Locked
        Locked --> CanReverse: Create reversal entry
    }
```

### State Machine Detail — Aturan Transisi

Berikut adalah aturan lengkap untuk setiap transisi state pada jurnal. Setiap transisi memiliki pre-condition yang harus dipenuhi sebelum sistem mengizinkan perubahan status.

| Dari State | Ke State | Trigger | Pre-Conditions | Efek Samping |
| - | - | - | - | - |
| `[*]` | `draft` | Create jurnal baru | `line_items` tidak kosong, `description` terisi | Generate `entry_number` unik, set `is_balanced` berdasarkan validasi debit/kredit |
| `draft` | `draft` | Edit jurnal | Status masih `draft` | Update field yang diubah, re-kalkulasi `total_debit`, `total_credit`, `is_balanced` |
| `draft` | `posted` | Post jurnal | `is_balanced = true`, minimal 2 line items, semua `account_id` valid dan aktif | Set `posted_date = today`, `posted_by = current_user`, kunci jurnal (tidak bisa edit) |
| `draft` | `deleted` | Hapus jurnal | Status masih `draft` | Soft-delete atau hard-delete tergantung konfigurasi |
| `posted` | `reversed` | Reverse jurnal | Status = `posted`, dibuatkan jurnal reversal baru | Jurnal asli ditandai `reversed`, jurnal reversal baru di-post otomatis |

### Diagram Transisi State Lengkap

```mermaid theme={null}
stateDiagram-v2
    direction LR

    state "Draft" as D {
        state "Validasi Line Items" as V1
        state "Hitung total_debit & total_credit" as V2
        state "Cek is_balanced" as V3
        [*] --> V1
        V1 --> V2
        V2 --> V3
    }

    state "Posted" as P {
        state "Kunci Jurnal (Read-Only)" as L1
        state "Update Buku Besar" as L2
        state "Update Saldo Akun" as L3
        [*] --> L1
        L1 --> L2
        L2 --> L3
    }

    state "Reversed" as R {
        state "Buat Jurnal Reversal" as R1
        state "Swap Debit ↔ Kredit" as R2
        state "Post Jurnal Reversal" as R3
        [*] --> R1
        R1 --> R2
        R2 --> R3
    }

    [*] --> D : Create
    D --> D : Edit (selama is_balanced belum true)
    D --> P : Post (syarat: is_balanced = true)
    D --> [*] : Delete (soft/hard)
    P --> R : Reverse
    R --> [*] : Terminal state
    P --> [*] : Terminal state (jika tidak di-reverse)
```

| Status | Deskripsi | Aksi yang Bisa Dilakukan |
| - | - | - |
| **Draft** | Jurnal baru dibuat, belum diposting | Edit, Delete, Post |
| **Posted** | Sudah diposting ke buku besar | Reverse (tidak bisa edit langsung) |
| **Reversed** | Sudah dibalik dengan jurnal koreksi | Read-only |

### Posting Jurnal

1. Validasi: total debit = total kredit (toleransi Rp 0.01)
2. Set `posted_at` = now, `posted_by` = current user
3. Update status: draft → posted
4. Update saldo akun di buku besar

### Reverse Jurnal

1. Buat jurnal baru dengan debits/credits yang **dibalik**
2. Reference ke jurnal original
3. Mark jurnal original sebagai `reversed`
4. Post jurnal reversal

## Validasi Double-Entry

Setiap jurnal yang masuk ke sistem SNISHOP ERP harus memenuhi aturan validasi double-entry bookkeeping berikut. Validasi dilakukan secara otomatis oleh sistem pada saat create dan sebelum posting.

### Aturan Validasi

| # | Aturan | Deskripsi | Toleransi | Kapan Dicek |
| - | - | - | - | - |
| 1 | **Debit = Kredit** | Total debit seluruh line items harus sama dengan total kredit | `Math.abs(totalDebit - totalCredit) < 0.01` (Rp 0.01) | Setiap kali save dan wajib saat post |
| 2 | **Minimal 2 Line Items** | Setiap jurnal harus memiliki minimal 2 baris (satu debit, satu kredit) | — | Saat create/post |
| 3 | **Account Aktif** | Semua `account_id` di line items harus merujuk ke `GLAccount` yang `is_active = true` | — | Saat create/post |
| 4 | **Single-Sided Entry** | Dalam satu line item, hanya boleh ada nilai di `debit` ATAU `credit`, tidak keduanya | `debit > 0 XOR credit > 0` | Setiap kali save |
| 5 | **Company Isolation** | `GLAccount` di line items harus memiliki `company_id` yang sama dengan `GLJournalEntry` | — | Saat create |
| 6 | **Level Valid** | Hanya akun level 2 (sub account) yang boleh digunakan di line items. Level 0 (header) dan level 1 (main) tidak boleh langsung dijurnalkan | `line_item.account.level === 2` | Saat post |
| 7 | **Tanggal Valid** | `entry_date` tidak boleh lebih dari periode fiskal yang sudah ditutup | — | Saat post |
| 8 | **Idempotency** | Untuk auto-journaling, `idempotency_key` mencegah duplikasi jurnal jika request di-retry | Unique constraint | Saat create (auto-journal) |

### Contoh Validasi Double-Entry

**Benar** (balanced):

```
Line 1: Debit Kas & Bank (BCA)     = Rp 5.000.000
Line 2: Kredit Piutang Usaha        = Rp 5.000.000
─────────────────────────────────────────────────
Total Debit  = Rp 5.000.000
Total Kredit = Rp 5.000.000
Selisih      = Rp 0 ✓ (balanced)
```

**Salah** (tidak balanced):

```
Line 1: Debit Kas & Bank (BCA)     = Rp 5.000.000
Line 2: Kredit Piutang Usaha        = Rp 4.500.000
─────────────────────────────────────────────────
Total Debit  = Rp 5.000.000
Total Kredit = Rp 4.500.000
Selisih      = Rp 500.000 ✗ (TIDAK balanced — sistem menolak post)
```

### Penanganan Selisih Pembulatan (Rounding)

Dalam praktik, selisih hingga Rp 0.01 (satu sen) ditoleransi karena pembulatan desimal pada perhitungan pajak atau kurs. Jika selisih lebih dari Rp 0.01, sistem akan menolak posting dan menampilkan pesan error beserta detail selisihnya.

## Sequence Diagram — Alur Jurnal Manual

Berikut adalah sequence diagram lengkap untuk proses input jurnal manual oleh akuntan melalui UI SNISHOP ERP.

```mermaid theme={null}
sequenceDiagram
    participant U as Akuntan (User)
    participant UI as Frontend UI
    participant API as Backend API
    participant COA as GLAccount Service
    participant JE as GLJournalEntry Service
    participant DB as Database

    U->>UI: Buka form "Jurnal Baru"
    UI->>API: GET /gl-accounts?company_id=X&is_active=true
    API->>COA: Query COA aktif
    COA->>DB: SELECT * FROM gl_accounts WHERE ...
    DB-->>COA: List akun aktif
    COA-->>API: Return COA list
    API-->>UI: Return COA list (for dropdown)

    U->>UI: Pilih tanggal, isi deskripsi
    U->>UI: Tambah line item 1: Debit Kas & Bank = Rp 5.000.000
    U->>UI: Tambah line item 2: Kredit Piutang = Rp 5.000.000
    UI->>UI: Validasi client-side: totalDebit === totalCredit

    U->>UI: Klik "Simpan Draft"
    UI->>API: POST /gl-journal-entries
    Note over API: Payload: {entry_date, description, line_items[], reference_type: "manual"}
    API->>JE: Create journal entry
    JE->>JE: Hitung total_debit, total_credit
    JE->>JE: Set is_balanced = (totalDebit === totalCredit)
    JE->>JE: Generate entry_number (auto)
    JE->>DB: INSERT INTO gl_journal_entries (status='draft')
    DB-->>JE: Return created entry
    JE-->>API: Return entry
    API-->>UI: Return 201 Created
    UI-->>U: Tampilkan "Jurnal berhasil disimpan (Draft)"

    U->>UI: Klik "Post Jurnal"
    UI->>API: POST /gl-journal-entries/:id/post
    API->>JE: Post entry
    JE->>JE: Validasi: is_balanced === true
    JE->>JE: Validasi: semua account_id aktif & level 2
    JE->>JE: Validasi: minimal 2 line items
    JE->>JE: Set status = 'posted', posted_date = today, posted_by = user
    JE->>DB: UPDATE gl_journal_entries SET status='posted'
    JE->>DB: Update buku besar (saldo per akun)
    DB-->>JE: Success
    JE-->>API: Return posted entry
    API-->>UI: Return 200 OK
    UI-->>U: Tampilkan "Jurnal berhasil diposting"
```

## Sequence Diagram — Auto-Journaling dari Modul

Berikut adalah sequence diagram yang menunjukkan bagaimana berbagai modul di SNISHOP ERP secara otomatis menghasilkan jurnal ke General Ledger tanpa input manual dari akuntan.

```mermaid theme={null}
sequenceDiagram
    participant MOD as Modul Sumber<br/>(POS/Expense/Invoice/MFG)
    participant SVC as Module Service
    participant GL as GL Journal Service
    participant COA as COA Resolver
    participant DB as Database
    participant FR as FinancialRecord

    Note over MOD: === EXPENSE APPROVAL ===
    MOD->>SVC: Expense approved by manager
    SVC->>COA: resolveExpenseGLMapping(category)
    COA-->>SVC: Return {debit_account, credit_account}
    SVC->>GL: Create GLJournalEntry(reference_type='expense')
    GL->>GL: Build line_items: [Debit: Expense/Inventory, Kredit: Kas/Bank]
    GL->>GL: Validate balanced
    GL->>DB: Insert & auto-post
    GL-->>SVC: Return journal entry

    Note over MOD: === INVOICE PAYMENT ===
    MOD->>SVC: Payment verified (verification_status='verified')
    SVC->>GL: Create GLJournalEntry(reference_type='invoice')
    GL->>GL: Build line_items: [Debit: Kas/Bank, Kredit: Piutang Usaha]
    GL->>GL: Validate balanced
    GL->>DB: Insert & auto-post
    GL-->>SVC: Return journal entry

    Note over MOD: === POS TRANSACTION ===
    MOD->>FR: Create FinancialRecord(source='pos', type='income')
    FR->>FR: Set cogs_amount, tax_amount, channel_fee
    FR->>DB: Insert FinancialRecord
    Note over FR: POS tidak langsung buat GLJournalEntry.<br/>Data FinancialRecord digabung ke laporan P&L.

    Note over MOD: === MANUFACTURING (2-PHASE) ===
    MOD->>SVC: completeProductionBatch()
    SVC->>GL: Phase 1: GLJournalEntry(reference_type='manual')
    GL->>GL: Line items: [Debit: WIP, Kredit: Persediaan Bahan Baku]
    GL->>DB: Insert & auto-post
    MOD->>SVC: verifyAndReleaseProductionBatch()
    SVC->>GL: Phase 2: GLJournalEntry(reference_type='manual')
    GL->>GL: Line items: [Debit: Finished Goods, Kredit: WIP]
    GL->>DB: Insert & auto-post
    Note over GL: Best-effort: jika GL gagal,<br/>production tetap jalan,<br/>tandai RECONCILIATION_REQUIRED

    Note over MOD: === TRANSFER ANTAR REKENING ===
    MOD->>GL: Create GLJournalEntry(reference_type='transfer')
    GL->>GL: Line items: [Debit: Bank Tujuan, Kredit: Bank Asal]
    GL->>GL: Validate balanced
    GL->>DB: Insert & auto-post
```

## Buku Besar Per Akun

Buku besar menampilkan seluruh transaksi yang mempengaruhi satu akun tertentu beserta saldo berjalan.

### Field Buku Besar

| Field | Deskripsi |
| - | - |
| Tanggal | Tanggal transaksi |
| Keterangan | Deskripsi jurnal |
| Reference | Nomor jurnal / nomor referensi |
| Debit | Jumlah debit |
| Kredit | Jumlah kredit |
| Saldo | Saldo setelah transaksi |

### Saldo Berjalan

```
Saldo Awal + Total Debit - Total Kredit = Saldo Akhir
```

Untuk akun dengan saldo normal **debit** (Asset, Expense):

* Debit menambah saldo
* Kredit mengurangi saldo

Untuk akun dengan saldo normal **kredit** (Liability, Equity, Revenue):

* Kredit menambah saldo
* Debit mengurangi saldo

## Trial Balance

Trial balance menampilkan ringkasan saldo dari seluruh akun pada periode tertentu. Total debit dan kredit harus seimbang.

### Field Trial Balance

| Field | Deskripsi |
| - | - |
| Account Code | Kode akun |
| Account Name | Nama akun |
| Type | Tipe akun (asset/liability/equity/revenue/expense) |
| Opening Balance | Saldo awal periode |
| Total Debit | Total debit selama periode |
| Total Credit | Total kredit selama periode |
| Closing Balance | Saldo akhir periode |

### Validasi

```
Math.abs(totalDebit - totalCredit) < 1
```

Jika tidak seimbang, berarti ada kesalahan pencatatan yang perlu diperbaiki sebelum membuat laporan keuangan.

### Color Coding

| Tipe Akun | Warna |
| - | - |
| Asset | Biru |
| Liability | Merah |
| Equity | Ungu |
| Revenue | Hijau |
| Expense | Oranye |

## Entity Schema — Account (Rekening/Kantong)

Entitas `Account` merepresentasikan rekening kas, bank, e-wallet, atau kantong dana yang digunakan dalam transaksi keuangan. Berbeda dengan `GLAccount` (Chart of Accounts) yang merupakan akun pembukuan, `Account` adalah rekening fisik/digital tempat uang benar-benar berada.

| Field | Tipe | Required | Default | Deskripsi |
| - | - | - | - | - |
| `id` | string (UUID) | ya | auto-generated | Primary key |
| `user_id` | string (UUID) | **ya** | — | ID pengguna pemilik rekening |
| `company_id` | string (UUID) | tidak | — | ID perusahaan (null untuk personal) |
| `name` | string | **ya** | — | Nama rekening/kantong (e.g., `Kas`, `BCA`, `Mandiri`, `Dompet`) |
| `type` | enum | **ya** | `cash` | Jenis rekening: `cash`, `bank`, `e-wallet`, `other` |
| `account_number` | string | tidak | — | Nomor rekening (optional) |
| `bank_name` | string | tidak | — | Nama bank (untuk `type=bank`) |
| `initial_balance` | number | tidak | `0` | Saldo awal saat pembuatan rekening |
| `current_balance` | number | tidak | `0` | Saldo saat ini; di-update oleh server saat ada transaksi POS, invoice payment, expense, atau void |
| `currency` | string | tidak | `IDR` | Mata uang |
| `icon` | string | tidak | `💰` | Icon rekening untuk UI |
| `color` | string | tidak | `#3B82F6` | Warna untuk UI |
| `is_active` | boolean | tidak | `true` | Status aktif rekening |
| `is_default_pos` | boolean | tidak | `false` | Rekening default untuk POS Kasir |
| `notes` | string | tidak | — | Catatan tambahan |
| `mode` | enum | tidak | `personal` | Mode rekening: `personal` atau `business` |

## Entity Schema — FinancialRecord

Entitas `FinancialRecord` adalah pencatatan transaksi keuangan yang berasal dari berbagai sumber (manual, AI, POS, Manufacturing, Distribution, dll). Entitas ini melengkapi `GLJournalEntry` — sementara `GLJournalEntry` menggunakan prinsip double-entry ke COA, `FinancialRecord` lebih fokus pada pencatatan arus kas riil dari/to rekening (`Account`).

| Field | Tipe | Required | Default | Deskripsi |
| - | - | - | - | - |
| `id` | string (UUID) | ya | auto-generated | Primary key |
| `user_id` | string (UUID) | **ya** | — | ID pengguna |
| `company_id` | string (UUID) | tidak | — | ID perusahaan (null untuk personal) |
| `account_id` | string (UUID) | tidak | — | ID rekening/kantong sumber dana (referensi ke `Account`) |
| `type` | enum | **ya** | — | Jenis transaksi: `income`, `expense`, `transfer` |
| `amount` | number | **ya** | — | Jumlah transaksi |
| `category` | string | tidak | — | Kategori transaksi |
| `description` | string | tidak | — | Deskripsi singkat |
| `date` | datetime | **ya** | — | Tanggal transaksi |
| `attachment_url` | string | tidak | — | URL bukti transaksi (foto struk, dll) |
| `source` | enum | tidak | `manual` | Sumber transaksi: `manual`, `ai_text`, `ai_scan`, `pos`, `manufacturing`, `distribution`, `recall`, `stock_opname` |
| `mode` | enum | tidak | `personal` | Mode pencatatan: `personal` atau `business` |
| `transfer_to_account_id` | string (UUID) | tidak | — | ID rekening tujuan (untuk `type=transfer`) |
| `transfer_fee` | number | tidak | `0` | Biaya transfer (jika ada) |
| `reference_id` | string (UUID) | tidak | — | ID referensi transaksi sumber (invoice, POS, expense) |
| `reference_type` | enum | tidak | — | Tipe referensi: `invoice_payment`, `pos_transaction`, `expense`, `manual`, `transfer`, `production_order`, `distribution_shipment`, `distribution_return`, `batch_recall`, `stock_opname` |
| `idempotency_key` | string | tidak | — | Kunci idempoten untuk mencegah duplikasi transaksi |
| `is_inventory_material` | boolean | tidak | `false` | Apakah pembelian bahan baku persediaan (anti double-counting di P\&L) |
| `channel_fee` | number | tidak | `0` | Potongan biaya marketplace / platform fee |
| `cogs_amount` | number | tidak | `0` | Biaya pokok penjualan (HPP/COGS) yang dibekukan saat transaksi POS |
| `tax_amount` | number | tidak | `0` | Jumlah pajak (PPN keluaran untuk revenue) |

## Auto-Journaling dari Modul Lain

SNISHOP ERP otomatis membuat jurnal dari transaksi di modul lain:

### Expense → GL

```mermaid theme={null}
sequenceDiagram
    participant E as Expense
    participant P as Policy Engine
    participant GL as GLJournalEntry

    E->>P: Expense approved
    P->>P: resolveExpenseGLMapping()
    alt is_inventory_material = true
        P->>GL: Debit: Persediaan (Asset)<br/>Kredit: Kas/Bank
    else raw_material / operational / etc
        P->>GL: Debit: Expense (by category)<br/>Kredit: Kas/Bank
    end
```

**11 Expense Categories**:

* salary, raw\_material, operational, production\_cost
* travel, meals, accommodation, equipment
* office\_supplies, training, other

### Manufacturing → GL (Two-Phase)

| Phase | Trigger | Jurnal |
| - | - | - |
| Phase 1 | `completeProductionBatch` | Debit: WIP, Kredit: Persediaan |
| Phase 2 | `verifyAndReleaseProductionBatch` | Debit: Finished Goods, Kredit: WIP |

**Best-effort posting**: GL failure tidak membatalkan production. Jika gagal, ditandai `RECONCILIATION_REQUIRED`.

### Invoice Payment → GL

```
Debit: Kas/Bank (Asset)
Kredit: Piutang Usaha (Asset)
```

Hanya pembayaran dengan `verification_status = 'verified'` yang membuat jurnal.

### POS Transaction → FinancialRecord

POS transaction tidak langsung membuat GLJournalEntry, tapi membuat `FinancialRecord` dengan `source='pos'`. Laporan P\&L menggabungkan keduanya.

## Reference Types

| Reference Type | Sumber | Deskripsi |
| - | - | - |
| manual | Input manual | Jurnal manual dari accountant |
| invoice | Invoice | Pembayaran invoice B2B |
| purchase\_order | Purchase Order | Penerimaan barang dari supplier |
| transfer | Transfer | Transfer antar rekening |
| expense | Expense | Expense yang sudah approved |

## Filter & Search

| Filter | Opsi |
| - | - |
| Date Range | Hari ini, Minggu ini, Bulan ini, Kuartal ini, Tahun ini, Custom |
| Account | Pilih akun tertentu atau semua |
| Status | Draft, Posted, Reversed |
| Reference Type | Manual, Invoice, PO, Transfer, Expense |
| Entry Number | Search by nomor jurnal |
| Description | Search by kata kunci |

## Export

| Format | Use Case |
| - | - |
| PDF | Arsip, audit, presentasi |
| Excel | Analisis lebih lanjut, manipulasi data |
| CSV | Integrasi dengan sistem lain |

## Tips Penggunaan

* Review trial balance secara berkala untuk memastikan pencatatan sudah seimbang
* Jangan menunda posting jurnal terlalu lama agar saldo di buku besar selalu update
* Gunakan deskripsi jurnal yang jelas supaya mudah ditelusuri saat audit
* Manfaatkan filter untuk melihat transaksi pada periode atau akun tertentu
* Untuk koreksi, gunakan reverse (jangan edit jurnal yang sudah posted)
* Export trial balance bulanan untuk arsip dan audit


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.