Sistem mempertahankan tabel exchange_rates dengan kolom currency_pair, rate_date, buy_rate, sell_rate, dan mid_rate. Kurs diambil setiap hari dari API Bank Indonesia melalui job cron NestJS pukul 08.00 WIB, dan semua kurs historis disimpan tanpa pernah ditimpa, sehingga ekuivalen IDR untuk transaksi masa lalu kapan pun dapat direkonstruksi kembali.
PSAK 10 mengharuskan penggunaan kurs tanggal transaksi, tetapi dalam praktiknya kurs yang diberikan bank untuk transaksi tertentu sering sedikit berbeda dari kurs BI yang dipublikasikan. Menyimpan keduanya menjaga kepatuhan terhadap standar akuntansi Indonesia sekaligus mencerminkan ekonomi transaksi yang sebenarnya.
Jika kurs IDR/USD berubah antara tanggal faktur dan tanggal pembayaran, pembayaran tersebut menghasilkan entri laba atau rugi selisih kurs yang sudah terealisasi. Entri ini diposting ke akun Laba Kurs/Rugi Kurs khusus dan muncul di laporan Laba Rugi.
Pada akhir bulan, saldo mata uang asing terbuka — faktur yang belum dibayar dan rekening bank dalam mata uang asing — harus dinilai ulang pada kurs penutup. Sistem memposting entri jurnal laba/rugi belum terealisasi atas selisihnya, yang kemudian dibalik di awal periode berikutnya. Melewatkan langkah ini diketahui menjadi penyebab neraca yang tidak akurat secara material.
Ketika dua entitas dalam grup bertransaksi dalam mata uang berbeda, masing-masing sisi dapat mencatat jumlah IDR yang berbeda tergantung kurs pada masing-masing tanggal. Sistem menggunakan modul eliminasi antar-perusahaan khusus dan mewajibkan semua transaksi antar-perusahaan menggunakan kurs rata-rata bulanan yang terstandarisasi, bukan kurs spot, sehingga selisih konsolidasi dapat dikendalikan dan dieliminasi secara eksplisit.
Untuk bisnis Indonesia yang berdagang secara internasional — mengimpor bahan baku, mengekspor produk, atau menagih klien dalam USD atau SGD — penanganan multi-mata uang dalam ERP bukan pilihan. Produsen yang mengimplementasikan kapabilitas ERP multi-mata uang biasanya melihat pengurangan 60-80% dalam waktu penutupan keuangan melalui konsolidasi otomatis. Dalam praktiknya, saya telah melihat perusahaan dagang Indonesia menghabiskan 3-4 hari per bulan secara manual mengonversi transaksi mata uang asing ke Rupiah untuk pembukuan mereka.
Sistem yang saya bangun memiliki satu mata uang fungsional (IDR — Rupiah Indonesia) sebagai basis. Semua laporan keuangan dalam IDR. Transaksi mata uang asing disimpan dengan jumlah mata uang asing asli dan ekuivalen IDR pada tanggal transaksi. Pendekatan dual-amount ini adalah standar dalam sistem akuntansi dan menyederhanakan pelaporan.
Saya mempertahankan tabel exchange_rates dengan kolom: currency_pair (misalnya 'USD/IDR'), rate_date, buy_rate, sell_rate, dan mid_rate. Kurs diambil setiap hari dari API Bank Indonesia (sumber resmi untuk bisnis Indonesia) melalui job cron NestJS pukul 08.00 WIB. Bisnis juga dapat memasukkan kurs kustom secara manual untuk transaksi tertentu.
Multi-Currency ERP Architecture (IDR as Functional Currency)
Exchange Rate Source
├── Bank Indonesia API (daily, 08:00 WIB) ← official source
│ └── exchange_rates table (historical, never overwritten)
└── Manual override (for negotiated bank rates)
Foreign Currency Transaction Flow:
┌─────────────────────────────────────────────────────────┐
│ AP Invoice: USD 10,000 │
│ │
│ ERP looks up USD/IDR rate: 15,850 (from BI API) │
│ │
│ Journal Entry posted: │
│ DEBIT Expense Account IDR 158,500,000 │
│ CREDIT AP Payable — USD USD 10,000 / IDR 158,500K │
└──────────────────────────┬──────────────────────────────┘
│ Payment date: rate 15,920
▼
┌─────────────────────────────────────────────────────────┐
│ Payment: USD 10,000 × 15,920 = IDR 159,200,000 │
│ │
│ DEBIT AP Payable — USD IDR 158,500,000 │
│ DEBIT Forex Loss Account IDR 700,000 ← diff │
│ CREDIT Bank — USD IDR 159,200,000 │
└─────────────────────────────────────────────────────────┘
Month-end Revaluation:
Open FC balances × closing rate − original IDR = unrealized P/L
Posted as reversing journal (reverses on day 1 of next period)Dari pengalaman saya membangun sistem ERP dalam pekerjaan konsultasi ERP freelance saya: gunakan kurs Bank Indonesia sebagai default, tetapi selalu izinkan pengguna mengganti untuk transaksi tertentu. Standar akuntansi Indonesia (PSAK 10) mengharuskan penggunaan kurs tanggal transaksi, tetapi 'kurs tanggal transaksi' sering kali adalah kurs yang diberikan bank Anda, yang sedikit berbeda dari kurs BI yang dipublikasikan. Simpan kurs BI dan kurs yang diterapkan pada setiap transaksi.
Ketika faktur AP datang dalam USD, layar entri menampilkan jumlah USD, dan sistem mencari kurs USD/IDR saat ini untuk menghitung ekuivalen IDR. Entri jurnal mencatat: debit Beban (ekuivalen IDR), kredit Utang Usaha — USD (jumlah USD asli + ekuivalen IDR). Ketika pembayaran dilakukan, jika kurs IDR/USD telah berubah, pembayaran menghasilkan entri laba atau rugi selisih kurs.
Pada akhir bulan, saldo mata uang asing terbuka (faktur yang belum dibayar, rekening bank dalam mata uang asing) harus dinilai ulang pada kurs penutup. Job revaluasi membaca semua saldo FC terbuka, menghitung perbedaan antara ekuivalen IDR asli dan ekuivalen IDR saat ini pada kurs penutup, dan memposting entri jurnal laba/rugi belum terealisasi. Ini adalah praktik akuntansi standar per PSAK 10 dan IAS 21.
// NestJS: Bank Indonesia exchange rate fetcher
@Cron('0 8 * * 1-5', { timeZone: 'Asia/Jakarta' })
async fetchBankIndonesiaRates() {
const pairs = ['USD/IDR', 'EUR/IDR', 'SGD/IDR', 'CNY/IDR'];
const today = new Date().toISOString().split('T')[0];
const res = await fetch(
`https://www.bi.go.id/biwebservice/wskursbi.asmx/...`
);
// Parse BI XML response and insert rates
for (const { pair, buy, sell, mid } of parsedRates) {
await this.exchangeRateRepo.upsert({
currencyPair: pair,
rateDate: today,
buyRate: buy,
sellRate: sell,
midRate: mid,
source: 'bank_indonesia',
}, ['currencyPair', 'rateDate']);
}
this.logger.log(`Updated ${pairs.length} exchange rates from BI`);
}
// PostgreSQL: transaction schema with dual amounts
ALTER TABLE journal_lines ADD COLUMN foreign_currency VARCHAR(3);
ALTER TABLE journal_lines ADD COLUMN foreign_amount NUMERIC(15,2);
ALTER TABLE journal_lines ADD COLUMN exchange_rate NUMERIC(12,6);
-- Computed IDR amount check (6 decimal precision)
ALTER TABLE journal_lines ADD CONSTRAINT chk_idr_consistency
CHECK (
foreign_amount IS NULL
OR ABS((debit - credit) - (foreign_amount * exchange_rate)) < 1
);Implementasi multi-mata uang menambahkan tiga bidang ke setiap transaksi keuangan: foreign_currency, foreign_amount, dan exchange_rate. Jumlah IDR selalu dihitung sebagai foreign_amount × exchange_rate. Fungsi PostgreSQL memvalidasi bahwa jumlah IDR pada baris jurnal cocok dengan perhitungan yang tersimpan. Saya menggunakan 6 desimal untuk kurs tukar.
Jika klien Anda memiliki beberapa entitas yang bertransaksi satu sama lain dalam mata uang berbeda, eliminasi antar-perusahaan dalam akun konsolidasi menjadi jauh lebih kompleks. Kedua sisi transaksi antar-perusahaan mungkin mencatat jumlah IDR yang berbeda. Ini menciptakan perbedaan rekonsiliasi antar-perusahaan yang harus dieliminasi secara eksplisit dalam konsolidasi.
Rekonsiliasi bank lebih kompleks dengan beberapa mata uang: Anda merekonsiliasi laporan bank USD terhadap transaksi USD di ERP, kemudian juga merekonsiliasi ekuivalen IDR. Saya menyediakan layar rekonsiliasi per rekening bank per mata uang, menampilkan transaksi outstanding dalam mata uang asing diurutkan berdasarkan jumlah dan tanggal.
Semua laporan manajemen disajikan dalam IDR, dengan kolom sekunder opsional yang menampilkan jumlah mata uang asing asli untuk bisnis yang banyak bertransaksi dalam FC. L/R menampilkan total pendapatan dan biaya dalam IDR, dengan baris Forex untuk laba/rugi selisih kurs yang terealisasi dan belum terealisasi. Menampilkan komponen Forex secara terpisah membantu manajemen memahami kinerja operasional sebenarnya versus dampak mata uang.