FX handling
Anything not denominated in INR — SAR hotel invoices, USD flight blocks, AED visa fees — needs an exchange rate to enter the books. This runbook tells you where rates come from, how the app captures them at posting time, and what to do at month-end for open foreign-currency balances.
Base currency for the Al Huda books is INR. Every voucher ultimately
posts INR values on JournalLine.debit and JournalLine.credit; the
source-currency amount and the rate used are pinned alongside.
1. When FX matters
Any time you post a voucher whose source amount is not INR:
- SAR hotel invoices (Makkah / Madinah suppliers bill in SAR).
- USD / AED airline or ticketing DMC invoices.
- SAR / USD ground-transport contracts.
- Foreign agent commissions or settlements.
If the customer-facing invoice is in INR but one of your cost lines came from a foreign supplier, you still need an FX rate — but only for the cost-side posting.
2. How the rate is captured at posting
Two migrations added the audit trail for FX postings:
supabase/migrations/20260418060325_journal_fx_rate.sql— addsJournalEntry.fxRateUsed(the header-level rate).supabase/migrations/20260418150000_journal_line_original_amounts.sql— addsJournalLine.originalDebit,originalCredit,originalCurrency,fxRate(per-line source values).
What this means in practice:
- When you post a SAR hotel bill, the voucher stores
originalDebit: 4500 SAR,fxRate: 22.10,originalCurrency: 'SAR'anddebit: ₹99,450all on the same line. - The rate at the moment you clicked Post is frozen. If someone later edits the supplier contract's exchange rate, the historical voucher does not change — it still reports the rate that was used when it was posted.
- For pure INR postings, all four columns are
NULL— no conversion happened.
FX rate cannot be edited after posting
Once a voucher is approved, the FX rate pinned on it is immutable in the UI. The only way to change a posted rate is to reverse the voucher and re-post with the correct rate. See the reversing-and-correcting runbook.
3. Where the rate comes from
The app asks you for the rate at posting time. It does not currently fetch from a live feed.
Policy (recommended — confirm with your Finance Manager)
- For supplier invoices linked to an inventory record (airline block, hotel contract, ground-transfer contract), the contract's stored exchange rate is the default — the one agreed at the time the contract was booked. Use it unless the supplier bills against a different rate in the invoice itself.
- For standalone supplier invoices (a one-off SAR bill with no inventory contract), use the RBI reference rate for the invoice date. Source: https://www.rbi.org.in/scripts/ReferenceRateArchive.aspx
- For customer receipts in foreign currency (rare), use the rate at which the bank actually credited your INR account — that's the honest number.
Record the rate source in the memo
Always write the rate source in the voucher memo, e.g. Rate:
RBI ref 2026-04-14 — 1 SAR = 22.10 INR. This saves the auditor a
round-trip. It is also free evidence if the rate is ever questioned.
4. Posting a foreign-currency voucher — worked example
Scenario: Hotel Al Haram, Makkah, bills SAR 45,000 for April block. You agreed rate at contract was SAR 1 = INR 22.10.
Steps
- Finance → Vouchers → Journal (or the supplier-linked voucher form, if the invoice ties back to a hotel inventory record).
- Currency: set to
SAR. - Original Amount:
45,000.00. - FX Rate:
22.10. The form auto-computes the INR column (9,94,500.00). - Fill the rest of the voucher exactly like an INR bill — expense line
debits
5101 Hotel Purchases, credit line credits the supplier's creditor account. Amounts in the INR columns flow from your rate. - In the Memo, note the rate source:
SAR 45,000 @ 22.10 (contract rate) = INR 9,94,500 — Hotel Al Haram April invoice INV-2326. - Verify Total Dr = Total Cr in INR terms.
- Click Post.
What the journal stores
| Column | Debit line (expense) | Credit line (supplier) |
|---|---|---|
debit |
994500.00 | — |
credit |
— | 994500.00 |
originalDebit |
45000.00 | — |
originalCredit |
— | 45000.00 |
originalCurrency |
SAR | SAR |
fxRate |
22.10 | 22.10 |
Header fxRateUsed is also 22.10.
5. Period-end FX revaluation
Open foreign-currency balances (unpaid SAR invoices, USD advances still sitting with suppliers) carry a rate risk: the rupee moves, so the INR value of those balances at period-end is different from the INR value captured when they were posted.
Automated procedure (preferred)
The app runs period-end FX revaluation as a one-click operation:
- Finance → Settings tab → Accounting Periods.
- Find the period you're closing. Click Run FX revaluation
(gated on
finance.periods.close, same as the Close action). - In the modal:
- Posting date defaults to the period end; usually leave it.
- Spot rates — enter INR-per-foreign-currency-unit for every currency you have open balances in. Pre-populated with USD / SAR / AED; add more rows if you have exposure in other currencies. Rows left blank are skipped.
- Click Post Revaluation. The app then:
- Pulls every approved journal line posted in the period on a non-INR sub-ledger (CUS- / SUP- / AGR- / AGP-).
- Groups by (party, currency), sums the open balance in source currency.
- Computes the revalued INR value = (source balance) × (entered rate).
- Diffs vs the historical carrying INR value (sum of the historical rates embedded in the lines).
- Posts a single balanced adjustment journal,
fx_revaluationas reference type, with:- One line per (party, currency) — DR sub-ledger if the rupee equivalent rose on a debit-normal account; CR sub-ledger if it fell. Opposite side on credit-normal (supplier / agent payable) accounts.
- Net P&L legs: credit
4501 Unrealised FX Gainfor the total gains; debit5501 Unrealised FX Lossfor the total losses.
- The voucher is tagged with
autoReverseOn = <first day of next period>so a reversing entry is queued for Day 1 next period.
What gets posted — worked example
April 2026 close. Two suppliers have open USD invoices, one agent has an open SAR receivable:
| Party | Source balance | Carrying INR (hist rate) | Revalued INR (spot) | Δ INR | Kind |
|---|---|---|---|---|---|
| Supplier A (SUP-AB12CD34) | 1,000 USD | 83,000 @ 83.00 | 85,000 @ 85.00 | +2,000 | Loss (payable rose) |
| Supplier B (SUP-EF56GH78) | 2,500 USD | 207,500 @ 83.00 | 212,500 @ 85.00 | +5,000 | Loss (payable rose) |
| Partner X (AGR-IJ90KL12) | 50,000 SAR | 1,105,000 @ 22.10 | 1,122,500 @ 22.45 | +17,500 | Gain (receivable rose) |
Posting:
DR SUP-AB12CD34 2,000 (offsets part of the USD credit)
DR SUP-EF56GH78 5,000
DR AGR-IJ90KL12 17,500 (grows the INR value of the receivable)
CR 4501 Unrealised FX Gain 17,500
DR 5501 Unrealised FX Loss 7,000
Totals balance at 24,500 on each side. Auto-reverse flag points at 2026-05-01 so the entry is unwound on Day 1 of May.
Idempotency + re-running
Posting is gated to one revaluation per period + posting-date. A second POST with the same pair returns 409 Conflict with the voucher number of the first run. To re-run:
- Reverse the existing revaluation voucher (standard Journal →
Reverse flow — the
isReversalsibling auto-approves). - Re-run the revaluation with corrected rates.
Manual procedure (fallback)
If the automated flow is unavailable (the endpoint is down, or Finance has a non-standard adjustment to make), you can still revalue by hand:
- Get the period-end closing rate from the RBI reference page for every foreign currency you have open balances in.
- For each supplier / customer with an open foreign-currency balance:
- Pull their ledger. Note the open balance in source currency.
- Compute period-end INR value = (source-currency balance) × (period-end rate).
- Compute the difference vs the INR value currently on the books. This is your unrealised gain or loss.
- Post one adjustment journal per currency with a row per
supplier / customer affected:
- Account heads:
4501 Unrealised FX Gain(INCOME) and5501 Unrealised FX Loss(EXPENSE) — seeded by20260419120000_fx_revaluation_accounts.sql. - If rupee has weakened (foreign currency worth more INR):
- Debit
5501 Unrealised FX Loss(payable) / Credit4501 Unrealised FX Gain(receivable) depending on account type. - Opposite side hits the supplier creditor / customer debtor per-party.
- Debit
- Memo:
Period-end FX revaluation, April 2026 close, RBI rate 1 SAR = 22.45 (prev contract 22.10).
- Account heads:
- Approve the revaluation journal via the normal maker-checker flow.
- At the start of the next period, post a reversing entry for the whole revaluation (see reversing-and-correcting.md §1). Without the reversal, next month the revaluation sits on top of the actual realised FX gain/loss when the invoice finally settles.
Why reverse at period start
Standard Indian accounting practice: unrealised FX adjustments at period-end are reversed on Day 1 of the next period so that when the invoice is actually paid, the realised gain/loss hits the books as a single number, not mixed with the prior-period estimate.
6. Verifying an FX voucher posted correctly
- [ ] Trial Balance: run in INR (default). The voucher contributes the correct INR debit and INR credit; overall balance is zero.
- [ ] Drill into the journal (Journal tab → click the voucher row): the Voucher Detail dialog should show both the INR amounts and the original-currency amounts side by side, with the rate.
- [ ] Supplier ledger: the invoice is visible. If you drill into the line, the source-currency amount is carried through.
- [ ] Audit log: the
finance.voucher.createentry records the voucher id; headerfxRateUsedis populated.
Reports are INR-only
Trial Balance, Balance Sheet, P&L, Aging — every report prints in
INR. The source-currency information is only visible on the
individual voucher / ledger line. If the auditor asks for a
foreign-currency report, you export the relevant account
statement and filter the originalCurrency column.
7. Common mistakes
- Typing the wrong rate direction —
1 SAR = 22.10 INRis correct.1 INR = 22.10 SARis wrong. The form accepts either number, but the INR amount will be off by a factor of 500+ if you invert it. Sanity check: if your INR amount has one more digit than it should, you probably inverted the rate. - Using today's rate for an old invoice — the rate should match the invoice date, not today. RBI reference page has historical rates.
- Forgetting to note the rate source in the memo — auditors will always ask. Put it in once, save everyone the email chain.
- Revaluing then forgetting to reverse on day 1 of next period — you end up double-counting FX gain / loss next month when the invoice actually settles.