Style một report tưởng đơn giản: viết SCSS, khai vào bundle, xong. Odoo có hẳn một bundle riêng cho bản in — web.report_assets_common. Tôi làm đúng bài: tạo file SCSS, đăng ký, gắn class. Rồi in ra... y như cũ, chẳng có gì đổi. Không lỗi, không cảnh báo. Bài này đi từ cách chuẩn đến cái bẫy khiến nó lặng lẽ không ăn, và cách làm chắc chắn ăn ở mọi môi trường.

Cách chuẩn: SCSS vào bundle report

Report của Odoo dùng các bundle asset riêng, tách khỏi giao diện backend: web.report_assets_common (áp cho cả bản HTML lẫn PDF) và web.report_assets_pdf (chỉ PDF). Muốn thêm style cho report, bạn khai SCSS vào các bundle này trong __manifest__.py, rồi dùng class thay cho style="..." rải rác:

Ảnh chụp đoạn mã nền tối ba phần, phần trên là manifest khai assets với khoá web report_assets_common trỏ tới file report_the scss và file report_the scss định nghĩa class o_the_document với h2 gạch chân đôi màu nâu và table thead th nền kem chữ nâu; phần giữa cảnh báo bẫy dev workers bằng 0 bundle vào PDF qua thẻ link rel stylesheet href tới web assets report_assets_common min css và chú thích style không áp bản in ra như chưa style giống bẫy barcode; phần dưới cách chắc ăn mọi nơi là style nội tuyến trong template div class page o_the_document chứa thẻ style với các quy tắc rồi tới nội dung

Hình 1: Cách chuẩn (trên) — SCSS gom vào web.report_assets_common, dùng class .o_the_document để style tập trung thay vì nhét style vào từng thẻ. Đây là cách nên dùng khi nhiều report chia sẻ chung một bộ style.

Về mặt tổ chức code, cách này đẹp: style tách khỏi cấu trúc, tái dùng được, dễ bảo trì. Nhưng khi tôi nâng cấp module và in ra, bản PDF không đổi gì.

Đào vào HTML mà Odoo sinh ra để đưa cho wkhtmltopdf, lý do lộ ngay:

Ảnh chụp phiên odoo shell nền tối, render_qweb_html của report rồi lọc các dòng chứa report_assets in ra một thẻ link rel stylesheet href tới đường dẫn web assets web report_assets_common min css, kiểm tra chuỗi thẻ style có trong html trả về False nghĩa là bundle không được nội tuyến mà là link ngoài, kèm chú thích wkhtmltopdf phải gọi ngược HTTP để lấy file css đó, workers bằng 0 thì tiến trình đang bận dựng PDF nên fetch bị treo và CSS rỗng, giống hệt bẫy report barcode, cách chắc ăn là style nội tuyến trong template

Hình 2: Bundle report được nhúng vào PDF dưới dạng <link rel="stylesheet" href="/web/assets/.../web.report_assets_common.min.css"> — một file ngoài, không phải CSS nội tuyến ('<style' in html trả về False). Để áp được, wkhtmltopdf phải gọi ngược HTTP về máy chủ Odoo lấy file đó. Với workers = 0 (mặc định khi phát triển), tiến trình duy nhất đang bận dựng PDF nên không ai phục vụ request CSS → style rơi mất. Đúng cùng gốc rễ với bẫy nhúng barcode bằng URL.

Nói cách khác: trên production nhiều worker thì bundle chạy tốt, nhưng trong môi trường dev một tiến trình, style biến mất mà không báo gì. Rất dễ ngồi sửa SCSS hàng giờ mà không hiểu vì sao không ăn.

Cách chắc ăn mọi nơi: <style> nội tuyến

wkhtmltopdf đọc thẳng thẻ <style> nằm trong HTML — không cần fetch gì. Nên với style riêng của một report mà bạn muốn chắc chắn hiển thị ở mọi môi trường, đặt luôn một khối <style> trong template:

<div class="page o_the_document">
    <style>
        .o_the_document h2 { border-bottom: 3px double #6f4e37; padding-bottom: 4px; }
        .o_the_document table.table thead th {
            background-color: #f3e9df; color: #6f4e37;
        }
        .o_the_document table.table tbody tr:nth-child(even) td {
            background-color: #faf6f0;
        }
    </style>
    ... nội dung ...
</div>

Nâng cấp lại, in ra — lần này style ăn ngay:

Ảnh so sánh hai trang PDF thẻ VIP-0001 cạnh nhau, bên trái nhãn TRƯỚC không style riêng có tiêu đề không gạch chân và đầu bảng lịch sử không nền, bên phải nhãn SAU có CSS style nội tuyến ăn ngay tiêu đề Thẻ thành viên có gạch chân đôi màu nâu, đầu bảng lịch sử nền kem chữ nâu viền dưới đậm, và bảng thông tin có các hàng chẵn tô nền nhạt kiểu sọc zebra

Hình 3: Cùng thẻ, cùng dữ liệu. Bên trái chưa style; bên phải khối <style> nội tuyến đã ăn: tiêu đề gạch chân đôi, đầu bảng nền kem, và các hàng chẵn tô nền nhạt (sọc zebra bằng :nth-child(even)). Không cần fetch file ngoài nên render ở đâu cũng ra đúng.

Giới hạn của WebKit trong wkhtmltopdf

Một điều nữa phải nhớ: wkhtmltopdf chạy trên một bản WebKit rất cũ. Nên khi style report:

  • Flexbox và CSS Grid không đáng tin. Muốn chia cột, dùng <table> hoặc float/Bootstrap grid (row/col-*) — những thứ WebKit cũ hiểu chắc. Đừng dựng layout bản in bằng display:flex.
  • :nth-child, border, background, padding đều ổn — như sọc zebra ở trên chạy tốt.
  • Class Bootstrap có sẵn trong report (text-end, table, table-sm, mt-3...), tận dụng trước khi tự viết CSS.
  • Style nội tuyến style="..." luôn ăn vì nó nằm ngay trong thẻ, không phụ thuộc fetch. Dùng cho những chỉnh sửa nhỏ, lẻ.

Ba ý mang về

  1. Bundle web.report_assets_common là cách chuẩn để chia sẻ CSS/SCSS cho nhiều report — khai trong manifest assets, dùng class tập trung.
  2. Nhưng bundle vào PDF qua <link> ngoài mà wkhtmltopdf phải fetch; với workers=0 (dev) nó rơi mất lặng lẽ. Cách chắc ăn mọi nơi: đặt khối <style> nội tuyến trong template.
  3. wkhtmltopdf là WebKit cũ: tránh flexbox/grid, dùng table/float/Bootstrap grid; border/background/:nth-child/style nội tuyến đều chạy tốt.

Xong phần report, ta rời QWeb để quay lại tầng action — nơi quyết định người dùng thấy gì khi mở một menu. Phần sau mổ xẻ context và domain mặc định của action — cách một menu lọc sẵn dữ liệu và đặt sẵn giá trị cho bản ghi mới.