From 39ff2f14d0d4a90e1ce669eb2a01d50978b95f67 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BD=99=E5=88=99=E9=9C=96?=
<7624863+yuzelin@user.noreply.gitee.com>
Date: Sun, 25 Jan 2026 09:36:16 +0800
Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E8=B5=84=E4=BA=A7=E8=B4=9F?=
=?UTF-8?q?=E5=80=BA=E4=B8=8E=E6=8D=9F=E7=9B=8A=E6=8A=A5=E8=A1=A8=E8=AE=BE?=
=?UTF-8?q?=E7=BD=AE=E8=AE=A1=E7=AE=97=E5=85=AC=E5=BC=8F=E5=BC=95=E7=94=A8?=
=?UTF-8?q?=E5=8F=82=E6=95=B0=E6=A0=A1=E9=AA=8C?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
erpnext_china/__init__.py | 2 +-
.../balance_sheet_settings.py | 46 +-----
.../profit_and_loss_statement_settings.py | 134 ++++++++++++++----
.../fin_profit_and_loss_statement.py | 8 +-
erpnext_china/translations/zh.csv | 18 ++-
5 files changed, 133 insertions(+), 75 deletions(-)
diff --git a/erpnext_china/__init__.py b/erpnext_china/__init__.py
index 7a7ef42..148b794 100644
--- a/erpnext_china/__init__.py
+++ b/erpnext_china/__init__.py
@@ -7,7 +7,7 @@ import frappe
patches_loaded = False
-__version__ = '1.0.4'
+__version__ = '1.0.5'
def load_monkey_patches():
diff --git a/erpnext_china/erpnext_china/doctype/balance_sheet_settings/balance_sheet_settings.py b/erpnext_china/erpnext_china/doctype/balance_sheet_settings/balance_sheet_settings.py
index 365915e..ae7739d 100644
--- a/erpnext_china/erpnext_china/doctype/balance_sheet_settings/balance_sheet_settings.py
+++ b/erpnext_china/erpnext_china/doctype/balance_sheet_settings/balance_sheet_settings.py
@@ -1,11 +1,14 @@
# Copyright (c) 2023, Vnimy and contributors
# For license information, please see license.txt
+import os
import frappe
from frappe import _
from frappe.model.document import Document
+from erpnext_china.erpnext_china.doctype.profit_and_loss_statement_settings.profit_and_loss_statement_settings import (
+ validate_report_settings
+)
-import os
class BalanceSheetSettings(Document):
@frappe.whitelist()
@@ -13,43 +16,4 @@ class BalanceSheetSettings(Document):
return frappe.get_file_json(os.path.join(os.path.dirname(__file__), "example_data.json"))
def validate(self):
- self.check_duplicate_account_number()
-
- def check_duplicate_account_number(self):
-
- def check_one_row(row, check_row):
- if row.name == check_row.name:
- return
- if row.lft_calc_type == "Closing Balance" and row.lft_calc_sources:
- for account_number in row.lft_calc_sources.split(','):
- check_account_number(account_number, check_row)
- if row.rgt_calc_type == "Closing Balance" and row.rgt_calc_sources:
- for account_number in row.rgt_calc_sources.split(','):
- check_account_number(account_number, check_row)
-
- def check_account_number(account_number, check_row):
- if not account_number: return
- if (check_row.lft_calc_type == "Closing Balance" and check_row.lft_calc_sources
- and account_number in check_row.lft_account_numbers
- ):
- frappe.msgprint(_("row {0} account number {1} already in row {2}").format(
- row.idx, account_number, check_row.idx
- ))
-
- if (check_row.rgt_calc_type == "Closing Balance" and check_row.rgt_calc_sources
- and account_number in check_row.rgt_account_numbers
- ):
- frappe.msgprint(_("row {0} account number {1} already in row {2}").format(
- row.idx, account_number, check_row.idx
- ))
-
- all_rows = []
- for row in self.items:
- if (row.lft_calc_type == "Closing Balance" and row.lft_calc_sources):
- row.lft_account_numbers = set(row.lft_calc_sources.split(','))
- if (row.rgt_calc_type == "Closing Balance" and row.rgt_calc_sources):
- row.rgt_account_numbers = set(row.rgt_calc_sources.split(','))
- all_rows.append(row)
- for row in self.items:
- for check_row in all_rows:
- check_one_row(row, check_row)
\ No newline at end of file
+ validate_report_settings(self)
\ No newline at end of file
diff --git a/erpnext_china/erpnext_china/doctype/profit_and_loss_statement_settings/profit_and_loss_statement_settings.py b/erpnext_china/erpnext_china/doctype/profit_and_loss_statement_settings/profit_and_loss_statement_settings.py
index f6ea53f..e3a0327 100644
--- a/erpnext_china/erpnext_china/doctype/profit_and_loss_statement_settings/profit_and_loss_statement_settings.py
+++ b/erpnext_china/erpnext_china/doctype/profit_and_loss_statement_settings/profit_and_loss_statement_settings.py
@@ -1,43 +1,117 @@
# Copyright (c) 2023, Vnimy and contributors
# For license information, please see license.txt
+import os
import frappe
from frappe import _
from frappe.model.document import Document
-import os
class ProfitandLossStatementSettings(Document):
- @frappe.whitelist()
- def get_example_data(self):
- return frappe.get_file_json(os.path.join(os.path.dirname(__file__), "example_data.json"))
+ @frappe.whitelist()
+ def get_example_data(self):
+ return frappe.get_file_json(os.path.join(os.path.dirname(__file__), "example_data.json"))
- def validate(self):
- self.check_duplicate_account_number()
+ def validate(self):
+ validate_report_settings(self)
- def check_duplicate_account_number(self):
+def validate_report_settings(doc):
+ """通用报表设置校验入口:支持单列和双列(Balance Sheet)结构"""
+ errors = []
+ warnings = []
+
+ # 1. 检查重复科目 (Warning)
+ check_duplicate_account_numbers(doc, warnings)
+ # 2. 检查计算行逻辑 (Error)
+ check_calculation_row_logic(doc, errors)
- def check_one_row(row, check_row):
- if row.name == check_row.name:
- return
- if row.calc_type == "Closing Balance" and row.calc_sources:
- for account_number in row.calc_sources.split(','):
- check_account_number(account_number, check_row)
+ if warnings:
+ frappe.msgprint(title=_("Configuration Warnings"), indicator="orange", msg="
".join(warnings))
- def check_account_number(account_number, check_row):
- if (account_number and check_row.calc_type == "Closing Balance"
- and check_row.calc_sources
- and account_number in check_row.account_numbers
- ):
- frappe.msgprint(_("row {0} account number {1} already in row {2}").format(
- row.idx, account_number, check_row.idx
- ))
-
- all_rows = []
- for row in self.items:
- if (row.calc_type == "Closing Balance" and row.calc_sources):
- row.account_numbers = set(row.calc_sources.split(','))
- all_rows.append(row)
- for row in self.items:
- for check_row in all_rows:
- check_one_row(row, check_row)
\ No newline at end of file
+ if errors:
+ frappe.throw(title=_("Logic Errors"), msg="
".join(errors))
+
+def check_duplicate_account_numbers(doc, warnings):
+ """检查重复科目,兼容 lft_ 和 rgt_,支持完整汉化"""
+ # 收集器:{科目号: (行号, 侧边名称)}
+ all_accounts = {}
+
+ # 定义方位翻译字典
+ side_map = {
+ "Left Column": _("Left Column"),
+ "Right Column": _("Right Column")
+ }
+
+ for row in doc.items:
+ # 定义需要检查的配置对 (内部标识, 类型字段, 来源字段)
+ checks = [
+ ("Single", "calc_type", "calc_sources"),
+ ("Left Column", "lft_calc_type", "lft_calc_sources"),
+ ("Right Column", "rgt_calc_type", "rgt_calc_sources")
+ ]
+
+ for side, type_field, source_field in checks:
+ ctype = getattr(row, type_field, None)
+ csource = getattr(row, source_field, None)
+
+ if ctype == "Closing Balance" and csource:
+ accounts = [s.strip() for s in csource.split(',') if s.strip()]
+ for acc in accounts:
+ # 去掉负号进行科目匹配校验
+ clean_acc = acc.replace('-', '')
+
+ if clean_acc in all_accounts:
+ prev_idx, prev_side = all_accounts[clean_acc]
+
+ # 组装当前行描述:如 "(左栏)第 5 行"
+ curr_side_label = f"({side_map.get(side)})" if side != "Single" else ""
+ curr_desc = _("{0}Row {1}").format(curr_side_label, row.idx)
+
+ # 组装之前出现的行描述:如 "(右栏)第 2 行"
+ prev_side_label = f"({side_map.get(prev_side)})" if prev_side != "Single" else ""
+ prev_desc = _("{0}Row {1}").format(prev_side_label, prev_idx)
+
+ # 使用带占位符的完整翻译 Key
+ msg = _("{0}: Account {1} already in {2}").format(curr_desc, clean_acc, prev_desc)
+
+ if msg not in warnings:
+ warnings.append(msg)
+ else:
+ all_accounts[clean_acc] = (row.idx, side)
+
+def check_calculation_row_logic(doc, errors):
+ existing_indices = [int(item.idx) for item in doc.items]
+ # 定义方位翻译字典
+ side_map = {
+ "Left": _("Left Column"),
+ "Right": _("Right Column")
+ }
+
+ for row in doc.items:
+ checks = [
+ ("Left", "lft_calc_type", "lft_calc_sources"),
+ ("Right", "rgt_calc_type", "rgt_calc_sources"),
+ ("Single", "calc_type", "calc_sources")
+ ]
+
+ for side, type_field, source_field in checks:
+ ctype = getattr(row, type_field, None)
+ csource = getattr(row, source_field, None)
+
+ if ctype == "Calculate Rows" and csource:
+ # 获取翻译后的方位标签,如 "(左侧)"
+ side_label = f"({side_map.get(side)})" if side != "Single" else ""
+
+ sources = [s.strip() for s in csource.split(',') if s.strip()]
+ for s in sources:
+ clean_idx_str = s.replace('-', '')
+ if not clean_idx_str: continue
+
+ try:
+ ref_idx = int(clean_idx_str)
+ if ref_idx not in existing_indices:
+ errors.append(_("{0}Row {1}: Referenced row {2} does not exist").format(side_label, row.idx, ref_idx))
+ elif ref_idx >= int(row.idx):
+ errors.append(_("{0}Row {1}: Referenced row {2} must be a previous row").format(side_label, row.idx, ref_idx))
+ except ValueError:
+ errors.append(_("{0}Row {1}: '{2}' is not a valid row number").format(side_label, row.idx, clean_idx_str))
\ No newline at end of file
diff --git a/erpnext_china/erpnext_china/report/fin_profit_and_loss_statement/fin_profit_and_loss_statement.py b/erpnext_china/erpnext_china/report/fin_profit_and_loss_statement/fin_profit_and_loss_statement.py
index 01b94a3..bdad6c7 100644
--- a/erpnext_china/erpnext_china/report/fin_profit_and_loss_statement/fin_profit_and_loss_statement.py
+++ b/erpnext_china/erpnext_china/report/fin_profit_and_loss_statement/fin_profit_and_loss_statement.py
@@ -173,6 +173,10 @@ def get_data(data, filters):
for (i, row_num) in enumerate(d.acc_nums):
minus_factor = d.minus_factor[i]
row = rows_map.get(cstr(row_num))
+ if row is None:
+ # 如果找不到该行,打印一个日志或提示,跳过此数值
+ frappe.throw(_("Row {0} refers to non-existent row {1}").format(d.idx, row_num))
+
monthly_amount += row.get("amount", 0.0) * minus_factor
print(i, row_num, monthly_amount)
month_end_amount += row.get("month_end_amount", 0.0) * minus_factor
@@ -203,7 +207,7 @@ def get_columns(filters):
"label": "金额",
"fieldname": "amount",
"fieldtype": "Currency",
- "width": 120,
+ "width": 140,
}
]
@@ -213,7 +217,7 @@ def get_columns(filters):
"label": "月底累计数",
"fieldname": "month_end_amount",
"fieldtype": "Currency",
- "width": 120,
+ "width": 140,
}
])
diff --git a/erpnext_china/translations/zh.csv b/erpnext_china/translations/zh.csv
index 8888543..0e78219 100644
--- a/erpnext_china/translations/zh.csv
+++ b/erpnext_china/translations/zh.csv
@@ -12118,4 +12118,20 @@ Dec 2028,2028年12月
"Created By {0}","由 {0} 创建"
"{0} Web page views","{0} 次网页浏览"
"Repeats {0}","重复 {0}"
-Open Sidebar,打开侧边栏
\ No newline at end of file
+Open Sidebar,打开侧边栏
+Row {0} refers to non-existent row {1},第 {0} 行公式配置错误,引用了不存在的行号 {1}
+Row {0}: Account number {1} already in Row {2},行次 {0}:科目编号 {1} 已存在于行次 {2} 中
+Row {0}: Referenced row {1} does not exist,行次 {0}:引用的行号 {1} 不存在
+Row {0}: Referenced row {1} must be a previous row,行次 {0}:引用的行号 {1} 必须是之前的行次
+Row {0}: '{1}' is not a valid row number,行次 {0}:'{1}' 不是有效的行号
+Configuration Warnings,配置警告
+Logic Errors,逻辑错误
+Left Column,左栏
+Right Column,右栏
+{0}Row {1}: Referenced row {2} does not exist,{0}第 {1} 行:引用的行号 {2} 不存在
+{0}Row {1}: Referenced row {2} must be a previous row,{0}第 {1} 行:引用的行号 {2} 必须小于当前行次
+{0}Row {1}: '{2}' is not a valid row number,{0}第 {1} 行:'{2}' 不是有效的行号
+Row {0}: Account {1} already in Row {2},行次 {0}:科目 {1} 已在行次 {2} 中使用。
+Row {0}: '{1}' is not a valid row number,行次 {0}:'{1}' 不是有效的数字。
+{0}Row {1},{0}第 {1} 行
+{0}: Account {1} already in {2},{0}:科目 {1} 已在 {2} 中使用
\ No newline at end of file