Phase 2 · Foundation Data · Oracle RMS Series

Currencies, Exchange Rates & VAT Regions

Oracle RMS is built for multi-currency retail from the ground up. Understanding how currency conversion, exchange rate types, and VAT regions work at the database level is essential for any developer working on purchasing, costing, or finance integrations.

11 min read📅April 21, 2026✍️Priyanshu Pandey📚Oracle RMS Series

How RMS Handles Multi-Currency

Oracle RMS is designed from the ground up to operate across multiple currencies. Every monetary value in the system — supplier costs, retail prices, order totals, stock ledger entries — is stored alongside a currency code, and the system maintains a complete exchange rate history to convert between them.

At the top of every financial transaction in RMS sits the concept of the primary currency — the currency in which the retailer's books are kept and to which all other monetary values eventually convert. The primary currency is set in SYSTEM_OPTIONS and cannot be changed after go-live without a full data migration.

Below the primary currency, RMS supports three scenarios:

  • Single-currency operations — the vast majority of Indian retailers. Everything from suppliers to stores uses INR.
  • Multi-currency purchasing — the retailer buys from international suppliers in USD, EUR, or GBP, but sells in INR. RMS converts costs at the point of PO creation using the configured exchange rate type.
  • Multi-currency retail — full multi-currency operations including different retail prices in different currencies, typically for cross-border or franchise scenarios.
💱
Currency codes follow ISO 4217

Every currency in RMS is identified by its three-letter ISO 4217 code — INR for Indian Rupee, USD for US Dollar, EUR for Euro. These codes are used as primary and foreign keys throughout the schema. Always store and pass currency codes in uppercase.

Currency Master

The CURRENCIES Table — Currency Master

The CURRENCIES table is the master list of all currencies the RMS instance recognises. A currency must exist here before it can be referenced anywhere else in the system — on suppliers, locations, exchange rates, or price zones.

01CURRENCIES
Currency Master

The CURRENCY_CODE is the primary key and is the value referenced as a foreign key across the entire schema. The DECIMALS_USED column is critical — it determines how monetary values for this currency are rounded and displayed throughout the system. Setting this incorrectly at setup causes rounding drift across cost and retail calculations.

ColumnTypeDescription
CURRENCY_CODEPK
VARCHAR2(3)ISO 4217 three-letter currency code (INR, USD, EUR, GBP)
CURRENCY_DESC
VARCHAR2(120)Full currency name (e.g. 'Indian Rupee')
DECIMALS_USED
NUMBER(1)Number of decimal places for this currency. 2 for INR/USD, 0 for JPY
CURRENCY_FORMAT
VARCHAR2(30)Display format mask used by the RMS UI (e.g. '999G999G990D00')
STATUS
VARCHAR2(1)A = Active. Inactive currencies cannot be used on new transactions.
Exchange Rate Types

Exchange Rate Types — Operational vs Buy vs Sell

Oracle RMS does not use a single exchange rate. It maintains three separate rate types that serve different business purposes. Understanding which rate type is used where is essential for any developer working on purchasing or finance integrations.

ColumnTypeDescription
Operational
'O'

Retail price conversion, internal cost calculations, stock ledger valuation

Buying
'B'

Purchase order cost conversion at time of PO creation

Selling
'S'

Sales audit and revenue conversion for multi-currency stores

⚠️
PO cost conversion uses the Buying rate, not Operational

A very common integration mistake: when converting a supplier's USD cost to your primary currency for display or calculation, developers often use the Operational rate. But Oracle RMS uses the Buying rate (RATE_TYPE = 'B') at PO creation time. Using the wrong rate type causes PO cost discrepancies that are hard to trace after the fact.

CURRENCY_RATES

CURRENCY_RATES — The Daily Rate Feed

Exchange rates in RMS are date-effective. Every day's rate for every currency pair is stored as a separate row in CURRENCY_RATES, creating a full history. The batch job CURRCONV is responsible for loading new exchange rates into this table — it's one of the most critical foundation batches that must run before any purchasing activities.

02CURRENCY_RATES
Exchange Rates

The composite primary key is FROM_CURRENCY + TO_CURRENCY + RATE_DATE + RATE_TYPE. To get the rate in effect for a given date, query with the date range condition shown in the SQL examples below — RMS uses the most recent rate on or before the transaction date.

ColumnTypeDescription
FROM_CURRENCYPKFK
VARCHAR2(3)Source currency code — FK → CURRENCIES
TO_CURRENCYPKFK
VARCHAR2(3)Target currency code — FK → CURRENCIES
RATE_DATEPK
DATEEffective date of this exchange rate
RATE_TYPEPK
VARCHAR2(1)O = Operational · B = Buying · S = Selling
CONVERSION_RATE
NUMBER(20,10)Units of TO_CURRENCY per 1 unit of FROM_CURRENCY
💡
CURRCONV batch must run before purchasing

The CURRCONV batch job loads exchange rates from your source system (ERP, treasury management, or a manual feed) into CURRENCY_RATES. If this batch hasn't run, any PO raised in a foreign currency will fail validation — RMS cannot calculate the primary currency equivalent cost without a valid rate. Always check CURRENCY_RATES has today's rates before investigating PO creation failures.

Primary Currency Flow

Primary Currency & Cost Conversion Flow

Understanding exactly how RMS converts costs when a PO is created against a foreign-currency supplier is important for both developers and finance teams. The conversion follows a strict chain:

  1. Supplier cost is stored in ITEM_SUPPLIER.UNIT_COST in the supplier's currency (SUPS.CURRENCY_CODE)
  2. At PO creation, RMS looks up CURRENCY_RATES for FROM_CURRENCY = supplier_currency, TO_CURRENCY = primary_currency, RATE_TYPE = 'B', with the effective date ≤ the PO's order date
  3. The converted cost is stored on ORDLOC.UNIT_COST in the primary currency
  4. All stock ledger entries use the primary currency cost
RC:OMS · Multi-Channel Order Management
Managing orders across currencies and marketplaces?

RC:OMS handles currency-aware inventory costing natively — every stock receipt is valued in your primary currency regardless of the supplier's billing currency. Connect Amazon Global, Flipkart, and your own Storefront into one unified ledger.

Explore RC:OMS
VAT Regions

VAT Regions — How Tax Geography Works in RMS

In Oracle RMS, tax is not calculated at the item level in isolation. Tax is determined by the intersection of where the item is sold (the VAT Region) and what the item is (the VAT Code on the item's Department or the item itself). This two-axis model allows a single item to be taxed differently in different states or countries.

A VAT Region is a geographic grouping — it could be a country, a state, a tax jurisdiction, or any other geographic boundary that has its own tax rules. In India, for example, you might have a VAT Region per GST state code, or a single national VAT Region if you operate a simplified tax structure.

03VAT_REGION
Tax Geography

Every Store and Warehouse in RMS has a VAT_REGION column that links it to its applicable tax region. This drives which VAT rates apply when items are sold from or transferred to that location.

ColumnTypeDescription
VAT_REGIONPK
NUMBER(4)Unique VAT region identifier
VAT_REGION_NAME
VARCHAR2(120)Descriptive name (e.g. 'India — GST Standard', 'Maharashtra')
COUNTRY_ID
VARCHAR2(3)ISO country code this VAT region belongs to
DEFAULT_TAX_TYPE
VARCHAR2(6)Default tax calculation method for items without a specific VAT code
VAT Codes

VAT Codes & the Tax Calculation Chain

VAT Codes define the actual tax rates and calculation methods. They link to VAT Regions via the VAT_CODE_RATES table, which stores the actual percentage for each VAT Code within each VAT Region. This allows the same VAT Code (e.g., "Standard Rate") to map to 18% in one region and 12% in another.

04VAT_CODES
Tax Codes
ColumnTypeDescription
VAT_CODEPK
VARCHAR2(6)Unique VAT code identifier
VAT_CODE_DESC
VARCHAR2(120)Human-readable description (e.g. 'GST 18%', 'GST Exempt')
INCL_IN_COST_IND
VARCHAR2(1)Y = Tax is included in cost price (tax-inclusive costing)
INCL_IN_RETAIL_IND
VARCHAR2(1)Y = Tax is included in retail price (MRP-inclusive model, common in India)
🇮🇳
India GST — INCL_IN_RETAIL_IND is typically Y

Indian retail pricing is almost always MRP-inclusive (Maximum Retail Price includes GST). This means INCL_IN_RETAIL_IND = 'Y' for most Indian RMS deployments. When this is set, RMS backs the tax out of the retail price for ledger valuation rather than adding it on top. Misunderstanding this setting causes the most common margin calculation errors in Indian RMS go-lives.

Quick Reference

Key Tables — Quick Reference

Currency & VAT Tables — Oracle RMS
CURRENCIES
Currency Master
CURRENCY_RATES
Exchange Rates
SYSTEM_OPTIONS
Primary Currency
VAT_REGION
Tax Geography
VAT_CODES
Tax Codes
VAT_CODE_RATES
Region × Code Rates
Practical SQL

Practical SQL Examples

1. Get the current Buying rate for a currency pair

SQL

-- Most recent Buying rate from USD → INR -- Uses MAX(RATE_DATE) to get the latest rate on or before today SELECT cr.from_currency, cr.to_currency, cr.rate_date, cr.conversion_rate FROM currency_rates cr WHERE cr.from_currency = 'USD' AND cr.to_currency = 'INR' AND cr.rate_type = 'B' AND cr.rate_date = ( SELECT MAX(cr2.rate_date) FROM currency_rates cr2 WHERE cr2.from_currency = cr.from_currency AND cr2.to_currency = cr.to_currency AND cr2.rate_type = cr.rate_type AND cr2.eff_date = TRUNC(SYSDATE) );

2. Convert a supplier cost to primary currency

SQL

-- Convert unit cost from supplier currency to INR (primary) -- for all active item-supplier records for a given supplier SELECT im.item, im.item_desc, is2.unit_cost orig_cost, s.currency_code orig_currency, ROUND(is2.unit_cost * cr.conversion_rate, 2) inr_cost, cr.rate_date rate_used FROM item_supplier is2 JOIN item_master im ON im.item = is2.item JOIN sups s ON s.supplier = is2.supplier JOIN currency_rates cr ON cr.from_currency = s.currency_code AND cr.to_currency = 'INR' AND cr.rate_type = 'B' AND cr.rate_date = ( SELECT MAX(cr2.rate_date) FROM currency_rates cr2 WHERE cr2.from_currency = cr.from_currency AND cr2.to_currency = cr.to_currency AND cr2.rate_type = cr.rate_type ) WHERE is2.supplier = :v_supplier AND im.status = 'A' AND s.currency_code != 'INR' -- only foreign-currency suppliers ORDER BY im.item;

3. VAT rate applicable at a store for an item's department

SQL

-- Full VAT calculation chain: item → dept → vat_code → store → vat_region → rate SELECT im.item, im.item_desc, dp.vat_code, vc.vat_code_desc, s.store, s.store_name, vr.vat_region_name, vcr.vat_rate, vc.incl_in_retail_ind FROM item_master im JOIN deps dp ON dp.dept = im.dept JOIN vat_codes vc ON vc.vat_code = dp.vat_code JOIN store s ON s.store = :v_store JOIN vat_region vr ON vr.vat_region = s.vat_region JOIN vat_code_rates vcr ON vcr.vat_region = s.vat_region AND vcr.vat_code = dp.vat_code AND vcr.eff_date = ( SELECT MAX(vcr2.eff_date) FROM vat_code_rates vcr2 WHERE vcr2.vat_region = vcr.vat_region AND vcr2.vat_code = vcr.vat_code ) WHERE im.item = :v_item;

4. PL/SQL — validate currency rates exist for today before batch

PL/SQL

-- Pre-batch validation: confirm today's buying rates exist -- for all currencies used by active suppliers -- Run this before CURRCONV-dependent batches PROCEDURE validate_currency_rates_today IS CURSOR c_missing IS SELECT DISTINCT s.currency_code FROM sups s WHERE s.sup_status = 'A' AND s.currency_code != (SELECT currency_code FROM system_options) AND NOT EXISTS ( SELECT 1 FROM currency_rates cr WHERE cr.from_currency = s.currency_code AND cr.to_currency = (SELECT currency_code FROM system_options) AND cr.rate_type = 'B' AND cr.rate_date = TRUNC(SYSDATE) ); BEGIN FOR r IN c_missing LOOP -- Log or raise alert for each missing currency rate INSERT INTO batch_error_log ( batch_name, error_date, error_message ) VALUES ( 'PRE_BATCH_CURRENCY_CHECK', SYSDATE, 'Missing today''s Buying rate for currency: ' || r.currency_code ); END LOOP; COMMIT; END validate_currency_rates_today;

Gotchas

Common Gotchas for Developers

  • !

    Using Operational rate instead of Buying rate for PO costs. RMS uses RATE_TYPE = 'B' (Buying) for purchase order cost conversion. Using 'O' (Operational) gives a different number. This discrepancy is invisible in single-rate setups but breaks immediately when your treasury feeds different buy/sell/operational rates.

  • !

    Not handling the "latest rate on or before date" pattern. Exchange rates in CURRENCY_RATES are effective-dated. A simple WHERE RATE_DATE = TRUNC(SYSDATE) will return zero rows on weekends and holidays when no rate feed runs. Always use the MAX subquery pattern to get the most recent rate on or before the transaction date.

  • !

    Confusing INCL_IN_RETAIL_IND with INCL_IN_COST_IND. In India, retail prices are MRP-inclusive (INCL_IN_RETAIL = Y), but costs from suppliers are typically exclusive (INCL_IN_COST = N). Getting these backwards causes all margin calculations to be wrong — and the error only surfaces when comparing cost and retail in reporting.

  • !

    Setting DECIMALS_USED incorrectly at currency setup. Once transactions exist in the system using a currency, changing DECIMALS_USED is a data migration, not a config change. Setting 2 decimals for JPY (which uses 0) or vice versa causes systemic rounding errors across all cost and retail calculations. Audit this at setup time.

  • !

    Forgetting that VAT_CODE is on DEPS, not ITEM_MASTER. Individual items inherit their VAT treatment from their Department's VAT_CODE. Items can have their own VAT code override, but the default is always at Department level. Queries that look for VAT code on ITEM_MASTER without a fallback to DEPS will miss the vast majority of items.

Key Takeaways

Key Takeaways
  • The CURRENCIES table is the master list of all recognised currencies. DECIMALS_USED must be set correctly at setup and cannot easily be changed post-go-live.
  • CURRENCY_RATES stores date-effective exchange rates with three types: O (Operational), B (Buying), S (Selling). PO cost conversion uses the Buying rate.
  • Always use the MAX(RATE_DATE) subquery pattern to retrieve exchange rates — never filter by RATE_DATE = SYSDATE directly.
  • VAT in RMS is determined by two axes: the VAT Region (where the item is sold) and the VAT Code (what the item is). The intersection in VAT_CODE_RATES gives the actual tax rate.
  • In India, INCL_IN_RETAIL_IND = 'Y' is the norm (MRP-inclusive pricing). RMS backs tax out of the retail price rather than adding it on top.
  • The CURRCONV batch must run successfully before any purchasing activity. Always validate CURRENCY_RATES has today's rates before running PO-related batches.
RetailCoder
All systems operational
v1.0 Live

RC:OMS

Multi-channel order management with double-entry inventory ledger. Amazon, Flipkart, Shopify, WooCommerce — one source of truth.

Launch demo →
v1.0 Live

RC:Storefront

Self-hosted headless e-commerce. Your server, your data, zero transaction fees. Native RC:OMS inventory sync.

Visit Storefront →
Pipeline

RC:Pulse

AI-powered retail analytics and demand forecasting — built natively on top of your RC:OMS and Storefront data.

Request early access →
Built in India 🇮🇳  ·  Architected by Priyanshu PandeyTalk to an engineer →