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ủa website_sale trả 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.

Ảnh chụp mã Python nền tối. Phần trên override _get_combination_info đổi giá hiển thị: class ProductTemplate inherit product template, hàm _get_combination_info nhận args kwargs, bước 1 lấy info bằng super _get_combination_info giá gốc của Odoo, bước 2 áp logic riêng giá thành viên giảm 20 phần trăm nếu info get price thì info price bằng round info price nhân 0.8, has_discounted_price True, return info. Chú thích info là dict Odoo trả cho trang sản phẩm gồm price giá bán list_price giá gốc gạch ngang has_discounted_price, đổi price ở đây là đổi giá hiển thị không đụng list_price trong CSDL. Phần dưới chuỗi hook tính giá từ ngoài vào trong: product template _get_combination_info giá hiển thị trên shop, gọi pricelist _get_product_price giá theo bảng giá, gọi _compute_price_rule từng luật bảng giá; lúc đặt hàng sale order line _compute_price_unit giá trên dòng đơn. Ghi chú muốn giá theo nhóm số lượng dùng pricelist chuẩn không cần code, muốn logic đặc thù override đúng tầng hiển thị vs 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:

Ảnh chụp trang sản phẩm thật trên shop Odoo 19. Đường dẫn All products Cà phê đóng gói Cà phê rang xay Arabica. Tên sản phẩm Cà phê rang xay Arabica, giá 185.000, các lựa chọn Khối lượng 250g 500g 1kg, Kiểu rang Rang vừa Rang đậm, ô số lượng và nút Add to cart

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:

Ảnh chụp cùng trang sản phẩm Cà phê rang xay Arabica trên shop Odoo 19 sau khi override giá. Giá hiện là 148.000 với giá cũ 185.000 bị gạch ngang bên cạnh. Vẫn có lựa chọn Khối lượng 250g 500g 1kg và Kiểu rang, nút Add to cart

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ặc sale.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ề

  1. 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.
  2. Override _get_combination_info để đổi giá hiển thị: gọi super(), sửa info['price'], bật has_discounted_price — list_price giữ nguyên nên Odoo tự gạch giá gốc; không sửa dữ liệu.
  3. 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.