Quick Answer
Multi-currency support enables global commerce with automatic currency conversion. The problem: Using Float fields = no currency awareness = wrong amounts in reports = broken accounting = $50k+ compliance fines. The solution: (1) Monetary fields: Use fields.Monetary(currency_field='currency_id') instead of fields.Float(). (2) Currency link: Add currency_id = fields.Many2one('res.currency') field. (3) Automatic conversion: Use currency_id._convert(amount, to_currency, company, date) for rate-based conversion. (4) Dual amounts: Store original currency (order currency) + company currency (reporting). (5) Exchange rates: Configure res.currency.rate records, Odoo uses latest rate automatically. (6) Gain/loss tracking: Calculate exchange_difference = current_amount - original_amount for compliance. Architecture: 3 components = Monetary fields (currency-aware amounts), Currency configuration (res.currency, exchange rates), Conversion logic (_convert method, _get_conversion_rate). Impact: Wrong = $60k-$150k audit costs. Right = global compliance automatic, flawless international sales.
The Multi-Currency Challenge
Your D2C brand operates globally:
๐บ๐ธ US customers (USD)
๐ช๐บ EU customers (EUR)
๐ฌ๐ง UK customers (GBP)
๐จ๐ฆ Canada customers (CAD)
๐ฆ๐บ Australia customers (AUD)
A customer in France orders $1,000 worth of product at current exchange rates. 2 weeks later, EUR strengthens 5%. The invoice amount hasn't changed (still EUR equivalent), but your internal reports show different values depending on today's exchange rate.
Get Multi-Currency Wrong
- โ Orders show wrong amounts in reports
- โ Accounting entries have floating values
- โ Exchange rate differences cause reconciliation nightmares
- โ International compliance fails ($50,000+ fines)
Get Multi-Currency Right
- โ Each transaction records original and company currencies
- โ Reports show both original and converted amounts
- โ Exchange gains/losses are automatically recorded
- โ International compliance is automatic
We've implemented 150+ Odoo systems. The ones where developers implement multi-currency properly? They serve global customers flawlessly. The ones who don't? They're stuck with USD-only systems or they have broken currency conversions causing monthly audit headaches. That's $60,000-$150,000 in compliance risk and manual reconciliation.
Multi-Currency Architecture
Odoo's multi-currency system has three components:
| Component | Purpose | Key Elements |
|---|---|---|
| Monetary Fields | Currency-aware amount storage | fields.Monetary(), currency_field parameter |
| Currency Configuration | Setup currencies and rates | res.currency, res.currency.rate |
| Conversion Logic | Automatic rate-based conversion | _convert(), _get_conversion_rate() |
Part 1: Monetary Fields
What it is: A field that stores an amount with currency awareness. Handles automatic formatting and conversion.
Simple Monetary Field
class SaleOrder(models.Model):
_inherit = 'sale.order'
# Regular Float (no currency awareness)
amount = fields.Float() # Just a number
# Monetary Field (currency-aware)
amount_total = fields.Monetary(
string='Total Amount',
currency_field='currency_id', # Links to currency field
help='Total order amount'
)
# Currency selection
currency_id = fields.Many2one(
'res.currency',
string='Currency',
default=lambda self: self.env.company.currency_id, # Default to company currency
help='Currency for this order'
)
How Monetary Fields Work
order = self.env['sale.order'].create({
'amount_total': 1500,
'currency_id': self.env.ref('base.EUR').id, # EUR currency
})
# Display: Shows "$1,500.00 โฌ" or "1.500,00 EUR" based on locale
# Database: Stores (1500, res.currency.id=2) # EUR
# Calculations: Automatically converts based on exchange rates
Real D2C Example: Multi-Currency Order
class SaleOrder(models.Model):
_inherit = 'sale.order'
currency_id = fields.Many2one(
'res.currency',
string='Currency',
default=lambda self: self.env.company.currency_id,
required=True,
help='Currency for this order'
)
# MONETARY FIELDS (currency-aware)
subtotal = fields.Monetary(
string='Subtotal',
currency_field='currency_id',
compute='_compute_subtotal',
store=True
)
tax_amount = fields.Monetary(
string='Tax',
currency_field='currency_id',
compute='_compute_tax',
store=True
)
total = fields.Monetary(
string='Total',
currency_field='currency_id',
compute='_compute_total',
store=True
)
# Amount in company currency (for reporting)
amount_in_company_currency = fields.Monetary(
string='Total (Company Currency)',
currency_field='company_currency_id',
compute='_compute_company_currency_total',
store=True
)
company_currency_id = fields.Many2one(
'res.currency',
related='company_id.currency_id',
readonly=True,
help='Company\'s base currency'
)
@api.depends('order_line.price_total')
def _compute_subtotal(self):
for order in self:
order.subtotal = sum(line.price_total for line in order.order_line)
@api.depends('subtotal')
def _compute_tax(self):
for order in self:
order.tax_amount = order.subtotal * 0.08 # 8% tax
@api.depends('subtotal', 'tax_amount')
def _compute_total(self):
for order in self:
order.total = order.subtotal + order.tax_amount
@api.depends('total', 'currency_id')
def _compute_company_currency_total(self):
"""Convert order total to company currency."""
for order in self:
if order.currency_id == order.company_currency_id:
order.amount_in_company_currency = order.total
else:
# Convert using current exchange rate
rate = order.currency_id._get_conversion_rate(
from_currency=order.currency_id,
to_currency=order.company_currency_id,
company=order.company_id,
date=fields.Date.today()
)
order.amount_in_company_currency = order.total * rate
Part 2: Currency Configuration
Enable Multi-Currency in Odoo
Settings โ Accounting โ Allow multi-currencies
Or programmatically:
# Enable multi-currency
config = self.env['ir.config_parameter'].sudo()
config.set_param('allow_multi_currency', True)
Set Company Currency
company = self.env.company
company.currency_id = self.env.ref('base.USD') # Set to USD
Configure Exchange Rates
# Create exchange rate
exchange_rate = self.env['res.currency.rate'].create({
'name': fields.Date.today(),
'rate': 1.08, # 1 USD = 1.08 EUR (example)
'currency_id': self.env.ref('base.EUR').id,
'company_id': self.env.company.id,
})
# Odoo automatically uses latest rate for conversions
Part 3: Currency Conversion
Manual Conversion
from_currency = self.env.ref('base.EUR')
to_currency = self.env.ref('base.USD')
amount = 1000
# Get exchange rate
rate = from_currency._get_conversion_rate(
from_currency=from_currency,
to_currency=to_currency,
company=self.env.company,
date=fields.Date.today()
)
converted_amount = amount * rate # 1000 EUR * 1.08 = 1080 USD
Automatic Conversion
@api.depends('order_id.total', 'order_id.currency_id')
def _compute_amount_in_company_currency(self):
"""Automatically convert to company currency."""
for line in self:
amount = line.order_id.total
if line.order_id.currency_id != line.company_id.currency_id:
# Odoo automatically converts
amount = line.order_id.currency_id.with_context(
date=fields.Date.today()
)._convert(
amount,
line.company_id.currency_id,
line.company_id,
fields.Date.today()
)
line.amount_in_company_currency = amount
Complete Multi-Currency Order Example
class SaleOrder(models.Model):
_inherit = 'sale.order'
# Currency selection
currency_id = fields.Many2one(
'res.currency',
string='Currency',
default=lambda self: self.env.company.currency_id,
required=True,
domain="[('active', '=', True)]"
)
# MONETARY FIELDS
subtotal = fields.Monetary(
string='Subtotal',
currency_field='currency_id',
compute='_compute_subtotal',
store=True
)
discount_amount = fields.Monetary(
string='Discount',
currency_field='currency_id',
compute='_compute_discount',
store=True
)
tax_amount = fields.Monetary(
string='Tax',
currency_field='currency_id',
compute='_compute_tax',
store=True
)
total = fields.Monetary(
string='Total',
currency_field='currency_id',
compute='_compute_total',
store=True
)
# Company currency conversions (for reporting)
subtotal_company = fields.Monetary(
string='Subtotal (Company)',
currency_field='company_currency_id',
compute='_compute_company_currency_amounts',
store=True
)
total_company = fields.Monetary(
string='Total (Company)',
currency_field='company_currency_id',
compute='_compute_company_currency_amounts',
store=True
)
exchange_rate = fields.Float(
string='Exchange Rate',
compute='_compute_exchange_rate',
store=True,
help='Rate used for conversion (1 order currency = X company currency)'
)
exchange_gain_loss = fields.Monetary(
string='Exchange Gain/Loss',
currency_field='company_currency_id',
help='Gain or loss from currency fluctuation'
)
company_currency_id = fields.Many2one(
'res.currency',
related='company_id.currency_id',
readonly=True
)
@api.depends('currency_id', 'company_currency_id')
def _compute_exchange_rate(self):
for order in self:
if order.currency_id == order.company_currency_id:
order.exchange_rate = 1.0
else:
order.exchange_rate = order.currency_id._get_conversion_rate(
from_currency=order.currency_id,
to_currency=order.company_currency_id,
company=order.company_id,
date=order.order_date or fields.Date.today()
)
@api.depends('total', 'subtotal', 'exchange_rate')
def _compute_company_currency_amounts(self):
for order in self:
order.subtotal_company = order.subtotal * order.exchange_rate
order.total_company = order.total * order.exchange_rate
Handling Exchange Rate Differences
Problem: Order placed at $1 USD = 1.05 EUR on Jan 1. Paid on Jan 31 at $1 USD = 1.08 EUR. Difference = loss.
Solution: Automatic exchange gain/loss recording
class AccountMove(models.Model):
_inherit = 'account.move'
@api.depends('invoice_line_ids.amount_currency')
def _compute_exchange_difference(self):
"""Record exchange gain/loss."""
for invoice in self:
if invoice.currency_id == invoice.company_id.currency_id:
invoice.exchange_difference = 0
else:
# Calculate difference
original_amount_company = sum(
line.amount_currency * line.exchange_rate
for line in invoice.invoice_line_ids
)
current_amount_company = invoice.amount_total * \
invoice.currency_id._get_conversion_rate(
from_currency=invoice.currency_id,
to_currency=invoice.company_id.currency_id,
company=invoice.company_id,
date=fields.Date.today()
)
invoice.exchange_difference = current_amount_company - original_amount_company
Multi-Currency Reports
Generate reports showing both original and company currencies:
def _get_order_data(self):
"""Get order data for reporting."""
orders = self.env['sale.order'].search([])
report_data = []
for order in orders:
report_data.append({
'order_number': order.name,
'customer': order.partner_id.name,
'currency': order.currency_id.name,
'amount': order.total, # In order currency
'amount_company': order.total_company, # In company currency
'exchange_rate': order.exchange_rate,
'date': order.order_date,
})
return report_data
Action Items: Implement Multi-Currency
Set Up Multi-Currency
โ Enable multi-currency in Settings
โ Set company currency
โ Configure currencies that you use (USD, EUR, GBP, etc.)
โ Set up automatic exchange rate updates
In Custom Modules
โ Use Monetary fields, not Float
โ Link Monetary to currency_field parameter
โ Convert to company currency for reports
โ Handle exchange differences
โ Test with different currencies
Compliance
โ Store both original and company currency amounts
โ Record exchange gains/losses automatically
โ Generate multi-currency reports
โ Audit trail for rate changes
Frequently Asked Questions
What is the difference between Float and Monetary fields in Odoo?
Float fields store plain numbers without currency awareness. They don't format properly, don't convert automatically, and cause broken reports. Monetary fields are currency-awareโthey link to a currency_id field, format amounts correctly based on locale (โฌ1.500,00 vs $1,500.00), and enable automatic conversion. Syntax: fields.Monetary(currency_field='currency_id') vs fields.Float(). Critical difference: Monetary fields require a currency_id Many2one to res.currency, enabling Odoo to track which currency the amount is in. Impact: Using Float for money = no multi-currency support, broken accounting, compliance failures. Always use Monetary for any amount representing money.
How do I convert amounts between currencies in Odoo?
Use the _convert() method on res.currency. Syntax: from_currency._convert(amount, to_currency, company, date). Example: eur = self.env.ref('base.EUR'); usd = self.env.ref('base.USD'); converted = eur._convert(1000, usd, self.env.company, fields.Date.today()). This automatically looks up the exchange rate from res.currency.rate for the given date and company. Alternative: Use _get_conversion_rate() to get the rate, then multiply manually: rate = eur._get_conversion_rate(eur, usd, company, date); converted = 1000 * rate. Key: Odoo uses the latest rate before/on the specified date. Configure rates in res.currency.rate records.
How do I handle exchange gains and losses in Odoo?
Exchange gain/loss occurs when currency rates change between transaction date and payment date. Example: Order placed Jan 1 at 1 USD = 1.05 EUR. Paid Jan 31 at 1 USD = 1.08 EUR. Difference = 0.03 EUR loss. Implementation: Store original_amount_company (amount converted at transaction date) and current_amount_company (amount converted at today's rate). exchange_difference = current_amount_company - original_amount_company. Compliance: Record this in a Monetary field with company currency. For invoices, use @api.depends('invoice_line_ids.amount_currency') to compute automatically. Accounting entry: Debit/credit exchange gain/loss accounts (Odoo does this automatically in account.move if configured). Required for international compliance and accurate financial reporting.
How do I set up automatic exchange rate updates in Odoo?
Odoo supports automatic rate updates from external services. Setup: (1) Enable multi-currency: Settings โ Accounting โ Currencies โ Enable. (2) Configure rate provider: Settings โ Accounting โ Currencies โ Automatic Currency Rates. (3) Select provider (ECB - European Central Bank, or other APIs). (4) Set update interval (daily recommended). (5) Select currencies to update. Manual alternative: Create res.currency.rate records programmatically or via UI. Code example: self.env['res.currency.rate'].create({'name': fields.Date.today(), 'rate': 1.08, 'currency_id': eur_id, 'company_id': company_id}). Best practice: Use automatic updates for production, manual rates for testing. Always verify rates are updating correctlyโstale rates = wrong conversions = broken reports.
Free Multi-Currency Architecture Review
Stop building single-currency systems. We'll design currency structure for your business, configure currencies and exchange rates, build Monetary fields correctly, implement currency conversion logic, handle exchange gains/losses, and test with multiple currencies. Most D2C brands expand internationally and realize their systems aren't multi-currency ready. Retrofitting costs $30,000-$80,000. Building it right from the start is painless.
