Skip to content

Monetary Fields and Currency Handling in Odoo: Complete Guide

DeployMonkey Team · March 23, 2026 11 min read

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) and currency.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)