mirror of
https://gitee.com/yuzelin/erpnext_china.git
synced 2026-03-02 05:14:04 +08:00
增加资产负债与损益报表设置计算公式引用参数校验
This commit is contained in:
@@ -7,7 +7,7 @@ import frappe
|
||||
|
||||
patches_loaded = False
|
||||
|
||||
__version__ = '1.0.4'
|
||||
__version__ = '1.0.5'
|
||||
|
||||
|
||||
def load_monkey_patches():
|
||||
|
||||
@@ -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)
|
||||
validate_report_settings(self)
|
||||
@@ -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="<br>".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)
|
||||
if errors:
|
||||
frappe.throw(title=_("Logic Errors"), msg="<br>".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))
|
||||
@@ -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,
|
||||
}
|
||||
])
|
||||
|
||||
|
||||
@@ -12118,4 +12118,20 @@ Dec 2028,2028年12月
|
||||
"Created By {0}","由 {0} 创建"
|
||||
"{0} Web page views","{0} 次网页浏览"
|
||||
"Repeats {0}","重复 {0}"
|
||||
Open Sidebar,打开侧边栏
|
||||
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} 中使用
|
||||
|
Can't render this file because it is too large.
|
Reference in New Issue
Block a user