Skip to content

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 — adds JournalEntry.fxRateUsed (the header-level rate).
  • supabase/migrations/20260418150000_journal_line_original_amounts.sql — adds JournalLine.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' and debit: ₹99,450 all 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.

  1. 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.
  2. 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
  3. 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

  1. Finance → Vouchers → Journal (or the supplier-linked voucher form, if the invoice ties back to a hotel inventory record).
  2. Currency: set to SAR.
  3. Original Amount: 45,000.00.
  4. FX Rate: 22.10. The form auto-computes the INR column (9,94,500.00).
  5. 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.
  6. 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.
  7. Verify Total Dr = Total Cr in INR terms.
  8. 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:

  1. Finance → Settings tab → Accounting Periods.
  2. Find the period you're closing. Click Run FX revaluation (gated on finance.periods.close, same as the Close action).
  3. 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.
  4. 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_revaluation as 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 Gain for the total gains; debit 5501 Unrealised FX Loss for the total losses.
  5. 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:

  1. Reverse the existing revaluation voucher (standard Journal → Reverse flow — the isReversal sibling auto-approves).
  2. 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:

  1. Get the period-end closing rate from the RBI reference page for every foreign currency you have open balances in.
  2. For each supplier / customer with an open foreign-currency balance:
    1. Pull their ledger. Note the open balance in source currency.
    2. Compute period-end INR value = (source-currency balance) × (period-end rate).
    3. Compute the difference vs the INR value currently on the books. This is your unrealised gain or loss.
  3. Post one adjustment journal per currency with a row per supplier / customer affected:
    • Account heads: 4501 Unrealised FX Gain (INCOME) and 5501 Unrealised FX Loss (EXPENSE) — seeded by 20260419120000_fx_revaluation_accounts.sql.
    • If rupee has weakened (foreign currency worth more INR):
      • Debit 5501 Unrealised FX Loss (payable) / Credit 4501 Unrealised FX Gain (receivable) depending on account type.
      • Opposite side hits the supplier creditor / customer debtor per-party.
    • Memo: Period-end FX revaluation, April 2026 close, RBI rate 1 SAR = 22.45 (prev contract 22.10).
  4. Approve the revaluation journal via the normal maker-checker flow.
  5. 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.create entry records the voucher id; header fxRateUsed is 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

  1. Typing the wrong rate direction1 SAR = 22.10 INR is correct. 1 INR = 22.10 SAR is 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.
  2. Using today's rate for an old invoice — the rate should match the invoice date, not today. RBI reference page has historical rates.
  3. Forgetting to note the rate source in the memo — auditors will always ask. Put it in once, save everyone the email chain.
  4. 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.