Số tiền không phải số thực bình thường. 123456.789 VND là vô nghĩa (không có 0.789 đồng), còn 99.999 USD phải thành 100.00. Mỗi loại tiền có quy tắc làm tròn riêng. Odoo có một kiểu trường chuyên cho tiền — Monetary — tự lo việc đó. Nối tiếp Selection, bài này bàn trường Monetary.

Monetary luôn đi kèm tiền tệ

Điểm bắt buộc: trường Monetary phải trỏ tới một trường tiền tệ qua currency_field:

Trường Monetary số tiền đi kèm tiền tệ trong Odoo 19: Monetary luôn cần một trường currency_field đi kèm, currency_id bằng fields.Many2one res.currency default lambda self trả self.env.company.currency_id, so_du bằng fields.Monetary Số dư với currency_field bằng currency_id bắt buộc trỏ tới trường tiền tệ, thiếu currency_field thì Odoo báo lỗi lúc nạp module; vì sao không dùng Float cho tiền, Monetary tự làm tròn theo số chữ số thập phân của tiền tệ, VND 0 chữ số làm tròn về đồng còn USD 2 chữ số cent, hiển thị kèm ký hiệu đúng đồng hay đô đặt trước hoặc sau theo chuẩn tiền tệ, cộng gộp số tiền khác tiền tệ thì Odoo cảnh báo không cộng bừa; làm tròn thủ công bằng API tiền tệ currency.round số

Hình 1: Monetary bắt buộc khai currency_field='currency_id' trỏ tới một trường Many2one tới res.currency. Thiếu nó, Odoo báo lỗi lúc nạp module. Đây là lý do bạn luôn thấy cặp currency_id + trường tiền đi cùng nhau trong mọi model có tiền của Odoo.

Vì sao không dùng Float? Đo thật

Điểm khác biệt lớn nhất: Monetary tự làm tròn theo số chữ số thập phân của đúng loại tiền. Đây là output thật trên db blog19:

Monetary tự làm tròn theo tiền tệ chạy thật trên database blog19: hai loại tiền có số chữ số thập phân khác nhau, VND rounding 1.0 decimal_places 0 symbol đồng, USD rounding 0.01 decimal_places 2 symbol đô; gán số dư có phần thập phân thì Monetary tự làm tròn theo tiền tệ, T.create với currency_id VND và so_du 123456.789 cho so_du bằng 123457.0 vì VND làm tròn về đồng 0 chữ số, T.create với currency_id USD và so_du 99.999 cho so_du bằng 100.0 vì USD làm tròn 2 chữ số thành 100.00; API làm tròn của tiền tệ dùng khi tính toán, vnd.round 123456.789 bằng 123457.0, usd.round 99.999 bằng 100.0; cùng con số tiền tệ khác thì kết quả làm tròn khác đừng dùng Float cho tiền

Hình 2: Output thật. VND có rounding=1.0 (0 chữ số) nên 123456.789 tự làm tròn về 123457 (đồng nguyên). USD có rounding=0.01 (2 chữ số) nên 99.999 thành 100.00. Cùng một con số nhưng tiền tệ khác → kết quả khác. Dùng Float thì bạn phải tự làm tròn đúng cho từng loại tiền — cực dễ sai. Monetary lo hết.

Ba việc Monetary làm cho bạn

1. Làm tròn theo tiền tệ

Như Hình 2: mỗi tiền tệ có decimal_places và rounding riêng; Monetary tự áp. Yên nữa (JPY) 0 chữ số, dinar (KWD) 3 chữ số — bạn không cần nhớ, chỉ chọn đúng currency_id.

2. Hiển thị đúng ký hiệu và vị trí

Giao diện hiện 1.000.000 ₫ (ký hiệu sau) hay $1,000.00 (ký hiệu trước) theo chuẩn từng tiền tệ, kèm dấu phân cách hàng nghìn — tự động, không cần format tay.

3. Cảnh báo khi cộng tiền khác loại

Cộng "100 USD + 50 VND" là vô nghĩa. Khi bạn read_group/sum các Monetary khác tiền tệ, Odoo cảnh báo hoặc yêu cầu quy đổi — tránh cộng bừa.

API làm tròn tiền tệ

Khi tính toán số tiền trong code, dùng currency.round() để làm tròn đúng chuẩn:

gia_sau_thue = currency.round(gia * 1.08)   # làm tròn theo tiền tệ
# So sánh tiền phải qua compare_amounts để tránh sai số float:
if currency.compare_amounts(a, b) == 0:      # a == b theo độ chính xác tiền tệ
    ...

compare_amounts quan trọng: so sánh trực tiếp hai Float tiền có thể sai vì sai số dấu phẩy động; compare_amounts so theo đúng số chữ số của tiền.

Lưu ý

  • currency_field mặc định tên 'currency_id': nếu trường tiền tệ của bạn tên khác, phải khai rõ.
  • Nhiều tiền tệ trên một model: có thể có currency_id (tiền giao dịch) và company_currency_id (tiền công ty), mỗi Monetary trỏ tới cái phù hợp.
  • Đừng đổi tiền tệ sau khi có dữ liệu mà không quy đổi — số tiền cũ sẽ bị hiểu sai loại tiền.

Nhớ ba ý

  1. Monetary cho số tiền, bắt buộc khai currency_field trỏ tới trường Many2one res.currency — thiếu là lỗi nạp module.
  2. Monetary tự làm tròn theo tiền tệ: đo thật 123456.789 VND → 123457 (0 chữ số), 99.999 USD → 100.00 (2 chữ số) — đừng dùng Float cho tiền.
  3. Tính toán tiền dùng currency.round() và so sánh bằng compare_amounts() để tránh sai số float; giao diện tự hiện đúng ký hiệu (₫/$) và vị trí.

Còn vài loại trường đặc biệt nữa cho nội dung phong phú. Phần sau đi vào Html, Binary, Image — lưu văn bản định dạng, tệp nhị phân, và ảnh; cùng cách chúng được làm sạch và tối ưu.