news 2026/10/1 17:24:17

ERPNext v6.25 变更深度解析:财务报表零值隐藏、库存结存强制筛选与打印、结算细节优化

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ERPNext v6.25 变更深度解析:财务报表零值隐藏、库存结存强制筛选与打印、结算细节优化
  • 后端
  • 企业应用

【免费下载链接】erpnext

Free and Open Source Enterprise Resource Planning (ERP)

项目地址:https://gitcode.com/GitHub_Trending/er/erpnext
点击查看免费下载

本文基于 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)

其核心逻辑是:

  1. 遍历所有行,凡has_value为 True 的账户(或用户显式勾选了 “Show zero values”)进入“待展示集合”;
  2. 通过get_all_parents递归向上收集其父级科目——即使父科目本身余额为零,只要存在非零子科目,父级也必须保留以维持报表的层级结构;
  3. 最终仅输出待展示集合内的行,从而同时隐藏“自身为零且无有效子项”的叶子账户和“整棵子树都为空”的父级账户。

1.3 哪些报表受益

filter_out_zero_value_rows目前被以下报表复用:

报表调用位置
资产负债表 Balance Sheetbalance_sheet.py
损益表 Profit & Lossprofit_and_loss_statement.py
合并财务报表 Consolidated Financial Statementconsolidated_financial_statement.py
合并试算表 Consolidated Trial Balanceconsolidated_trial_balance.py
维度账户余额报表 Dimension-wise Accounts Balancedimension_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

即开启紧凑打印后:

  1. 定义“紧凑字段集”为description / qty / rate / amount;
  2. 将 description 单元格的渲染模板替换为专用的 item_table_description.html;
  3. 挂载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 系列其余版本同样以类似的短条目形式记录迭代,便于对照排查历史行为变化。

升级或配置时建议按以下顺序验证:

  1. 财务报表:勾选/取消 “Show zero values”,确认资产负债表与损益表的行数随零值账户显隐正确变化;
  2. 库存报表:确认 Stock Balance 在未提供 Item Code/Warehouse 时无法全量运行,并提供后可正常出数;
  3. 打印模板:在 Print Settings 中切换 Compact Item Print,预览 Sales Invoice / Purchase Invoice 的 Item 表列结构是否符合预期;
  4. Global Settings:勾选 Disable 'In Words' 后,抽查上述 9 类单据的表单与打印预览,确认 in_words 字段均已隐藏;
  5. POS 结算:将默认付款方式分别设为现金与非现金,各做一笔完整销售,核对 paid_amount、outstanding_amount 与 change_amount 的联动结果。
  • 后端
  • 企业应用

【免费下载链接】erpnext

Free and Open Source Enterprise Resource Planning (ERP)

项目地址:https://gitcode.com/GitHub_Trending/er/erpnext
点击查看免费下载

相关推荐

上一篇:终极 Temporal Polyfill 指南:现代 JavaScript 日期时间处理的完整解决方案
下一篇:React Native Audio Toolkit安装与使用指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/1 17:23:31

Unity实时同步Windows桌面:Windows Capture插件从黑屏到60帧实战

简介&#xff1a;这是一款面向Unity开发者的Windows桌面实时采集插件&#xff0c;用于在Unity场景中同步呈现Windows桌面画面&#xff0c;适合需要将桌面内容嵌入三维应用、虚拟展厅或录屏演示的开发者使用&#xff0c;对具备一定Unity基础的中级用户更为友好。资源包共141个文…

作者头像 李华
网站建设 2026/10/1 17:23:15

Python实现UDP可靠传输:滑动窗口、校验和与重传机制全解析

简介&#xff1a;面向网络编程课程设计与实验场景&#xff0c;这份基于Python的可靠数据传输协议实现资料包含完整设计报告与可运行源码&#xff0c;覆盖停等协议、GBN协议和SR协议的逐步演进&#xff0c;帮助学习者在UDP之上构建可靠的单向与双向数据传输机制&#xff0c;并通…

作者头像 李华
网站建设 2026/10/1 17:22:30

门限自回归TAR模型原理与R实现:机制切换时间序列建模指南

简介&#xff1a;面向时间序列分析与计量经济学研究者&#xff0c;这份资源提供基于MATLAB的门限自回归&#xff08;TAR&#xff09;模型实现示例&#xff0c;旨在解决数据存在阈值效应时线性AR模型拟合不足的问题。压缩包共6个文件&#xff0c;3个m脚本分别承担阈值检测、分段…

作者头像 李华
网站建设 2026/10/1 17:21:41

3D点云自编码与生成实战:从潜空间重建到WGAN-GP

简介&#xff1a;本资源是一套基于Python与Jupyter Notebook实现的3D点云自动编码与生成完整项目&#xff0c;面向计算机视觉、三维深度学习方向的中高级学习者与研究者&#xff0c;聚焦于点云数据的降维表征学习与可控生成任务。包内共44个文件&#xff0c;涵盖24个Python核心…

作者头像 李华
网站建设 2026/10/1 17:21:36

2026年厦门GEO运营服务商行业口碑汇总与挑选全攻略

找GEO运营服务商有推荐吗&#xff0c;很多泉州、厦门及闽南周边的企业老板最近都在问&#xff0c;哪些GEO运营服务商比较靠谱&#xff0c;有没有好一点的GEO运营服务商可以推荐。进入AI生成式搜索的普及期&#xff0c;越来越多用户的查询习惯已经从传统搜索引擎转向AI智能问答&…

作者头像 李华
网站建设 2026/10/1 17:21:34

使用Claude Code的一些基本操作:从MCP到SubAgent的TaoToken配置实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华