# Implementing Multi-Currency Support in a Custom ERP: Exchange Rates, Revaluation, and Reporting

> A technical deep-dive into building multi-currency capability in a custom ERP — from exchange rate storage and transaction handling to unrealized gain/loss revaluation and consolidated reporting.

URL: https://www.matthewswong.com/en/blog/erp-multi-currency-implementation/

- Author: Matthews Wong (https://www.matthewswong.com/en/about/)
- Published: 2025-06-10
- Updated: 2026-10-13
- Category: ERP

For Indonesian businesses that trade internationally — importing raw materials, exporting products, or billing clients in USD or SGD — multi-currency handling in an ERP is not optional. It also means living with two official sets of rates: Bank Indonesia's daily rates, which this module uses for the books, and the Ministry of Finance's weekly tax rate (kurs pajak), which the PPN on a foreign-currency tax invoice must use. In the projects I have worked on, finance teams at trading companies spent several days each month converting foreign currency transactions into Rupiah by hand. As a freelance ERP consultant, I built a multi-currency module for one of my clients that handles the full cycle: transaction entry in any currency, automatic IDR equivalent calculation, period-end revaluation, and consolidated Rupiah reporting.

## The Currency Architecture

The system I built has a single functional currency (IDR — Indonesian Rupiah) as the base. All financial reports are in IDR. Foreign currency transactions are stored with both the original foreign currency amount and the IDR equivalent at the transaction date. This dual-amount approach is standard in accounting systems and simplifies reporting: the IDR amounts flow directly into the journal and require no conversion at report time.

### Exchange Rate Storage and Updates

I maintain an exchange_rates table with columns: currency_pair (e.g., 'USD/IDR'), rate_date, buy_rate, sell_rate, and mid_rate. Each working day a NestJS cron job at 08:30 WIB pulls the Kurs Transaksi BI from Bank Indonesia's wsKursBI web service: buy and sell rates announced at 08:00 WIB, with the USD midpoint set at the previous working day's JISDOR (BI's USD/IDR reference rate, published at 15:15 WIB). The business can also enter custom rates manually when they use a rate negotiated with their bank — common for large USD transactions. All historical rates are retained (never overwritten) so I can reconstruct the exact IDR amount for any past transaction.

> From my freelance ERP consulting work: use Bank Indonesia's rate as the default, but always let the user override it for a specific transaction. PSAK 221 (renumbered from PSAK 10 by IAI from 1 January 2024) requires the spot rate on the transaction date, and the rate your bank actually applied often differs slightly from BI's, so store both on each transaction. Tax is separate: PP 44/2022 Article 21 requires PPN on a foreign-currency sale to be converted at the Minister of Finance's rate in force when the tax invoice should have been made, the weekly kurs pajak, so keep that rate on the invoice too.

## Handling Foreign Currency Transactions

When an AP invoice arrives in USD, the entry screen shows USD amount, and the system looks up the current USD/IDR rate to compute the IDR equivalent. The journal entry records: debit Expense (IDR equivalent), credit AP Payable — USD (original USD amount + IDR equivalent). When the payment is made, if the IDR/USD rate has changed, the payment generates a foreign exchange gain or loss entry for the difference. This realized gain/loss is posted to a dedicated Forex Gain/Forex Loss account and appears on the P&L.

### Period-End Revaluation

At month-end, open foreign currency balances (unpaid invoices, bank accounts in foreign currency) must be revalued at the closing rate. The revaluation job reads all open FC balances, computes the difference between the original IDR equivalent and the IDR equivalent at the closing rate, and posts unrealized gain/loss entries. PSAK 221 and IAS 21 require monetary items at the closing rate with the difference in profit or loss; reversing those entries at the start of the next period (marked with a reverse_on_date) is my design choice, so the payment still measures its realized gain or loss against the original rate. Without ERP support it is completely manual — I've seen companies skip it and produce materially inaccurate balance sheets as a result.

> **Warning: Intercompany Transactions in Multi-Currency Environments**

> If your client has multiple entities that transact with each other in different currencies, intercompany elimination in the consolidated accounts becomes significantly more complex. Both sides of the intercompany transaction may record different IDR amounts (because the rate on the invoice date differs from the rate on the payment date). This creates intercompany reconciliation differences that must be explicitly eliminated in the consolidation. I handle this with a dedicated intercompany elimination module and require all intercompany transactions to use a standardized monthly average rate rather than the spot rate — an approximation IAS 21 accepts only while rates do not fluctuate significantly.

## How I Approached This

The multi-currency implementation adds three fields to every financial transaction: foreign_currency, foreign_amount, and exchange_rate. The IDR amount is always computed as foreign_amount × exchange_rate, and a PostgreSQL check constraint validates it on every journal line, catching rounding issues. I use 6 decimal places for exchange rates: USD/IDR (JISDOR Rp17,899.00 on 12 October 2026) does not need them, but Bank Indonesia quotes JPY per 100 yen and VND at 0.69, so the fetcher divides by the quote unit and stores per-unit rates.

## Multi-Currency Bank Reconciliation

Bank reconciliation is more complex with multiple currencies: you're reconciling a USD bank account statement against USD transactions in the ERP, then also reconciling the IDR equivalents. I provide a reconciliation screen per bank account per currency, showing outstanding transactions in the foreign currency sorted by amount and date. The reconciliation marks which bank statement entries match which ERP transactions. Unmatched items flag as reconciliation exceptions for the finance team to investigate.

## Management Reporting in IDR

All management reports are presented in IDR, with an optional secondary column showing the original foreign currency amounts for FC-heavy businesses. The P&L shows total revenue and costs in IDR, with a Forex line for net realized and unrealized gains/losses. This is important for management — a good operating month can show a poor bottom line if the Rupiah depreciated sharply against USD liabilities. Surfacing the Forex component separately helps management understand the true operating performance versus currency impact.

## Sources & Further Reading

- Bank Indonesia — JISDOR, Kurs Acuan Non-USD/IDR, dan Kurs Transaksi — https://www.bi.go.id/id/fungsi-utama/moneter/informasi-kurs/default.aspx
- NetSuite — The Multi-Currency Accounting Guide — https://www.netsuite.com/portal/resource/articles/accounting/multi-currency-accounting.shtml
- Dynamic Netsoft Technologies — ERP Framework for Multi-Country Operations — https://medium.com/@dynamicnetsoft/erp-framework-for-multi-country-operations-the-complete-architecture-guide-64b795408a2f

> **Key takeaway:** Multi-currency in an Indonesian ERP means one functional currency, IDR, with every foreign transaction stored at its original amount and its Rupiah equivalent, plus the Ministry of Finance's weekly kurs pajak for PPN on tax invoices. Period-end revaluation then restates open balances at the closing rate, as PSAK 221 (formerly PSAK 10) requires, keeping the ledger consistent after the rate moves.

## Source links

- https://www.bi.go.id/id/fungsi-utama/moneter/informasi-kurs/default.aspx
- https://www.netsuite.com/portal/resource/articles/accounting/multi-currency-accounting.shtml
- https://medium.com/@dynamicnetsoft/erp-framework-for-multi-country-operations-the-complete-architecture-guide-64b795408a2f
- https://www.bi.go.id/id/statistik/informasi-kurs/transaksi-bi/default.aspx
- https://www.bi.go.id/id/statistik/informasi-kurs/jisdor/default.aspx
- https://web.iaiglobal.or.id/assets/files/file_publikasi/Perubahan_Penomoran_PSAK_ISAK_dalam_SAK_Indonesia.pdf
- https://fiskal.kemenkeu.go.id/informasi-publik/kurs-pajak
- https://peraturan.bpk.go.id/Details/232656/pp-no-44-tahun-2022
- https://www.bdo.nz/en-nz/blogs/financial-reporting-insights/accounting-for-forex-transactions

## FAQ

### Which Bank Indonesia exchange rate does the ERP store, and when is it published?

It stores the Kurs Transaksi BI, the buy and sell rates Bank Indonesia announces at 08:00 WIB on each working day, pulled by a NestJS cron job at 08:30 WIB into an exchange_rates table that is never overwritten. The USD midpoint of those rates is the previous working day's JISDOR, BI's USD/IDR reference rate, which is published at 15:15 WIB. Keeping every historical rate means the IDR equivalent of any past transaction can always be reconstructed.

### Why are both the Bank Indonesia rate and the applied rate stored on each transaction?

PSAK 221, which the IAI renumbered from PSAK 10 with effect from 1 January 2024, requires the spot rate on the transaction date, but the rate a bank actually applies to a specific transaction often differs slightly from BI's published rate. Storing both keeps the books traceable to an official rate while matching the real economics of the transaction.

### Which exchange rate applies to the PPN on a foreign-currency tax invoice?

The Minister of Finance's rate, not Bank Indonesia's. PP 44/2022 Article 21 requires the PPN on a foreign-currency transaction to be converted into Rupiah at the Minister's rate in force when the Faktur Pajak should have been made. The Ministry sets this kurs pajak every week, valid from Wednesday to Tuesday, so the ERP needs a second rate table for tax.

### How does the ERP handle realized and unrealized foreign exchange gains and losses?

When a foreign-currency invoice is paid at a different rate from the invoice date, the payment posts a realized gain or loss to a dedicated Forex Gain or Forex Loss account. At month-end, open monetary balances such as unpaid invoices and foreign-currency bank accounts are restated at the closing rate, as PSAK 221 and IAS 21 require, and the difference is posted as an unrealized gain or loss. The module reverses that entry at the start of the next period, so the eventual payment still measures its realized result against the original rate.

### How are intercompany multi-currency transactions handled to avoid consolidation errors?

When two entities in a group transact in different currencies, each side may record a different IDR amount depending on the rate on each date. The system uses a dedicated intercompany elimination module and requires intercompany transactions to use a standardized monthly average rate rather than the spot rate. IAS 21 accepts an average rate only as an approximation while rates do not fluctuate significantly.

---

- HTML version: https://www.matthewswong.com/en/blog/erp-multi-currency-implementation/
- Bahasa Indonesia: https://www.matthewswong.com/id/blog/erp-multi-currency-implementation/
- 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
