Khách đưa thẻ thành viên, nhân viên quét một cái là ra hồ sơ — cái mã vuông đó (QR) và cái mã sọc dưới nó (Code128) đều do Odoo tự sinh, không cần thư viện ngoài. Nhưng nếu bạn làm y hệt tài liệu hướng dẫn, rất có thể tờ PDF in ra chỉ có... hai ô trống. Bài này chỉ cách sinh mã cho report, và quan trọng hơn: vì sao cách phổ biến hay hỏng và cách nào chắc ăn.
Odoo sinh mã bằng gì
Không cần cài qrcode hay python-barcode. Model ir.actions.report có sẵn method barcode(barcode_type, value, **kwargs) trả về bytes ảnh PNG. Gõ thử trong odoo shell:

Hình 1: ir.actions.report.barcode() trả về ảnh PNG thật — 8 byte đầu đúng chữ ký \x89PNG. QR ~3 KB, Code128 ~0.5 KB. Method này bọc thư viện reportlab nên hỗ trợ nhiều kiểu: QR, Code128, EAN13, EAN8, Code39, UPCA, I2of5, Codabar, POSTNET... humanreadable=1 in luôn chuỗi gốc dưới mã vạch.
Tài liệu Odoo (ngay trong docstring của controller /report/barcode) gợi ý dùng trong QWeb thế này:
<img t-att-src="'/report/barcode/QR/%s' % o.name"/>
Trông gọn. Và nó hỏng trong nhiều môi trường.
Cái bẫy: ô trống thay vì mã QR
Tôi làm đúng theo mẫu đó, thêm vào report, in ra — và nhận về hai khung viền rỗng. Không lỗi, không cảnh báo, chỉ là ảnh không tải được.
Lý do nằm ở cách Odoo dựng PDF. Bộ render (wkhtmltopdf) nhận HTML rồi tự gọi ngược HTTP về chính máy chủ Odoo để lấy từng ảnh <img src="/report/barcode/...">. Nếu Odoo đang chạy workers = 0 (một tiến trình — mặc định khi phát triển), thì tiến trình duy nhất ấy đang bận dựng PDF, không còn ai phục vụ cái request ảnh kia. Ảnh timeout → khung rỗng. Trên server nhiều worker thì thường ổn, nhưng bạn không muốn report của mình phụ thuộc vào việc đúng lúc đó có worker rảnh hay không.
Cách chắc ăn: sinh ảnh sẵn rồi nhúng thẳng
Bỏ vòng HTTP đi. Sinh mã trong Python thành một trường Binary của model, rồi để QWeb nhúng bằng image_data_uri — ảnh đã nằm sẵn trong HTML dưới dạng data-URI, không cần gọi ngược gì cả:

Hình 2: Sinh mã một lần trong _compute_ma_vach — Report.barcode(...) cho ra bytes PNG, base64.b64encode để nhét vào trường Binary. Trong report, image_data_uri(o.qr_ma) biến trường Binary đó thành data:image/png;base64,... ngay tại chỗ. Không có request HTTP nào phát sinh khi dựng PDF, nên không có gì để nghẽn.
image_data_uri là hàm dựng sẵn của QWeb, chuyên nhận một chuỗi base64 (giá trị của field Binary) và trả về data-URI hoàn chỉnh cho thuộc tính src. Đây cũng chính là cách Odoo nhúng logo công ty vào header — logo cũng là field Binary.
Nâng cấp module, in lại cùng thẻ VIP-0001:

Hình 3: PDF in ra với mã QR (mã hoá https://caphe-viet.vn/the/VIP-0001 — quét bằng điện thoại ra đúng đường dẫn) và mã vạch Code128 kèm chuỗi đọc được "VIP-0001". Cả hai đặc nét, không còn ô rỗng. Dung lượng PDF tăng từ 62.881 lên 72.029 byte vì ảnh giờ nằm hẳn trong tài liệu — đo bằng _render_qweb_pdf.
Vài lưu ý khi dùng thật
- QR nên mã hoá URL, không mã hoá mỗi cái mã thẻ. Quét "VIP-0001" thì điện thoại chẳng biết làm gì; quét
https://caphe-viet.vn/the/VIP-0001thì mở thẳng trang tra cứu. Giá trị QR là dữ liệu bạn muốn máy quét đọc được. - Code128 nhận gần như mọi ký tự ASCII; còn EAN13 đòi đúng 13 chữ số (12 số + 1 số kiểm) — truyền sai định dạng,
barcode()némValueError, nên value phải sạch trước khi đưa vào. - Field
Binarycompute mặc định không lưu (store=False) — mã sinh lại mỗi lần đọc. Với thẻ in thỉnh thoảng thì ổn; nếu in hàng loạt và cần nhanh, thêmstore=Trueđể cache lại, đổinamethì@api.depends('name')tự tính lại. humanreadable=1rất đáng bật cho mã vạch tuyến tính: khi máy quét chập chờn, nhân viên còn đọc tay được con số.
Ba ý mang về
- Odoo sinh QR/Code128/EAN13... sẵn qua
ir.actions.report.barcode()(bọcreportlab), trả về bytes PNG — không cần thư viện ngoài. - Nhúng bằng URL
/report/barcode/có thể ra ô rỗng khiworkers=0, vìwkhtmltopdfgọi ngược HTTP mà không ai phục vụ. Cách chắc ăn: sinh ảnh thành fieldBinaryrồiimage_data_uri(o.field)— không vòng HTTP, render offline được. - QR mã hoá URL để quét ra hành động thật; Code128 bật
humanreadable=1; giá trị phải đúng định dạng của từng loại mã kẻobarcode()ném lỗi.
Report đã có đủ khung, mã vạch, giờ tới lúc cho người dùng chọn trước khi in: Phần sau dựng report in ra từ một wizard — mở hộp thoại hỏi tham số rồi mới xuất PDF theo lựa chọn đó.