Cùng một mẫu in, nhưng thẻ đang hiệu lực thì cần một dải xanh trấn an, thẻ hết hạn cần một dải cam nhắc gia hạn, thẻ bị khoá cần một dải đỏ cảnh báo. Bạn không làm ba mẫu in — bạn làm một mẫu biết tự đổi mặt theo dữ liệu. Công cụ là bộ ba t-if / t-elif / t-else của QWeb, cộng thêm một mẹo nhỏ nhưng quan trọng: dùng t-if để bỏ hẳn phần không có dữ liệu, thay vì in ra một khoảng trắng vô duyên.

Ba trạng thái, ba nhánh

Model thẻ có field trang_thai với các giá trị dang_dung, het_han, khoa, moi. Trong QWeb, mỗi giá trị dẫn tới một nhánh — và chỉ một nhánh khớp đầu tiên được in:

Ảnh chụp đoạn mã QWeb nền tối hai phần, phần trên là chuỗi t-if o trang_thai bằng dang_dung in div nền xanh chữ Thẻ đang hiệu lực, t-elif o trang_thai bằng het_han in div nền cam chữ Thẻ đã hết hạn, t-elif o trang_thai bằng khoa in div nền đỏ chữ Thẻ đã bị khoá, t-else in div chữ Thẻ mới với chú thích bắt mọi giá trị còn lại; phần dưới là t-if đơn o diem bằng 0 in dòng chữ đỏ nghiêng Thẻ chưa tích điểm nào với chú thích 0 điểm thì cả đoạn biến mất không để lại dòng trống, và một dòng div groups base group_system chú thích ẩn theo quyền chỉ admin thấy

Hình 1: t-if mở chuỗi, t-elif nối các nhánh tiếp theo, t-else bắt phần còn lại. QWeb xét lần lượt và dừng ở nhánh đúng đầu tiên — giống if/elif/else của Python. t-else="" không cần điều kiện; nó là lưới hứng cho mọi giá trị chưa khớp (ở đây là moi). Lưu ý cú pháp: t-elif/t-else phải là thẻ liền kề ngay sau t-if, chen nội dung khác vào giữa là đứt chuỗi.

Dữ liệu thật của bốn thẻ quyết định banner nào hiện:

Ảnh chụp phiên odoo shell nền tối, lặp qua các thẻ in tên trạng thái và điểm, VIP-0001 dang_dung 150 chú thích banner xanh t-if, VIP-0003 dang_dung 150 banner xanh, VIP-0002 het_han 85 chú thích banner cam t-elif, VIP-0004 khoa 0 chú thích banner đỏ t-elif kèm dòng Thẻ chưa tích điểm, dưới cùng chú thích t-if t-elif t-else chỉ chạy một nhánh khớp đầu tiên và t-if o diem bằng 0 ẩn hẳn khi có điểm

Hình 2: Bốn thẻ với ba trạng thái khác nhau (đọc thẳng từ hệ thống). VIP-0001/0003 đang dùng, VIP-0002 hết hạn, VIP-0004 bị khoá và 0 điểm. Mỗi thẻ sẽ rơi vào đúng một nhánh khi in.

In ra: ba thẻ, ba khuôn mặt

Cùng report_the_document, in ba thẻ ở ba trạng thái, cắt lấy phần đầu để so sánh:

Ảnh ba trang PDF thẻ thành viên đặt cạnh nhau trên nền kem, cột trái nhãn trang_thai bằng dang_dung thẻ VIP-0001 có dải banner nền xanh chữ xanh Thẻ đang hiệu lực phía trên bảng thông tin; cột giữa nhãn trang_thai bằng het_han thẻ VIP-0002 có dải banner nền cam chữ nâu Thẻ đã hết hạn vui lòng gia hạn tại quầy; cột phải nhãn trang_thai bằng khoa thẻ VIP-0004 có dải banner nền đỏ chữ đỏ Thẻ đã bị khoá và ngay dưới là dòng chữ đỏ nghiêng Thẻ chưa tích điểm nào; cả ba đều có mã QR và mã vạch

Hình 3: Cùng một template, ba kết quả. Banner xanh/cam/đỏ do t-if/t-elif/t-elif chọn theo trang_thai. Riêng thẻ VIP-0004 còn có thêm dòng đỏ "Thẻ chưa tích điểm nào." vì nó 0 điểm — trong khi hai thẻ kia (150 và 85 điểm) không có dòng đó, và cũng không để lại khoảng trống nào ở vị trí đó.

t-if để bỏ hẳn, không phải để ẩn

Đây là điểm khác biệt với ẩn/hiện trên giao diện web (nơi phần tử vẫn tồn tại, chỉ display:none). Trong report PDF, t-if sai nghĩa là node đó không được sinh ra — không có thẻ, không có khoảng trắng, không có dòng rỗng. Nhờ vậy:

<!-- 0 điểm: đoạn này biến mất hoàn toàn, không đẩy layout xuống -->
<t t-if="o.diem == 0">
    <p style="color:#c0392b; font-style:italic;">Thẻ chưa tích điểm nào.</p>
</t>

Nếu bạn dùng CSS display:none kiểu web, wkhtmltopdf vẫn có thể để lại chỗ trống hoặc tính sai chiều cao. Với report, luôn dùng t-if để không sinh phần thừa — sạch và chắc.

Ẩn theo quyền: thuộc tính groups

Ngoài điều kiện theo dữ liệu, report còn ẩn/hiện theo người xem. Thuộc tính groups trên bất kỳ node QWeb nào chỉ cho nhóm quyền tương ứng nhìn thấy:

<div groups="base.group_system">
    Giá vốn nội bộ: <span t-field="o.gia_von"/>   <!-- chỉ admin thấy -->
</div>

Nhân viên thường in ra sẽ không có đoạn này; admin in ra mới thấy. Rất tiện cho các con số nhạy cảm (giá vốn, chiết khấu) mà bạn muốn đưa vào bản in nội bộ nhưng giấu khỏi bản đưa khách.

Vài lưu ý

  • t-elif/t-else phải liền kề t-if. Chèn một node khác vào giữa là chuỗi đứt, t-elif thành lỗi "unexpected". Nếu cần, gom cả cụm vào một thẻ bao.
  • So sánh đúng giá trị kỹ thuật, không phải nhãn: o.trang_thai == 'khoa', không phải == 'Đã khoá'. Nhãn tiếng Việt là để hiển thị; điều kiện chạy trên giá trị lưu.
  • t-if nhận biểu thức Python bất kỳ: o.diem > 100, o.lich_su_ids, o.partner_id and o.partner_id.vip. Recordset rỗng là falsy nên t-if="o.lich_su_ids" = "có ít nhất một dòng".

Ba ý mang về

  1. t-if / t-elif / t-else chọn đúng một nhánh theo dữ liệu bản ghi — một mẫu in phục vụ mọi trạng thái. t-elif/t-else phải là thẻ liền kề t-if.
  2. t-if bỏ hẳn node khi điều kiện sai (không phải display:none) — đây là cách sạch để không in ra khoảng trắng cho phần thiếu dữ liệu.
  3. groups="..." ẩn/hiện theo quyền người xem — đưa số nhạy cảm vào bản in nội bộ mà vẫn giấu khỏi bản cho khách.

Điều kiện đã điều khiển cái gì hiện ra. Phần sau lo trông thế nào: Phần sau đi vào style report bằng CSS — nhúng SCSS/CSS vào bản in, dùng class thay vì style nội tuyến, và những gì wkhtmltopdf hiểu (và không hiểu).