- 后端
- 企业应用
【免费下载链接】erpnext
Free and Open Source Enterprise Resource Planning (ERP)
本文基于 ERPNext 仓库中 v6.25 变更日志 展开,系统梳理该版本在财务报表展示、库存报表筛选、单据打印模板与 POS 结算四个方向上的关键改动,并结合仓库源码逐一还原其底层实现机制。读完本文,你将理解零值账户隐藏的判定逻辑、Stock Balance 报表的筛选约束、Compact Item Print 的列合并原理、全局禁用金额大写(In Words)的实现方式,以及 POS 默认付款方式非现金时 Paid Amount 计算的修正思路。
v6.25 是 ERPNext 在 v6 系列中期的一个“体验打磨型”版本:它不引入新模块,而是集中修正报表可读性、打印输出质量与 POS 结算准确性。其中多项改动(如 Compact Item Print)与前一版本 v6.22 的功能一脉相承,共同构成了当时打印与报表体系的完整优化闭环。
一、资产负债表与损益表:隐藏余额为零的账户
1.1 变更内容
Improved logic to hide accounts with zero values in Balance Sheet and Profit & Loss
v6.25 优化了**资产负债表(Balance Sheet)与损益表(Profit & Loss)**中对余额为零账户的隐藏逻辑。在此之前,即使账户没有发生额,也会在报表中占据一行,导致财务报表冗长难读;此版本后,只要账户在所选期间内所有列的值均为零,该行及其空父级科目将不再出现在报表中。
1.2 底层实现:从has_value到filter_out_zero_value_rows
该能力由财务报表共用引擎 financial_statements.py 提供。报表在组装每一行时,会先按币种精度阈值判断该行是否“有值”:
if abs(row[period.key]) >= get_zero_cutoff(company_currency): # ignore zero values has_value = True row["has_value"] = has_value(见 financial_statements.py)
随后由filter_out_zero_value_rows完成真正的“剪枝”:
def filter_out_zero_value_rows(data, parent_children_map, show_zero_values=False): def get_all_parents(account, parent_children_map): for parent, children in parent_children_map.items(): for child in children: if child["name"] == account and parent: accounts_to_show.add(parent) get_all_parents(parent, parent_children_map) data_with_value = [] accounts_to_show = set() for d in data: if show_zero_values or d.get("has_value"): accounts_to_show.add(d.get("account")) get_all_parents(d.get("account"), parent_children_map) for d in data: if d.get("account") in accounts_to_show: data_with_value.append(d) return data_with_value(见 financial_statements.py)
其核心逻辑是:
- 遍历所有行,凡
has_value为 True 的账户(或用户显式勾选了 “Show zero values”)进入“待展示集合”; - 通过
get_all_parents递归向上收集其父级科目——即使父科目本身余额为零,只要存在非零子科目,父级也必须保留以维持报表的层级结构; - 最终仅输出待展示集合内的行,从而同时隐藏“自身为零且无有效子项”的叶子账户和“整棵子树都为空”的父级账户。
1.3 哪些报表受益
filter_out_zero_value_rows目前被以下报表复用:
| 报表 | 调用位置 |
|---|---|
| 资产负债表 Balance Sheet | balance_sheet.py |
| 损益表 Profit & Loss | profit_and_loss_statement.py |
| 合并财务报表 Consolidated Financial Statement | consolidated_financial_statement.py |
| 合并试算表 Consolidated Trial Balance | consolidated_trial_balance.py |
| 维度账户余额报表 Dimension-wise Accounts Balance | dimension_wise_accounts_balance_report.py |
在报表前端,Balance Sheet 与 P&L 均提供了“Show zero values”复选框(见 balance_sheet.js),默认不勾选。也就是说:默认隐藏零值账户,勾选后恢复展示——这正是 v6.25 “改进隐藏逻辑”后形成的最终交互形态。
实操提示:当你的资产负债表“莫名缺少”某些长期未使用、余额为零的科目时,多半不是数据丢失,而是零值隐藏逻辑生效;勾选 “Show zero values” 即可核对。
二、Stock Balance 报表:强制提供 Item Code 或 Warehouse 筛选
2.1 变更内容
Item Code or Warehouse filter mandatory in Stock Balance Report
v6.25 起,库存结存(Stock Balance)报表要求用户在运行报表前必须提供Item Code(物料)或Warehouse(仓库)筛选条件之一。其动机非常直观:Stock Balance 以“物料 × 仓库”为基本聚合维度(源码中分组键即(item_code, warehouse),见 stock_balance.py),若两者都不限定,报表会一次性扫描全公司全部物料的全部仓库,既拖慢查询又难以阅读;限定任一维度后,数据范围被显著收敛,报表输出才具备实际分析价值。
2.2 报表过滤器结构
当前版本中,Stock Balance 的过滤器定义集中在共享的 stock_balance_report.js,并由 stock_balance.js 以及serial_and_batch_wise_stock_balance等衍生报表复用。其中与筛选约束相关的关键字段包括:
- Items(item_code):
MultiSelectList类型,支持多选物料,并通过item_query联动 Item Group 过滤; - Warehouses(warehouse):
MultiSelectList类型,支持多选仓库,可联动 Warehouse Type 与 Company; - Include Zero Stock Items:默认开启,控制是否展示余额为零的行。
2.3 与零库存行隐藏的联动
与第一条改动类似,Stock Balance 报表自身也有零值行处理逻辑。在数据组装阶段:
def is_hidden_zero_stock(self, row) -> bool: return not self.filters.get("include_zero_stock_items") and row.bal_qty == 0 and row.bal_val == 0(见 stock_balance.py)
即:当include_zero_stock_items未勾选时,期末数量与期末价值同时为零的行会被跳过。配套的单元测试 test_stock_balance.py 专门验证了这一行为——先产生数量为 5 的入库,再产生数量为 5 的移库,使余额净零,此时默认过滤器返回空结果集;只有开启include_zero_stock_items后行才会出现。测试同时断言了报表的核心不变量(bal_qty = opening_qty + in_qty - out_qty、bal_val与 Stock Ledger Entry 的qty_after_transaction/stock_value一致),为报表正确性提供了自动化保障。
实操提示:v6.25 引入的“强制筛选”与“零库存行隐藏”是两个独立维度——前者限定查询范围,后者控制零值行的显隐。排查“报表行数异常少”时,应先确认 Item Code / Warehouse 是否已限定,再确认
include_zero_stock_items开关状态。
三、Compact Item Print:将物料多列合并进 Description 列
3.1 变更内容
For Item table print, combine Item Code, Item Name, Description and additional columns in Description only ifCompact Item Printis checked inFeatures Setup
v6.25 对上一版本(v6.22)引入的Compact Item Print做了关键增强。v6.22 首次提供该开关(见 v6_22_0.md),使 Item 表在打印时仅展示 “Description、Quantity、Rate、Amount” 四列;v6.25 在此基础上进一步规定:当 Compact Item Print 开启时,Item Code、Item Name、Description 以及其余附加列将全部合并进 Description 单元格内,以“标签: 值”的形式逐行呈现,而不是各自独占一列。
3.2 配置入口
该开关并非 Frappe 自带字段,而是 ERPNext 安装时通过自定义字段注入到Print Settings中的:
def create_print_setting_custom_fields(): create_custom_fields( { "Print Settings": [ { "label": _("Compact Item Print"), "fieldname": "compact_item_print", "fieldtype": "Check", "default": "1", "insert_after": "with_letterhead", }, ... ] } )(见 setup/install.py)
注意两点:一是字段默认值即为1(默认开启紧凑打印);二是变更日志所称 “Features Setup” 在当时的界面中即指打印设置相关配置(该开关字段位于 Print Settings),在现代版本中可通过Setup > Customize > Print Settings调整。
3.3 实现机制:模板分发与列裁剪
核心逻辑位于 controllers/print_settings.py:
doc.flags.compact_item_fields = ["description", "qty", "rate", "amount"] if settings.compact_item_print: doc.child_print_templates["items"][ "description" ] = "templates/print_formats/includes/item_table_description.html" doc.flags.format_columns = format_columns即开启紧凑打印后:
- 定义“紧凑字段集”为
description / qty / rate / amount; - 将 description 单元格的渲染模板替换为专用的 item_table_description.html;
- 挂载
format_columns,用于把image、item_code、item_name追加进紧凑字段集,使它们在表格列头中被排除、转而进入 Description 单元格渲染。
在 items.html 中,列头与单元格的可见性均由同一条件控制:
{% if (data and not print_settings.compact_item_print) or tdf.fieldname in doc.flags.compact_item_fields %} <th ...>{{ _(tdf.label) }}</th> {% endif %}因此开启紧凑打印后,表格只保留 Sr、Description、Qty、Rate、Amount 等核心列;而 item_table_description.html 会在 Description 单元格内依次渲染:
- Item Code(
compact模式下以加粗主标题样式展示); - Item Name(当与 Item Code 不同时展示);
- Description文本;
- 其余所有被排除的附加列,以
字段标签: 格式化值的形式逐行罗列(columns即format_columns裁剪后的结果)。
这套机制的好处是:单据的打印版式不再受限于物料主数据中“Item Code/Name/Description 各不相同”或“附加列很多”的情况,所有信息都收纳进一个自适应的 Description 区块,特别适合小票、送货单等窄幅打印场景。
四、全局禁用金额大写(In Words)
4.1 变更内容
Disable 'In Words'from all documents via Setup > Global Settings
v6.25 在Setup > Global Settings中新增了Disable 'In Words'开关,允许管理员一次性隐藏全部业务单据上的金额大写(In Words)字段,无需逐个单据修改。对于不使用英文大写金额表述(或打印模板已不需要该字段)的企业,这是一个显著的配置简化。
4.2 实现机制:Property Setter 批量隐藏
字段定义与联动逻辑位于 global_defaults.py:
disable_in_words: DF.Check(见 global_defaults.py)
保存 Global Defaults 时,toggle_in_words会对所有受影响的单据批量生成 Property Setter:
def toggle_in_words(self): # Make property setters to hide in words fields for doctype in ROUNDED_TOTAL_DOCTYPES: make_property_setter( doctype, "in_words", "hidden", cint(self.disable_in_words), "Check", validate_fields_for_doctype=False, ) make_property_setter( doctype, "in_words", "print_hide", cint(self.disable_in_words), "Check", validate_fields_for_doctype=False, )(见 global_defaults.py)
它同时下发hidden(表单内隐藏)与print_hide(打印时隐藏)两条 Property Setter,即一处勾选,表单与打印输出同步失效。影响范围由ROUNDED_TOTAL_DOCTYPES常量决定:
ROUNDED_TOTAL_DOCTYPES = ( "Quotation", "Sales Order", "POS Invoice", "Sales Invoice", "Delivery Note", "Supplier Quotation", "Purchase Order", "Purchase Invoice", "Purchase Receipt", )(见 global_defaults.py)
覆盖了采购、销售、POS 三条主链路上的全部核心单据。
4.3 与大写生成逻辑的关系
值得注意的是,该开关只是隐藏字段,并不会关闭大写金额的计算。单据保存时,大写金额仍由各控制器生成,例如采购侧 buying_controller.py 与销售侧 selling_controller.py 的set_total_in_words():
def set_total_in_words(self): from frappe.utils import money_in_words if self.meta.get_field("in_words"): if self.meta.get_field("rounded_total") and not self.is_rounded_total_disabled(): amount = abs(flt(self.rounded_total)) else: amount = abs(flt(self.grand_total)) self.in_words = money_in_words(amount, self.currency)(见 buying_controller.py)
金额取值遵循“启用舍入总额时用rounded_total,否则用grand_total”的规则,而舍入开关本身也来自 Global Defaults(disable_rounded_total,判断逻辑见 accounts_controller.py)。因此,Disable 'In Words'与Disable Rounded Total是彼此独立的两组开关:前者只管显示,后者影响金额计算口径。
五、Bug 修复:POS 默认付款方式非现金时的 Paid Amount
5.1 变更内容与问题背景
Bug fix: Paid Amount in POS view when the default Mode of Payment is not Cash
这是 v6.25 收录的一处 POS 结算缺陷修复。在 POS 界面中,新建单据会根据POS Profile的默认付款方式自动预填付款行(POS Profile 中对应字段为set_grand_total_to_default_mop,见 pos_invoice.py)。当默认付款方式是现金(Cash)时,传统逻辑按“实收现金 → 计算找零”的思路工作正常;但当默认付款方式被设置为银行卡、移动支付等非现金方式时,预填的付款行金额与最终paid_amount的计算曾出现偏差,导致单据在付款环节显示错误的已付金额。
5.2 修复相关的结算逻辑
修复后的结算金额计算集中在 POS Invoice 的update_payments白名单方法中(见 pos_invoice.py):
paid_amount = flt(self.paid_amount) total = flt(self.rounded_total) or flt(self.grand_total) if paid_amount >= total: frappe.throw(title=_("Invoice Paid"), msg=_("This invoice has already been paid.")) for d in payments: payment = create_payments_on_invoice(self, idx, frappe._dict(d)) paid_amount += flt(payment.amount) self.append("payments", payment) self.save() paid_amount = flt(flt(paid_amount), self.precision("paid_amount")) base_paid_amount = flt(flt(paid_amount * self.conversion_rate), self.precision("base_paid_amount")) outstanding_amount = ( flt(flt(total - paid_amount), self.precision("outstanding_amount")) if total > paid_amount else 0 ) change_amount = ( flt(flt(paid_amount - total), self.precision("change_amount")) if paid_amount > total else 0 )关键点在于:paid_amount不再单纯依赖“现金”假设,而是以单据当前已付金额为基数,逐行累加传入的付款记录金额,再按单据精度统一回写paid_amount、base_paid_amount、outstanding_amount与change_amount四个字段。change_amount仅在“已付金额大于应付款总额”时才会产生,从逻辑上天然兼容任何默认付款方式。此外,reset_mode_of_payments(见 pos_invoice.py)会在 POS Profile 变更时重置付款行并清零paid_amount,避免跨 Profile 切换时残留旧付款数据。
实操提示:升级后若发现 POS 单据付款金额异常,可重点检查 POS Profile 中“默认付款方式”与
set_grand_total_to_default_mop的组合配置;该修复确保的是“任何默认付款方式下已付金额都按付款行实算”,而非特定付款方式专属行为。
六、版本背景与升级建议
v6.25 属于 v6 系列的中期维护版本,全部改动均围绕“报表可读性、打印可控性、结算准确性”三条主线,不含破坏性的数据模型变更,升级成本较低。从仓库的 change_log/v6 目录结构看,v6 系列其余版本同样以类似的短条目形式记录迭代,便于对照排查历史行为变化。
升级或配置时建议按以下顺序验证:
- 财务报表:勾选/取消 “Show zero values”,确认资产负债表与损益表的行数随零值账户显隐正确变化;
- 库存报表:确认 Stock Balance 在未提供 Item Code/Warehouse 时无法全量运行,并提供后可正常出数;
- 打印模板:在 Print Settings 中切换 Compact Item Print,预览 Sales Invoice / Purchase Invoice 的 Item 表列结构是否符合预期;
- Global Settings:勾选 Disable 'In Words' 后,抽查上述 9 类单据的表单与打印预览,确认 in_words 字段均已隐藏;
- POS 结算:将默认付款方式分别设为现金与非现金,各做一笔完整销售,核对 paid_amount、outstanding_amount 与 change_amount 的联动结果。
- 后端
- 企业应用
【免费下载链接】erpnext
Free and Open Source Enterprise Resource Planning (ERP)
相关推荐
ERPNext v5.0.25 变更日志深度解读:财务报告性能、制造校验与采购销售细节优化
ERPNext v5.0.25 变更日志深度解读:财务报告性能、制造校验与采购销售细节优化 本篇指南围绕开源 ERP 项目 ERPNext 的 v5.0.25
后端企业应用ERPNext v15.56.0版本深度解析:库存与财务模块的重大优化
ERPNext v15.56.0版本深度解析:库存与财务模块的重大优化 项目简介 ERPNext是一款开源的综合性企业资源规划系统,涵盖了财务、库存、制造、销售
后端企业应用ERPNext v6.26 变更实战解读:财务年度从交易单据退场、假日列表区间化与库存报表性能治理
ERPNext v6.26 变更实战解读:财务年度从交易单据退场、假日列表区间化与库存报表性能治理 本篇文章基于 erpnext/change_log/v6/v
后端企业应用
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考