Skip to Content
OWL forms

Monetary Field

Odoo 19 OWL component — Monetary Field (views)

Live preview Interactive
Source excerpt web/static/src/views/fields/monetary/monetary_field.js
import { registry } from "@web/core/registry";
import { _t } from "@web/core/l10n/translation";
import { formatMonetary } from "../formatters";
import { parseMonetary } from "../parsers";
import { useInputField } from "../input_field_hook";
import { useNumpadDecimal } from "../numpad_decimal_hook";
import { standardFieldProps } from "../standard_field_props";
import { nbsp } from "@web/core/utils/strings";

import { Component, useState, useEffect } from "@odoo/owl";
import { getCurrency } from "@web/core/currency";

export class MonetaryField extends Component {
    static template = "web.MonetaryField";
    static props = {
        ...standardFieldProps,
        currencyField: { type: String, optional: true },
        inputType: { type: String, optional: true },
        useFieldDigits: { type: Boolean, optional: true },
        hideSymbol: { type: Boolean, optional: true },
        trailingZeros: { type: Boolean, optional: true },
    };
    static defaultProps = {
        hideSymbol: false,
        inputType: "text",
        trailingZeros: true,
    };

    setup() {
        this.inputRef = useInputField(this.inputOptions);
        this.state = useState({ value: undefined });
        this.nbsp = nbsp;
        useNumpadDecimal();
        useEffect(() => {
            if (this.inputRef?.el) {
                this.state.value = this.inputRef.el.value;
            }
        });
    }

    get inputOptions() {
        return {
            getValue: () => this.formattedValue,
            refName: "numpadDecimal",
            parse: (v) => parseMonetary(v, { allowOperation: true }),
        };
    }

    get currencyId() {
        const currencyField =
            this.props.currencyField ||
            this.props.record.fields[this.props.name].currency_field ||
            "currency_id";
        const currency = this.props.record.data[currencyField];
        return currency && currency.id;
    }
    get currency() {
        if (!isNaN(this.currencyId)) {
            return getCurrency(this.currencyId) || null;
        }
        return null;
    }

    get currencySymbol() {
        return this.currency ? this.currency.symbol : "";
    }

    get currencyDigits() {
        if (this.props.useFieldDigits) {
            return this.props.record.fields[this.props.name].digits;
        }
        if (!this.currency) {
            return null;
        }
        return getCurrency(this.currencyId).digits;
    }

    get value() {
        return this.props.record.data[this.props.name];
    }

    get formattedValue() {
        if (this.props.inputType === "number" && !this.props.readonly && this.value) {
            return this.value;
        }
        return formatMonetary(this.value, {
            digits: this.currencyDigits,
            minDigits: this.props.useFieldDigits && this.props.record.fields[this.props.name].min_display_digits,
            currencyId: this.currencyId,
            noSymbol: !this.props.readonly || this.props.hideSymbol,
            trailingZeros: this.props.trailingZeros,
        });
    }

    onInput(ev) {
        this.state.value = ev.target.value;
    }
}

export const monetaryField = {
    component: MonetaryField,
    supportedOptions: [
        {
            label: _t("Hide symbol"),
            name: "no_symbol",
            type: "boolean",
        },
        {
            label: _t("Currency"),
            name: "currency_field",
            type: "field",
            availableTypes: ["many2one"],
        },
        {
            label: _t("Hide trailing zeros"),
            name: "hide_trailing_zeros",
            type: "boolean",
            help: _t("Hide zeros to the right of the last non-zero digit, e.g. 1.20 becomes 1.2"),
        },
    ],
Registry / API
Registry name
MonetaryField
Category
Module
web
Slug
monetary-field
Nav group
forms