Cái ô tìm kiếm trên mỗi màn hình Odoo, cùng với các nút "Bộ lọc" và "Nhóm theo" bên cạnh, không phải mặc định có sẵn — chúng do một loại view riêng định nghĩa: search view. Nắm search view, bạn quyết định được người dùng tìm được theo field nào, lọc nhanh bằng nút gì, và nhóm dữ liệu ra sao. Và có một mẹo hay bị bỏ qua: search_default_ — thứ bật sẵn một bộ lọc hoặc cách nhóm ngay khi màn hình vừa mở. Bài này dựng search view thật cho thẻ thành viên và xem tất cả chạy.

Ba phần của một search view

<search string="Tìm thẻ thành viên">
  <!-- 1) FIELD: gõ vào ô tìm kiếm sẽ khớp các field này -->
  <field name="name" string="Mã thẻ / Khách"
         filter_domain="['|', ('name','ilike',self), ('partner_id.name','ilike',self)]"/>
  <field name="partner_id"/>
  <separator/>
  <!-- 2) FILTER: nút lọc sẵn, mỗi cái một domain -->
  <filter name="con_diem" string="Còn điểm" domain="[('diem','>',0)]"/>
  <filter name="vip" string="VIP (từ 100 điểm)" domain="[('diem','>=',100)]"/>
  <separator/>
  <filter name="hang_vang" string="Hạng Vàng" domain="[('hang_the','=','vang')]"/>
  <!-- 3) GROUP BY: nhóm bằng context group_by -->
  <group name="group_by">
    <filter name="gb_hang" string="Hạng thẻ" context="{'group_by': 'hang_the'}"/>
    <filter name="gb_trang_thai" string="Trạng thái" context="{'group_by': 'trang_thai'}"/>
  </group>
</search>

Ảnh chụp mã XML nền tối định nghĩa search view Odoo 19, thẻ search có ba phần, phần một các field name với filter_domain khớp cả mã thẻ lẫn tên khách và field partner_id, phần hai các filter con_diem domain diem lớn hơn 0 và vip domain diem từ 100 trở lên và hang_vang domain hang_the bằng vang mỗi filter là một nút lọc sẵn, phần ba một group name group_by chứa hai filter dùng context group_by hang_the và group_by trang_thai để nhóm dữ liệu, cuối cùng một dòng field context trong action đặt search_default_gb_hang bằng 1 để bật sẵn nhóm theo hạng thẻ khi mở màn hình

Hình 1: Ba phần của search view. <field>: field gõ tìm được (dùng filter_domain để khớp linh hoạt — ở đây gõ một chuỗi khớp cả mã thẻ lẫn tên khách). <filter domain=...>: nút lọc bấm sẵn. <group name="group_by"> với <filter context="{'group_by': ...}">: cách nhóm. Dòng cuối là mẹo search_default_gb_hang trong context của action — bật sẵn nhóm khi mở.

Xem nó chạy: mở là đã nhóm sẵn

Đặt context="{'search_default_gb_hang': 1}" trong action, và đây là màn hình thật khi vừa mở (mình bấm mở luôn panel bộ lọc để thấy đủ):

Ảnh chụp giao diện Odoo 19 màn hình Thẻ thành viên ở chế độ list đã tự nhóm theo hạng thẻ, ô tìm kiếm có sẵn thẻ Hạng thẻ được áp dụng, danh sách gom thành hai nhóm Bạc có 3 thẻ tổng điểm 205 và Vàng có 1 thẻ tổng điểm 150, tổng cộng chân bảng 355, panel thả xuống đang mở hiện ba cột Bộ lọc gồm Còn điểm VIP từ 100 điểm Hạng Vàng và Bộ lọc tuỳ chỉnh, cột Nhóm theo gồm Hạng thẻ đang được tick chọn Trạng thái và Nhóm tuỳ chỉnh, cột Danh sách yêu thích gồm Lưu bộ lọc hiện tại

Hình 2: Search view chạy thật. Nhờ search_default_gb_hang, màn hình mở ra đã nhóm sẵn theo Hạng thẻ: nhóm "Bạc (3)" (tổng điểm 205 = 120+85+0) và "Vàng (1)" (150), và một chip "Hạng thẻ" nằm sẵn ở ô tìm kiếm. Panel thả xuống hiện đủ ba cột: Bộ lọc (các filter), Nhóm theo (các filter group_by, "Hạng thẻ" đang tick), Danh sách yêu thích (lưu bộ lọc hiện tại).

Điều đáng chú ý: mình không viết code JavaScript nào để nhóm. Chỉ khai context="{'group_by': 'hang_the'}" trên một filter, và bật nó sẵn qua search_default_.

search_default_: bật sẵn filter hay group

Quy tắc rất gọn: trong context của action, thêm khoá search_default_<tên_filter> với giá trị 1, Odoo sẽ bật sẵn filter/group đó khi mở màn hình.

<field name="context">{'search_default_gb_hang': 1, 'search_default_con_diem': 1}</field>

Dòng trên vừa nhóm theo hạng, vừa lọc sẵn "Còn điểm". Rất hữu ích để mỗi menu mở ra đúng góc nhìn bạn muốn — ví dụ menu "Đơn của tôi" thực chất là cùng một model nhưng bật sẵn filter my_orders.

Các thuộc tính hay dùng

  • <field filter_domain="...">: tự định nghĩa cách khớp khi gõ (dùng self là chuỗi người dùng nhập). Không có filter_domain thì mặc định khớp ilike trên chính field.
  • <filter domain="[...]">: lọc theo domain. Nhiều filter trong cùng nhóm (giữa hai separator) kết hợp bằng HOẶC; khác nhóm kết hợp bằng VÀ.
  • <filter context="{'group_by': 'field'}">: nhóm theo field. Với field ngày, thêm :month/:week (ví dụ {'group_by': 'ngay_cap:month'}) để nhóm theo tháng.
  • <separator/>: chia nhóm filter, ảnh hưởng cách kết hợp OR/AND ở trên.
  • <group name="group_by">: gom các filter nhóm vào cột "Nhóm theo". Tên phải là group_by.

Vài lưu ý Odoo 19

  • <group> trong search KHÔNG nhận expand hay string — chỉ name="group_by". Đặt sai là view lỗi parse, module không nạp được. (Mình đã vấp đúng lỗi này khi viết bài.)
  • Field trong search phải nằm trước filter theo trật tự hợp lệ; xen kẽ lung tung gây lỗi "extra content".
  • search_default_ chỉ bật thứ có name — filter/group phải được đặt name thì mới tham chiếu được.

Ba ý mang về

  1. Search view có ba phần: <field> (gõ tìm được, tuỳ biến bằng filter_domain), <filter domain=...> (nút lọc sẵn), <group name="group_by"><filter context="{'group_by':...}"> (cách nhóm). Không cần JavaScript.
  2. search_default_<name>: 1 trong context của action bật sẵn một filter/group khi mở màn hình — đã chứng minh: mở ra tự nhóm theo hạng thẻ (Bạc 3 / Vàng 1) với chip sẵn trong ô tìm kiếm.
  3. Odoo 19: <group> search chỉ dùng name="group_by" (không expand/string); field đứng trước filter; nhiều filter cùng nhóm kết hợp OR, khác nhóm AND.

Search view khép lại nhóm bài về giao diện dữ liệu. Lần sau tới hai view chuyên cho báo cáo, biến dữ liệu thành bảng chéo và biểu đồ: Phần sau mổ xẻ pivot và graph view — cách khai chúng để người dùng tự xoay số liệu và vẽ biểu đồ mà không cần code.