Zhiz Print

> 出品维护 广德智兆科技有限公司 | 开发者 智兆.黄 +8613913019935 | 官方仓库 > > 适用版本 v15+ > > 基于 Frappe / ERPNext 的可视化打印模板设计器 — 表格设计 · 数据绑定 · 表达式运算 · 多引擎 PDF · 批量打印 · 持久化日志。

Install Zhiz Print

bench get-app https://github.com/yowlion/zhiz_print

Add the Frappe Gems badge to your README

Maintain Zhiz Print? Paste this into your README:

[![Listed on Frappe Gems](https://frappegems.com/api/method/frappe_gems.seo.badge?app=yowlion%2Fzhiz_print)](https://frappegems.com/gems/apps/yowlion/zhiz_print)

About Zhiz Print

zhiz_print · 高级打印设计器 使用说明书

> 出品维护 广德智兆科技有限公司开发者 智兆.黄 +8613913019935 | 官方仓库 > > 适用版本 v15+ > > 基于 Frappe / ERPNext 的可视化打印模板设计器 — 表格设计 · 数据绑定 · 表达式运算 · 多引擎 PDF · 批量打印 · 持久化日志。


关于 Zhiz Print

Zhiz Print广德智兆科技有限公司(Guangde Zhizhao Technology Co., Ltd.)出品并长期维护,开发人员 智兆.黄(Gitee @gdzhiz)全力迭代更新。

  • 定位:适用于 ERPNext / Frappe 的复杂格式打印设计专用 App —— 表格网格所见即所得设计,专攻多页版面、单元格合并、表达式运算、条码/二维码、多引擎 PDF 导出等复杂打印格式
  • 官方仓库:(唯一官方源,其余均为社区 fork)
  • GitHub 镜像:(自动同步自 Gitee 官方源)
  • 发布讨论
  • 版权:Copyright © 2026 广德智兆科技有限公司(Guangde Zhizhao Technology Co., Ltd.),保留所有权利,见 LICENSE

目录

  1. 概述与架构
  2. 安装与启用
  3. 设计器入门
  4. 数据绑定(占位符)
  5. 单元格值的四种语义
  6. 行控制与排序
  7. 单元格类型
  8. 分页与多页设计
  9. 页眉/页脚/左眉/右脚
  10. 启用条件
  11. 草稿拦截
  12. 输出(预览/PDF/Excel/批量)
  13. 打印日志
  14. 全局设置
  15. 打印模板平台
  16. 附录·速查表与常见问题

一、概述与架构

zhiz_print 用表格式网格设计打印版面,支持单元格合并、四种取值语义(静态 / 逻辑 / 表达式 / 合计)、按列排序、自动分页、二维码条码图片,以及多引擎 PDF / Excel 导出。所有打印操作记录为持久化日志(渲染快照,不随后续设计改动而变化)。

v15.22 起支持报表打印:设计目标可选「报表」(Report),ERPNext 报表(Query Report / Script Report,如库存余额 / 总账)同样可以用本设计器设计打印模板 —— 报表行绑定 rep 占位符、筛选值进页眉、合计用 =rowsum() 自行设计,与单据打印共用同一套渲染 / 分页 / 输出管线。

数据流

阶段 说明 关键代码
1. 数据获取 单据模式:从目标单据(doc)读主表字段、子表(如 items)、自定义查询(design_queries)、用户参数(param);报表模式:以当前用户身份同步执行报表查询,报表行注入为 __report_main__ 伪查询 frappe.get_doc · _execute_query_with_doc_context · zhiz_print.api.report_print._run_report
2. 运算 四种单元格值语义:静态替换、logic 条件表达式、= 算术表达式、=rowsum() 合计 _eval_logic_code · _eval_expression_cell · _eval_rowsum
3. 渲染 展开数据驱动行 → 按行级 sorts 排序 → 列值计算 → 客户端实测分页 → 组装 HTML _build_expanded_rows_v2 · _build_row_html · build_measurement_html
4. 输出 打印预览 / PDF(wkhtmltopdf·WeasyPrint·Chromium) / Excel(openpyxl) / 批量 render_print_preview · generate_print_pdf · export_print_excel

DocType 清单

DocType 类型 作用
Super Print Design 主文档 打印模板设计(网格 + 单元格 + 样式)
Super Print Design Item 子表 单元格定义(row/col/span/类型/值/page_no)
Super Print Design Parameter 子表 打印参数定义(打印前弹窗填)
Super Print Design Query 子表 数据查询定义(SQL/函数)
Super Print Paper 主文档 纸张尺寸(mm)
Super Print Log 主文档 打印日志(含渲染快照)
Super Print Enabled Doctype 子表 启用超级打印的单据类型
Super Print Enabled Report 子表 启用超级打印的报表(报表打印指定启用)
Zprint Setting 单文档 全局设置(单据/报表打印启用、PDF 引擎等)

二、安装与启用

安装

cd /home/frappe/frappe-bench
git clone git@gitee.com:gdzhiz/zhiz_print.git apps/zhiz_print
bench get-app zhiz_print ./apps/zhiz_print
bench --site  install-app zhiz_print
bench build

启用流程

  1. Zprint Setting → 勾选「启用超级打印页面」
  2. Super Print Paper → 定义纸张尺寸(如 A4 = 210×297mm)
  3. Super Print Enabled Doctype → 把要用的单据类型(如 Sales Invoice)加入启用列表
  4. Super Print Design → 设计模板,填 target_doctypeprint_paper
  5. 打开任意目标单据,点打印 → 左侧显示自定义模板选择器

报表打印启用 (v15.22 新)

  1. Zprint Setting →「报表打印」段 → 勾选「启用报表打印」
  2. 启用方式二选一:全部启用(所有报表) / 指定启用(在「启用的报表」子表加入目标报表,如 Stock Balance)
  3. 打开目标报表页(设好筛选、跑出数据)→ 菜单「打印」→ 直达高级打印预览页(原生 PDF / 导出菜单自动隐藏,导出功能收进预览页)
  4. 预览页「新建报表设计」→ 自动预填设计目标=报表 → 开始设计(见三、设计器入门·报表模式))

> 💡 报表打印与单据打印开关独立,互不影响;关闭开关即完整还原报表页原生菜单。


三、设计器入门

设计器是一个表格式网格编辑器:左侧行号、顶部列号,中间是单元格。上方工具栏控制行列增删、字体、行类型;选中行/列/单元格时右侧出现属性面板。

网格操作

操作 说明
选中整行 / 整列 点击左侧行号 / 顶部列号 → 右侧出现行/列属性面板
选中单元格 点击单元格 → 出现单元格属性面板(类型/值/样式)
插入行 / 列 选中行/列后点工具栏「插入行」「插入列」(在选中位置左侧/上方插入)
合并单元格 单元格属性面板设 rowspan / colspan(被合并的格子自动标记 `| MERGED::R#C# `)
调整列宽 (新) 鼠标移到顶部列号右边界 光标,按住左右拖动实时改列宽(列签与网格同步,拖动时鼠标旁实时显示列宽像素值),松开固化

演示预览 (新)

工具栏「演示预览」按钮一键把当前网格按 sample_doc 数据渲染成实际纸张效果(灰底白纸),无需进入打印页面;再点「回到设计」返回编辑。预览态禁用一切编辑操作。

页面设置入口

除「页面设置」按钮外,点击画布空白区域直接打开页面设置:点击纸张上半自动选中页眉页签、下半自动选中页脚页签(详见九、页眉/页脚)。

报表模式设计 (v15.22 新)

新建设计时「设计目标」可选 DocType(单据)Report(报表):

  • 选「报表」后填「目标报表」(如 Stock Balance),网格设计操作与单据模式完全一致
  • 单元格绑定「数据查询」→ 查询选「报表数据」,数据键下拉分三类:
    • 报表字段:name / report_name / ref_doctype 等(报表本身的属性)
    • 筛选字段:filters.company / filters.from_date 等(当前报表筛选)
    • 报表数据列:items.item_code / items.bal_qty 等(报表行数据)
  • 选择数据键后单元格自动写入对应占位符并即时显示,如选「物料」→ 单元格显示 {rep.items.item_code}
  • 数据行:把行类型设为「数据驱动行」(或单元格含 {rep.items.字段} 自动识别),打印时按报表行数自动展开
  • 报表模式下「模板演示单据」「草稿拦截」等单据专属功能自动隐藏

三个核心区域(表单 Tab)

  • Design View(设计视图) — 可视化网格编辑器
  • Settings(设置) — 启用条件 / 草稿拦截 / 优先级 / 纸张 等
  • Design Items — 单元格明细(只读,序列化的格子数据)

四、数据绑定(占位符)

在单元格的 cell_value 里用占位符绑定数据。占位符打印时被替换为实际值。

占位符

语法 数据来源 示例
{doc.字段名} 目标单据主表字段 {doc.name} · {doc.customer} · {doc.posting_date}
{doc.子表.字段} 子表行字段(自动触发数据驱动行) {doc.items.item_code} · {doc.items.qty}
{param.参数名} 用户打印前填入的参数 {param.remark}
{查询名.列名} 自定义查询结果(首行) {bom_list.qty}
{rep.字段} 报表本身字段(报表模式) {rep.name} · {rep.ref_doctype}
{rep.filters.字段} 当前报表筛选值(报表模式) {rep.filters.company} · {rep.filters.from_date}
{rep.items.字段} 报表行数据(自动触发数据驱动行,报表模式) {rep.items.item_code} · {rep.items.bal_qty}

rep 报表占位符 (v15.22 新)

报表模式下 repdoc / param 同级,三段语义:

写法 含义
{rep.name}(单级) 报表本身字段(Report 文档:name / reportname / refdoctype / module 等)
{rep.filters.字段} 当前报表筛选值 —— 任意单元格与页眉页脚均可用,常用于打印标题区(公司 / 日期区间)
{rep.items.字段} 报表当前行数据 —— 仅数据驱动行内生效;含此占位符的行自动按报表行展开

> ⚠️ rep 是保留前缀,数据查询名称不能命名为 rep;报表打印会剔除报表引擎自动附加的合计行,合计请用 =rowsum() 自行设计(见五、单元格值语义)。

> 💡 富文本字段(Text Editor 类型,如 terms)打印时自动取纯文本显示,不输出 HTML 标签。

子表 → 数据驱动行

只要某行有单元格含 {doc.子表.字段},该行自动变成数据驱动行,按子表条数自动复制;每行对应一条子表记录。无需手动设 row_type

{doc.items.idx}              ← 行号
{doc.items.item_code}        ← 物料
{doc.items.qty}              ← 数量
{doc.items.rate}             ← 单价
{doc.items.amount}           ← 金额

参数(打印前弹窗)

design_parameters 子表定义参数,打印前弹窗让用户填写,用 {param.参数名} 引用。

Parameter ID Display Label Type 示例用途
remark 备注 Data 打印时手输一句话
warehouse 仓库 Link → Warehouse 选择仓库
copies 份数 Int (reqd=1, default=1) 打印份数

数据查询 (design_queries)

当需要跨表取数、聚合统计、复杂 SQL{doc.字段} 和子表不够用时,在设计的 design_queries 子表定义查询,结果用 {query_name.column} 引用。

查询定义字段(Super Print Design Query)

字段 说明
Query Name (query_name) 查询名,唯一标识,用于 {query_name.column} 引用
Python Code (query_code) Python 代码或函数路径,结果必须赋给变量 result
Parameters (parameters) 参数映射,每行 key=value

query_code 两种形式

① frappe.qb 查询构建器(Python 代码) — 把结果赋给 result

代码在 safe_exec 沙箱里执行,暴露以下对象:qb · DocType · desc/asc · Sum/Count/Avg/Max/Min/Round/Concat/Coalesce/Abs/GROUP_CONCAT · filters(参数字典)。result 必须是 list(每行 dict) 或 dict。

# 示例:按物料查库存汇总(filters.item_code 由 parameters 注入)
bin = DocType("Bin")
result = (qb.from_(bin)
          .select(bin.item_code, Sum(bin.actual_qty).as_("qty"))
          .where(bin.item_code == filters.get("item_code"))
          .groupby(bin.item_code)
          .run(as_dict=True))
# 示例:查客户最近 5 笔销售(聚合 + 排序 + 限制)
si = DocType("Sales Invoice")
result = (qb.from_(si)
          .select(si.name, si.grand_total, si.posting_date)
          .where(si.customer == filters.get("customer"))
          .orderby(si.posting_date, order=desc())
          .limit(5)
          .run(as_dict=True))

② 函数路径(app.module.file.function) — 调用项目里的 Python 函数,parameters 作为 kwargs 传入

query_code: zhiz_gy.api.report.get_sales_summary

函数返回 list[dict](每行一个字典) 或 list[list](自动转为 c0/c1/.. 列名)。适合复杂业务逻辑(多表 JOIN、Python 计算)封装在项目 app 里复用。

参数(parameters)

每行 key=value,value 支持三种:

company=广优                                  ← 字面量
company=doc.company                           ← 单据字段(打印时动态替换)
from_date={{doc.posting_date}}                ← 双花括号写法(等价上一行)
item_code=doc.items.item_code                 ← 子表字段(数据驱动行展开时按行取)

参数注入到 filters 字典,在 query_code 里用 filters.get("key") 读取。

引用查询结果

单值(取首行){query_name.column}:

当前库存:{stock.qty}
本月汇总:{summary.total_amount}

数据驱动行(按查询结果展开) — 在单元格属性面板设 query_name + data_key,该行按查询结果条数自动展开,每行一条结果:

cell 配置 说明
query_name = bom_detail 绑定查询名
data_key = item_code 每行取该列作为主键
cell_value {bom_detail.qty} 自动按行匹配该行的 qty

完整示例:打印销售订单的 BOM 子件清单

  1. design_queries 新增查询:
    • queryname = bom_detail
    • querycode(查 BOM 子件):
      bom = DocType("BOM")
      bi = DocType("BOM Item")
      result = (qb.from_(bi)
                .select(bi.item_code, bi.qty, bi.stock_uom)
                .left_join(bom).on(bom.name == bi.parent)
                .where(bom.name == filters.get("bom_no"))
                .run(as_dict=True))
      
    • parameters:bom_no=doc.bom_no
  2. 数据驱动行 cell 设 queryname=bom_detail、datakey=item_code
  3. 单元格引用:{bom_detail.item_code} / {bom_detail.qty} / {bom_detail.stock_uom}

> 💡 查询执行失败会记 Error Log 并返回空结果(单元格显示空),不会中断整个打印。


五、单元格值的四种语义 (核心)

这是 zhiz_print 最强大的能力:cell_value 不只是文本替换,而是根据写法自动选择四种运算语义之一。

① static — 直接替换(默认)

普通文本 + {占位符},逐一替换。无 = 前缀、cell_type 非 logic。

客户:{doc.customer}
金额:{doc.grand_total}
备注:{doc.items.additional_notes}

② logic — 条件表达式(Python) · cell_type = logic

把单元格类型设为 logic,cell_value 写 Python 表达式。用于跨表取值、条件判断。暴露变量与助手:

可用 说明
doc / row 目标单据 / 当前子表行(数据驱动行展开时)
get_value(doctype, name, field) 跨表单字段查询(返回字符串,空时返回 '')
fmt(value, precision=2) 数字格式化(str.format 被 safe_eval 禁,必须用 fmt)
flt · max · min · round 数值与内置函数(safe_eval 默认禁内置,需显式暴露)
get_value("Item", row.item_code, "classification")     ← 取物料的"分类"字段
"滑板" if get_value("Item", row.item_code, "classification") == "滑板" else "其他"
fmt(row.qty * row.weight_per_unit, 3)

> ⚠️ 注意:safe_eval 禁用 str.formatfrappe.utils.* 属性访问、内置函数。务必用上表的 fmt/flt/get_value,不要写 frappe.utils.flt(x)

③ =expression — 算术表达式 (新)

cell_value= 开头:先做占位符替换,再对整个表达式 safe_eval 求值。用于行内运算。

={doc.items.qty}*{doc.items.weight_per_unit}     ← 总重 = 数量 × 件重
={doc.items.rate}*{doc.items.qty}               ← 金额 = 单价 × 数量
=round({doc.items.weight_per_unit}, 4)          ← 件重保留 4 位

求值结果用 _fmt_val 格式化(去尾零:50.0 → "50"5.10 → "5.1")。字段为空导致表达式非法时(如 100*)记录错误日志并显示原式,便于发现缺数据。

④ =rowsum(R:C) — 合计函数 (新)

第 R 行(数据驱动行)所有展开项的第 C 列显示值求和。用于合计行。兼容中英文括号 () / ()

=rowsum(5:5)     ← 第5行第5列(数量)合计
=rowsum(5:7)     ← 第5行第7列(总重)合计 —— 第7列本身是 =round(...) 表达式也能正确累加
=rowsum(5:9)     ← 第5行第9列(金额)合计

> 💡 关键:rowsum 复用列值计算逻辑,所以合计对象即使是 = 表达式列、logic 列,都能算对。非数字单元格自动跳过。 > > 📊 报表打印(v15.22):报表引擎自动附加的合计行不会带入打印 —— 报表合计请用 =rowsum() 在模板里自行设计(如数据驱动行为第 8 行、数量在第 4 列,合计行单元格填 =rowsum(8:4);每页小计用 =pagerowsum(8:4))。

⑤ =pagerowsum(R:C) — 当前打印页合计函数 (新)

=rowsum(R:C) 用法完全相同(R=数据驱动行号、C=列号),区别只在合计范围:当数据驱动行的数据多到跨多张打印页时,=pagerowsum(R:C) 只合计当前这一张打印页上显示的数据,而 =rowsum(R:C) 合计当前逻辑页的全部数据。兼容中英文括号。

=pagerowsum(5:7)   ← 第5行第7列,只合计本页显示的数据(本页小计)
=rowsum(5:7)       ← 第5行第7列,合计全部数据(总计)

> 💡 用法:把 =pagerowsum(R:C) 单元格放在每页都重复出现的行(把该行的行类型设为 Repeat Title Row 重复标题行)里,这样每一张打印页渲染时它都会算一次,且用的是当前页的数据,正好得到「本页小计」;把 =rowsum(R:C) 放在数据行之后的普通行(只出现一次),用于「总计」。若 pagerowsum 放在普通行,它只在第一页出现、只算第一页的数据。

四种语义对照表

写法 触发条件 示例 结果
static = 前缀,非 logic 客户:{doc.customer} 替换占位符
logic cell_type = logic get_value("Item", row.item_code, "x") Python 表达式求值
=expression = 开头(非 rowsum) ={doc.items.qty}*{doc.items.rate} 替换后算术求值
=rowsum =rowsum(R:C) =rowsum(5:7) 第R行第C列合计
=pagerowsum `=pag

Related Other apps for Frappe & ERPNext

  • Erpnext — Free and Open Source Enterprise Resource Planning (ERP)
  • Helpdesk — Modern, Streamlined, Free and Open Source Customer Service Software
  • Print Designer — Visual print designer for Frappe / ERPNext
  • Ctr — CTR模型代码和学习笔记总结
  • Whitelabel — Whitelabel ERPNext
  • Fossunited — fossunited.org
  • Helm — Helm Chart Repository for Frappe/ERPNext
  • Frappe Attachments S3 — A frappe app to upload file attachments in doctypes to s3.