Bạn sửa số điện thoại công ty trong Cài đặt → Công ty, in lại một tờ báo giá, và... chân trang vẫn ghi số cũ. Không phải cache, không phải bug. Đó là vì dòng chân trang của report không đọc trường phone — nó đọc một trường khác. Bài này mổ xẻ header/footer của report Odoo: nó nằm ở đâu, đọc trường nào của res.company, và làm sao đổi cho đúng.
Một t-call, mọi report cùng khung
Điều đầu tiên cần nắm: bạn không tự vẽ logo và địa chỉ công ty trong từng mẫu in. Report của bạn chỉ cần bọc nội dung trong web.external_layout, phần khung (header + footer) do Odoo lo — và lo giống hệt nhau cho mọi report trong hệ thống:

Hình 1: Bọc nội dung trong web.external_layout là xong phần khung. Bên trong, external_layout đọc thẳng company.report_footer cho chân trang và company.company_details cho khối địa chỉ đầu trang. Muốn đổi kiểu khung thì đổi external_report_layout_id của công ty — chọn một trong bảy view external_layout_* có sẵn, không phải sửa template.
Nhờ thiết kế này, sửa thông tin công ty một lần thì tất cả report cùng cập nhật. Cái giá phải trả là bạn cần biết chính xác từng phần lấy từ trường nào — vì tên trường không phải lúc nào cũng như bạn đoán.
Nguồn thật: hỏi thẳng res.company
Đây là điểm hay gây nhầm. Mở odoo shell và soi từng trường trên công ty đang dùng:

Hình 2: Nguồn thật của header/footer. Để ý report_footer chính là cả dòng chân trang — số điện thoại, email, website, mã số thuế nằm gọn trong một trường HTML tự do, chứ không phải Odoo tự ghép từ phone + email + vat. company_details là khối địa chỉ đầu trang. report_header đang rỗng (False). Và có bảy view external_layout_* để chọn làm kiểu khung.
Đây là lời giải cho câu hỏi đầu bài: report_footer là một trường HTML riêng, được sinh sẵn khi tạo công ty nhưng sau đó sống độc lập với phone/email/website. Sửa số điện thoại ở trường phone không đụng gì tới chuỗi đã nằm trong report_footer. Muốn đổi chân trang, phải sửa đúng report_footer.
Phép thử: đổi đúng một trường, xem chân trang đổi
Không tin thì đo. Tôi giữ nguyên mọi thứ, chỉ ghi đè company.report_footer bằng một dòng khác rồi render lại cùng thẻ VIP-0001:
from markupsafe import Markup
c = env.company
old = c.report_footer
c.report_footer = Markup('<p>☕ Cà Phê Việt · Hotline 1900 6789 · '
'Giao hàng toàn quốc · Cảm ơn quý khách!</p>')
env.cr.commit()
# ... render PDF ...
c.report_footer = old # trả lại nguyên trạng
env.cr.commit()
Kết quả in ra, cắt lấy đúng vùng chân trang hai lần render:

Hình 3: Cùng một report, cùng nội dung thân bài ("Cảm ơn quý khách đã đồng hành...") và cùng "Page 1/1" — chỉ dòng chân trang đổi, vì tôi chỉ chạm vào report_footer. Dung lượng PDF cũng đổi (61.105 → 60.638 byte), đo bằng _render_qweb_pdf. Đây là bằng chứng chân trang bám vào đúng trường đó.
Đổi kiểu khung mà không viết template
Ngoài nội dung (các trường company), report còn có kiểu khung — viền, vị trí logo, màu tiêu đề. Odoo 19 kèm sẵn bảy kiểu, mỗi kiểu là một view và được ánh xạ qua bản ghi report.layout:
# đổi kiểu khung cho toàn công ty — không sửa report_the_document
env.company.external_report_layout_id = env.ref('web.external_layout_boxed').id
env.cr.commit()
Trên giao diện, đây chính là mục Cài đặt → chọn bố cục tài liệu với các lựa chọn Light / Boxed / Bold / Striped / Bubble / Wave / Folder. Đổi ở đây thì mọi report đổi khung theo, vì tất cả cùng t-call web.external_layout và layout đó rẽ nhánh theo external_report_layout_id.
Khi bảy kiểu có sẵn vẫn chưa đủ (cần thêm mã số thuế cố định, đổi cỡ logo, thêm dòng pháp lý), bạn kế thừa template layout thay vì chép — đúng kỹ thuật inherit_id + xpath ở phần trước:
<template id="them_mst_header" inherit_id="web.external_layout_standard">
<xpath expr="//div[hasclass('o_company_address')]" position="inside">
<span>MST: <t t-out="company.vat"/></span>
</xpath>
</template>
Ba ý mang về
- Header/footer dùng chung qua
web.external_layout— report của bạn chỉt-callmột lần, không tự vẽ logo/địa chỉ. Sửares.companymột chỗ, mọi report cập nhật. - Dòng chân trang =
company.report_footer(một trường HTML tự do), không phải Odoo ghép từphone/email/vat. Đây là lý do đổiphonemà chân trang đứng yên. Khối địa chỉ đầu trang =company.company_details. - Đổi kiểu khung qua
external_report_layout_id(bảy layout có sẵn); cần sâu hơn thì kế thừa viewexternal_layout_*bằngxpath, đừng chép template.
Header/footer đã chuẩn, phần sau ta thêm thứ mà khách quét được bằng điện thoại: Phần sau nói về barcode và QR trong report — Odoo sinh mã vạch, mã QR ngay trong QWeb thế nào.