Báo cáo treo, PDF mất định dạng, wkhtmltopdf crash trên tài liệu lớn. Một danh sách kiểm tra đi qua worker, report.url, font, phiên bản và giới hạn tài nguyên.

Người dùng nhấp “Print” và Odoo cứ đứng im. Hoặc file PDF trả về, nhưng trông kỳ lạ: mất màu, mất logo, chữ chạy tràn ra ngoài trang. Tệ nhất là: file hỏng.

Trình xem PDF hiển thị một hóa đơn Odoo vừa sinh ra kèm thông báo: The document contains no pages

Việc sinh báo cáo trong Odoo đụng tới nhiều bộ phận hơn phần lớn mọi người hình dung: worker, binary wkhtmltopdf, các thư viện python… Dưới đây là danh sách kiểm tra bạn có thể đi qua khi gặp sự cố với chúng.

Lời khuyên đầu tiên: hãy bắt đầu bằng việc xem báo cáo HTML trên trình duyệt

Mọi báo cáo đều có bản xem trước dạng HTML ở route /report/html/<report_name>/<record_ids>. Nếu phần định dạng đã hỏng ngay tại đó, vấn đề nằm ở template QWeb hoặc asset bundle của bạn, không phải ở wkhtmltopdf, và bạn nên thôi hẳn việc soi luồng tạo PDF.

Odoo treo hoặc hết thời gian chờ khi sinh báo cáo?

Nếu nhấp “Print” làm cả giao diện đứng hình, hoặc request mãi không quay về, hãy kiểm tra số lượng worker trong file cấu hình Odoo trước khi làm bất cứ điều gì khác.

[options]
workers = 4

workers phải được đặt từ 4 trở lên thì việc sinh báo cáo mới chạy ổn định. Odoo giao việc render PDF cho một tiến trình riêng, và khi có quá ít worker, tiến trình đó có thể bỏ đói vòng lặp request chính, hoặc nằm chờ một chỗ worker không bao giờ được giải phóng. Đây là một vấn đề đã biết và đã được ghi nhận: odoo/odoo#199880.

PDF render ra được nhưng mất định dạng?

Nội dung vẫn ở đó: đúng dữ liệu, đúng bố cục, nhưng không màu, không font riêng, không ảnh nền. Gần như luôn luôn điều này nghĩa là wkhtmltopdf đã render báo cáo mà không nạp được các asset CSS và hình ảnh nó cần.

Báo cáo render ra với phần CSS bị hỏng

Hãy kiểm tra tham số hệ thống report.url. Vào Settings → Technical → System Parameters và tìm report.url.

wkhtmltopdf không nhận một khối HTML khép kín — phần HTML Odoo đưa cho nó có các tham chiếu ngược về chính Odoo cho toàn bộ CSS và asset nó cần. Phần thân một báo cáo điển hình bắt đầu như sau:

<base href="http://instance-name:8069?debug=0"/>
<link type="text/css" rel="stylesheet" href="/web/assets/2194678-b048873/web.report_assets_pdf.min.css" data-asset-bundle="web.report_assets_pdf" data-asset-version="b048873"/>
<link type="text/css" rel="stylesheet" href="/web/assets/2196817-cf6127f/web.report_assets_common.min.css" data-asset-bundle="web.report_assets_common" data-asset-version="cf6127f"/>

<base href> được đặt bằng report.url. wkhtmltopdf phân giải mọi URL asset tương đối dựa trên nó, rồi tải các asset đó từ máy chủ Odoo qua HTTP. Nếu report.url trỏ tới một địa chỉ wkhtmltopdf không với tới được, nó sẽ render trang mà không nạp được stylesheet nào, và đó là lý do đầu ra trông trơ trụi.

Asset nạp quá muộn: chỉ những trang cuối mất định dạng

Trên các báo cáo nhiều tài liệu — chẳng hạn in hơn 5 phiếu giao hàng cùng lúc — bạn có thể thấy một hiện tượng: các trang đầu render đúng nhưng các trang cuối mất logo hoặc mất nền. Nguyên nhân là thời điểm: wkhtmltopdf dùng một engine WebKit chạy JavaScript để nạp asset, và nó có một khoảng chờ sẵn có trước khi coi trang là “sẵn sàng” để in. Mặc định (200 ms) không đủ khi tài liệu lớn và việc tải asset chậm.

Từ Odoo 14.0 (odoo/odoo#114503), Odoo truyền --javascript-delay cho wkhtmltopdf và lấy giá trị từ tham số hệ thống report.print_delay, mặc định 1000 ms. Nếu gặp hiện tượng này, hãy vào Settings → Technical → System Parameters và tăng report.print_delay (giá trị tính bằng mili giây). Bắt đầu với 2000 hoặc 3000 rồi kiểm tra xem các trang cuối đã có lại định dạng chưa.

Báo cáo lớn bị crash?

Lỗi báo cáo của wkhtmltopdf

Nếu một báo cáo cụ thể làm wkhtmltopdf crash, nhất là báo cáo dài nhiều trang hoặc có bảng nặng, hãy nhìn vào phiên bản wkhtmltopdf trước khi đụng tới bất cứ thứ gì khác.

wkhtmltopdf --version

Đối chiếu nó với khuyến nghị chính thức của Odoo về wkhtmltopdf, nơi ghi bản build nào đã được kiểm chứng với phiên bản Odoo nào. wkhtmltopdf có một lịch sử dài các lỗi hồi quy theo từng phiên bản trên tài liệu lớn, và cách sửa thường chỉ là chọn một bản vá khác, không phải đổi gì trong code của bạn.

Chúng tôi đã gặp chuyện này không chỉ một lần:

  • wkhtmltopdf 0.12.2.1 trên Odoo 8 được biết là làm font phóng to và chồng lên nhau, sửa bằng cách hạ xuống 0.12.1.
  • trên một hệ thống Odoo 9.0 cũ, một báo cáo lớn gây Segmentation fault với wkhtmltopdf 0.12.4. Hạ xuống 0.12.3 là hết, không đổi gì khác.
  • 0.12.4 trên Odoo 13 làm mất header và footer với một số người dùng, sửa bằng cách nâng lên 0.12.5.
  • Gần đây hơn, trên một hệ thống Odoo 18.0, một báo cáo lớn crash trên 0.12.6.1-3 nhưng chạy tốt trên 0.12.5-2.

Không có một phiên bản “đúng” duy nhất chạy được cho mọi báo cáo trên mọi hệ điều hành. Nếu gặp crash trên một báo cáo lớn, hãy xem phiên bản wkhtmltopdf như một biến cần thử, không phải một hằng số, và khi tìm được bản chạy tốt thì ghim nó lại trong quy trình triển khai của bạn.

Crash với mã lỗi -11 hoặc -9? Hãy kiểm tra giới hạn tài nguyên trước khi nghi ngờ chính cái binary

Không phải crash nào trên báo cáo lớn cũng là lỗi phiên bản. Hai mã lỗi chỉ về một hướng hoàn toàn khác:

  • Mã lỗi -11 (segmentation fault) kèm thông báo kiểu “Memory limit too low or maximum file number of subprocess reached” nghĩa là bản thân wkhtmltopdf đã cạn RAM, hoặc chạm trần số file mở được, trong lúc render. Hãy kiểm tra ulimit -n của user chạy Odoo, và kiểm tra bộ nhớ còn trống trên máy chủ trong lúc sinh báo cáo, chứ không phải lúc máy rảnh.
  • Mã lỗi -9 nghĩa là tiến trình bị giết, thường bởi chính các giới hạn worker của Odoo: limit_time_real hoặc các giới hạn bộ nhớ mềm/cứng trong file cấu hình. Một báo cáo trước đây chạy được, nay bị giết sau khi lượng dữ liệu tăng lên, là vấn đề giới hạn chứ không phải lỗi hồi quy của wkhtmltopdf: hãy nâng giới hạn tương ứng thay vì đi săn phiên bản.

Font render sai, hoặc không hiện ra

Nếu PDF hiện font dự phòng, chữ phóng to hoặc chồng lên nhau, hoặc một font riêng trông vẫn ổn trong /report/html nhưng hỏng trong PDF, nguyên nhân gần như luôn là wkhtmltopdf không thấy được font đó, chứ không phải font bị hỏng:

  • wkhtmltopdf render bằng các font cài trên máy chủ, không phải font trong trình duyệt đang gửi request. Một font riêng cần được cài gói của nó trên đúng máy chạy wkhtmltopdf, điều rất dễ quên khi máy đó là một worker báo cáo hoặc một container tách biệt với máy bạn dùng để kiểm tra trên trình duyệt.
  • Font cũng cần được kéo vào qua một asset bundle như web.report_assets_common. Một font chỉ được tham chiếu từ stylesheet nằm ngoài bundle đó là vô hình với wkhtmltopdf, ngay cả khi nó đã cài trên máy chủ và hiển thị tốt trên trình duyệt.
  • Các ngôn ngữ không dùng chữ Latin đôi khi cần gói font riêng cho ngôn ngữ đó cài ở phía máy chủ; hiện tượng lộ ra là ô vuông hoặc ký tự bị thiếu chỉ với một số ngôn ngữ nhất định.

Ảnh hưởng của module bên thứ ba

Hãy kiểm tra xem module OCA report_wkhtmltopdf_param có được cài không và nó có mang giá trị tùy chỉnh nào không. Module này cho phép truyền thêm cờ dòng lệnh cho wkhtmltopdf, và có một cờ đặc biệt hay xuất hiện khi xử lý sự cố: --disable-smart-shrinking. Nó đổi cách wkhtmltopdf co nội dung cho vừa trang, và một giá trị đặt cho một báo cáo có thể gây tác dụng phụ lên các báo cáo khác nếu nó được áp cho toàn hệ thống.

Cũng hãy kiểm tra những module khác có tùy biến các đường đi sinh báo cáo gốc của Odoo: _render_qweb_pdf, _run_wkhtmltopdf, _render_qweb_pdf_prepare_streams. Có thể bạn sẽ gặp vài bất ngờ: chúng tôi từng gặp một trường hợp sinh ra PDF hỏng với Python <=3.10 (account_invoice_en16931).

Kiểm tra các thư viện python liên quan tới PDF

Odoo dựa vào PyPDF2 (hoặc gần đây hơn là PyPDF trên Python ≥ 3.13) để thao tác PDF, gồm cả việc gộp nhiều báo cáo thành một file và xử lý các PDF đính kèm sẵn có. Nếu những báo cáo gộp nhiều tài liệu thì lỗi trong khi báo cáo một tài liệu vẫn chạy tốt, hãy kiểm tra thư viện đó:

pip freeze | grep -i pdf

Dùng widget barcode, đừng dùng route barcode

Nếu báo cáo của bạn có mã vạch, hãy tránh sinh chúng qua route /report/barcode/... bằng thẻ <img> thuần:

<!-- tránh cách này -->
<img t-att-src="'/report/barcode/Code128/%s' % o.code"/>

Khi wkhtmltopdf gặp các thẻ <img> này, nó gửi một request HTTP ngược về máy chủ Odoo cho mỗi mã vạch để lấy ảnh. Trên một báo cáo có nhiều mã vạch, điều này có thể làm máy chủ ngập trong các request đồng thời và khiến wkhtmltopdf crash hoặc hết thời gian chờ.

Hãy dùng widget barcode của QWeb, nó nhúng mã vạch dưới dạng dữ liệu base64 nội tuyến — không phát sinh request HTTP nào:

<div t-field="o.code" t-options="{'widget': 'barcode', 'width': 600, 'height': 100}"/>

Các module của chính Odoo đã được cập nhật theo hướng này ở odoo/odoo#64211. Nếu bạn có báo cáo tùy chỉnh vẫn dùng route, chuyển chúng sang widget thường là đủ để dứt điểm những lần crash không giải thích được trên các tài liệu nhiều mã vạch.