Setiap laporan keuangan pada dasarnya adalah agregasi atas bagan akun (CoA) dan entri jurnal yang diposting terhadapnya. Hierarki empat level — Kategori, Kelompok, Sub-kelompok, Akun — menentukan akun mana yang dirangkum ke baris L/R dan neraca mana. Mengubah CoA setelah data diposting sangat menyulitkan, sehingga disarankan untuk meluangkan waktu bersama akuntan klien agar strukturnya benar sebelum go-live.
Mesin jurnal menerapkan batasan CHECK PostgreSQL yang menolak INSERT mana pun jika total debit tidak sama dengan total kredit. Ini berarti integritas akuntansi double-entry dijamin di tingkat database, bukan hanya di lapisan aplikasi. Setiap transaksi yang diposting dengan benar — faktur AP, pembayaran AR, penggajian — otomatis muncul di semua laporan keuangan tanpa pemetaan tambahan.
Period lock mencegah entri jurnal baru diposting ke periode akuntansi yang sudah ditutup. Perusahaan Indonesia perlu menutup buku setiap bulan untuk pelaporan PPN dan setiap tahun untuk pajak penghasilan badan, sehingga perubahan retroaktif yang dapat membatalkan laporan pajak yang sudah diserahkan harus diblokir. Kunci ini diberlakukan dengan trigger di tingkat database, bukan hanya di lapisan aplikasi, agar tidak bisa dilewati.
Ketiga laporan keuangan inti — L/R, Neraca, dan Arus Kas — dibuat melalui endpoint NestJS dan di-cache di Redis dengan TTL 5 menit. Membuat ulang L/R penuh dari baris jurnal selama setahun dengan ribuan entri bisa memakan waktu 2-3 detik, yang masih dapat diterima untuk permintaan on-demand tetapi terlalu lambat untuk dashboard yang diperbarui setiap 30 detik. Cache Redis menyerap beban permintaan berulang tersebut.
Setiap baris jurnal memiliki field cost_center_id dan project_id opsional, sehingga memungkinkan query tersegmentasi berdasarkan dimensi apa pun yang dibutuhkan manajemen — pengeluaran departemen, profitabilitas proyek, atau analisis margin produk. CFO di klien yang disebutkan dalam artikel mendapatkan email mingguan setiap Senin pukul 06.00 WIB berisi L/R per pusat biaya yang dilampirkan sebagai file Excel yang dibuat via exceljs di NestJS.
Pasar software ERP mencapai $70 miliar pada 2025, didorong sebagian besar oleh permintaan pelaporan keuangan otomatis. Tim keuangan yang menggunakan otomasi laporan ERP melihat pengurangan waktu pembuatan laporan hingga 70%, dan Gartner memproyeksikan ERP canggih dengan AI dapat mempersingkat waktu penutupan keuangan hingga 30% pada 2028. Di UKM Indonesia, saya menemukan bahwa pelaporan keuangan biasanya merupakan modul bernilai tertinggi — yang dilihat CFO dan pemilik setiap minggu. Dalam pekerjaan konsultasi ERP lepas saya, saya membangun mesin pelaporan keuangan yang menghasilkan Laba Rugi, Neraca, Arus Kas, dan laporan manajemen sepenuhnya dari buku besar umum.
Setiap laporan keuangan pada dasarnya adalah tampilan atas bagan akun (CoA) dan entri jurnal yang diposting terhadapnya. Sebelum menulis kode pelaporan apa pun, dapatkan struktur CoA yang benar. Saya menggunakan hierarki empat level: Kategori (Aset, Kewajiban, Ekuitas, Pendapatan, Beban) → Kelompok → Sub-kelompok → Akun.
Mesin jurnal yang saya bangun menegakkan akuntansi double-entry di tingkat database: setiap entri jurnal harus memiliki total debit sama dengan total kredit, atau INSERT ditolak oleh batasan CHECK PostgreSQL. Setiap baris journal_entry memiliki header (tanggal, referensi, deskripsi, jenis) dan beberapa journal_lines (account_id, debit, kredit).
Financial Reporting Engine — NestJS + PostgreSQL
Chart of Accounts (4-level hierarchy)
├── ASSETS
│ ├── Current Assets
│ │ ├── Cash & Bank [11000–11999]
│ │ └── Accounts Receiv. [12000–12999]
│ └── Fixed Assets [15000–15999]
├── LIABILITIES
│ └── Accounts Payable [21000–21999]
├── EQUITY [30000–39999]
├── REVENUE [40000–49999]
└── EXPENSES [50000–59999]
journal_lines (the single source of truth)
┌─────────────────────────────────────────────────┐
│ id │ journal_id │ account_id │ debit │ credit │
│ ...│ ... │ 11001 │ 1000 │ 0 │ <- Cash receipt
│ ...│ ... │ 40001 │ 0 │ 1000 │ <- Revenue
└─────────────────────────────────────────────────┘
↓ Aggregated by NestJS ReportService
P&L Report Balance Sheet Cash Flow
Revenue: 500M Assets: 2.1B Operating: +400M
COGS: (300M) Liab: 800M Investing: (50M)
Gross: 200M Equity: 1.3B Financing: (20M)
Net: 150M Net: 330MDari pengalaman saya membangun sistem ERP sebagai konsultan ERP lepas: selalu implementasikan fitur 'period lock'. Perusahaan Indonesia perlu menutup buku setiap bulan untuk pelaporan PPN dan setiap tahun untuk pajak penghasilan badan. Setelah periode dikunci, tidak ada entri jurnal baru yang dapat diposting — ini mencegah perubahan retroaktif yang akan membatalkan laporan pajak yang sudah diserahkan. Kunci diberlakukan di tingkat database dengan trigger.
Tiga laporan keuangan inti yang saya bangun dibuat sesuai permintaan melalui endpoint NestJS. Endpoint L/R menerima rentang tanggal dan mengembalikan akun pendapatan yang dijumlahkan sebagai pemasukan, akun beban yang dijumlahkan sebagai biaya, dengan laba kotor dan laba bersih yang dihitung. Endpoint Neraca menghitung total aset, kewajiban, dan ekuitas pada tanggal tertentu.
Selain laporan wajib, manajemen menginginkan L/R per pusat biaya (berapa banyak yang dihabiskan setiap departemen?), profitabilitas proyek, dan analisis margin produk. Saya mendukung ini melalui cost_center_id dan project_id di setiap baris jurnal. CFO di klien saya mendapatkan email mingguan (dibuat secara otomatis setiap Senin pukul 06.00 WIB) dengan L/R per pusat biaya dilampirkan sebagai file Excel.
-- PostgreSQL: P&L report query
SELECT
coa.category,
coa.group_name,
coa.account_name,
coa.account_code,
SUM(jl.debit - jl.credit) AS net_amount
FROM journal_lines jl
JOIN journal_entries je ON jl.journal_id = je.id
JOIN chart_of_accounts coa ON jl.account_id = coa.id
WHERE
je.posted_at BETWEEN :start_date AND :end_date
AND je.status = 'posted'
AND coa.category IN ('REVENUE', 'EXPENSE')
AND (je.locked_period IS NULL OR je.locked_period >= :start_date)
GROUP BY coa.category, coa.group_name, coa.account_name, coa.account_code
ORDER BY coa.account_code;
-- Period lock enforcement (trigger)
CREATE OR REPLACE FUNCTION check_period_lock()
RETURNS TRIGGER AS $$
BEGIN
IF EXISTS (
SELECT 1 FROM period_locks
WHERE period_year = EXTRACT(YEAR FROM NEW.posting_date)
AND period_month = EXTRACT(MONTH FROM NEW.posting_date)
AND locked = TRUE
) THEN
RAISE EXCEPTION 'Period % is locked — no new entries allowed',
TO_CHAR(NEW.posting_date, 'YYYY-MM');
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trg_check_period_lock
BEFORE INSERT ON journal_entries
FOR EACH ROW EXECUTE FUNCTION check_period_lock();Mesin pelaporan yang saya bangun adalah modul NestJS dengan empat service: JournalService (posting dan validasi), ReportService (query L/R, Neraca, Arus Kas), SegmentService (filter pusat biaya dan proyek), dan ExportService (PDF via Puppeteer, Excel via exceljs). Semua laporan di-cache di Redis dengan TTL 5 menit.
Jika klien Anda memiliki beberapa entitas legal atau bertransaksi dalam mata uang asing, pelaporan keuangan konsolidasi memerlukan translasi mata uang. Ini adalah sumber kesalahan umum saat ditangani secara manual. Saya menyimpan kurs tukar harian dan menerapkan kurs tanggal transaksi untuk item laporan laba rugi dan kurs akhir periode untuk item neraca, dengan PSAK 10 sebagai standar referensi.
Bisnis Indonesia harus mengajukan SPT Masa PPN bulanan melalui e-Faktur dan SPT Tahunan PPh. ERP yang saya bangun menghubungkan setiap baris jurnal ke kode pajak opsional (PPN 11%, PPh 23 2%, dll.). Laporan PPN mengelompokkan berdasarkan masa pajak, menampilkan pajak masukan (pembelian) dan pajak keluaran (penjualan), dan menghitung utang pajak bersih atau kredit.
Untuk perusahaan Indonesia yang tunduk pada audit eksternal, setiap entri jurnal memerlukan dokumen pendukung. Sistem yang saya bangun menghubungkan entri jurnal ke dokumen sumber — faktur AP, faktur AR, rekening koran, voucher pembayaran. Auditor dapat mengklik baris jurnal apa pun dan melihat dokumen aslinya. Kapabilitas 'drill-down ke sumber' ini yang membedakan ERP nyata dari spreadsheet canggih.