Monetary Fields in Odoo
Odoo's Monetary field type handles currency-aware values with proper formatting, rounding, and multi-currency support. Unlike plain Float fields, Monetary fields are aware of the associated currency and apply correct decimal precision and rounding rules automatically.
Defining Monetary Fields
Every Monetary field requires an associated currency field:
class Invoice(models.Model):
_name = 'custom.invoice'
currency_id = fields.Many2one(
'res.currency', string='Currency',
default=lambda self: self.env.company.currency_id,
required=True)
amount_untaxed = fields.Monetary(
string='Untaxed Amount',
currency_field='currency_id')
amount_tax = fields.Monetary(
string='Tax Amount',
currency_field='currency_id')
amount_total = fields.Monetary(
string='Total',
currency_field='currency_id',
compute='_compute_total', store=True)
@api.depends('amount_untaxed', 'amount_tax')
def _compute_total(self):
for inv in self:
inv.amount_total = inv.amount_untaxed + inv.amount_tax
If currency_field is not specified, Odoo defaults to looking for a field named currency_id. It is best practice to always specify it explicitly for clarity.
Currency Rounding
Each currency has a rounding precision (e.g., USD rounds to 0.01, JPY rounds to 1). Use the currency's round() method for proper rounding:
currency = self.env.ref('base.USD')
# Round to currency precision
rounded = currency.round(123.456) # 123.46
# Check if two amounts are equal (within rounding)
currency.is_zero(0.004) # True for USD (precision 0.01)
currency.compare_amounts(10.001, 10.005) # 0 (equal)
Never use Python's built-in round() for monetary calculations. Always use currency.round() to respect the currency's decimal places.
Form View Widget
<group>
<field name="currency_id"
options="{'no_create': true, 'no_open': true}"/>
<field name="amount_untaxed"/>
<field name="amount_tax"/>
<field name="amount_total"/>
</group>
The monetary widget automatically shows the currency symbol, formats with the correct number of decimals, and aligns values for readability. You do not need to specify widget="monetary" since Monetary fields use it by default.
Multi-Currency Support
Odoo supports multiple currencies with automatic exchange rate conversion:
# Convert amount from one currency to another
source_currency = self.env.ref('base.EUR')
target_currency = self.env.ref('base.USD')
company = self.env.company
date = fields.Date.today()
converted = source_currency._convert(
100.00, # amount
target_currency, # to currency
company, # company for rate lookup
date, # date for rate lookup
)
The _convert() method looks up exchange rates for the given date and company, applies the conversion, and rounds to the target currency's precision.
Exchange Rate Configuration
Exchange rates are stored in res.currency.rate:
# Programmatic rate update
self.env['res.currency.rate'].create({
'currency_id': self.env.ref('base.EUR').id,
'rate': 1.0850,
'name': fields.Date.today(),
'company_id': self.env.company.id,
})
Rates are relative to the company's main currency. If USD is the main currency and EUR rate is 0.92, then 1 EUR = 1/0.92 = 1.087 USD.
Company Currency
Every company has a main currency set in settings. Many models default to it:
currency_id = fields.Many2one(
'res.currency', string='Currency',
default=lambda self: self.env.company.currency_id)
Computed Monetary Totals
class SaleOrder(models.Model):
_name = 'sale.order'
currency_id = fields.Many2one('res.currency')
line_ids = fields.One2many('sale.order.line', 'order_id')
amount_total = fields.Monetary(
compute='_compute_amounts', store=True)
@api.depends('line_ids.price_subtotal')
def _compute_amounts(self):
for order in self:
total = sum(order.line_ids.mapped('price_subtotal'))
order.amount_total = order.currency_id.round(total)
Decimal Precision
For non-monetary numeric fields that need specific precision, use digits:
# Fixed digits
price = fields.Float(digits=(16, 4)) # 16 total, 4 decimal
# Named precision (configurable in Settings)
weight = fields.Float(digits='Product Unit of Measure')
unit_price = fields.Float(digits='Product Price')
Named precision references decimal.precision records that administrators can configure in Settings > Technical > Database > Decimal Accuracy.
Reporting with Monetary Values
<!-- QWeb report -->
<span t-field="doc.amount_total"
t-options='{"widget": "monetary",
"display_currency": doc.currency_id}'/>
<!-- Format in email template -->
<p>Total: {{ format_amount(object.amount_total,
object.currency_id) }}</p>
Common Pitfalls
- Using Float instead of Monetary: Float fields do not respect currency rounding, do not show currency symbols, and cause precision errors in multi-currency scenarios.
- Python round() vs currency.round(): Python's round uses banker's rounding; currency.round uses the currency's rounding method. Always use currency.round for monetary values.
- Missing currency_id field: If the currency field is not on the form, Monetary fields fail silently or use the company currency.
- Comparing floats directly: Use
currency.is_zero(amount)andcurrency.compare_amounts(a, b)instead of==or<operators. - Ignoring exchange rate dates: Always pass a specific date to
_convert(). Using today's rate for historical transactions gives incorrect results.
Tax Computation
Taxes in Odoo are computed through the tax model's compute_all() method:
taxes = line.tax_ids.compute_all(
price_unit=line.price_unit,
currency=line.currency_id,
quantity=line.quantity,
product=line.product_id,
partner=line.partner_id,
)
# taxes['total_included'], taxes['total_excluded'],
# taxes['taxes'] (list of tax detail dicts)