关于 odoo-report
面向 Odoo 开发者的邮件模板与 QWeb 报告技能,覆盖 14–19 版本。支持创建、调试、验证、预览和按版本迁移,并提供渲染与 PDF 报告相关指导。
主要功能
- 01创建和分析邮件模板
- 02构建 QWeb PDF 报告
- 03调试渲染与模板错误
- 04验证并预览模板
- 05跨 Odoo 版本迁移模板
- 06提供按版本区分的渲染指导
适用场景
- 01创建报价单邮件模板
- 02构建采购订单 PDF 报告
- 03排查邮件被渲染为纯文本
- 04在不同 Odoo 版本间迁移模板
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ ahmed-lakosha/ odoo-plugins --skill odoo-report安装后在 Claude Code 中输入/odoo-report确认已加载。
详细介绍
Odoo 邮件模板与 QWeb 报表技能(v2.1)
用于跨 Odoo 14-19 版本创建、管理、调试和迁移 Odoo 邮件模板与 QWeb 报表的技能。
工作流
此技能可通过自然语言处理所有模板和报表工作流:
| 目标 | 可以这样说 |
|---|---|
| 创建邮件模板 | “为 sale.order 报价单创建邮件模板” |
| 创建 QWeb PDF 报表 | “为 purchase.order 创建 QWeb PDF 报表” |
| 迁移模板 | “将此模板从 Odoo 14 迁移到 17” |
| 分析模板 | “分析此模板是否存在问题” |
| 调试渲染 | “调试我的邮件为何显示为纯文本” |
| 修复模板 | “修复损坏的 QWeb 表达式” |
| 部署前验证 | “在部署前验证此模板” |
| 列出模板 | “列出 sale.order 的所有模板” |
| 预览输出 | “预览此邮件模板” |
配置
- 支持的版本:Odoo 14、15、16、17、18、19
- 核心模型:
mail.template - 渲染引擎:用于标头的 inline_template(类似 Jinja2),用于 body_html 的 QWeb
用户覆盖设置:检查项目 .claude/ 目录中是否存在 odoo-report.local.md。如果存在,则将其 YAML frontmatter 设置(default_version、author、default_layout)用作默认值。
模板架构
mail.template
├── Inherits: mail.render.mixin (rendering engine)
├── Inherits: template.reset.mixin (reset - Odoo 16+)
│
├── Header Fields (inline_template engine):
│ subject, email_from, email_to, email_cc, reply_to, partner_to
│
├── Content Fields (QWeb engine):
│ body_html
│
├── Attachment Fields:
│ attachment_ids (static)
│ report_template (Odoo 14-16) / report_template_ids (Odoo 17+)
│ report_name (dynamic filename)
│
└── Configuration:
email_layout_xmlid, auto_delete, mail_server_id,
use_default_to, scheduled_date, template_category (16+)
渲染流程
mail.template.send_mail_batch(res_ids)
├─► _generate_template(res_ids, render_fields)
│ ├─► _classify_per_lang()
│ ├─► _render_field() per field (inline_template or qweb)
│ ├─► _generate_template_recipients()
│ ├─► _generate_template_attachments() (static + PDF report)
│ └─► Return rendered values dict
├─► Create mail.mail records
├─► Apply email_layout_xmlid (if set)
└─► Send via mail_server_id or default
两种渲染引擎
1. Inline 模板(类似 Jinja2)
用于:subject、email_from、email_to、email_cc、reply_to、partner_to、lang、scheduled_date
{{ object.name }}
{{ object.partner_id.name }}
{{ (object.user_id.email_formatted or user.email_formatted) }}
{{ object.state == 'draft' and 'Quotation' or 'Order' }}
{{ ctx.get('proforma') and 'Proforma' or '' }}
2. QWeb 引擎
用于:body_html
<t t-out="object.name"/>
<t t-out="format_amount(object.amount_total, object.currency_id)"/>
<t t-if="object.state == 'draft'">Draft</t>
<t t-elif="object.state == 'sent'">Sent</t>
<t t-else="">Confirmed</t>
<t t-foreach="object.order_line" t-as="line">
<tr><td t-out="line.name"/></tr>
</t>
<a t-att-href="object.get_portal_url()">View</a>
版本决策矩阵
| 功能 | 14 | 15 | 16 | 17 | 18 | 19 |
|---|---|---|---|---|---|---|
| 输出标签 | t-esc | t-out | t-out | t-out | t-out | t-out |
report_template(M2O) | Y | Y | Y | - | - | - |
report_template_ids(M2M) | - | - | - | Y | Y | Y |
template_category | - | - | Y | Y | Y | Y |
| 公司品牌颜色 | - | - | - | - | - | Y |
format_datetime() | - | Y | Y | Y | Y | Y |
is_html_empty() | - | - | Y | Y | Y | Y |
版本检测优先级
- 用户指定的
--version标志 - 目录路径(
odoo14、odoo17等) __manifest__.py版本字符串(例如'17.0.1.0.0')- 如果不明确,则询问用户
邮件布局模板
| 布局 | 宽度 | 用途 |
|---|---|---|
mail.mail_notification_layout | 900px | 带标头和页脚的完整通知 |
mail.mail_notification_light | 590px | 简单通知 |
mail.mail_notification_layout_with_responsible_signature | 900px | 使用记录的 user_id 签名(16+) |
最佳实践
1. 始终使用回退值
<t t-out="object.partner_id.name or 'Valued Customer'"/>
2. 使用格式辅助函数
<t t-out="format_amount(object.amount_total, object.currency_id)"/>
<t t-out="format_date(object.date_order)"/>
3. 使用布局保持一致性
<field name="email_layout_xmlid">mail.mail_notification_layout</field>
4. 处理空 HTML(16+)
<t t-if="not is_html_empty(object.description)">
<div t-out="object.description"/>
</t>
5. 使用适用于对应版本的输出方式
<t t-out="value"/>
<t t-esc="value"/>
6. 按版本设置报表附件
<field name="report_template" ref="module.report_action"/>
<field name="report_template_ids" eval="[(4, ref('module.report_action'))]"/>
7. 使用邮件表格以确保兼容性
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr><td style="padding: 20px;">Content</td></tr>
</table>
8. 安全地生成动态文件名
<field name="report_name">{{ (object.name or 'Document').replace('/', '-') }}</field>
wkhtmltopdf(PDF 报表必需)
安装
# Windows
winget install wkhtmltopdf.wkhtmltox
# Ubuntu/Debian
sudo apt-get install wkhtmltopdf
# macOS
brew install wkhtmltopdf
Odoo 配置(必需)
[options]
bin_path = C:\Program Files\wkhtmltopdf\bin # Windows
# bin_path = /usr/local/bin # Linux
关键限制:离线模式
wkhtmltopdf 运行时无法访问网络:
- 无法加载 Google Fonts CDN
- 无法渲染外部图片
- 使用
web.external_layout加载字体 - 使用系统字体:
'DejaVu Sans', 'Arial', sans-serif
常见错误
| 错误 | 原因 | 解决方案 |
|---|---|---|
Unable to find Wkhtmltopdf | 未配置 bin_path | 在 odoo.conf 中添加 bin_path |
| PDF 生成超时 | 请求了外部资源 | 移除 CDN URL |
| 生成的 PDF 为空白 | CSS/SCSS 错误 | 验证 SCSS 语法 |
| 以代码 1 退出 | HTML 语法错误 | 验证 QWeb XML |
阿拉伯语/RTL 支持
非拉丁文本必须使用的模板包装器
<template id="report_document">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="o">
<t t-call="web.external_layout">
<div class="page">
</div>
</t>
</t>
</t>
</template>
如果不使用 web.html_container,阿拉伯语文本会显示为 ÙØ§ØªÙˆØ±Ø©(将 UTF-8 字节按 Latin-1 读取)。
双语标签
<th style="background: #1a5276; color: white; padding: 12px;">
Date | التاريخ
</th>
RTL 对齐
<div style="direction: rtl; text-align: right;">
يرجى إرسال حوالاتكم على الحساب المذكور أعلاه
</div>
报告 SCSS 样式
资源包
'assets': {
'web.report_assets_common': [
'module/static/src/scss/report_styles.scss',
],
},
Google Fonts 注意事项
// BROKEN - Semicolons in URL break SCSS parsing
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap');
// FIXED - Use weight range syntax
@import url('https://fonts.googleapis.com/css2?family=Poppins:[email protected]&display=swap');
// BEST - Don't use Google Fonts (wkhtmltopdf is offline!)
推荐字体栈
$report-font-family: 'DejaVu Sans', 'Arial', 'Helvetica', sans-serif;
纸张格式配置
<record id="paperformat_custom" model="report.paperformat">
<field name="name">Custom A4</field>
<field name="format">A4</field>
<field name="orientation">Portrait</field>
<field name="margin_top">20</field>
<field name="margin_bottom">20</field>
<field name="margin_left">15</field>
<field name="margin_right">15</field>
<field name="header_line" eval="False"/>
<field name="dpi">90</field>
</record>
报告链接:<field name="paperformat_id" ref="module.paperformat_custom"/>
| 格式 | 尺寸 | 用途 |
|---|---|---|
| A4 | 210 x 297 mm | 国际标准 |
| Letter | 216 x 279 mm | 美国标准 |
| Legal | 216 x 356 mm | 法律文档 |
| 横向 | 任意 + Landscape | 宽表格、图表 |
调试工作流
诊断报告或模板问题时,请按以下顺序操作:
- 基础设施:是否已安装 wkhtmltopdf?odoo.conf 中的 bin_path 是否正确?服务器是否已重启?
- 模板结构:web.html_container → web.external_layout → div.page?
- 报告操作:report_name 是否与模板 ID 匹配?binding_model_id 是否正确?
- Shell 测试:
env.ref('module.report_action')._render_qweb_pdf([record_id]) - 浏览器缓存:清除缓存(Ctrl+Shift+R)
参考文件
处理特定任务时,请从插件目录中阅读以下文件,以获取更深入的上下文:
创建模板
- 按用途/模型选择模式:
memories/template_patterns.md - 入门电子邮件 XML:
templates/email/basic_notification.xml、templates/email/document_email.xml - 入门报告 XML:
templates/qweb/basic_report.xml、templates/qweb/table_report.xml - 字段定义:
data/template_fields.json - 按版本划分的上下文变量:
data/context_variables.json - 布局选项:
data/layouts.json
版本决策
- 版本路由规则和迁移路径:
memories/version_routing.md - 版本检测逻辑和功能映射:
helpers/version_helper.md
验证
- 预检验证清单:
validators/template_validator.md
QWeb 质量
- 安全性、性能、电子邮件/报告指南:
memories/qweb_best_practices.md
模块专属模板
- 按模块整理的已知 Odoo 模板 ID:
data/module_templates.md
迁移参考
自动转换
| 从 → 到 | 转换 |
|---|---|
| 14 → 15+ | t-esc → t-out,t-raw → t-out |
| 14-16 → 17+ | report_template → report_template_ids,并添加 eval="[(4, ref(...))]" |
| Any → 16+ | 添加 template_category 字段 |
| Any → 19 | 添加公司品牌设置:company.email_secondary_color、company.email_primary_color |
迁移工作流
- 检测源版本(语法特征或目录名称)
- 创建备份:
{file}.bak - 按顺序应用转换
- 验证迁移后的 XML
- 显示差异摘要
完整迁移矩阵和规则请参阅 memories/version_routing.md。
常见错误
| 错误 | 原因 | 解决方案 |
|---|---|---|
AttributeError: 'NoneType' has no attribute 'name' | 访问空字段 | 使用 object.field.name or '' |
QWebException: t-esc is deprecated | 15+ 版本仍使用旧语法 | 将 t-esc 替换为 t-out |
KeyError: 'format_amount' | 缺少上下文辅助函数 | 确保已继承 mail.render.mixin |
ValidationError: Invalid XML | QWeb 格式错误 | 检查标签是否闭合 |
阿拉伯语文本显示为 ÙØ§ØªÙˆØ±Ø© | 缺少 web.html_container | 添加 UTF-8 包装器 |
| PDF 空白 | 找不到 wkhtmltopdf 或 CSS 错误 | 检查 bin_path,验证 SCSS |
评论
支持 Markdown