MY HATCH

入力画面の動きを変える — Odoo の画面の拡張(onchange・計算項目・OWL)

数量を入れたら注意を出す、納期までの日数を出す、全角の注文番号を半角にそろえる。Odoo 19 Community の受注の画面で、小さなアドオンを入れて実際に動かした例で説明します。

6分
最終更新: 2026年10月26日
Odoo Communityアドオン開発OWL
目次

目次

このページには目次がありません

入力画面の動きを変える — Odoo の画面の拡張(onchange・計算項目・OWL)

受注の画面で数量を打ったら、その場で「多すぎませんか」と出てほしい。全角で打たれた注文番号を、保存の前に半角へそろえたい。こうした「入力の途中で画面が反応する」動きは、Odoo ではアドオンで作ります。Community には、ブラウザで動く JavaScript を画面の上で登録する設定がありません(画面を作る道具 Studio も Enterprise 側です)。

この記事では、Odoo 19 Community の受注の画面に小さなアドオンを入れ、4 つの道具を実際に動かした結果を見せます。

道具は「サーバーで決める」と「ブラウザで変える」の2系統

道具どこで動くか向いていること
@api.onchangeサーバー(Python)値が変わった瞬間に、別の項目を埋める・注意を出す
計算項目(compute)サーバー(Python)他の項目から決まる値を、常に画面に出す
ビューの条件(invisible・readonly・required)ブラウザ(XML の式)状態に応じて項目を隠す・触れなくする・必須にする
OWL の部品と patchブラウザ(JavaScript)入力のしかたや、画面の部品そのものを変える

業務の決まり(いくつ以上なら注意、何日前なら警告)は Python 側に置くのが基本です。一覧の取り込みや API から入った値にも同じ計算が効き、ブラウザの JavaScript は打ち手の手元の見た目に絞れます。

なお Community に入っている自動化ルール(base_automation)にも「UI変更時」という起動条件があり、Python のコードを画面から登録できます。ソースを読むと、この条件は「Python コードを実行」の動作でしか使えない決まりでした(画面からの登録は今回は試していません)。

例1:数量を入れたら注意を出す(onchange)

明細の数量が 100 以上になったら、ケースと個の取り違えを疑う注意を出します。

class SaleOrderLine(models.Model):
    _inherit = "sale.order.line"

    @api.onchange("product_uom_qty")
    def _onchange_jp_large_qty(self):
        if self.product_uom_qty >= 100:
            return {"warning": {
                "title": "数量が多めです",
                "message": "%s を %s%s で入れています。ケースと個の取り違えがないか確かめてください。"
                % (self.product_id.display_name, int(self.product_uom_qty), self.product_uom_id.name),
            }}

見積の明細で数量を 120 にして次の欄へ移ると、保存の前にこの注意が出ました。下の合計も、同じ瞬間に新しい数量で計算し直されています。

見積の明細に 120 を入れたところ。「数量が多めです」の注意が出る

数量を 120 にして欄を移った直後(画面は見本のデータです)

warning を返す onchange は、注意を出すだけで保存は止めません。入れてはいけない値を止めたいときは、保存のときに検査する仕組み(@api.constrains)と組み合わせます。

例2:納期までの日数を出す(計算項目+onchange)

納品予定日から「あと何日か」を計算する項目を足し、3 日を切ったら注意も出します。

class SaleOrder(models.Model):
    _inherit = "sale.order"

    x_jp_days_left = fields.Integer("納期まで(日)", compute="_compute_x_jp_days_left")

    @api.depends("commitment_date")
    def _compute_x_jp_days_left(self):
        today = fields.Date.context_today(self)
        for order in self:
            if order.commitment_date:
                due = fields.Datetime.context_timestamp(order, order.commitment_date).date()
                order.x_jp_days_left = (due - today).days
            else:
                order.x_jp_days_left = 0

    @api.onchange("commitment_date")
    def _onchange_jp_commitment_date(self):
        if self.commitment_date and self.x_jp_days_left < 3:
            return {"warning": {"title": "納期が近い受注です",
                                "message": "納期まで %s 日です。出荷の手配を先に確かめてください。" % self.x_jp_days_left}}

画面には、ビューの継承で項目を1行足します。invisible の式で、納品予定日が空のときは隠します。

<xpath expr="//group[@name='sale_shipping']" position="inside">
  <field name="x_jp_days_left" invisible="not commitment_date"/>
</xpath>

「その他情報」のタブで納品予定日を 2 日後にすると、注意が出ました。

納品予定日を入れた直後に「納期が近い受注です」の注意が出る

納品予定日を 2 日後にして欄を移った直後(画面は見本のデータです)

注意を閉じると、配送の欄の下に「納期まで(日)」が 2 と出ています。保存しなくても、入力した時点で計算されます。

配送の欄。納品予定日の下に「納期まで(日)」が出ている

足した計算項目。納品予定日が空なら行ごと隠れます(画面は見本のデータです)

つまずいた所:他のアドオンが置き換えた場所は指せない

最初は、納品予定日を囲む div[@name='commitment_date_div'] の後ろに足そうとして、インストールが止まりました。依存に書いたのは販売(sale_management)だけですが、在庫のアドオン(sale_stock)が入っていると、その div を position="replace" で置き換えています。元の XML にある名前でも、継承が重なった後の画面には残っていないことがあります。欄の枠(group の名前)のように、置き換えられにくい場所を指すと通りました。

例3:全角の注文番号を半角にそろえる(入力の部品を足す)

先方の注文番号(画面の名前は「顧客参照」)は、全角と半角が混ざると検索で見つからなくなります。打った文字を半角にそろえる入力の部品を、標準の文字の部品から派生させて足します。

import { registry } from "@web/core/registry";
import { CharField, charField } from "@web/views/fields/char/char_field";

export function toHalfWidth(value) {
    return value
        .replace(/[!-~]/g, (c) => String.fromCharCode(c.charCodeAt(0) - 0xfee0))
        .replace(/ /g, " ");
}

export class JpHalfwidthField extends CharField {
    parse(value) {
        return super.parse(toHalfWidth(value));
    }
}

registry.category("fields").add("jp_halfwidth", { ...charField, component: JpHalfwidthField });

使う側は、ビューの継承でその項目の widget を差し替えるだけです。

<xpath expr="//field[@name='client_order_ref']" position="attributes">
  <attribute name="widget">jp_halfwidth</attribute>
</xpath>

顧客参照に全角で「PO-2026-012」と打っている途中

打っている間は全角のまま。

欄を移ると「PO-2026-012」と半角になる

欄を移ると半角に(画面は見本のデータです)

DB に入った値も半角でした。JavaScript のファイルはマニフェストの assets に書くと、Odoo の画面の読み込みに組み込まれます。

"assets": {"web.assets_backend": ["jp_demo_clientscript/static/src/**/*"]},

例4:保存の後に一言出す(patch)

最後は、既にある部品の動きを patch で変える例です。受注を保存したとき、顧客参照が空なら黄色の通知を出します。

import { patch } from "@web/core/utils/patch";
import { useService } from "@web/core/utils/hooks";
import { FormController } from "@web/views/form/form_controller";

patch(FormController.prototype, {
    setup() {
        super.setup(...arguments);
        this.jpNotification = useService("notification");
    },
    async onRecordSaved(record, changes) {
        await super.onRecordSaved(...arguments);
        if (record.resModel === "sale.order" && !record.data.client_order_ref) {
            this.jpNotification.add("顧客参照(先方の注文番号)が空のままです。",
                { title: "保存しました", type: "warning" });
        }
    },
});

受注を保存した直後、右上に「顧客参照(先方の注文番号)が空のままです」の通知が出る

保存した直後の右上の通知(画面は見本のデータです)

FormController は、受注に限らず全部のフォームの画面で使われている部品です。patch はそのすべてに効くので、上のように対象のモデルで必ず絞ります。また super を呼ばない書き方をすると、元の処理や他のアドオンの patch が黙って消えます。Python の super() と同じ決まりです(「本格拡張はアドオンで」)。

試して分かった細かいこと

  • 入れるのに Odoo の再起動は要りませんでした。動いている Odoo の横で、コマンドで同じ DB にアドオンを入れたところ、画面を読み直すだけで Python の処理も JavaScript も効きました
  • 値を確定する操作で、注意が2回出ることがありました。日付の欄で Enter を押すと同じ注意が2つ重なり、Tab で欄を移ると1つでした。注意の文は、2回出ても困らない書き方にしておきます
  • 画面を離れると、編集中の値が保存されます。試している途中でブラウザを閉じたら、入れた数量と日付が保存されていました。試すときは捨ててよい DB で行います

どれを使うか

やりたいこと道具
入れた値に応じて、別の項目を埋める・注意を出す@api.onchange
他の項目から決まる値を画面に出す計算項目
状態で項目を隠す・必須にするビューの invisible・required の式
打ち方そのものを変える(半角・桁区切り・候補)入力の部品を派生させて足す
既存の画面の部品の動きを変えるpatch(対象で絞り、super を呼ぶ)

画面の動きは、Python で決まりを持ち、JavaScript は手元の打ちやすさに絞ると、取り込みや API から入った値とも食い違いません。当社が日本の業務向けに足すアドオンでも、この分け方で作ります。アドオンを外したときに何が残るか、更新で壊さない書き方は「アップデートしても壊れない — Odoo の拡張の作り方」にまとめています。

「作り方」の記事をもっと見る

ERP を、借りずに所有する。

Odoo Community を日本の業務に合わせ、ソース一式を人数無制限でお渡しします。

相談する(無料)