はじめに
MonetaryフィールドはOdooで最も便利でありながら、誤用されやすいフィールドの一つです。見た目は数値、保存は浮動小数点ですが、単なる数値フィールドとは違う挙動をします。その特性を把握すれば、金額をFloatで扱う過ちを避けられます。
受注の合計や請求書金額、製品の単価を見たことがあるなら、すでにMonetaryフィールドを扱っています。Odooの標準データモデルに広く使われ、通貨表示や丸め、表示桁数といった処理の多くを裏で担っています。
このガイドはOdooの開発者、コンサルタント、あるいは技術的に理解を深めたい業務担当者向けです。Monetaryフィールドの内部動作を知ることで、カスタム開発時の丸めずれや表示の不一致などのトラブルを未然に防げます。
Odooにおける「Monetaryフィールド」とは何か
fields.MonetaryはOdooの基本フィールドタイプの一つで、通貨単位の値(価格、金額、合計、予算など)を扱う目的で設計されています。
Monetaryが通常のFloatと異なるのは常に通貨と結びつく点です。フィールドは自身がどの通貨を前提にしているかを認識し、その通貨の設定に応じて小数桁数や丸め規則を反映します。
ユーザーインターフェースでの見え方
UI上では、Monetaryフィールドは関連付けられた通貨の表示ルールに従って整形されます。例えばユーロなら「€ 1,234.50」、米ドルなら「$ 1,234.50」のように、通貨レコードの桁数設定に基づいて小数が表示されます。
フォームでは編集可能、一覧では見やすく集計にも自然に馴染みます。エンドユーザーはフォーマットを気にする必要がなく、Odooが表示処理を透過的に行います。
裏側のデータ型について
データベース側ではMonetaryはPostgreSQLのdouble precision(浮動小数点)で保存されます。通貨情報自体は同じカラムに入らず、同モデル上の別のMany2oneフィールド(res.currencyへの参照)から取得されます。
値と通貨を分けて管理する設計は意図的です。これにより値自体はシンプルに保たれ、通貨を独立して変更できる柔軟性が確保されます。
Monetaryフィールドの仕組み
Monetaryフィールドの動作原理を理解すると、丸めや表示で発生しがちな問題を避けられ、正確な会計処理や請求処理につながります。
currency_field パラメータについて
Monetaryフィールドは必ず res.currency を参照するMany2oneと組み合わせて使います。デフォルトでは同モデル上に currency_id という名前を期待しますが、currency_field パラメータで別名を指定できます。
amount = fields.Monetary(string='Amount', currency_field='currency_id')
もし通貨フィールドが欠けていたりレコードで未設定だと、Odooは会社通貨にフォールバックします。致命的なエラーを避けはしますが、多通貨環境では誤表示を招くため、必ず明示的にcurrency_fieldを宣言してください。
丸めと精度の扱い
MonetaryとFloatの重要な差は丸めの扱いです。res.currency レコードが各通貨の小数桁数と丸め単位を定義しており、表示や読み込み時にそのルールが自動で適用されます。
たとえばEURで1.2349999が保存されていても画面には1.23と表示されます(1.235ではない)。税計算や請求合計、照合処理ではこの違いが重要です。単なるFloatを使うと、丸め誤差が蓄積して追跡困難な差分を生みます。
Odoo ORMとの相互作用
ORMでMonetaryを読むと常にPythonのfloatが返ります。通貨コンテキストは同レコード上の通貨フィールドから得られるため、計算時は通貨の round() を使って精度を守るのが推奨です。
rounded_value = self.currency_id.round(self.amount)
こうすることでマルチライン合計や反復計算で生じやすい浮動小数点の蓄積誤差を抑えられます。
QWebレポートでのMonetary表示
QWebテンプレート内ではMonetary用のウィジェットを使うことでPDFやHTMLレポートでも正しい通貨記号と小数表示を保証できます。
<span t-esc="record.amount"
t-options='{"widget": "monetary", "display_currency": record.currency_id}'/>
こうすることで、出力される帳票がレコードの通貨に関係なく常に正しく整形されます。
実務での利用シーン
Monetaryフィールドは標準モジュールのあらゆる箇所で使われています。以下は業務で特に重要な利用例です。
1. セールス:製品価格と注文合計
受注や受注行の price_unit、price_subtotal、amount_total 等はすべてMonetaryです。顧客注文で選択された通貨に従って表示・丸めが行われ、会社通貨と異なる場合でも正しく扱われます。
たとえば営業がEUR運営の会社でUSD受注を登録しても、Monetaryが通貨コンテキストを持つため表示や丸め、必要な換算処理が期待どおりに機能します。
2. 会計:請求書金額と税額
会計モジュールでは請求書の各金額列(amount_untaxed、amount_tax、amount_total 等)がMonetaryです。請求書に設定された通貨がすべての丸めを決定します。
税額の丸めが間違っていると仕訳の不整合を招き、照合作業が煩雑になります。Monetaryの通貨依存丸めがこれを防ぎます。
3. CRM:商談の期待収益
商談の expected_revenue はMonetaryです。営業は見込み先の通貨でパイプラインの金額を入力でき、ダッシュボードや部門集計では会社通貨に揃えて分析できます。
この設計が機能するのは、Monetaryが数値と通貨情報を組で保持するためです。
4. 購買:仕入単価と発注合計
購買発注でも単価や合計はMonetaryで管理され、取引先の通貨(例:日本円やユーロ)に合わせて表示と丸めがなされます。購買担当者が手動で調整する必要はありません。
5. カスタム:予算や目標の管理
プロジェクトや部門、独自モデルに予算や目標金額を追加する際はMonetaryが最適です。会社通貨との親和性、フォーム表示の整合性、レポートやエクスポート時の扱いやすさが理由です。
Floatで代用は可能ですが、多通貨が絡むと表示ずれや丸め問題が発生しやすくなるため避けるべきです。
Monetaryフィールドの追加・カスタマイズ方法
Monetaryフィールドを追加する方法は大きく分けて2つあります:ノーコードでStudioを使うか、Pythonモジュールで定義して完全制御するかです。
Odoo Studioを使う方法
Odoo Studioのフィールド作成ではMonetaryタイプがあり、Studioは必要に応じて自動で currency_id を追加します。コードを書かずに一般的なニーズを満たせるため、業務ユーザーにとって素早い手段です。
Studioが生成するフィールド名は x_ プレフィックス付き(例:x_studio_budget)になります。モデルに既に currency_id があればそれを使い、なければ新しい通貨フィールドを作成します。複数のMonetaryフィールドがあり各々別通貨を想定する場合は、共有通貨フィールドを意図的に見直す必要があります。
単純なケースではStudioが最速で安全にフィールド追加を行えます。
技術的アプローチ:Pythonによる定義
カスタムモジュールでMonetaryを定義するには、Monetary本体と対応する通貨Many2oneの2つを宣言するのが標準パターンです。
from odoo import fields, models
class ProjectTask(models.Model):
_inherit = 'project.task'
x_budget = fields.Monetary(
string='Budget',
currency_field='x_budget_currency_id',
)
x_budget_currency_id = fields.Many2one(
comodel_name='res.currency',
string='Budget Currency',
default=lambda self: self.env.company.currency_id,
)
内部利用のフィールドには会社通貨をデフォルトにするのが実務的です。新規レコードで通貨が空欄にならずUIの表示が壊れるのを防げます。
計算フィールドとしてのMonetary
行金額の合計や算式の結果をMonetaryで扱うのは自然な使い方です。以下は標準的なパターンです。
x_total_budget = fields.Monetary(
string='Total Budget',
currency_field='currency_id',
compute='_compute_total_budget',
store=True,
)
@api.depends('x_line_ids.x_amount')
def _compute_total_budget(self):
for record in self:
record.x_total_budget = sum(record.x_line_ids.mapped('x_amount'))
store=True を付けると一覧での検索・並び替え・集計に使えるようになります。保存しない計算フィールドはORMのドメインで使えないため注意が必要です。
API経由でフィールドを追加する方法
XML-RPCなどで遠隔からフィールドを作成する場合、ir.model.fields に対してMonetary型のフィールドを作れます。
models.execute_kw(ODOO_DB, uid, ODOO_API_KEY,
'ir.model.fields', 'create',
[{
'name': 'x_budget',
'field_description': 'Budget',
'model_id': model_id,
'ttype': 'monetary',
'currency_field': 'currency_id',
'state': 'manual',
}]
)
この方法はリモート設定スクリプトや一括カスタマイズで使われ、APIを通じたカスタマイズ手法の一部です。
運用上のおすすめ(ベストプラクティス)
Monetaryフィールドを正しく運用するための基本ルールを以下にまとめます。
1. 金額には絶対にFloatを使わない
金額や価格、合計、予算など通貨付きの値は必ず fields.Monetary を使ってください。Floatは通貨認識がなく、通貨ごとの丸めに対応しないため誤差を招きます。
2. 通貨フィールドを明示的に宣言する
Odooの currency_id フォールバックに頼らず、currency_field パラメータを明確に設定し、対応するMany2oneを必ず定義してください。これにより多通貨運用時のサイレントな誤動作を防げます。
3. デフォルト通貨を設定する
内部用途で会社通貨が主なら、通貨フィールドにデフォルトを設定してください(例:default=lambda self: self.env.company.currency_id)。新規レコードで通貨が空になるのを防ぎます。
4. 検索・集計する計算フィールドは store=True にする
計算されたMonetaryをフィルターや並び替え、レポートで使うなら store=True を付けて保存してください。保存しない計算フィールドはORMドメインに使えません。
5. 中間計算では通貨の round() を使う
複数ステップで算術処理を行う場合は各段階で self.currency_id.round(value) を適用すると浮動小数点誤差の蓄積を防げます。
6. 多通貨集計は意識的に正規化する
異なる通貨のレコードを合算する際は、そのまま足さずに事前に単一通貨へ換算するか、レポート自体を特定通貨に限定してください。通貨混在のまま合計すると意味のない数字になります。
よくある落とし穴(注意点)
Monetaryでよくあるトラブル例と回避策を挙げます。
落とし穴1:通貨フィールドの未作成
Monetaryを追加する際に対応するMany2oneを忘れるケースが最も多いです。currency_id が存在しないと文脈によっては会社通貨にフォールバックされ、別のケースではエラーになります。Monetaryと同時に通貨フィールドを必ず用意してください。
落とし穴2:意図の異なる2つのMonetaryが同一通貨フィールドを共有している
顧客向け価格はEUR、仕入原価はUSDといった具合に別通貨を扱う予定がある場合、複数のMonetaryが1つの currency_id を共有してはいけません。それぞれ専用の通貨参照が必要です。共有してしまうと一方の設定がもう一方に影響し、データや表示が狂います。
落とし穴3:通貨を跨いだ集計での丸め違い
異なる通貨の値を単純に合算すると見た目が変になることがあります。必ず集計前に共通通貨へ換算するか、通貨別に集計してください。
落とし穴4:浮動小数点を用いた厳密な等価検索
Monetaryを = で検索するとDB上の浮動小数点表現の差でヒットしないことがあります。>=/<= と小さな許容差を使うか、比較前に通貨丸めを適用するなどして対処してください。
落とし穴5:データインポート時の丸め無視
CSVやXML-RPCでMonetaryをインポートすると、渡した数値がそのまま保存されます。ソースにターゲット通貨が許す小数桁を超える値があれば表示と保存に差が出ます。インポート前に通貨丸めをかけるか、インポートスクリプトで処理してください。
まとめ
Monetaryフィールドは見た目はシンプルでも、通貨レコードとの密接な関係があるため、会計データとしての整合性(正しい丸め、統一された表示、帳票との一致)を担保します。
Monetaryを正しく使う — つまり明示的な通貨フィールドとセットで使うこと、金額にFloatを代用しないこと — は、本番の運用で発生する追跡困難なバグを未然に防ぎます。Odooのデータモデルがこのフィールドに依存しているのは当然のことです。
開発ガイドに従う場合でも、既存モジュールをカスタマイズする場合でも、新規実装を行う場合でも、Monetary設計を最初に正しく決めることがOdooで金銭を正確に扱うための基礎となります。
Odoo導入の支援が必要ですか?
私たちDasoloは、企業の規模や構成に応じたOdoo導入、カスタマイズ、最適化を支援しています。データモデルの整理、フィールド設計、複数通貨対応、あるいは全面的な導入まで、豊富な実務経験でサポートします。
Monetaryフィールドやその他のOdooに関するご質問があれば、お気軽にご相談ください。 お問い合わせはこちらから どんなシステムを作りたいか、ぜひお聞かせください。