# General Ledger ERP: Merancang Inti Double-Entry

> Cara merancang general ledger double-entry yang menjadi jantung sistem ERP, mulai dari chart of accounts hingga trial balance, menggunakan constraint dan trigger PostgreSQL agar entri yang tidak seimbang mustahil terjadi.

URL: https://www.matthewswong.com/id/blog/erp-general-ledger-double-entry-design/

- Author: Matthews Wong (https://www.matthewswong.com/id/about/)
- Published: 2026-07-03
- Updated: 2026-07-03
- Category: ERP

Setiap modul ERP pada akhirnya berujung ke tempat yang sama: general ledger. Sales order, invoice pembelian, proses payroll, dan pergerakan inventory semuanya bermuara menjadi baris data di satu inti akuntansi yang sama, dan jika inti itu salah, seluruh laporan turunannya juga ikut salah. Dashboard, pelaporan pajak, dan update untuk investor semuanya hanyalah tampilan berbeda dari journal yang sama, sehingga skema di baliknya tidak boleh dianggap sebagai urusan sampingan yang ditambahkan setelah sisa ERP selesai dibangun.

Tulisan ini membahas cara merancang inti tersebut dari nol di PostgreSQL: chart of accounts, aturan double-entry yang menjaga setiap transaksi tetap seimbang, periode fiskal beserta penutupan periode, dan query trial balance yang membuktikan seluruh sistem benar-benar berjumlah nol. Tujuannya sepanjang tulisan ini adalah mendorong sebanyak mungkin kebenaran logika turun ke level database itu sendiri, lewat constraint dan trigger, agar bug di kode aplikasi salah satu modul tidak bisa diam-diam merusak ledger bersama yang menjadi tumpuan modul lainnya.

## Chart of accounts dan lima jenis akun

Akuntansi double-entry berpijak pada gagasan sederhana, yang pertama kali dirumuskan berabad-abad lalu dan masih menjadi tulang punggung setiap sistem akuntansi modern: setiap transaksi menyentuh minimal dua akun, satu didebit dan satu dikredit, dan kedua sisi itu selalu berjumlah nol. Sebelum menulis logika transaksi apa pun, sebuah ERP membutuhkan chart of accounts yang mengklasifikasikan setiap akun ke dalam salah satu dari lima jenis, karena klasifikasi itu menentukan apakah debit menambah atau mengurangi saldo akun tersebut. Jika klasifikasi ini salah, setiap laporan keuangan yang dihasilkan dari ledger, baik balance sheet, income statement, maupun cash flow, akan diam-diam menggambarkan bisnis secara keliru meskipun setiap entry secara individu tetap seimbang.

Jenis akun

Saldo normal

Bertambah oleh

Aset

Debit

Debit

Liabilitas

Kredit

Kredit

Ekuitas

Kredit

Kredit

Pendapatan

Kredit

Kredit

Beban

Debit

Debit

## Memodelkan chart of accounts dan periode fiskal

Chart of accounts adalah tabel hierarkis: akun header mengelompokkan akun yang bisa diposting untuk keperluan pelaporan, sehingga skemanya membutuhkan kolom parent yang merujuk ke dirinya sendiri serta sebuah flag yang menandai apakah suatu akun benar-benar boleh menerima posting. Periode fiskal mendapat tabel tersendiri agar ledger dapat melacak bulan mana yang masih terbuka untuk posting normal, mana yang sudah ditutup untuk entri biasa namun masih terbuka untuk penyesuaian, dan mana yang sudah terkunci penuh setelah audit. Menyimpan mata uang pada akun itu sendiri, bukan mengasumsikan satu mata uang tunggal untuk seluruh perusahaan, juga penting sejak awal: ERP multi entitas pada akhirnya akan membutuhkan akun dalam lebih dari satu mata uang, dan menambahkannya belakangan berarti harus menyentuh ulang setiap query posting yang sudah terlanjur ditulis terhadap tabel tersebut.

> Berikan setiap akun kode numerik yang stabil, bukan hanya nama, dan urutkan berdasarkan kode di mana pun. Mengubah nama akun tidak boleh pernah merusak laporan, dan tim finance sudah terbiasa membaca rentang akun berdasarkan angka jauh sebelum mereka membaca label di sampingnya.

## Journal entry: unit kerja yang harus selalu seimbang

Journal entry adalah unit atomik dari sistem akuntansi: satu baris header ditambah dua atau lebih baris line, di mana setiap line hanya boleh berupa debit atau kredit, tidak boleh keduanya sekaligus, dan jumlah seluruh baris debit pada satu entry harus sama dengan jumlah seluruh baris kredit. Memodelkan ini dengan benar di skema berarti menuangkan tiga aturan langsung sebagai constraint database, bukan mempercayakan sepenuhnya pada kode aplikasi untuk menjaganya benar di setiap jalur kode. Modul apa pun yang memposting ke ledger, entah layanan invoicing, job payroll, atau layar penyesuaian manual, pada akhirnya melewati tabel journal_entries dan journal_lines yang persis sama, dan itulah yang membuat ledger menjadi satu sumber kebenaran, bukan lima implementasi berbeda dari ide yang sama.

- Setiap baris line harus murni debit atau murni kredit, ditegakkan dengan check constraint agar satu baris tidak pernah membawa nilai bukan nol di kedua kolom sekaligus.
- Setiap entry membutuhkan minimal dua baris line, karena posting satu sisi bukanlah transaksi double-entry yang valid menurut definisinya.
- Jumlah debit harus sama dengan jumlah kredit di seluruh baris line milik satu entry sebelum entry itu diperbolehkan diposting.

> Check constraint biasa tidak dapat melihat baris lain, sehingga tidak mampu memverifikasi bahwa baris-baris sebuah entry berjumlah nol dengan sendirinya. PostgreSQL mengevaluasi check constraint per baris, secara langsung, tanpa cara untuk menunda pemeriksaannya melewati satu statement. Validasi keseimbangan lintas baris line milik satu entry harus ditempatkan pada constraint trigger, dan trigger itu sebaiknya bersifat deferrable agar sebuah insert multi statement dapat selesai dulu sebelum pemeriksaan saldo dijalankan.

## Menegakkan aturan keseimbangan dengan deferred constraint trigger

Karena PostgreSQL tidak dapat menunda check constraint melewati satu baris, cara standar untuk menegakkan invarian lintas baris seperti entry yang seimbang adalah constraint trigger yang dideklarasikan DEFERRABLE INITIALLY DEFERRED. Ini memungkinkan aplikasi memasukkan satu header beserta seluruh baris line-nya dalam satu transaksi, lalu database baru memverifikasi jumlahnya sekali saja, tepat sebelum commit, bukan gagal di tengah proses insert. Membuat trigger ini hanya berjalan saat posting, bukan setiap kali sebuah line dimasukkan, juga menjaga draft tetap bisa dipakai: entry yang belum diposting boleh berada dalam kondisi setengah jadi, diedit, bahkan dihapus sepenuhnya, tanpa pernah memicu pemeriksaan saldo yang seharusnya hanya berlaku untuk entry yang akan menjadi permanen.

- Aplikasi membuka satu transaksi dan memasukkan satu baris ke journal_entries dengan kolom posted diset false.
- Aplikasi memasukkan setiap baris line ke journal_lines, masing-masing berupa debit murni atau kredit murni terhadap akun yang bisa diposting.
- Aplikasi mengubah entry menjadi posted true, yang memicu constraint trigger dan menjumlahkan setiap line yang terkait dengan id entry tersebut.
- Jika jumlah debit dan kredit tidak cocok, trigger memunculkan exception dan seluruh transaksi dibatalkan secara atomik.

## Trial balance: membuktikan ledger benar-benar seimbang

Trial balance adalah pemeriksaan klasik dari setiap sistem akuntansi: daftar setiap akun beserta total debit dan total kreditnya untuk satu periode, dan jika setiap entry yang menyentuh periode itu benar-benar seimbang, total besar seluruh debit di semua akun harus sama dengan total besar seluruh kredit. Menjalankan ini sebagai satu query agregat tunggal atas entry yang sudah diposting dalam satu periode sekaligus menjadi artefak pelaporan yang diharapkan akuntan dan pemeriksaan integritas langsung terhadap ledger itu sendiri. Menyaring query dengan e.posted membuat draft yang belum selesai tidak ikut masuk ke laporan sama sekali, dan ini penting karena entry berstatus draft, menurut definisinya, memang boleh sementara tidak seimbang selama masih diedit oleh seseorang.

> Jika query trial balance itu pernah mengembalikan nilai selain nol, berarti ada sesuatu yang melewati constraint trigger, kemungkinan besar berupa bulk import atau perbaikan data manual yang melewati jalur insert normal. Perlakukan trial balance yang tidak nol sebagai insiden produksi, bukan sekadar keanehan pembulatan.

## Penutupan periode dan imutabilitas

Setelah periode fiskal ditutup, entry yang sudah diposting di dalamnya seharusnya menjadi praktis imutabel. Daripada mengizinkan update atau delete pada baris periode yang tertutup, ledger ERP sebaiknya mewajibkan koreksi berupa entry pembalik baru yang bertanggal di periode yang masih terbuka, sehingga jejak audit tetap lengkap dan append only, dan setiap trial balance historis tetap dapat direproduksi persis seperti saat pertama dilaporkan. Di sinilah desain ini benar-benar terbayar di luar sekadar kebenaran pembukuan: auditor, otoritas pajak, dan engineer di masa depan yang sedang menelusuri selisih data semuanya mengandalkan kemampuan untuk memutar ulang persis apa yang diposting dan kapan, tanpa perlu bertanya-tanya apakah ada baris yang diam-diam diubah belakangan.

- Ubah kolom fiscal_periods.status menjadi CLOSED dan tambahkan rule atau trigger tingkat database yang menolak posting baru bertanggal di dalam periode yang sudah tertutup.
- Wajibkan koreksi terhadap periode yang tertutup berupa journal entry pembalik yang diposting di periode terbuka saat ini, bukan edit langsung pada baris historis.
- Simpan status LOCKED untuk periode yang sudah melewati audit eksternal, dengan pembatasan yang lebih ketat dibanding CLOSED, agar pelaporan historis tidak pernah bisa berubah secara diam diam.

## Sumber

- Double-entry bookkeeping — Wikipedia
- General ledger — Wikipedia
- Dokumentasi PostgreSQL: Constraints
- Dokumentasi PostgreSQL: SET CONSTRAINTS

> **Key takeaway:** Setiap modul ERP pada akhirnya bermuara di general ledger: sales order, invoice pembelian, payroll dan pergerakan stok semuanya menjadi baris di satu inti akuntansi bersama. Kalau inti itu salah, semua laporan di atasnya ikut salah, dan karena itu double-entry harus ada dalam desain sejak awal.

## Source links

- https://en.wikipedia.org/wiki/Double-entry_bookkeeping
- https://en.wikipedia.org/wiki/General_ledger
- https://www.postgresql.org/docs/current/ddl-constraints.html
- https://www.postgresql.org/docs/current/sql-set-constraints.html

## FAQ

### Apa itu double-entry bookkeeping dan mengapa general ledger ERP membutuhkannya?

Double-entry bookkeeping mencatat setiap transaksi sebagai minimal dua baris line, satu debit dan satu kredit, yang jumlahnya harus nol. General ledger ERP membutuhkan ini karena hanya cara ini yang dapat menjamin secara matematis bahwa inti akuntansi tetap konsisten secara internal, tidak peduli berapa banyak modul yang menulis ke dalamnya, mulai dari sales hingga payroll dan inventory.

### Bagaimana cara menegakkan journal entry yang seimbang di PostgreSQL?

Check constraint biasa tidak bisa melihat baris lain, sehingga tidak dapat memastikan sendiri bahwa seluruh line pada satu journal entry berjumlah nol. Pendekatan yang andal adalah constraint trigger yang dideklarasikan deferrable dan initially deferred, sehingga aplikasi dapat memasukkan header beserta seluruh baris line-nya dalam satu transaksi dan pemeriksaan saldo hanya dijalankan sekali, tepat sebelum commit.

### Mengapa menggunakan lima jenis akun dibanding flag debit kredit yang lebih sederhana?

Lima jenis akun, yaitu aset, liabilitas, ekuitas, pendapatan, dan beban, menentukan apakah debit menambah atau mengurangi saldo suatu akun, karena aset dan beban bertambah dengan debit sementara liabilitas, ekuitas, dan pendapatan bertambah dengan kredit. Tanpa klasifikasi tersebut pada chart of accounts, laporan seperti balance sheet atau income statement tidak dapat dihasilkan dengan benar dari baris journal mentah.

### Bisakah saya mengedit journal entry setelah periode fiskal ditutup?

Tidak, entry pada periode yang sudah tertutup sebaiknya diperlakukan sebagai imutabel, bukan diedit atau dihapus langsung. Koreksi seharusnya berupa entry pembalik baru yang bertanggal di periode yang masih terbuka, sehingga jejak audit tetap lengkap dan setiap trial balance yang pernah dilaporkan tetap dapat direproduksi persis seperti saat pertama diajukan.

### Apa artinya jika query trial balance menghasilkan total selain nol?

Trial balance yang benar selalu berjumlah nol di seluruh entry yang sudah diposting dalam satu periode, karena setiap entry yang melewati jalur insert normal sudah diseimbangkan satu per satu oleh constraint trigger. Hasil yang tidak nol hampir selalu berarti ada sesuatu yang melewati trigger itu, biasanya berupa bulk import data atau perbaikan manual, dan sebaiknya diselidiki sebagai insiden integritas data, bukan dianggap sekadar selisih pembulatan.

---

- HTML version: https://www.matthewswong.com/id/blog/erp-general-ledger-double-entry-design/
- English: https://www.matthewswong.com/en/blog/erp-general-ledger-double-entry-design/
- Site index for agents: https://www.matthewswong.com/llms.txt
- Sitemap: https://www.matthewswong.com/sitemap.xml
- API description (OpenAPI): https://www.matthewswong.com/openapi.json
