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