Khách hàng thân thiết được giảm giá; combo mua nhiều rẻ hơn; giá đặc biệt cho một nhóm. Những yêu cầu về giá này rất hay gặp trong eCommerce, và cám dỗ đầu tiên là "tìm chỗ nào đặt giá rồi ghi đè". Nhưng giá trong Odoo không nằm một chỗ — nó được tính qua nhiều tầng: bảng giá (pricelist), thông tin combination trên trang, dòng đơn hàng. Móc sai tầng thì giá đúng ở chỗ này nhưng sai ở chỗ khác. Bài này chỉ đúng chỗ để can thiệp giá hiển thị trên trang sản phẩm — _get_combination_info — minh hoạ bằng một "giá thành viên -20%" chạy thật.
Giá đi qua đâu
Trước khi override, phải biết luồng. Với một sản phẩm trên trang /shop, giá bạn thấy đi qua chuỗi:
product.template._get_combination_info()— hàm củawebsite_saletrả về dict thông tin hiển thị cho trang sản phẩm:price(giá bán),list_price(giá gốc để gạch ngang),has_discounted_price... Đây là nơi quyết định con số hiện trên trang.- Bên trong nó gọi
pricelist._get_product_price()→_compute_price_rule()— áp bảng giá (giảm theo nhóm, số lượng...). - Khi khách đặt hàng,
sale.order.line._compute_price_unit()tính lại giá trên dòng đơn.

Hình 1: Override _get_combination_info — gọi super() lấy giá gốc rồi chỉnh info['price']. Vì hàm trả về một dict, ta chỉ sửa price (giá bán) và bật has_discounted_price; list_price giữ nguyên nên Odoo tự gạch ngang giá gốc. Sơ đồ dưới: chuỗi hook từ trang sản phẩm xuống dòng đơn.
Kết quả thật: 185.000 → 148.000
Trước khi override, sản phẩm "Cà phê rang xay Arabica" hiển thị giá gốc:

Hình 2: Giá gốc trên trang — 185.000 (đúng list_price trong CSDL).
Sau khi cài override _get_combination_info (-20%), cùng URL đó hiển thị giá thành viên, giá gốc bị gạch ngang:

Hình 3: Sau override — 148.000 (= 185.000 × 0,8) là giá bán, 185.000 bị gạch ngang. Quan trọng: list_price trong CSDL vẫn là 185.000 — ta chỉ đổi giá hiển thị/bán, không sửa dữ liệu gốc. Đó là điểm mạnh của việc móc vào _get_combination_info thay vì ghi đè trường giá.
Pricelist trước, code sau
Đây là nguyên tắc quan trọng nhất, kẻo viết code thừa: rất nhiều yêu cầu giá không cần override gì cả — chỉ cần khai một bảng giá (pricelist):
- Giảm giá theo nhóm khách (khách VIP -10%): tạo pricelist gán cho nhóm/đối tác.
- Giá theo số lượng (mua ≥ 10 rẻ hơn): luật pricelist theo
min_quantity. - Giá theo thời gian (khuyến mãi cuối tuần): luật pricelist có ngày bắt đầu/kết thúc.
Pricelist là cơ chế chuẩn, người vận hành tự chỉnh trên giao diện, không đụng code. Chỉ override khi logic không diễn đạt được bằng pricelist — ví dụ giá phụ thuộc một điều kiện phức tạp (số điểm tích luỹ của khách, tồn kho, một API bên ngoài).
Override đúng tầng
Nếu buộc phải code, chọn tầng theo mục tiêu:
- Đổi giá HIỂN THỊ trên trang sản phẩm → override
_get_combination_info(như bài này). Nhưng lưu ý: đây chỉ là hiển thị; nếu không đụng tầng dưới, lúc thêm vào giỏ/đặt hàng giá có thể khác. - Đổi giá THẬT khi đặt hàng → tầng
pricelist._get_product_price(đúng nhất, ảnh hưởng cả hiển thị lẫn đơn) hoặcsale.order.line._compute_price_unit. - Nhất quán hai chỗ: cách an toàn nhất cho giá thật là làm ở tầng pricelist, để hiển thị và đơn hàng cùng lấy một nguồn.
Bài này cố ý chỉ đổi hiển thị để minh hoạ hook; production muốn giá thật giảm thì nên đặt luật ở pricelist.
Ba ý mang về
- Giá eCommerce đi qua nhiều tầng:
_get_combination_info(hiển thị trên trang) →pricelist._get_product_price/_compute_price_rule(bảng giá) →sale.order.line._compute_price_unit(dòng đơn). Móc đúng tầng theo mục tiêu. - Override
_get_combination_infođể đổi giá hiển thị: gọisuper(), sửainfo['price'], bậthas_discounted_price—list_pricegiữ nguyên nên Odoo tự gạch giá gốc; không sửa dữ liệu. - Pricelist trước, code sau: giảm theo nhóm/số lượng/thời gian dùng bảng giá (không code); chỉ override khi logic không diễn đạt được bằng pricelist, và nhớ giữ nhất quán giữa hiển thị và đơn hàng.
Xong mảng giá, phần sau quay lại tương tác với khách trên portal: Phần sau mổ xẻ chatter và gửi tin từ khách trên portal — cho khách trao đổi, để lại lời nhắn ngay trên trang tài khoản của họ, và cách những tin đó vào đúng luồng chatter của bản ghi.