This commit is contained in:
余则霖
2026-01-07 08:57:34 +08:00
parent 921167d59a
commit f38cb78eb6
80 changed files with 42737 additions and 264 deletions

View File

@@ -1,33 +1,62 @@
### ERPNext China # ERPNext China ERPNext中国本地化
ERPNext China
### Installation ## 安装
You can install this app using the [bench](https://github.com/frappe/bench) CLI: bench get-app erpnext_china https://gitee.com/yuzlin/erpnext_china.git
```bash
cd $PATH_TO_YOUR_BENCH
bench get-app $URL_OF_THIS_REPO --branch develop
bench install-app erpnext_china bench install-app erpnext_china
```
### Contributing
This app uses `pre-commit` for code formatting and linting. Please [install pre-commit](https://pre-commit.com/#installation) and enable it for this repository: ## 功能说明
```bash ### 一、界面中文汉化(简体)
cd apps/erpnext_china 1. 在系统自带汉化基础上的人工汉化
pre-commit install
```
Pre-commit is configured to use the following tools for checking and formatting your code: ### 二、中国科目表
1. 系统首次初始化及初始化后创建新公司代码时可选中国会计科目表
- ruff ### 三、创建公司时设置默认值
- eslint 1. **税务设置**
- prettier 1. 创建税种:
- pyupgrade - 内销增值税
- 外销增值税
- 小规模纳税人
- 一般纳税人
2. 创建税率模板:
- 13%
- 0%
- 1%
### License 2. **公司设置**
1. 分配默认科目
2. 设置仓库库存科目
3. 配置物料组费用科目
4. 设置小数精度尾差科目
mit ### 四、中国财务报表(新增工作区)
1. **中国资产负债表**
- 需设置报表项与科目号对照及计算公式
- 自动检查科目遗漏
2. **中国利润表**
- 需设置报表项与科目号对照及计算公式
- 自动检查科目遗漏
3. **中国现金流量表(直接法)**
- 需预先设置现金流编码
### 五、初始化配置
### 基础设置
1. 默认语言:中文
2. 默认地区:中国
3. 隐藏印度专用字段如PAN
### app安装后设置
1. 修改一词多义字段标签(如采购、销售税费明细中的税率)
2. 隐藏本地化不适用字段如PAN号等印度专用字段
3. 修改默认系统流水码前缀-改短
#### License
MIT

View File

@@ -1 +1,48 @@
__version__ = "0.0.1" # -*- coding: utf-8 -*-
from __future__ import unicode_literals
import os
import importlib
import frappe
patches_loaded = False
__version__ = '1.0.0'
def load_monkey_patches():
global patches_loaded
if (
patches_loaded
or not getattr(frappe, "conf", None)
or not "erpnext_china" in frappe.get_installed_apps()
):
return
for app in frappe.get_installed_apps():
if app in ['frappe', 'erpnext']: continue
try:
folder = frappe.get_app_path(app, "monkey_patches")
if os.path.exists(folder):
for module_name in os.listdir(folder):
if not module_name.endswith(".py") or module_name == "__init__.py":
continue
importlib.import_module(f"{app}.monkey_patches.{module_name[:-3]}")
except:
method = f"Failed load app {app} monkey patches"
if not frappe.db.exists("Error Log", {"method": method, "seen":0}):
frappe.log_error(method, frappe.get_traceback(with_context=True))
frappe.log(f'{app} load failed in erpnext_china.__init__.py, check whether you removed the app')
patches_loaded = True
connect = frappe.connect
def custom_connect(*args, **kwargs):
out = connect(*args, **kwargs)
load_monkey_patches()
return out
frappe.connect = custom_connect

View File

@@ -0,0 +1,35 @@
default_bank_account,银行存款
default_cash_account,库存现金
default_payable_account,应付账款
default_payable_account,应付账款-供应商款
default_payable_account,应付账款-结算
default_payroll_payable_account,应付职工薪酬-职工工资
default_payroll_payable_account,职工工资
default_receivable_account,应收账款
round_off_account,财务费用-圆整差异
write_off_account,营业外支出-坏账损失
exchange_gain_loss_account,财务费用-汇兑损益
exchange_gain_loss_account,财务费用_汇兑差额
unrealized_exchange_gain_loss_account,财务费用-汇兑损益
default_income_account,主营业务收入
default_income_account,销售商品收入
default_expense_account,主营业务成本
default_expense_account,销售商品成本
default_expense_claim_payable_account,其他应付款
default_discount_account,财务费用-现金折扣
default_inventory_account,原材料
stock_adjustment_account,生产成本-库存调整
stock_adjustment_account,制造企业成本-库存调整
stock_received_but_not_billed,应付账款-暂估库存
default_provisional_account,应付账款-暂估服务
service_received_but_not_billed,应付账款-暂估服务
expenses_included_in_valuation,制造费用-结转库存
expenses_included_in_valuation,制造企业成本-结转库存
accumulated_depreciation_account,累计折旧
depreciation_expense_account,管理费用-折旧费
disposal_account,固定资产清理
asset_received_but_not_billed,应付账款-暂估资产
capital_work_in_progress_account,在建工程
expenses_included_in_asset_valuation,制造费用-结转资产
default_advance_received_account,预收账款
default_advance_paid_account,预付账款
1 default_bank_account 银行存款
2 default_cash_account 库存现金
3 default_payable_account 应付账款
4 default_payable_account 应付账款-供应商款
5 default_payable_account 应付账款-结算
6 default_payroll_payable_account 应付职工薪酬-职工工资
7 default_payroll_payable_account 职工工资
8 default_receivable_account 应收账款
9 round_off_account 财务费用-圆整差异
10 write_off_account 营业外支出-坏账损失
11 exchange_gain_loss_account 财务费用-汇兑损益
12 exchange_gain_loss_account 财务费用_汇兑差额
13 unrealized_exchange_gain_loss_account 财务费用-汇兑损益
14 default_income_account 主营业务收入
15 default_income_account 销售商品收入
16 default_expense_account 主营业务成本
17 default_expense_account 销售商品成本
18 default_expense_claim_payable_account 其他应付款
19 default_discount_account 财务费用-现金折扣
20 default_inventory_account 原材料
21 stock_adjustment_account 生产成本-库存调整
22 stock_adjustment_account 制造企业成本-库存调整
23 stock_received_but_not_billed 应付账款-暂估库存
24 default_provisional_account 应付账款-暂估服务
25 service_received_but_not_billed 应付账款-暂估服务
26 expenses_included_in_valuation 制造费用-结转库存
27 expenses_included_in_valuation 制造企业成本-结转库存
28 accumulated_depreciation_account 累计折旧
29 depreciation_expense_account 管理费用-折旧费
30 disposal_account 固定资产清理
31 asset_received_but_not_billed 应付账款-暂估资产
32 capital_work_in_progress_account 在建工程
33 expenses_included_in_asset_valuation 制造费用-结转资产
34 default_advance_received_account 预收账款
35 default_advance_paid_account 预付账款

View File

@@ -0,0 +1,91 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe, json
from frappe import _
from frappe.utils import cstr, unique, cint
from six import string_types
from frappe.desk.search import get_std_fields_list
import re
# searches for active employees
@frappe.whitelist()
def doctype_role_report_query(doctype, txt, searchfield=None, start=0, page_len=None, filters=None,reference_doctype=None,ignore_user_permissions=False):
start = cint(start)
filter_fields=None
if isinstance(filters, string_types):
filters = json.loads(filters)
if not searchfield:
searchfield = "name"
as_dict=False
meta = frappe.get_meta(doctype)
if isinstance(filters, dict):
filters_items = filters.items()
filters = []
for f in filters_items:
if isinstance(f[1], (list, tuple)):
filters.append([doctype, f[0], f[1][0], f[1][1]])
else:
filters.append([doctype, f[0], "=", f[1]])
if filters==None:
filters = []
or_filters = []
# build from doctype
if txt:
search_fields = ["name"]
if meta.title_field:
search_fields.append(meta.title_field)
if meta.search_fields:
search_fields.extend(meta.get_search_fields())
if meta.get("fields", {"fieldname":"enabled", "fieldtype":"Check"}):
filters.append([doctype, "enabled", "=", 1])
if meta.get("fields", {"fieldname":"disabled", "fieldtype":"Check"}):
filters.append([doctype, "disabled", "!=", 1])
# format a list of fields combining search fields and filter fields
fields = get_std_fields_list(meta, searchfield or "name")
if filter_fields:
fields = list(set(fields + json.loads(filter_fields)))
formatted_fields = ['`tab%s`.`%s`' % (meta.name, f.strip()) for f in fields]
# find relevance as location of search term from the beginning of string `name`. used for sorting results.
formatted_fields.append("""locate({_txt}, `tab{doctype}`.`name`) as `_relevance`""".format(
_txt=frappe.db.escape((txt or "").replace("%", "").replace("@", "")), doctype=doctype))
# In order_by, `idx` gets second priority, because it stores link count
from frappe.model.db_query import get_order_by
order_by_based_on_meta = get_order_by(doctype, meta)
# 2 is the index of _relevance column
order_by = "_relevance, {0}, `tab{1}`.idx desc".format(order_by_based_on_meta, doctype)
ptype = 'select' if frappe.only_has_select_perm(doctype) else 'read'
ignore_permissions = True if doctype == "DocType" else (cint(ignore_user_permissions) and frappe.has_permission(doctype, ptype=ptype))
page_length = None
values = frappe.get_list(doctype,
filters=filters,
fields=formatted_fields,
or_filters=or_filters,
limit_start=start,
limit_page_length=page_length,
order_by=order_by,
ignore_permissions=ignore_permissions,
reference_doctype=reference_doctype,
as_list=not as_dict,
strict=False)
values = tuple([v for v in list(values) if re.search(re.escape(txt)+".*", (_(v.name) if as_dict else _(v[0])), re.IGNORECASE)
or re.search(re.escape(txt)+".*", ((v.name) if as_dict else (v[0])), re.IGNORECASE)])
return [r[:-1] for r in values] #remove _relevancy

View File

@@ -0,0 +1,12 @@
tax_category,tax_type,customer_group,item_group,billing_country,shipping_country,priority,tax_template
P13专票含税,Sales,,,,,1,P13专票含税
P3专票含税,Sales,,,,,1,P3专票含税
P13专票未税,Sales,,,,,1,P13专票未税
P3专票未税,Sales,,,,,1,P3专票未税
P13专票含税,Purchase,,,,,1,P13专票含税
P3专票含税,Purchase,,,,,1,P3专票含税
P1专票含税,Purchase,,,,,1,P1专票含税
P13专票未税,Purchase,,,,,1,P13专票未税
P0无税,Purchase,,,,,1,P0无税
,Sales,,,China,China,99,P13专票含税
,Purchase,,,China,China,99,P13专票含税
1 tax_category tax_type customer_group item_group billing_country shipping_country priority tax_template
2 P13专票含税 Sales 1 P13专票含税
3 P3专票含税 Sales 1 P3专票含税
4 P13专票未税 Sales 1 P13专票未税
5 P3专票未税 Sales 1 P3专票未税
6 P13专票含税 Purchase 1 P13专票含税
7 P3专票含税 Purchase 1 P3专票含税
8 P1专票含税 Purchase 1 P1专票含税
9 P13专票未税 Purchase 1 P13专票未税
10 P0无税 Purchase 1 P0无税
11 Sales China China 99 P13专票含税
12 Purchase China China 99 P13专票含税

View File

@@ -0,0 +1,481 @@
{
"tax_categories": [
"P13专票含税",
"P3专票含税",
"P1专票含税",
"P13专票未税",
"P3专票未税",
"P1专票未税",
"P0无税"
],
"chart_of_accounts": {
"小企业会计准则": {
"sales_tax_templates": [
{
"title": "P13专票含税",
"tax_category": "P13专票含税",
"is_default": 0,
"taxes": [
{
"account_head": {
"account_name":"应交税费-应交增值税-销项税额",
"account_number":"22210108",
"included_in_print_rate": 1,
"tax_rate": 13
}
}
]
},
{
"title": "P3专票含税",
"tax_category": "P3专票含税",
"is_default": 0,
"taxes": [
{
"account_head": {
"account_name":"应交税费-应交增值税-销项税额",
"account_number":"22210108",
"included_in_print_rate": 1,
"tax_rate": 3
}
}
]
},
{
"title": "P13专票未税",
"tax_category": "P13专票未税",
"is_default": 0,
"taxes": [
{
"account_head": {
"account_name":"应交税费-应交增值税-销项税额",
"account_number":"22210108",
"included_in_print_rate": 0,
"tax_rate": 13
}
}
]
},
{
"title": "P3专票未税",
"tax_category": "P3专票未税",
"is_default": 0,
"taxes": [
{
"account_head": {
"account_name":"应交税费-应交增值税-销项税额",
"account_number":"22210108",
"included_in_print_rate": 0,
"tax_rate": 3
}
}
]
},
{
"title": "P0无税",
"tax_category": "P0无税",
"is_default": 0,
"taxes": [
{
"account_head": {
"account_name":"应交税费-应交增值税-销项税额",
"account_number":"22210108",
"included_in_print_rate": 0,
"tax_rate": 0
}
}
]
}
],
"purchase_tax_templates": [
{
"title": "P1专票含税",
"tax_category": "P1专票含税",
"is_default": 0,
"taxes": [
{
"account_head": {
"account_name":"应交税费-应交增值税-进项税额",
"account_number":"22210101",
"included_in_print_rate": 1,
"tax_rate": 1
}
}
]
},
{
"title": "P3专票含税",
"tax_category": "P3专票含税",
"is_default": 0,
"taxes": [
{
"account_head": {
"account_name":"应交税费-应交增值税-进项税额",
"account_number":"22210101",
"included_in_print_rate": 1,
"tax_rate": 3
}
}
]
},
{
"title": "P13专票含税",
"tax_category": "P13专票含税",
"is_default": 0,
"taxes": [
{
"account_head": {
"account_name":"应交税费-应交增值税-进项税额",
"account_number":"22210101",
"included_in_print_rate": 1,
"tax_rate": 13
}
}
]
},
{
"title": "P13专票未税",
"tax_category": "P13专票未税",
"is_default": 0,
"taxes": [
{
"account_head": {
"account_name":"应交税费-应交增值税-进项税额",
"account_number":"22210101",
"included_in_print_rate": 0,
"tax_rate": 13
}
}
]
},
{
"title": "P0无税",
"tax_category": "P0无税",
"is_default": 0,
"taxes": [
{
"account_head": {
"account_name":"应交税费-应交增值税-进项税额",
"account_number":"22210101",
"included_in_print_rate": 0,
"tax_rate": 0
}
}
]
}
]
},
"小企业会计准则(2024)": {
"sales_tax_templates": [
{
"title": "P13专票含税",
"tax_category": "P13专票含税",
"is_default": 0,
"taxes": [
{
"account_head": {
"account_name":"销项税额",
"account_number":"222105",
"included_in_print_rate": 1,
"tax_rate": 13
}
}
]
},
{
"title": "P3专票含税",
"tax_category": "P3专票含税",
"is_default": 0,
"taxes": [
{
"account_head": {
"account_name":"销项税额",
"account_number":"2221005",
"included_in_print_rate": 1,
"tax_rate": 3
}
}
]
},
{
"title": "P13专票未税",
"tax_category": "P13专票未税",
"is_default": 0,
"taxes": [
{
"account_head": {
"account_name":"销项税额",
"account_number":"22210005",
"included_in_print_rate": 0,
"tax_rate": 13
}
}
]
},
{
"title": "P3专票未税",
"tax_category": "P3专票未税",
"is_default": 0,
"taxes": [
{
"account_head": {
"account_name":"销项税额",
"account_number":"2221005",
"included_in_print_rate": 0,
"tax_rate": 3
}
}
]
},
{
"title": "P0无税",
"tax_category": "P0无税",
"is_default": 0,
"taxes": [
{
"account_head": {
"account_name":"销项税额",
"account_number":"2221005",
"included_in_print_rate": 0,
"tax_rate": 0
}
}
]
}
],
"purchase_tax_templates": [
{
"title": "P1专票含税",
"tax_category": "P1专票含税",
"is_default": 0,
"taxes": [
{
"account_head": {
"account_name":"进项税额",
"account_number":"2221001",
"included_in_print_rate": 1,
"tax_rate": 1
}
}
]
},
{
"title": "P3专票含税",
"tax_category": "P3专票含税",
"is_default": 0,
"taxes": [
{
"account_head": {
"account_name":"进项税额",
"account_number":"2221001",
"included_in_print_rate": 1,
"tax_rate": 3
}
}
]
},
{
"title": "P13专票含税",
"tax_category": "P13专票含税",
"is_default": 0,
"taxes": [
{
"account_head": {
"account_name":"进项税额",
"account_number":"2221001",
"included_in_print_rate": 1,
"tax_rate": 13
}
}
]
},
{
"title": "P13专票未税",
"tax_category": "P13专票未税",
"is_default": 0,
"taxes": [
{
"account_head": {
"account_name":"进项税额",
"account_number":"2221001",
"included_in_print_rate": 0,
"tax_rate": 13
}
}
]
},
{
"title": "P0无税",
"tax_category": "P0无税",
"is_default": 0,
"taxes": [
{
"account_head": {
"account_name":"进项税额",
"account_number":"2221001",
"included_in_print_rate": 0,
"tax_rate": 0
}
}
]
}
]
},
"一般企业会计准则(2024)": {
"sales_tax_templates": [
{
"title": "P13专票含税",
"tax_category": "P13专票含税",
"is_default": 0,
"taxes": [
{
"account_head": {
"account_name":"待转销项税额",
"account_number":"22210210",
"included_in_print_rate": 1,
"tax_rate": 13
}
}
]
},
{
"title": "P3专票含税",
"tax_category": "P3专票含税",
"is_default": 0,
"taxes": [
{
"account_head": {
"account_name":"待转销项税额",
"account_number":"22210210",
"included_in_print_rate": 1,
"tax_rate": 3
}
}
]
},
{
"title": "P13专票未税",
"tax_category": "P13专票未税",
"is_default": 0,
"taxes": [
{
"account_head": {
"account_name":"待转销项税额",
"account_number":"22210210",
"included_in_print_rate": 0,
"tax_rate": 13
}
}
]
},
{
"title": "P3专票未税",
"tax_category": "P3专票未税",
"is_default": 0,
"taxes": [
{
"account_head": {
"account_name":"待转销项税额",
"account_number":"22210210",
"included_in_print_rate": 0,
"tax_rate": 3
}
}
]
},
{
"title": "P0无税",
"tax_category": "P0无税",
"is_default": 0,
"taxes": [
{
"account_head": {
"account_name":"待转销项税额",
"account_number":"22210210",
"included_in_print_rate": 0,
"tax_rate": 0
}
}
]
}
],
"purchase_tax_templates": [
{
"title": "P1专票含税",
"tax_category": "P1专票含税",
"is_default": 0,
"taxes": [
{
"account_head": {
"account_name":"待抵扣进项税额",
"account_number":"22210190",
"included_in_print_rate": 1,
"tax_rate": 1
}
}
]
},
{
"title": "P3专票含税",
"tax_category": "P3专票含税",
"is_default": 0,
"taxes": [
{
"account_head": {
"account_name":"待抵扣进项税额",
"account_number":"22210190",
"included_in_print_rate": 1,
"tax_rate": 3
}
}
]
},
{
"title": "P13专票含税",
"tax_category": "P13专票含税",
"is_default": 0,
"taxes": [
{
"account_head": {
"account_name":"待抵扣进项税额",
"account_number":"22210190",
"included_in_print_rate": 1,
"tax_rate": 13
}
}
]
},
{
"title": "P13专票未税",
"tax_category": "P13专票未税",
"is_default": 0,
"taxes": [
{
"account_head": {
"account_name":"待抵扣进项税额",
"account_number":"22210190",
"included_in_print_rate": 0,
"tax_rate": 13
}
}
]
},
{
"title": "P0无税",
"tax_category": "P0无税",
"is_default": 0,
"taxes": [
{
"account_head": {
"account_name":"待抵扣进项税额",
"account_number":"22210190",
"included_in_print_rate": 0,
"tax_rate": 0
}
}
]
}
]
}
}
}

View File

@@ -0,0 +1,177 @@
import frappe, csv, os, json
from frappe import _
from erpnext.setup.setup_wizard.operations.taxes_setup import from_detailed_data
def set_company_default(company):
set_default_accounts(company)
setup_tax_template(company)
setup_tax_rule(company)
set_item_group_account(company)
set_warehouse_account(company)
def set_default_accounts(company_name):
try:
file_path = os.path.join(os.path.dirname(__file__), 'default_accounts.csv')
with open(file_path, 'r', encoding='utf-8') as in_file:
data = list(csv.reader(in_file))
company_doc = frappe.get_doc('Company', company_name)
account_map = frappe._dict(frappe.get_all("Account",
filters = {
"company": company_name,
"account_name": ("in", [d[1] for d in data]),
"is_group": 0
},
fields = ["account_name", "name"],
as_list = 1
))
if account_map:
values = {d[0]:account_map.get(d[1]) for d in data if account_map.get(d[1])}
company_doc.update(values)
frappe.local.flags.ignore_chart_of_accounts = 1
company_doc.db_update()
return values
except:
frappe.log_error("china_company_default.utils.set_default_accounts")
def setup_tax_template(company_name):
try:
file_path = os.path.join(os.path.dirname(__file__), 'tax_template.json')
with open(file_path, 'r', encoding='utf-8') as json_file:
tax_data = json.load(json_file)
from_detailed_data(company_name, tax_data)
#标准功能中未处理含税字段,这里单独处理
for prefix in ('Purchase', 'Sales'):
header = frappe.qb.DocType(f"{prefix} Taxes and Charges Template")
detail = frappe.qb.DocType(f"{prefix} Taxes and Charges")
frappe.qb.update(detail
).join(header
).on(header.name == detail.parent
).where(header.title.like('%含税%')
).set(detail.included_in_print_rate, 1
).run()
except:
frappe.log_error("china_company_default.utils.setup_tax_template")
def setup_tax_rule(company_name):
try:
abbr = frappe.db.get_value('Company', company_name, 'abbr')
file_path = os.path.join(os.path.dirname(__file__), 'tax_rule.csv')
with open(file_path, 'r', encoding='utf-8') as in_file:
data = list(csv.reader(in_file))
if data: data = data[1:]
for (tax_category, tax_type, customer_group, item_group, billing_country,
shipping_country, priority, tax_template) in data:
template_field_name = 'purchase_tax_template' if tax_type =='Purchase' else 'sales_tax_template'
tax_rule = frappe.get_doc({
'doctype':'Tax Rule',
'tax_category': tax_category,
'tax_type': tax_type,
'customer_group': customer_group,
'item_group': item_group,
'billing_country': billing_country,
'shipping_country': shipping_country,
'priority': priority,
template_field_name: f'{tax_template} - {abbr}',
'company': company_name})
tax_rule.insert(ignore_permissions = 1)
except:
frappe.log_error("china_company_default.utils.setup_tax_rule")
def set_warehouse_account(company):
abbr = frappe.db.get_value('Company', company, 'abbr')
try:
wh_account_list = [
[_("Finished Goods"), '库存商品'],
[_("Work In Progress"), '在产品']
]
account_map = frappe._dict(frappe.get_all('Account',
filters ={'account_name': ('in', [row[1] for row in wh_account_list]),
'company': company},
fields = ['account_name', 'name'],
as_list = True))
for (wh, account_name) in wh_account_list:
if account_id := account_map.get(account_name):
frappe.db.set_value('Warehouse', f'{wh} - {abbr}', 'account', account_id)
except:
frappe.log_error("china_company_default.utils.setup_warehouse_account")
def set_item_group_account(company):
try:
# 按会计准则分组配置科目映射
chart_of_accounts_config = {
'小企业会计准则': [
(_("Raw Material"), '生产成本-基本生产成本'),
(_("Sub Assemblies"), '生产成本-基本生产成本'),
(_("Consumable"), '生产成本-基本生产成本'),
(_("Services"), '生产成本-辅助生产成本'),
(_("Product"), '主营业务成本')
],
'一般企业会计准则(2024)': [
(_("Raw Material"), '制造企业成本-直接材料'),
(_("Sub Assemblies"), '制造企业成本-直接材料'),
(_("Consumable"), '制造企业成本-直接材料'),
#(_("Services"), '生产成本-辅助生产成本'),
(_("Product"), '销售商品成本')
]
}
# 获取公司的会计准则
company_doc = frappe.get_doc('Company', company)
chart_of_accounts = getattr(company_doc, 'chart_of_accounts', None)
# 如果没有设置会计准则,使用默认配置(小企业会计准则)
if not chart_of_accounts or chart_of_accounts not in chart_of_accounts_config:
chart_of_accounts = '小企业会计准则'
# 获取当前会计准则对应的科目配置
item_group_account_list = chart_of_accounts_config[chart_of_accounts]
# 获取科目映射
account_names = [row[1] for row in item_group_account_list]
account_map = frappe._dict(frappe.get_all('Account',
filters={
'account_name': ('in', account_names),
'company': company,
'is_group': 0
},
fields=['account_name', 'name'],
as_list=True
))
item_group_account_assigned = set()
for (item_group, account_name) in item_group_account_list:
if item_group in item_group_account_assigned:
continue
account_id = account_map.get(account_name)
if account_id and frappe.db.exists('Item Group', item_group):
item_group_doc = frappe.get_doc('Item Group', item_group)
# 检查是否已存在该公司的配置
existing_company_config = None
for default in item_group_doc.get('item_group_defaults', []):
if default.company == company:
existing_company_config = default
break
# 如果已存在配置,则更新;否则新增
if existing_company_config:
existing_company_config.expense_account = account_id
else:
item_group_doc.append('item_group_defaults', {
'company': company,
'expense_account': account_id
})
item_group_doc.save()
item_group_account_assigned.add(item_group)
except Exception as e:
frappe.log_error("china_company_default.utils.set_item_group_account")

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,255 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
import json
import os
import frappe
from frappe import _
from frappe.utils import cstr
from frappe.utils.nestedset import rebuild_tree
from unidecode import unidecode
from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import (
get_chart as original_get_chart,
add_suffix_if_duplicate,
identify_is_group,
get_account_tree_from_existing_company
)
def erpnext_china_create_charts(
company, chart_template=None, existing_company=None, custom_chart=None, from_coa_importer=None
):
chart = custom_chart or get_chart(chart_template, existing_company)
if chart:
accounts = []
def _import_accounts(children, parent, root_type, root_account=False):
for account_name, child in children.items():
if root_account:
root_type = child.get("root_type")
if account_name not in [
"account_name",
"account_number",
"account_type",
"root_type",
"is_group",
"tax_rate",
"account_currency",
]:
account_number = cstr(child.get("account_number")).strip()
account_name, account_name_in_db = add_suffix_if_duplicate(
account_name, account_number, accounts
)
is_group = identify_is_group(child)
report_type = (
"Balance Sheet"
if root_type in ["Asset", "Liability", "Equity"]
else "Profit and Loss"
)
account = frappe.get_doc(
{
"doctype": "Account",
"account_name": child.get("account_name") if from_coa_importer else account_name,
"company": company,
"parent_account": parent,
"is_group": is_group,
"root_type": root_type,
"report_type": report_type,
"account_number": account_number,
"account_type": child.get("account_type"),
"account_currency": child.get("account_currency")
or frappe.get_cached_value("Company", company, "default_currency"),
"tax_rate": child.get("tax_rate"),
}
)
if root_account or frappe.local.flags.allow_unverified_charts:
account.flags.ignore_mandatory = True
account.flags.ignore_permissions = True
account.insert()
accounts.append(account_name_in_db)
_import_accounts(child, account.name, root_type)
frappe.local.flags.ignore_update_nsm = True
try:
_import_accounts(chart, None, None, root_account=True)
rebuild_tree("Account")
except Exception as e:
frappe.log_error(f"Error rebuilding tree in create_charts2: {e}", title="Tree Rebuild Error")
frappe.local.flags.ignore_update_nsm = False
def add_suffix_if_duplicate(account_name, account_number, accounts):
if account_number:
account_name_in_db = unidecode(" - ".join([account_number, account_name.strip().lower()]))
else:
account_name_in_db = unidecode(account_name.strip().lower())
if account_name_in_db in accounts:
count = accounts.count(account_name_in_db)
account_name = account_name + " " + cstr(count)
return account_name, account_name_in_db
def identify_is_group(child):
if child.get("is_group"):
is_group = child.get("is_group")
elif len(
set(child.keys())
- set(
[
"account_name",
"account_type",
"root_type",
"is_group",
"tax_rate",
"account_number",
"account_currency",
]
)
):
is_group = 1
else:
is_group = 0
return is_group
@frappe.whitelist()
def get_coa(doctype, parent, is_root=None, chart=None):
from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import (
build_tree_from_json,
)
# add chart to flags to retrieve when called from expand all function
chart = chart if chart else frappe.flags.chart
frappe.flags.chart = chart
# 获取科目表数据
chart_data = get_chart(chart)
#传获取的科目表数据
parent = None if parent == _("All Accounts") else parent
accounts = build_tree_from_json(chart, chart_data) # returns alist of dict in a tree render-able form
# filter out to show data for the selected node only
accounts = [d for d in accounts if d["parent_account"] == parent]
return accounts
@frappe.whitelist()
def get_chart(chart_template, existing_company=None):
# 标准科目表直接调用源方法
if chart_template in ["Standard", "Standard with Numbers"]:
return original_get_chart(chart_template, existing_company)
chart = {}
if existing_company:
return get_account_tree_from_existing_company(existing_company)
else:
bench_dir = frappe.utils.get_bench_path()
erpnext_charts_path = os.path.join(bench_dir, "apps", "erpnext", "erpnext", "accounts", "doctype", "account", "chart_of_accounts")
folders = ("verified",)
if frappe.local.flags.allow_unverified_charts:
folders = ("verified", "unverified")
for folder in folders:
path = os.path.join(erpnext_charts_path, folder)
for fname in os.listdir(path):
fname = frappe.as_unicode(fname)
if fname.endswith(".json"):
try:
with open(os.path.join(path, fname)) as f:
chart = f.read()
if chart and json.loads(chart).get("name") == chart_template:
return json.loads(chart).get("tree")
except Exception as e:
frappe.log_error(f"Error reading chart file in get_chart: {e}", title="Chart File Read Error")
custom_path = os.path.join(bench_dir, "apps", "erpnext_china", "erpnext_china", "chart_of_accounts", "custom_accounts")
custom_folders = ("chart_of_accounts", "custom_of_accounts")
for custom_folder in custom_folders:
custom_charts_path = os.path.join(custom_path, custom_folder)
if os.path.exists(custom_charts_path):
for fname1 in os.listdir(custom_charts_path):
fname1 = frappe.as_unicode(fname1)
if fname1.endswith(".json"):
try:
with open(os.path.join(custom_charts_path, fname1)) as f1:
chart1 = f1.read()
if chart1 and json.loads(chart1).get("name") == chart_template:
return json.loads(chart1).get("tree")
except Exception as e:
frappe.log_error(f"Error reading custom chart file in get_chart: {e}", title="Custom Chart File Read Error")
return chart
@frappe.whitelist()
def get_charts_for_country(country, with_standard=False):
charts = []
bench_dir = frappe.utils.get_bench_path()
def _get_chart_name(content):
if content:
try:
content = json.loads(content)
if (
content and content.get("disabled", "No") == "No"
) or frappe.local.flags.allow_unverified_charts:
charts.append(content["name"])
except Exception as e:
frappe.log_error(f"Error parsing chart content in get_charts_for_country: {e}", title="Chart Content Parse Error")
country_code = frappe.get_cached_value("Country", country, "code")
if country_code:
folders = ("verified",)
if frappe.local.flags.allow_unverified_charts:
folders = ("verified", "unverified")
erpnext_charts_path = os.path.join(bench_dir, "apps", "erpnext", "erpnext", "accounts", "doctype", "account", "chart_of_accounts")
for folder in folders:
path = os.path.join(erpnext_charts_path, folder)
if os.path.exists(path):
for fname in os.listdir(path):
fname = frappe.as_unicode(fname)
if (fname.startswith(country_code) or fname.startswith(country)) and fname.endswith(".json"):
try:
with open(os.path.join(path, fname)) as f:
_get_chart_name(f.read())
except Exception as e:
frappe.log_error(f"Error reading country chart file in get_charts_for_country: {e}", title="Country Chart File Read Error")
# if more than one charts, returned then add the standard
if len(charts) != 1 or with_standard:
charts += ["Standard", "Standard with Numbers"]
custom_path = os.path.join(bench_dir, "apps", "erpnext_china", "erpnext_china", "chart_of_accounts", "custom_accounts")
custom_folders = ("chart_of_accounts", "custom_of_accounts")
for custom_folder in custom_folders:
custom_charts_path = os.path.join(custom_path, custom_folder)
if os.path.exists(custom_charts_path):
for fname1 in os.listdir(custom_charts_path):
fname1 = frappe.as_unicode(fname1)
if (fname1.startswith(country_code) or fname1.startswith(country)) and fname1.endswith(".json"):
try:
with open(os.path.join(custom_charts_path, fname1)) as f1:
_get_chart_name(f1.read())
except Exception as e:
frappe.log_error(f"Error reading custom country chart file in get_charts_for_country: {e}", title="Custom Country Chart File Read Error")
return charts
@frappe.whitelist()
def get_all_nodes(doctype, label, parent, tree_method, **filters):
from frappe.desk.treeview import get_all_nodes as original_get_all_nodes
tree_method = frappe.override_whitelisted_method(tree_method)
return original_get_all_nodes(doctype, label, parent, tree_method, **filters)

View File

@@ -0,0 +1,28 @@
// Copyright (c) 2023, Vnimy and contributors
// For license information, please see license.txt
frappe.ui.form.on('Balance Sheet Settings', {
refresh: function(frm) {
if (!frm.doc.items || frm.doc.items.length === 0){
frm.add_custom_button('导入范例数据', function() {
frm.events.import_example_data(frm);
});
}
if (!frm.is_new() && frm.doc.items && frm.doc.items.length) {
frm.add_custom_button(__('Check Missing Accounts'),
() =>{
frappe.set_route("query-report",
"BS and PL Missing Account",
{report: "BS" }
);
});
}
},
import_example_data(frm) {
frm.call('get_example_data').then(r => {
r.message.items.sort((a, b) => a.idx > b.idx ? 1 : -1);
frm.set_value(r.message);
});
},
});

View File

@@ -0,0 +1,76 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2023-09-12 15:14:47.840937",
"default_view": "List",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"asset_row",
"column_break_bekm2",
"liability_row",
"column_break_ajdro",
"equity_row",
"section_break_8tig1",
"items"
],
"fields": [
{
"fieldname": "asset_row",
"fieldtype": "Int",
"label": "\u8d44\u4ea7\u5408\u8ba1\u884c"
},
{
"fieldname": "column_break_bekm2",
"fieldtype": "Column Break"
},
{
"fieldname": "liability_row",
"fieldtype": "Int",
"label": "\u8d1f\u503a\u5408\u8ba1\u884c"
},
{
"fieldname": "column_break_ajdro",
"fieldtype": "Column Break"
},
{
"fieldname": "equity_row",
"fieldtype": "Int",
"label": "\u6743\u76ca\u5408\u8ba1\u884c"
},
{
"fieldname": "section_break_8tig1",
"fieldtype": "Section Break"
},
{
"fieldname": "items",
"fieldtype": "Table",
"label": "\u9879\u76ee\u8bbe\u5b9a",
"options": "Balance Sheet Settings Item"
}
],
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2026-01-06 10:44:17.240196",
"modified_by": "Administrator",
"module": "ERPNext China",
"name": "Balance Sheet Settings",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"print": 1,
"read": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}

View File

@@ -0,0 +1,55 @@
# Copyright (c) 2023, Vnimy and contributors
# For license information, please see license.txt
import frappe
from frappe import _
from frappe.model.document import Document
import os
class BalanceSheetSettings(Document):
@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 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)

View File

@@ -0,0 +1,597 @@
{
"asset_row": 31,
"liability_row": 22,
"equity_row": 30,
"doctype": "Balance Sheet Settings",
"items": [
{
"idx": 1,
"lft_empty": 0,
"lft_bold": 1,
"lft_name": "流动资产:",
"lft_indent": 0,
"lft_calc_type": "",
"lft_calc_sources": "",
"rgt_empty": 0,
"rgt_bold": 1,
"rgt_name": "流动负债:",
"rgt_indent": 0,
"rgt_calc_type": "",
"rgt_calc_sources": "",
"parent": "Balance Sheet Settings",
"parentfield": "items",
"parenttype": "Balance Sheet Settings",
"doctype": "Balance Sheet Settings Item"
},
{
"idx": 2,
"lft_empty": 0,
"lft_bold": 0,
"lft_name": "货币资金",
"lft_indent": 1,
"lft_calc_type": "Closing Balance",
"lft_calc_sources": "1001,100201,100202,1012",
"rgt_empty": 0,
"rgt_bold": 0,
"rgt_name": "短期借款",
"rgt_indent": 1,
"rgt_calc_type": "Closing Balance",
"rgt_calc_sources": "2001",
"parent": "Balance Sheet Settings",
"parentfield": "items",
"parenttype": "Balance Sheet Settings",
"doctype": "Balance Sheet Settings Item"
},
{
"idx": 3,
"lft_empty": 0,
"lft_bold": 0,
"lft_name": "短期投资",
"lft_indent": 1,
"lft_calc_type": "Closing Balance",
"lft_calc_sources": "1101",
"rgt_empty": 0,
"rgt_bold": 0,
"rgt_name": "应付票据",
"rgt_indent": 1,
"rgt_calc_type": "Closing Balance",
"rgt_calc_sources": "2201",
"parent": "Balance Sheet Settings",
"parentfield": "items",
"parenttype": "Balance Sheet Settings",
"doctype": "Balance Sheet Settings Item"
},
{
"idx": 4,
"lft_empty": 0,
"lft_bold": 0,
"lft_name": "应收票据",
"lft_indent": 1,
"lft_calc_type": "Closing Balance",
"lft_calc_sources": "1121",
"rgt_empty": 0,
"rgt_bold": 0,
"rgt_name": "应付帐款",
"rgt_indent": 1,
"rgt_calc_type": "Closing Balance",
"rgt_calc_sources": "2202",
"parent": "Balance Sheet Settings",
"parentfield": "items",
"parenttype": "Balance Sheet Settings",
"doctype": "Balance Sheet Settings Item"
},
{
"idx": 5,
"lft_empty": 0,
"lft_bold": 0,
"lft_name": "应收账款",
"lft_indent": 1,
"lft_calc_type": "Closing Balance",
"lft_calc_sources": "1122",
"rgt_empty": 0,
"rgt_bold": 0,
"rgt_name": "预收帐款",
"rgt_indent": 1,
"rgt_calc_type": "Closing Balance",
"rgt_calc_sources": "2203",
"parent": "Balance Sheet Settings",
"parentfield": "items",
"parenttype": "Balance Sheet Settings",
"doctype": "Balance Sheet Settings Item"
},
{
"idx": 6,
"lft_empty": 0,
"lft_bold": 0,
"lft_name": "预付账款",
"lft_indent": 1,
"lft_calc_type": "Closing Balance",
"lft_calc_sources": "1123",
"rgt_empty": 0,
"rgt_bold": 0,
"rgt_name": "应付职工薪酬",
"rgt_indent": 1,
"rgt_calc_type": "Closing Balance",
"rgt_calc_sources": "2211",
"parent": "Balance Sheet Settings",
"parentfield": "items",
"parenttype": "Balance Sheet Settings",
"doctype": "Balance Sheet Settings Item"
},
{
"idx": 7,
"lft_empty": 0,
"lft_bold": 0,
"lft_name": "应收股利",
"lft_indent": 1,
"lft_calc_type": "Closing Balance",
"lft_calc_sources": "1131",
"rgt_empty": 0,
"rgt_bold": 0,
"rgt_name": "应交税费",
"rgt_indent": 1,
"rgt_calc_type": "Closing Balance",
"rgt_calc_sources": "2221",
"parent": "Balance Sheet Settings",
"parentfield": "items",
"parenttype": "Balance Sheet Settings",
"doctype": "Balance Sheet Settings Item"
},
{
"idx": 8,
"lft_empty": 0,
"lft_bold": 0,
"lft_name": "应收利息",
"lft_indent": 1,
"lft_calc_type": "Closing Balance",
"lft_calc_sources": "1132",
"rgt_empty": 0,
"rgt_bold": 0,
"rgt_name": "应付利息",
"rgt_indent": 1,
"rgt_calc_type": "Closing Balance",
"rgt_calc_sources": "2231",
"parent": "Balance Sheet Settings",
"parentfield": "items",
"parenttype": "Balance Sheet Settings",
"doctype": "Balance Sheet Settings Item"
},
{
"idx": 9,
"lft_empty": 0,
"lft_bold": 0,
"lft_name": "其他应收款",
"lft_indent": 1,
"lft_calc_type": "Closing Balance",
"lft_calc_sources": "1221",
"rgt_empty": 0,
"rgt_bold": 0,
"rgt_name": "应付利润",
"rgt_indent": 1,
"rgt_calc_type": "Closing Balance",
"rgt_calc_sources": "2232",
"parent": "Balance Sheet Settings",
"parentfield": "items",
"parenttype": "Balance Sheet Settings",
"doctype": "Balance Sheet Settings Item"
},
{
"idx": 10,
"lft_empty": 0,
"lft_bold": 0,
"lft_name": "存货",
"lft_indent": 1,
"lft_calc_type": "Closing Balance",
"lft_calc_sources": "1401,1402,1403,1404,1405,1406,1407,1408,1409,1410,1411,1412,1413,1414,1415,1416,1417,1418,1419,1420,1421",
"rgt_empty": 0,
"rgt_bold": 0,
"rgt_name": "其他应付款",
"rgt_indent": 1,
"rgt_calc_type": "Closing Balance",
"rgt_calc_sources": "2241",
"parent": "Balance Sheet Settings",
"parentfield": "items",
"parenttype": "Balance Sheet Settings",
"doctype": "Balance Sheet Settings Item"
},
{
"idx": 11,
"lft_empty": 0,
"lft_bold": 0,
"lft_name": "其中:原材料",
"lft_indent": 2,
"lft_calc_type": "Closing Balance",
"lft_calc_sources": "1403",
"rgt_empty": 1,
"rgt_bold": 0,
"rgt_name": "",
"rgt_indent": 0,
"rgt_calc_type": "",
"rgt_calc_sources": "",
"parent": "Balance Sheet Settings",
"parentfield": "items",
"parenttype": "Balance Sheet Settings",
"doctype": "Balance Sheet Settings Item"
},
{
"idx": 12,
"lft_empty": 0,
"lft_bold": 0,
"lft_name": "库存商品",
"lft_indent": 2,
"lft_calc_type": "Closing Balance",
"lft_calc_sources": "1405,1406",
"rgt_empty": 1,
"rgt_bold": 0,
"rgt_name": "",
"rgt_indent": 0,
"rgt_calc_type": "",
"rgt_calc_sources": "",
"parent": "Balance Sheet Settings",
"parentfield": "items",
"parenttype": "Balance Sheet Settings",
"doctype": "Balance Sheet Settings Item"
},
{
"idx": 13,
"lft_empty": 0,
"lft_bold": 0,
"lft_name": "周转材料",
"lft_indent": 2,
"lft_calc_type": "Closing Balance",
"lft_calc_sources": "1411",
"rgt_empty": 1,
"rgt_bold": 0,
"rgt_name": "",
"rgt_indent": 0,
"rgt_calc_type": "",
"rgt_calc_sources": "",
"parent": "Balance Sheet Settings",
"parentfield": "items",
"parenttype": "Balance Sheet Settings",
"doctype": "Balance Sheet Settings Item"
},
{
"idx": 14,
"lft_empty": 0,
"lft_bold": 0,
"lft_name": "其他流动资产",
"lft_indent": 1,
"lft_calc_type": "",
"lft_calc_sources": "",
"rgt_empty": 0,
"rgt_bold": 0,
"rgt_name": "其他流动负债",
"rgt_indent": 1,
"rgt_calc_type": "",
"rgt_calc_sources": "",
"parent": "Balance Sheet Settings",
"parentfield": "items",
"parenttype": "Balance Sheet Settings",
"doctype": "Balance Sheet Settings Item"
},
{
"idx": 15,
"lft_empty": 0,
"lft_bold": 1,
"lft_name": "流动资产合计",
"lft_indent": 0,
"lft_calc_type": "Calculate Rows",
"lft_calc_sources": "2,3,4,5,6,7,8,9,10",
"rgt_empty": 0,
"rgt_bold": 1,
"rgt_name": "流动负债合计",
"rgt_indent": 0,
"rgt_calc_type": "Calculate Rows",
"rgt_calc_sources": "2,3,4,5,6,7,8,9,10",
"parent": "Balance Sheet Settings",
"parentfield": "items",
"parenttype": "Balance Sheet Settings",
"doctype": "Balance Sheet Settings Item"
},
{
"idx": 16,
"lft_empty": 0,
"lft_bold": 1,
"lft_name": "非流动资产",
"lft_indent": 0,
"lft_calc_type": "",
"lft_calc_sources": "",
"rgt_empty": 0,
"rgt_bold": 1,
"rgt_name": "非流动负债",
"rgt_indent": 0,
"rgt_calc_type": "",
"rgt_calc_sources": "",
"parent": "Balance Sheet Settings",
"parentfield": "items",
"parenttype": "Balance Sheet Settings",
"doctype": "Balance Sheet Settings Item"
},
{
"idx": 17,
"lft_empty": 0,
"lft_bold": 0,
"lft_name": "长期债券投资",
"lft_indent": 1,
"lft_calc_type": "Closing Balance",
"lft_calc_sources": "1501",
"rgt_empty": 0,
"rgt_bold": 0,
"rgt_name": "长期借款",
"rgt_indent": 1,
"rgt_calc_type": "Closing Balance",
"rgt_calc_sources": "2501",
"parent": "Balance Sheet Settings",
"parentfield": "items",
"parenttype": "Balance Sheet Settings",
"doctype": "Balance Sheet Settings Item"
},
{
"idx": 18,
"lft_empty": 0,
"lft_bold": 0,
"lft_name": "长期股权投资",
"lft_indent": 1,
"lft_calc_type": "Closing Balance",
"lft_calc_sources": "1511",
"rgt_empty": 0,
"rgt_bold": 0,
"rgt_name": "长期应付款",
"rgt_indent": 1,
"rgt_calc_type": "Closing Balance",
"rgt_calc_sources": "2701",
"parent": "Balance Sheet Settings",
"parentfield": "items",
"parenttype": "Balance Sheet Settings",
"doctype": "Balance Sheet Settings Item"
},
{
"idx": 19,
"lft_empty": 0,
"lft_bold": 0,
"lft_name": "固定资产原价",
"lft_indent": 1,
"lft_calc_type": "Closing Balance",
"lft_calc_sources": "1601",
"rgt_empty": 0,
"rgt_bold": 0,
"rgt_name": "递延收益",
"rgt_indent": 1,
"rgt_calc_type": "Closing Balance",
"rgt_calc_sources": "2401",
"parent": "Balance Sheet Settings",
"parentfield": "items",
"parenttype": "Balance Sheet Settings",
"doctype": "Balance Sheet Settings Item"
},
{
"idx": 20,
"lft_empty": 0,
"lft_bold": 0,
"lft_name": "减:累计折旧",
"lft_indent": 2,
"lft_calc_type": "Closing Balance",
"lft_calc_sources": "-1602",
"rgt_empty": 1,
"rgt_bold": 0,
"rgt_name": "其他非流动负债",
"rgt_indent": 0,
"rgt_calc_type": "",
"rgt_calc_sources": "",
"parent": "Balance Sheet Settings",
"parentfield": "items",
"parenttype": "Balance Sheet Settings",
"doctype": "Balance Sheet Settings Item"
},
{
"idx": 21,
"lft_empty": 0,
"lft_bold": 0,
"lft_name": "固定资产账面价值",
"lft_indent": 1,
"lft_calc_type": "Closing Balance",
"lft_calc_sources": "1601,1602",
"rgt_empty": 0,
"rgt_bold": 1,
"rgt_name": "非流动负债合计",
"rgt_indent": 0,
"rgt_calc_type": "Calculate Rows",
"rgt_calc_sources": "17,18,19,20",
"parent": "Balance Sheet Settings",
"parentfield": "items",
"parenttype": "Balance Sheet Settings",
"doctype": "Balance Sheet Settings Item"
},
{
"idx": 22,
"lft_empty": 0,
"lft_bold": 0,
"lft_name": "在建工程",
"lft_indent": 1,
"lft_calc_type": "Closing Balance",
"lft_calc_sources": "1604",
"rgt_empty": 0,
"rgt_bold": 1,
"rgt_name": "负债合计",
"rgt_indent": 0,
"rgt_calc_type": "Calculate Rows",
"rgt_calc_sources": "15,21",
"parent": "Balance Sheet Settings",
"parentfield": "items",
"parenttype": "Balance Sheet Settings",
"doctype": "Balance Sheet Settings Item"
},
{
"idx": 23,
"lft_empty": 0,
"lft_bold": 0,
"lft_name": "工程物资",
"lft_indent": 1,
"lft_calc_type": "Closing Balance",
"lft_calc_sources": "1605",
"rgt_empty": 1,
"rgt_bold": 0,
"rgt_name": "",
"rgt_indent": 0,
"rgt_calc_type": "",
"rgt_calc_sources": "",
"parent": "Balance Sheet Settings",
"parentfield": "items",
"parenttype": "Balance Sheet Settings",
"doctype": "Balance Sheet Settings Item"
},
{
"idx": 24,
"lft_empty": 0,
"lft_bold": 0,
"lft_name": "固定资产清理",
"lft_indent": 1,
"lft_calc_type": "Closing Balance",
"lft_calc_sources": "1606",
"rgt_empty": 1,
"rgt_bold": 0,
"rgt_name": "",
"rgt_indent": 0,
"rgt_calc_type": "",
"rgt_calc_sources": "",
"parent": "Balance Sheet Settings",
"parentfield": "items",
"parenttype": "Balance Sheet Settings",
"doctype": "Balance Sheet Settings Item"
},
{
"idx": 25,
"lft_empty": 0,
"lft_bold": 0,
"lft_name": "生产性生物资产",
"lft_indent": 1,
"lft_calc_type": "Closing Balance",
"lft_calc_sources": "1621,1622",
"rgt_empty": 0,
"rgt_bold": 1,
"rgt_name": "所有者权益(或股东权益)",
"rgt_indent": 0,
"rgt_calc_type": "",
"rgt_calc_sources": "",
"parent": "Balance Sheet Settings",
"parentfield": "items",
"parenttype": "Balance Sheet Settings",
"doctype": "Balance Sheet Settings Item"
},
{
"idx": 26,
"lft_empty": 0,
"lft_bold": 0,
"lft_name": "无形资产",
"lft_indent": 1,
"lft_calc_type": "Closing Balance",
"lft_calc_sources": "1701,1702",
"rgt_empty": 0,
"rgt_bold": 0,
"rgt_name": "实收资本(或股本)",
"rgt_indent": 1,
"rgt_calc_type": "Closing Balance",
"rgt_calc_sources": "3001",
"parent": "Balance Sheet Settings",
"parentfield": "items",
"parenttype": "Balance Sheet Settings",
"doctype": "Balance Sheet Settings Item"
},
{
"idx": 27,
"lft_empty": 0,
"lft_bold": 0,
"lft_name": "开发支出",
"lft_indent": 1,
"lft_calc_type": "",
"lft_calc_sources": "",
"rgt_empty": 0,
"rgt_bold": 0,
"rgt_name": "资本公积",
"rgt_indent": 1,
"rgt_calc_type": "Closing Balance",
"rgt_calc_sources": "3002",
"parent": "Balance Sheet Settings",
"parentfield": "items",
"parenttype": "Balance Sheet Settings",
"doctype": "Balance Sheet Settings Item"
},
{
"idx": 28,
"lft_empty": 0,
"lft_bold": 0,
"lft_name": "长期待摊费用",
"lft_indent": 1,
"lft_calc_type": "Closing Balance",
"lft_calc_sources": "1901",
"rgt_empty": 0,
"rgt_bold": 0,
"rgt_name": "盈余公积",
"rgt_indent": 1,
"rgt_calc_type": "Closing Balance",
"rgt_calc_sources": "3101",
"parent": "Balance Sheet Settings",
"parentfield": "items",
"parenttype": "Balance Sheet Settings",
"doctype": "Balance Sheet Settings Item"
},
{
"idx": 29,
"lft_empty": 0,
"lft_bold": 0,
"lft_name": "其他非流动资产",
"lft_indent": 1,
"lft_calc_type": "",
"lft_calc_sources": "",
"rgt_empty": 0,
"rgt_bold": 0,
"rgt_name": "未分配利润",
"rgt_indent": 1,
"rgt_calc_type": "Closing Balance",
"rgt_calc_sources": "3103,3104",
"parent": "Balance Sheet Settings",
"parentfield": "items",
"parenttype": "Balance Sheet Settings",
"doctype": "Balance Sheet Settings Item"
},
{
"idx": 30,
"lft_empty": 0,
"lft_bold": 1,
"lft_name": "非流动资产合计",
"lft_indent": 0,
"lft_calc_type": "Calculate Rows",
"lft_calc_sources": "17,18,21,22,23,24,25,26,27,28,29",
"rgt_empty": 0,
"rgt_bold": 1,
"rgt_name": "所有者权益(或股东权益)合计",
"rgt_indent": 0,
"rgt_calc_type": "Calculate Rows",
"rgt_calc_sources": "26,27,28,29",
"parent": "Balance Sheet Settings",
"parentfield": "items",
"parenttype": "Balance Sheet Settings",
"doctype": "Balance Sheet Settings Item"
},
{
"idx": 31,
"lft_empty": 0,
"lft_bold": 1,
"lft_name": "资产合计",
"lft_indent": 0,
"lft_calc_type": "Calculate Rows",
"lft_calc_sources": "15,30",
"rgt_empty": 0,
"rgt_bold": 1,
"rgt_name": "负债和所有者权益(或股东权益)",
"rgt_indent": 0,
"rgt_calc_type": "Calculate Rows",
"rgt_calc_sources": "22,30",
"parent": "Balance Sheet Settings",
"parentfield": "items",
"parenttype": "Balance Sheet Settings",
"doctype": "Balance Sheet Settings Item"
}
]
}

View File

@@ -0,0 +1,9 @@
# Copyright (c) 2023, Vnimy and Contributors
# See license.txt
# import frappe
from frappe.tests.utils import FrappeTestCase
class TestBalanceSheetSettings(FrappeTestCase):
pass

View File

@@ -0,0 +1,143 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2023-09-12 15:13:55.822442",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"lft_title",
"lft_empty",
"lft_bold",
"lft_name",
"lft_indent",
"lft_calc_type",
"lft_calc_sources",
"column_break_7w6wt",
"rgt_title",
"rgt_empty",
"rgt_bold",
"rgt_name",
"rgt_indent",
"rgt_calc_type",
"rgt_calc_sources"
],
"fields": [
{
"fieldname": "lft_title",
"fieldtype": "Heading",
"label": "\u8d44\u4ea7"
},
{
"default": "0",
"fieldname": "lft_empty",
"fieldtype": "Check",
"label": "\u7a7a\u767d"
},
{
"default": "0",
"fieldname": "lft_bold",
"fieldtype": "Check",
"label": "\u7c97\u4f53\u663e\u793a"
},
{
"columns": 1,
"depends_on": "eval:!doc.lft_empty",
"fieldname": "lft_name",
"fieldtype": "Data",
"in_list_view": 1,
"label": "\u540d\u79f0"
},
{
"default": "0",
"fieldname": "lft_indent",
"fieldtype": "Int",
"label": "\u7f29\u8fdb"
},
{
"columns": 1,
"default": "Closing Balance",
"depends_on": "eval:!doc.lft_empty",
"fieldname": "lft_calc_type",
"fieldtype": "Select",
"in_list_view": 1,
"label": "\u8ba1\u7b97\u65b9\u5f0f",
"options": "\nClosing Balance\nCalculate Rows"
},
{
"columns": 3,
"depends_on": "eval:!doc.lft_empty",
"description": "\u8ba1\u7b97\u65b9\u5f0f\u662f\u671f\u672b\u4f59\u989d\u65f6\u4e3a\u79d1\u76ee\u53f7\uff0c\u591a\u4e2a\u79d1\u76ee\u53f7\u4e4b\u95f4\u7528\u534a\u89d2\u9017\u53f7\u5206\u5f00\uff0c\u79d1\u76ee\u53f7\u524d\u53ef\u52a0-(\u51cf\u53f7)\u524d\u7f00\uff0c\n\u8ba1\u7b97\u65b9\u5f0f\u662f\u8ba1\u7b97\u516c\u5f0f\u65f6\u4e3a\u5176\u5b83\u884c\u53f7\uff0c\u884c\u53f7\u524d\u53ef\u52a0-(\u51cf\u53f7)\u524d\u7f00",
"fieldname": "lft_calc_sources",
"fieldtype": "Small Text",
"in_list_view": 1,
"label": "\u8ba1\u7b97\u6765\u6e90"
},
{
"fieldname": "column_break_7w6wt",
"fieldtype": "Column Break"
},
{
"fieldname": "rgt_title",
"fieldtype": "Heading",
"label": "\u8d1f\u503a\u548c\u6743\u76ca"
},
{
"default": "0",
"fieldname": "rgt_empty",
"fieldtype": "Check",
"label": "\u7a7a\u767d"
},
{
"default": "0",
"fieldname": "rgt_bold",
"fieldtype": "Check",
"label": "\u7c97\u4f53\u663e\u793a"
},
{
"columns": 1,
"depends_on": "eval:!doc.rgt_empty",
"fieldname": "rgt_name",
"fieldtype": "Data",
"in_list_view": 1,
"label": "\u540d\u79f0"
},
{
"default": "0",
"fieldname": "rgt_indent",
"fieldtype": "Int",
"label": "\u7f29\u8fdb"
},
{
"columns": 1,
"default": "Closing Balance",
"depends_on": "eval:!doc.rgt_empty",
"fieldname": "rgt_calc_type",
"fieldtype": "Select",
"in_list_view": 1,
"label": "\u8ba1\u7b97\u65b9\u5f0f",
"options": "\nClosing Balance\nCalculate Rows"
},
{
"columns": 3,
"depends_on": "eval:!doc.rgt_empty",
"description": "\u8ba1\u7b97\u65b9\u5f0f\u662f\u671f\u672b\u4f59\u989d\u65f6\u4e3a\u79d1\u76ee\u53f7\uff0c\u591a\u4e2a\u79d1\u76ee\u53f7\u4e4b\u95f4\u7528\u534a\u89d2\u9017\u53f7\u5206\u5f00\uff0c\u79d1\u76ee\u53f7\u524d\u53ef\u52a0-(\u51cf\u53f7)\u524d\u7f00\uff0c\n\u8ba1\u7b97\u65b9\u5f0f\u662f\u8ba1\u7b97\u516c\u5f0f\u65f6\u4e3a\u5176\u5b83\u884c\u53f7\uff0c\u884c\u53f7\u524d\u53ef\u52a0-(\u51cf\u53f7)\u524d\u7f00",
"fieldname": "rgt_calc_sources",
"fieldtype": "Small Text",
"in_list_view": 1,
"label": "\u8ba1\u7b97\u6765\u6e90"
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2026-01-06 10:44:17.240196",
"modified_by": "Administrator",
"module": "ERPNext China",
"name": "Balance Sheet Settings Item",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}

View File

@@ -0,0 +1,8 @@
# Copyright (c) 2023, Vnimy and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class BalanceSheetSettingsItem(Document):
pass

View File

@@ -0,0 +1,79 @@
// Copyright (c) 2023, Vnimy and contributors
// For license information, please see license.txt
frappe.ui.form.on('Cash Flow', {
setup: function(frm) {
frm.set_query("cash_flow_code", "items", function(doc, cdt, cdn) {
const child = locals[cdt][cdn];
const is_outflow = child.debit? 0 : 1;
return {
filters: {
formula: ['is', 'not set'],
is_outflow: is_outflow
}
}
});
},
refresh: function(frm) {
frm.add_custom_button(__('Download Cash Flow'), function() {
download_cash_flow(frm);
});
},
onload: function(frm) {
if (frm.is_new()){
const fiscal_year = erpnext.utils.get_fiscal_year(frappe.datetime.get_today());
fiscal_year && frm.set_value('fiscal_year', fiscal_year);
const date = new Date();
const month = date.getMonth() + 1;
frm.set_value('month', month);
}
},
get_cash_flow_items(frm) {
frappe.call({
method: "get_cash_flow_items",
doc: frm.doc,
callback: function(r) {
refresh_field("items");
frm.dirty();
}
});
},
});
frappe.ui.form.on('Cash Flow Item', {
cash_flow_code(frm, cdt, cdn){
const child = locals[cdt][cdn];
const cf_code = child.cash_flow_code;
frm.grids[0].grid.get_selected_children().forEach( (row) =>{
if (row.name !== child.name && !row.cash_flow_code){
frappe.model.set_value(row.doctype, row.name, 'cash_flow_code', cf_code);
}
}
)
}
})
var download_cash_flow = function(frm) {
var data = [];
var docfields = [];
data.push([]);
$.each(frappe.get_meta("Cash Flow Subtotal").fields, (i, df) => {
// don't include the read-only field in the template
if (frappe.model.is_value_type(df.fieldtype)) {
data[0].push(__(df.label));
docfields.push(df);
}
});
// add data
$.each(frm.doc.cash_flow_subtotal || [], (i, d) => {
var row = [];
$.each(docfields, (i, df) => {
var value = d[df.fieldname];
if (df.fieldtype === "Date" && value) {
value = frappe.datetime.str_to_user(value);
}
row.push(value || "");
});
data.push(row);
});
frappe.tools.downloadify(data, null, __("Cash Flow Report"));
}

View File

@@ -0,0 +1,158 @@
{
"actions": [],
"allow_rename": 1,
"autoname": "format:{company}_{fiscal_year}_{month}",
"creation": "2023-12-10 20:56:48.092166",
"default_view": "List",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"company",
"column_break_yj2lr",
"fiscal_year",
"column_break_a1f21",
"month",
"section_break_k11kx",
"get_cash_flow_items",
"items",
"amended_from",
"section_break_w6xue",
"cash_flow_subtotal"
],
"fields": [
{
"fieldname": "company",
"fieldtype": "Link",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Company",
"options": "Company",
"reqd": 1
},
{
"fieldname": "column_break_yj2lr",
"fieldtype": "Column Break"
},
{
"fieldname": "fiscal_year",
"fieldtype": "Link",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Fiscal Year",
"options": "Fiscal Year",
"reqd": 1
},
{
"fieldname": "column_break_a1f21",
"fieldtype": "Column Break"
},
{
"fieldname": "month",
"fieldtype": "Select",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Month",
"options": "\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12",
"reqd": 1
},
{
"fieldname": "section_break_k11kx",
"fieldtype": "Section Break"
},
{
"depends_on": "eval:doc.company&&doc.fiscal_year&&doc.month",
"fieldname": "get_cash_flow_items",
"fieldtype": "Button",
"label": "Get Cash Flow Items"
},
{
"fieldname": "items",
"fieldtype": "Table",
"label": "Cash Flow Items",
"no_copy": 1,
"options": "Cash Flow Item"
},
{
"fieldname": "amended_from",
"fieldtype": "Link",
"label": "Amended From",
"no_copy": 1,
"options": "Cash Flow",
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "section_break_w6xue",
"fieldtype": "Section Break"
},
{
"depends_on": "eval:doc.items",
"fieldname": "cash_flow_subtotal",
"fieldtype": "Table",
"label": "Cash Flow Subtotal",
"no_copy": 1,
"options": "Cash Flow Subtotal",
"read_only": 1
}
],
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2026-01-06 10:44:17.240196",
"modified_by": "Administrator",
"module": "ERPNext China",
"name": "Cash Flow",
"naming_rule": "Expression",
"owner": "Administrator",
"permissions": [
{
"amend": 1,
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"submit": 1,
"write": 1
},
{
"amend": 1,
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts User",
"share": 1,
"submit": 1,
"write": 1
},
{
"amend": 1,
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts Manager",
"share": 1,
"submit": 1,
"write": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}

View File

@@ -0,0 +1,229 @@
# Copyright (c) 2023, Vnimy and contributors
# For license information, please see license.txt
import frappe
from frappe import _
from frappe.model.document import Document
from frappe.query_builder.functions import Cast_
from frappe.utils import cint, cstr, flt, getdate, datetime, get_first_day, get_last_day, formatdate
from erpnext.accounts.report.trial_balance.trial_balance import get_rootwise_opening_balances
class CashFlow(Document):
def validate(self):
self.sync_subtotal()
self.validate_split_amount()
def validate_split_amount(self):
gl_entries = [row.gl_entry for row in self.items]
if gl_entries:
gl_entry_amount_list = frappe.get_all('GL Entry',
filters = {'name': ('in', gl_entries)},
fields = ['name', 'debit', 'credit']
)
gl_entry_amount_map = {
row.name: row.debit - row.credit for row in gl_entry_amount_list
}
gl_entry_wise_amount = {}
for row in self.items:
gl_entry = row.gl_entry
gl_entry_wise_amount.setdefault(gl_entry, 0)
gl_entry_wise_amount[gl_entry] += row.debit - row.credit
for (gl_entry, total_amount) in gl_entry_wise_amount.items():
gl_entry_amount = gl_entry_amount_map.get(gl_entry, 0)
if abs(flt(total_amount - gl_entry_amount)) > 0:
frappe.throw(_("GL Entry {0} total amount {1} does not match amount {2} in general ledger".format(
gl_entry, total_amount, gl_entry_amount
)))
def sync_subtotal(self):
self.cash_flow_subtotal = []
cash_flow_codes = frappe.get_all('Cash Flow Code',
fields =[
"code",
"cash_flow_name",
"cash_flow_type",
"formula",
"is_outflow"
],
order_by = 'report_sequence')
code_type_map = {row.code:row.cash_flow_type for row in cash_flow_codes}
subtotal_by_code_map = {}
subtotal_by_type_map = {}
for row in self.items:
cash_flow_code = row.cash_flow_code
cash_flow_type = code_type_map.get(cash_flow_code)
if cash_flow_code:
subtotal_by_code_map.setdefault(cash_flow_code, 0)
subtotal_by_code_map[cash_flow_code] += (row.debit or 0) - (row.credit or 0)
subtotal_by_type_map.setdefault(cash_flow_type, 0)
subtotal_by_type_map[cash_flow_type] += row.debit - row.credit
last_month_yearly_amount_map = {}
if self.month != '1':
cf = frappe.qb.DocType('Cash Flow')
cf_subtotal = frappe.qb.DocType('Cash Flow Subtotal')
last_cf = frappe.qb.from_(cf
).where(
(cf.company == self.company)
& (cf.fiscal_year == self.fiscal_year)
& (Cast_(cf.month,'integer') < cint(self.month))
& (cf.docstatus == 1)
).select(cf.name
).orderby(Cast_(cf.month,'integer'), order=frappe.qb.desc
).limit(1
).run(pluck='name')
if last_cf:
data = frappe.qb.from_(cf_subtotal
).where(cf_subtotal.parent.isin(last_cf)
).select(cf_subtotal.code, cf_subtotal.yearly_amount
).run(as_list = 1)
last_month_yearly_amount_map = frappe._dict(data)
year = frappe.db.get_value('Fiscal Year', self.fiscal_year,'year_start_date').year
from_date = get_first_day(datetime.date(
year=year, month=cint(self.month), day=1))
year_start_date = get_first_day(datetime.date(
year=year, month=1, day=1))
to_date = get_last_day(datetime.date(
year=year, month=cint(self.month), day=1))
filters = frappe._dict({
'company': self.company,
'from_date': from_date,
'to_date': to_date
})
opening_balances = get_rootwise_opening_balances(filters, "Balance Sheet")
#filters.from_date = year_start_date
#yearly_opening_balances = get_rootwise_opening_balances(filters, "Balance Sheet")
cash_accounts = frappe.get_all("Account",
filters = {"Company": self.company,
"account_type": ("in", ['Cash','Bank'])
},
pluck = 'name')
above_subtotal, above_yearly_subtotal = 0, 0
for row in cash_flow_codes:
cash_flow_type = code_type_map.get(row.code)
subtotal_doc = frappe._dict({
'code': row.code,
'cash_flow_name': row.cash_flow_name,
'cash_flow_type': row.cash_flow_type
})
if not row.formula:
subtotal_doc.monthly_amount = subtotal_by_code_map.get(row.code, 0)
above_subtotal += subtotal_doc.monthly_amount
if row.is_outflow and subtotal_doc.monthly_amount:
subtotal_doc.monthly_amount *= -1
else:
if row.formula == 'type_subtotal':
subtotal_doc.monthly_amount = subtotal_by_type_map.get(cash_flow_type, 0)
elif row.formula == 'last_period_balance':
subtotal_doc.monthly_amount = sum(
opening_balances.get(account,{}).get('opening_debit',0) -
opening_balances.get(account,{}).get('opening_credit',0)
for account in cash_accounts)
above_subtotal += subtotal_doc.monthly_amount
elif row.formula == 'above_subtotal':
subtotal_doc.monthly_amount = above_subtotal
#本年累计金额
last_month_yearly_amount = last_month_yearly_amount_map.get(row.code, 0)
if row.formula == 'last_period_balance':
subtotal_doc.yearly_amount = subtotal_doc.monthly_amount if self.month == '1' else last_month_yearly_amount
else:
subtotal_doc.yearly_amount = last_month_yearly_amount + (subtotal_doc.monthly_amount or 0)
self.append('cash_flow_subtotal', subtotal_doc)
@frappe.whitelist()
def get_cash_flow_items(self):
year = frappe.db.get_value('Fiscal Year', self.fiscal_year,'year_start_date').year
from_date = get_first_day(datetime.date(
year=year, month=cint(self.month), day=1))
to_date = get_last_day(datetime.date(
year=year, month=cint(self.month), day=1))
account = frappe.qb.DocType('Account')
gle = frappe.qb.DocType('GL Entry')
pe = frappe.qb.DocType('Payment Entry')
data = frappe.qb.from_(gle
).join(account
).on( (gle.account == account.name) & (gle.company == account.company)
).left_join(pe
).on((gle.voucher_type == 'Payment Entry') & (gle.voucher_no == pe.name)
).where(
(account.company == self.company) &
(gle.is_cancelled == 0) &
(gle.posting_date >= from_date) &
(gle.posting_date <= to_date) &
(account.account_type.isin(['Cash','Bank']))
).select(
gle.name.as_('gl_entry'),
gle.posting_date,
gle.account,
pe.party_type,
pe.party,
gle.cost_center,
gle.debit,
gle.credit,
gle.against,
gle.voucher_type,
gle.voucher_no,
gle.project,
gle.remarks
).run(as_dict = True)
existing_code_map = {}
if self.items:
existing_code_map = {row.gl_entry:row.cash_flow_code for row in self.items if row.cash_flow_code}
self.items = []
for d in data:
if d.against:
d.against = d.against[:140] #修改了字段类型为Data,避免出现超140字符的情况
d.cash_flow_code = existing_code_map.get(d.gl_entry)
self.append('items', d)
self.assign_default_cash_flow_code(existing_code_map = existing_code_map)
def assign_default_cash_flow_code(self, existing_code_map = {}):
accounts = {row.account for row in self.items}
accounts.update({row.against for row in self.items if row.against})
account_cf_map = frappe._dict(frappe.get_all(
'Account',
filters = {'name': ('in', accounts),
'cash_flow_code': ('is', 'set')
},
fields = ['name', 'cash_flow_code'],
as_list = 1
))
party_type_cf_map = frappe._dict(frappe.get_all(
'Cash Flow Code',
filters = {
'party_type':('is', 'set')
},
fields = ['party_type','name'],
as_list = 1
))
party_by_party_type = {} #{'Customer':{'customer1','customer2'}}
for row in self.items:
if row.party_type and row.party_type in ('Customer', 'Supplier'):
party_by_party_type.setdefault(row.party_type, set()).add(row.party)
party_cf_map = {}
for (party_type, parties) in party_by_party_type.items():
party_cf_map.update(
frappe._dict(frappe.get_all(
party_type,
filters = {'name': ('in', parties),
'cash_flow_code': ('is', 'set')
},
fields = ['name', 'cash_flow_code'],
as_list = 1
))
)
if account_cf_map or party_type_cf_map or party_cf_map:
for row in self.items:
if row.gl_entry not in existing_code_map:
cash_flow_code = (account_cf_map.get(row.account) or account_cf_map.get(row.against)
or party_cf_map.get(row.party) or party_type_cf_map.get(row.party_type)
)
if cash_flow_code:
row.cash_flow_code = cash_flow_code

View File

@@ -0,0 +1,9 @@
# Copyright (c) 2023, Vnimy and Contributors
# See license.txt
# import frappe
from frappe.tests.utils import FrappeTestCase
class TestCashFlow(FrappeTestCase):
pass

View File

@@ -0,0 +1,8 @@
// Copyright (c) 2023, Vnimy and contributors
// For license information, please see license.txt
frappe.ui.form.on('Cash Flow Code', {
// refresh: function(frm) {
// }
});

View File

@@ -0,0 +1,106 @@
{
"actions": [],
"allow_import": 1,
"allow_rename": 1,
"autoname": "field:code",
"creation": "2023-12-10 20:39:03.276010",
"default_view": "List",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"code",
"cash_flow_name",
"cash_flow_type",
"column_break_a45nw",
"formula",
"is_outflow",
"report_sequence",
"party_type"
],
"fields": [
{
"fieldname": "code",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Cash Flow Code",
"reqd": 1,
"unique": 1
},
{
"fieldname": "cash_flow_name",
"fieldtype": "Data",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Cash Flow Name"
},
{
"fieldname": "cash_flow_type",
"fieldtype": "Select",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Cash Flow Type",
"options": "\n\u4e00\u3001\u7ecf\u8425\u6d3b\u52a8\u4ea7\u751f\u7684\u73b0\u91d1\u6d41\u91cf:\n\u4e8c\u3001\u6295\u8d44\u6d3b\u52a8\u4ea7\u751f\u7684\u73b0\u91d1\u6d41\u91cf:\n\u4e09\u3001\u7b79\u8d44\u6d3b\u52a8\u4ea7\u751f\u7684\u73b0\u91d1\u6d41\u91cf:\n\u56db\u3001\u73b0\u91d1\u51c0\u589e\u52a0\u989d\n\u4e94\u3001\u671f\u672b\u73b0\u91d1\u4f59\u989d"
},
{
"fieldname": "column_break_a45nw",
"fieldtype": "Column Break"
},
{
"fieldname": "formula",
"fieldtype": "Select",
"label": "Formula",
"options": "\ntype_subtotal\nlast_period_balance\nabove_subtotal"
},
{
"default": "0",
"fieldname": "is_outflow",
"fieldtype": "Check",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Is Outflow"
},
{
"fieldname": "report_sequence",
"fieldtype": "Int",
"label": "Report Sequence"
},
{
"description": "\u6536\u4ed8\u6b3e\u51ed\u8bc1\u5f80\u6765\u7c7b\u578b\u9ed8\u8ba4\u7684\u73b0\u91d1\u6d41\u91cf\u7f16\u7801",
"fieldname": "party_type",
"fieldtype": "Select",
"label": "Default Party Type",
"options": "\nCustomer\nSupplier\nShareholder\nEmployee"
}
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2026-01-06 10:44:17.240196",
"modified_by": "Administrator",
"module": "ERPNext China",
"name": "Cash Flow Code",
"naming_rule": "By fieldname",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"import": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"search_fields": "code,is_outflow",
"show_title_field_in_link": 1,
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"title_field": "cash_flow_name",
"track_changes": 1
}

View File

@@ -0,0 +1,8 @@
# Copyright (c) 2023, Vnimy and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class CashFlowCode(Document):
pass

View File

@@ -0,0 +1,9 @@
# Copyright (c) 2023, Vnimy and Contributors
# See license.txt
# import frappe
from frappe.tests.utils import FrappeTestCase
class TestCashFlowCode(FrappeTestCase):
pass

View File

@@ -0,0 +1,201 @@
{
"actions": [],
"autoname": "hash",
"creation": "2023-12-10 20:49:24.262875",
"default_view": "List",
"doctype": "DocType",
"document_type": "Document",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"account",
"against",
"party_type",
"party",
"sec_break1",
"debit",
"col_break2",
"credit",
"manual_split",
"cash_flow_code",
"reference",
"gl_entry",
"voucher_type",
"voucher_no",
"col_break3",
"remarks",
"accounting_dimensions_section",
"cost_center",
"dimension_col_break",
"project"
],
"fields": [
{
"bold": 1,
"columns": 2,
"fieldname": "account",
"fieldtype": "Link",
"in_global_search": 1,
"in_list_view": 1,
"label": "Account",
"oldfieldname": "account",
"oldfieldtype": "Link",
"options": "Account",
"print_width": "250px",
"reqd": 1,
"search_index": 1,
"width": "250px"
},
{
"fieldname": "party_type",
"fieldtype": "Link",
"label": "Party Type",
"options": "DocType",
"search_index": 1
},
{
"columns": 2,
"fieldname": "party",
"fieldtype": "Dynamic Link",
"label": "Party",
"options": "party_type"
},
{
"fieldname": "sec_break1",
"fieldtype": "Section Break",
"label": "Amount"
},
{
"bold": 1,
"fieldname": "debit",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Debit in Company Currency",
"no_copy": 1,
"oldfieldname": "debit",
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
"read_only_depends_on": "evel:doc.manual_split === 0"
},
{
"fieldname": "col_break2",
"fieldtype": "Column Break"
},
{
"bold": 1,
"fieldname": "credit",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Credit in Company Currency",
"no_copy": 1,
"oldfieldname": "credit",
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
"read_only_depends_on": "evel:doc.manual_split === 0"
},
{
"fieldname": "cash_flow_code",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Cash Flow Code",
"options": "Cash Flow Code"
},
{
"fieldname": "reference",
"fieldtype": "Section Break",
"label": "Reference"
},
{
"fieldname": "col_break3",
"fieldtype": "Column Break"
},
{
"collapsible": 1,
"fieldname": "accounting_dimensions_section",
"fieldtype": "Section Break",
"label": "Accounting Dimensions"
},
{
"default": ":Company",
"description": "If Income or Expense",
"fieldname": "cost_center",
"fieldtype": "Link",
"label": "Cost Center",
"oldfieldname": "cost_center",
"oldfieldtype": "Link",
"options": "Cost Center",
"print_hide": 1,
"print_width": "180px",
"width": "180px"
},
{
"fieldname": "dimension_col_break",
"fieldtype": "Column Break"
},
{
"fieldname": "project",
"fieldtype": "Link",
"label": "Project",
"options": "Project"
},
{
"fieldname": "against",
"fieldtype": "Data",
"label": "Against Account",
"no_copy": 1,
"oldfieldname": "against_account",
"oldfieldtype": "Text"
},
{
"fieldname": "voucher_type",
"fieldtype": "Select",
"in_list_view": 1,
"label": "Voucher Type",
"no_copy": 1,
"options": "\nSales Invoice\nPurchase Invoice\nJournal Entry\nSales Order\nPurchase Order\nExpense Claim\nAsset\nLoan\nPayroll Entry\nEmployee Advance\nExchange Rate Revaluation\nInvoice Discounting\nFees\nFull and Final Statement\nPayment Entry"
},
{
"fieldname": "voucher_no",
"fieldtype": "Dynamic Link",
"in_list_view": 1,
"label": "Voucher No",
"no_copy": 1,
"options": "voucher_type"
},
{
"fieldname": "remarks",
"fieldtype": "Small Text",
"in_list_view": 1,
"label": "User Remark",
"no_copy": 1,
"print_hide": 1
},
{
"fieldname": "gl_entry",
"fieldtype": "Link",
"label": "GL Entry",
"options": "GL Entry"
},
{
"default": "0",
"fieldname": "manual_split",
"fieldtype": "Check",
"label": "Manual Split"
}
],
"istable": 1,
"links": [],
"modified": "2026-01-06 10:44:17.240196",
"modified_by": "Administrator",
"module": "ERPNext China",
"name": "Cash Flow Item",
"naming_rule": "Random",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}

View File

@@ -0,0 +1,8 @@
# Copyright (c) 2023, Vnimy and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class CashFlowItem(Document):
pass

View File

@@ -0,0 +1,75 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2023-12-14 08:46:30.885422",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"code",
"cash_flow_name",
"cash_flow_type",
"column_break_a45nw",
"monthly_amount",
"yearly_amount"
],
"fields": [
{
"columns": 1,
"fieldname": "code",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Cash Flow Code"
},
{
"columns": 3,
"fieldname": "cash_flow_name",
"fieldtype": "Data",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Cash Flow Name"
},
{
"columns": 3,
"fieldname": "cash_flow_type",
"fieldtype": "Select",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Cash Flow Type",
"options": "\n\u4e00\u3001\u7ecf\u8425\u6d3b\u52a8\u4ea7\u751f\u7684\u73b0\u91d1\u6d41\u91cf:\n\u4e8c\u3001\u6295\u8d44\u6d3b\u52a8\u4ea7\u751f\u7684\u73b0\u91d1\u6d41\u91cf:\n\u4e09\u3001\u7b79\u8d44\u6d3b\u52a8\u4ea7\u751f\u7684\u73b0\u91d1\u6d41\u91cf:\n\u56db\u3001\u73b0\u91d1\u51c0\u589e\u52a0\u989d\n\u4e94\u3001\u671f\u672b\u73b0\u91d1\u4f59\u989d"
},
{
"fieldname": "column_break_a45nw",
"fieldtype": "Column Break"
},
{
"columns": 1,
"fieldname": "yearly_amount",
"fieldtype": "Float",
"in_list_view": 1,
"label": "Yearly Amount"
},
{
"columns": 1,
"fieldname": "monthly_amount",
"fieldtype": "Float",
"in_list_view": 1,
"label": "Monthly Amount"
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2026-01-06 10:44:17.240196",
"modified_by": "Administrator",
"module": "ERPNext China",
"name": "Cash Flow Subtotal",
"owner": "Administrator",
"permissions": [],
"show_title_field_in_link": 1,
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"title_field": "cash_flow_name",
"track_changes": 1
}

View File

@@ -0,0 +1,8 @@
# Copyright (c) 2023, Vnimy and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class CashFlowSubtotal(Document):
pass

View File

@@ -0,0 +1,228 @@
{
"items": [
{
"idx": 1,
"label": "一、业务收入",
"indent": 0,
"calc_type": "Closing Balance",
"calc_sources": "5001,5051"
},
{
"idx": 2,
"label": "减:业务成本",
"indent": 0,
"calc_type": "Closing Balance",
"calc_sources": "5401,5402"
},
{
"idx": 3,
"label": "税金及附加",
"indent": 0,
"calc_type": "Closing Balance",
"calc_sources": "5403"
},
{
"idx": 4,
"label": "其中:消费税",
"indent": 1,
"calc_type": "Closing Balance",
"calc_sources": "222103"
},
{
"idx": 5,
"label": "营业税",
"indent": 1,
"calc_type": "Closing Balance",
"calc_sources": "222104"
},
{
"idx": 6,
"label": "城市维护建设税",
"indent": 1,
"calc_type": "Closing Balance",
"calc_sources": "222108"
},
{
"idx": 7,
"label": "资源税",
"indent": 1,
"calc_type": "Closing Balance",
"calc_sources": "222105"
},
{
"idx": 8,
"label": "土地增值税",
"indent": 1,
"calc_type": "Closing Balance",
"calc_sources": "222107"
},
{
"idx": 9,
"label": " 城镇土地使用税、房产税、车船税、印花税",
"indent": 1,
"calc_type": "Closing Balance",
"calc_sources": "222109,222110,222111,222117,"
},
{
"idx": 10,
"label": "教育费附加、矿产资源补偿费、排污费",
"indent": 1,
"calc_type": "Closing Balance",
"calc_sources": "222113,222114,222115,222116"
},
{
"idx": 11,
"label": "销售费用",
"indent": 0,
"calc_type": "Closing Balance",
"calc_sources": "560101,560102,560103,560104,560105,560106,560107,560108,560109,560110,560111,560112,560113,560114,560115,560116,560118,560118,560119,560120"
},
{
"idx": 12,
"label": "其中:商品维修费",
"indent": 1,
"calc_type": "Closing Balance",
"calc_sources": "560103"
},
{
"idx": 13,
"label": "广告费和业务宣传费",
"indent": 1,
"calc_type": "Closing Balance",
"calc_sources": "560115,560116"
},
{
"idx": 14,
"label": "管理费用",
"indent": 0,
"calc_type": "Closing Balance",
"calc_sources": "560201,560202,560203,560204,560205,560206,560207,560208,560209,560210,560211,560212,560213,560214,560215,560216,560217,560218,560219,560220"
},
{
"idx": 15,
"label": "其中:开办费",
"indent": 1,
"calc_type": "Closing Balance",
"calc_sources": "560209"
},
{
"idx": 16,
"label": "业务招待费",
"indent": 1,
"calc_type": "Closing Balance",
"calc_sources": "560202"
},
{
"idx": 17,
"label": "研究费用",
"indent": 1,
"calc_type": "Closing Balance",
"calc_sources": "560210"
},
{
"idx": 18,
"label": "财务费用",
"indent": 0,
"calc_type": "Closing Balance",
"calc_sources": "560301,560302,560303,560304,560305,560306"
},
{
"idx": 19,
"label": "其中:利息费用(收入以“-”号填列)",
"indent": 1,
"calc_type": "Closing Balance",
"calc_sources": "560301"
},
{
"idx": 20,
"label": "加:投资收益(亏损以“-”号填列)",
"indent": 0,
"calc_type": "Closing Balance",
"calc_sources": "5111"
},
{
"idx": 21,
"label": "二、营业利润(亏损以“-”号填列)",
"indent": 0,
"calc_type": "Calculate Rows",
"calc_sources": "1,-2,-3,-11,-14,-18,20"
},
{
"idx": 22,
"label": "加:营业外收入",
"indent": 0,
"calc_type": "Closing Balance",
"calc_sources": "530101,530102,530103,530104,530105,530106"
},
{
"idx": 23,
"label": "其中:政府补助",
"indent": 1,
"calc_type": "Closing Balance",
"calc_sources": "530102"
},
{
"idx": 24,
"label": "减:营业外支出",
"indent": 0,
"calc_type": "Closing Balance",
"calc_sources": "571101,571102,571103,571104,571105,571106,571107,571108,571109,571110,571111,571112"
},
{
"idx": 25,
"label": "其中:坏账损失",
"indent": 1,
"calc_type": "Closing Balance",
"calc_sources": "571105"
},
{
"idx": 26,
"label": "无法收回的长期债券投资损失",
"indent": 1,
"calc_type": "Closing Balance",
"calc_sources": "571107"
},
{
"idx": 27,
"label": "无法收回的长期股权投资损失",
"indent": 1,
"calc_type": "Closing Balance",
"calc_sources": "571108"
},
{
"idx": 28,
"label": "自然灾害等不可抗力因素造成的损失",
"indent": 1,
"calc_type": "Closing Balance",
"calc_sources": "571109"
},
{
"idx": 29,
"label": "税收滞纳金",
"indent": 1,
"calc_type": "Closing Balance",
"calc_sources": "571110"
},
{
"idx": 30,
"label": "三、利润总额(亏损总额以“-”号填列)",
"indent": 0,
"calc_type": "Calculate Rows",
"calc_sources": "21,22,-24"
},
{
"idx": 31,
"label": "减:所得税费用",
"indent": 0,
"calc_type": "Closing Balance",
"calc_sources": "5801"
},
{
"idx": 32,
"label": "四、净利润(净亏损以“-”号填列)",
"indent": 0,
"calc_type": "Calculate Rows",
"calc_sources": "30,-31"
}
]
}

View File

@@ -0,0 +1,28 @@
// Copyright (c) 2023, Vnimy and contributors
// For license information, please see license.txt
frappe.ui.form.on('Profit and Loss Statement Settings', {
refresh: function(frm) {
if (!frm.doc.items || frm.doc.items.length === 0){
frm.add_custom_button('导入范例数据', function() {
frm.events.import_example_data(frm);
});
}
if (!frm.is_new() && frm.doc.items && frm.doc.items.length) {
frm.add_custom_button(__('Check Missing Accounts'),
() =>{
frappe.set_route("query-report",
"BS and PL Missing Account",
{report: "PL" }
);
});
}
},
import_example_data(frm) {
frm.call('get_example_data').then(r => {
r.message.items.sort((a, b) => a.idx > b.idx ? 1 : -1);
frm.set_value(r.message);
});
},
});

View File

@@ -0,0 +1,43 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2023-09-13 09:29:03.482226",
"default_view": "List",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"items"
],
"fields": [
{
"fieldname": "items",
"fieldtype": "Table",
"label": "\u9879\u76ee\u8bbe\u5b9a",
"options": "Profit and Loss Statement Settings Item"
}
],
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2026-01-06 10:44:17.240196",
"modified_by": "Administrator",
"module": "ERPNext China",
"name": "Profit and Loss Statement Settings",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"print": 1,
"read": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}

View File

@@ -0,0 +1,43 @@
# Copyright (c) 2023, Vnimy and contributors
# For license information, please see license.txt
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"))
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.calc_type == "Closing Balance" and row.calc_sources:
for account_number in row.calc_sources.split(','):
check_account_number(account_number, check_row)
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)

View File

@@ -0,0 +1,9 @@
# Copyright (c) 2023, Vnimy and Contributors
# See license.txt
# import frappe
from frappe.tests.utils import FrappeTestCase
class TestProfitandLossStatementSettings(FrappeTestCase):
pass

View File

@@ -0,0 +1,79 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2023-09-13 09:28:18.774165",
"default_view": "List",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"label",
"indent",
"amount_from",
"cb_01",
"calc_type",
"calc_sources"
],
"fields": [
{
"columns": 2,
"depends_on": "eval:!doc.lft_empty",
"fieldname": "label",
"fieldtype": "Data",
"in_list_view": 1,
"label": "\u540d\u79f0"
},
{
"columns": 1,
"default": "0",
"fieldname": "indent",
"fieldtype": "Int",
"in_list_view": 1,
"label": "\u7f29\u8fdb"
},
{
"fieldname": "cb_01",
"fieldtype": "Column Break"
},
{
"columns": 1,
"default": "Closing Balance",
"fieldname": "calc_type",
"fieldtype": "Select",
"in_list_view": 1,
"label": "\u8ba1\u7b97\u65b9\u5f0f",
"options": "\nClosing Balance\nCalculate Rows"
},
{
"columns": 5,
"depends_on": "eval:in_list(['Closing Balance','Calculate Rows'], doc.calc_type)",
"description": "\u8ba1\u7b97\u65b9\u5f0f\u662f\u671f\u672b\u4f59\u989d\u65f6\u4e3a\u79d1\u76ee\u53f7\uff0c\u591a\u4e2a\u79d1\u76ee\u53f7\u4e4b\u95f4\u7528\u534a\u89d2\u9017\u53f7\u5206\u5f00\uff0c\u79d1\u76ee\u53f7\u524d\u53ef\u52a0-(\u51cf\u53f7)\u524d\u7f00\uff0c\n\u8ba1\u7b97\u65b9\u5f0f\u662f\u8ba1\u7b97\u516c\u5f0f\u65f6\u4e3a\u5176\u5b83\u884c\u53f7\uff0c\u884c\u53f7\u524d\u53ef\u52a0-(\u51cf\u53f7)\u524d\u7f00",
"fieldname": "calc_sources",
"fieldtype": "Small Text",
"in_list_view": 1,
"label": "\u8ba1\u7b97\u6765\u6e90",
"mandatory_depends_on": "eval:in_list(['Closing Balance','Calculate Rows'], doc.calc_type)"
},
{
"columns": 1,
"default": "Balance",
"fieldname": "amount_from",
"fieldtype": "Select",
"in_list_view": 1,
"label": "Amount From",
"options": "Balance\nCredit\nDebit"
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2026-01-06 10:44:17.240196",
"modified_by": "Administrator",
"module": "ERPNext China",
"name": "Profit and Loss Statement Settings Item",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}

View File

@@ -0,0 +1,8 @@
# Copyright (c) 2023, Vnimy and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class ProfitandLossStatementSettingsItem(Document):
pass

View File

@@ -0,0 +1,21 @@
frappe.query_reports["BS and PL Missing Account"] = {
filters: [
{
fieldname: "report",
label: __("Report"),
fieldtype: "Select",
width: "80",
options: "BS\nPL",
default: "BS",
},
{
fieldname: "company",
label: __("Company"),
fieldtype: "Link",
width: "80",
options: "Company",
reqd: 1,
default: frappe.defaults.get_default("company"),
}
]
}

View File

@@ -0,0 +1,32 @@
{
"add_total_row": 0,
"add_translate_data": 0,
"columns": [],
"creation": "2025-04-11 17:05:53.063829",
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"filters": [],
"idx": 0,
"is_standard": "Yes",
"letter_head": "\u65b0\u6c34\u5370",
"letterhead": null,
"modified": "2026-01-07 17:28:43.555159",
"modified_by": "Administrator",
"module": "ERPNext China",
"name": "BS and PL Missing Account",
"owner": "Administrator",
"prepared_report": 0,
"ref_doctype": "Account",
"report_name": "BS and PL Missing Account",
"report_type": "Script Report",
"roles": [
{
"role": "System Manager"
},
{
"role": "Accounts User"
}
],
"timeout": 0
}

View File

@@ -0,0 +1,82 @@
import frappe
from frappe import _
def execute(filters=None):
columns = get_column()
data = get_data(filters)
return columns, data
def get_column():
return [
{
"label": _("Account Number"),
"fieldname": "account_number",
"fieldtype": "Data",
"width": 100,
},
{
"label": _("Account"),
"fieldname": "account",
"fieldtype": "Link",
"options": "Account",
"width": 360,
}
]
def get_data(filters):
accounts = frappe.get_all("Account",
filters = {
"company": filters.company,
"report_type": "Balance Sheet" if filters.report == "BS" else "Profit and Loss",
"disabled": 0
},
fields = ["account_number", "name as account", "is_group", "lft", "rgt"]
)
account_number_set = set()
doctype = "Balance Sheet Settings Item" if filters.report == "BS" else "Profit and Loss Statement Settings Item"
if doctype == "Balance Sheet Settings Item":
account_number_in_report = frappe.get_all(doctype,
or_filters = {
"lft_calc_type": "Closing Balance",
"rgt_calc_type": "Closing Balance",
},
fields=[
"lft_calc_sources",
"rgt_calc_sources"
]
)
for row in account_number_in_report:
for value in [row.lft_calc_sources, row.rgt_calc_sources]:
if value:
for num in value.split(','):
account_number_set.add(num)
else:
account_number_in_report = frappe.get_all(doctype,
filters = {
"calc_type": "Closing Balance"
},
fields=["calc_sources"]
)
for row in account_number_in_report:
value = row.calc_sources
if value:
for num in value.split(','):
account_number_set.add(num)
group_accts_in_report = [row for row in accounts if row.is_group and row.account_number in account_number_set]
def is_group_account_in_report(row):
for group_acct in group_accts_in_report:
if row.lft > group_acct.lft and row.rgt < group_acct.rgt:
return True
missing_accounts = []
for row in accounts:
if (
not row.is_group and
not row.account_number in account_number_set and
not is_group_account_in_report(row)
):
missing_accounts.append(row)
return missing_accounts

View File

@@ -0,0 +1,118 @@
// Copyright (c) 2023, 杨嘉祥 and contributors
// For license information, please see license.txt
/* eslint-disable */
frappe.query_reports["Fin Balance Sheet"] = $.extend(
{},
erpnext.financial_statements
);
frappe.query_reports["Fin Balance Sheet"] = {
"filters": [
{
"fieldname": "company",
"label": __("Company"),
"fieldtype": "Link",
"options": "Company",
"default": frappe.defaults.get_user_default("Company"),
"reqd": 1
},
{
"fieldname": "fiscal_year",
"label": __("Fiscal Year"),
"fieldtype": "Link",
"options": "Fiscal Year",
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
"reqd": 1,
},
{
"fieldname": "month",
"label": "月份",
"fieldtype": "Select",
"options": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
"default": new Date().getMonth() + 1,
"depends_on": "eval: !doc.show_all_months",
},
{
"fieldname": "show_all_months",
"label": "显示所有月份",
"fieldtype": "Check",
"default": 0,
},
],
formatter: function (value, row, column, data, default_formatter) {
value = default_formatter(value, row, column, data);
if (!data) {
return value;
}
// 将所有负数变为正数,并且为货币格式
if (column.fieldname.endsWith('_balance')) {
if (data[column.fieldname] < 0) {
value = Math.abs(data[column.fieldname]);
value = format_currency(value, data.currency);
// 右对齐
column.align = 'right';
}
}
const prefix = column.fieldname.slice(0, 4);
if (['lft_', 'rgt_'].includes(prefix) && data[prefix + 'empty']) {
value = '';
} else if (data.empty) {
value = '';
}
if (column.fieldname.endsWith('_balance')) {
if (!data[prefix + 'calc_type']) {
value = '';
}
}
if (column.fieldname.startsWith('balance_')) {
if (!data.calc_type) {
value = '';
}
}
if (column.fieldname === 'lft_name' && data.lft_indent > 0) {
value = `<span style="padding-left: ${data.lft_indent * 20}px;">${value}</span>`;
}
if (column.fieldname === 'rgt_name' && data.rgt_indent > 0) {
value = `<span style="padding-left: ${data.rgt_indent * 20}px;">${value}</span>`;
}
if (column.fieldname.startsWith('lft_')) {
if (data.lft_bold && value) {
value = `<span style="font-weight:bold;">${value}</span>`;
}
} else if (column.fieldname.startsWith('rgt_')) {
if (data.rgt_bold && value) {
value = `<span style="font-weight:bold;">${value}</span>`;
}
} else {
if (data.bold && value) {
value = `<span style="font-weight:bold;">${value}</span>`;
}
}
return value;
},
onload: function(report) {
const views_menu = report.page.add_custom_button_group(__('Financial Statements'));
report.page.add_custom_menu_item(views_menu, __("Fin Balance Sheet"), function() {
var filters = report.get_values();
frappe.set_route('query-report', 'Fin Balance Sheet', {company: filters.company});
});
report.page.add_custom_menu_item(views_menu, __("Fin Profit and Loss Statement"), function() {
var filters = report.get_values();
frappe.set_route('query-report', 'Fin Profit and Loss Statement', {company: filters.company});
});
}
}

View File

@@ -0,0 +1,29 @@
{
"add_total_row": 0,
"columns": [],
"creation": "2023-12-08 09:57:56.480126",
"disable_prepared_report": 0,
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"filters": [],
"idx": 0,
"is_standard": "Yes",
"modified": "2026-01-07 17:28:43.555159",
"modified_by": "Administrator",
"module": "ERPNext China",
"name": "Fin Balance Sheet",
"owner": "Administrator",
"prepared_report": 0,
"ref_doctype": "Account",
"report_name": "Fin Balance Sheet",
"report_type": "Script Report",
"roles": [
{
"role": "Accounts Manager"
},
{
"role": "Accounts User"
}
]
}

View File

@@ -0,0 +1,649 @@
# Copyright (c) 2023, 杨嘉祥 and contributors
# For license information, please see license.txt
import inspect
import frappe
from frappe import _
from frappe.utils import cint, cstr, flt, getdate, datetime, get_first_day, get_last_day, formatdate
from erpnext.accounts.report.trial_balance.trial_balance import (
get_opening_balances as original_get_opening_balances,
)
from erpnext.accounts.utils import get_balance_on
def execute(filters):
if filters.show_all_months:
return BalanceSheetSingleColumn(filters).run()
else:
return BalanceSheetDoubleColumns(filters).run()
class BalanceSheetDoubleColumns():
def __init__(self, filters=None):
self.filters = frappe._dict(filters)
self.validate_filters()
def run(self):
self.get_columns()
settings = frappe.get_single("Balance Sheet Settings")
if not settings.items:
form_link = frappe.utils.get_link_to_form("Balance Sheet Settings",'')
frappe.msgprint(_("请先进行资产负债表(两栏式){0}设置").format(form_link))
return
self.data = list(map(lambda d: frappe._dict({
"idx": d.idx,
"lft_empty": d.lft_empty,
"lft_bold": d.lft_bold,
"lft_name": d.lft_name,
"lft_indent": d.lft_indent,
"lft_calc_type": d.lft_calc_type,
"lft_calc_sources": d.lft_calc_sources,
"rgt_empty": d.rgt_empty,
"rgt_bold": d.rgt_bold,
"rgt_name": d.rgt_name,
"rgt_indent": d.rgt_indent,
"rgt_calc_type": d.rgt_calc_type,
"rgt_calc_sources": d.rgt_calc_sources,
}), settings.items))
self.get_data()
currency = self.filters.presentation_currency or frappe.get_cached_value(
"Company", self.filters.company, "default_currency"
)
report_summary = self.get_report_summary(
asset_row=settings.asset_row,
liability_row=settings.liability_row,
equity_row=settings.equity_row,
currency=currency,
)
return self.columns, self.data, None, None, report_summary
def validate_filters(self):
self.company = self.filters.company
if not self.filters.fiscal_year:
frappe.throw(_("Fiscal Year {0} is required").format(self.filters.fiscal_year))
self.fiscal_year = self.filters.fiscal_year
fiscal_year = frappe.db.get_value(
"Fiscal Year", self.fiscal_year, ["year_start_date", "year_end_date"], as_dict=True
)
if not fiscal_year:
frappe.throw(_("Fiscal Year {0} does not exist").format(self.fiscal_year))
else:
self.year_start_date = getdate(fiscal_year.year_start_date)
self.year_end_date = getdate(fiscal_year.year_end_date)
if self.filters.month:
year = frappe.db.get_value('Fiscal Year', self.fiscal_year,'year_start_date').year
self.filters.from_date = get_first_day(datetime.date(year=year, month=cint(self.filters.month), day=1))
self.filters.to_date = get_last_day(datetime.date(year=year, month=cint(self.filters.month), day=1))
else:
self.filters.from_date = self.year_start_date
self.filters.to_date = self.year_end_date
# if not filters.from_date:
# filters.from_date = filters.year_start_date
# if not filters.to_date:
# filters.to_date = filters.year_end_date
self.filters.from_date = getdate(self.filters.from_date)
self.filters.to_date = getdate(self.filters.to_date)
if self.filters.from_date > self.filters.to_date:
frappe.throw(_("From Date cannot be greater than To Date"))
if (self.filters.from_date < self.year_start_date) or (self.filters.from_date > self.year_end_date):
frappe.msgprint(
_("From Date should be within the Fiscal Year. Assuming From Date = {0}").format(
formatdate(self.year_start_date)
)
)
self.filters.from_date = self.year_start_date
if (self.filters.to_date < self.year_start_date) or (self.filters.to_date > self.year_end_date):
frappe.msgprint(
_("To Date should be within the Fiscal Year. Assuming To Date = {0}").format(
formatdate(self.year_end_date)
)
)
self.filters.to_date = self.year_end_date
def get_report_summary(
self,
asset_row,
liability_row,
equity_row,
currency,
):
if not self.data:
return None
row_map = {}
for d in self.data:
row_map[d.idx] = d
net_asset = row_map.get(asset_row, {}).get("lft_closing_balance", 0.0)
net_liability = row_map.get(liability_row, {}).get(
"rgt_closing_balance", 0.0)
net_equity = row_map.get(equity_row, {}).get("rgt_closing_balance", 0.0)
net_provisional_profit_loss = net_asset - net_liability - net_equity
return [
{
"value": net_asset,
"label": _("Total Asset"),
"datatype": "Currency",
"currency": currency,
},
{
"value": net_liability,
"label": _("Total Liability"),
"datatype": "Currency",
"currency": currency,
},
{
"value": net_equity,
"label": _("Total Equity"),
"datatype": "Currency",
"currency": currency,
},
{
"value": net_provisional_profit_loss,
"label": _("Provisional Profit / Loss (Credit)"),
"indicator": "Green" if net_provisional_profit_loss > 0 else "Red",
"datatype": "Currency",
"currency": currency,
},
]
def filter_accounts(self, accounts):
parent_children_map = {}
accounts_by_num = {}
for d in accounts:
accounts_by_num[d.account_number] = d
parent_children_map.setdefault(d.parent_number or None, []).append(d)
return accounts, accounts_by_num, parent_children_map
def get_data(self):
accounts = frappe.db.sql(
"""select acc.name, acc.account_number, parent.account_number as parent_number,
acc.parent_account, acc.account_name,
acc.root_type, acc.report_type, acc.lft, acc.rgt
from `tabAccount` acc
left join `tabAccount` parent on parent.name = acc.parent_account
where acc.company=%s order by acc.lft""", #fisher and acc.root_type in ('Asset', 'Liability', 'Equity'), 要考虑未结转损益
self.company,
as_dict=True,
)
if not accounts:
return None
accounts, accounts_by_num, parent_children_map = self.filter_accounts(accounts)
opening_balances = get_opening_balances(self.filters.copy().update({
# 期初为上一年的期末
"from_date": self.year_start_date,
"to_date": self.year_end_date,
}))
for d in accounts:
# fisher 2025-04-03 外币科目需取本币余额所以加in_account_currency=False参数
closing_balance = get_balance_on(d.name, self.filters.to_date, in_account_currency=False)
if d.root_type == 'Asset':
d.opening_balance = opening_balances.get(d.name, {}).get(
"opening_debit", 0) - opening_balances.get(d.name, {}).get("opening_credit", 0)
d.closing_balance = closing_balance
else:
d.opening_balance = opening_balances.get(d.name, {}).get(
"opening_credit", 0) - opening_balances.get(d.name, {}).get("opening_debit", 0)
d.closing_balance = 0 - closing_balance
accounts_by_num[d.account_number].update({
"opening_balance": d.opening_balance,
"closing_balance": d.closing_balance,
})
for d in reversed(accounts):
if d.parent_number:
accounts_by_num[d.parent_number]["opening_balance"] += d["opening_balance"]
rows_map = {}
for d in self.data:
for prefix in ["lft_", "rgt_"]:
rows_map[prefix + cstr(d.idx)] = {
"name": d[prefix + "name"],
"opening_balance": 0.0,
"closing_balance": 0.0,
}
if not d[prefix + "empty"] and d[prefix + "calc_type"] and d[prefix + "calc_sources"] and d[prefix + "calc_type"] == "Closing Balance":
d[prefix + "opening_balance"] = d[prefix + "closing_balance"] = 0.0
d[prefix + "calc_sources"] = list(filter(None,
d[prefix + "calc_sources"].split(",")))
d[prefix + "accounts"] = []
for account_number in d[prefix + "calc_sources"]:
minus = False
if account_number.startswith("-"):
account_number = account_number[1:]
minus = True
account = accounts_by_num.get(account_number)
if account:
d[prefix + "accounts"].append({
"direction": (-1 if minus else 1),
"name": account.name,
"account_number": account.account_number,
"account_name": account.account_name,
"opening_balance": account.opening_balance,
"closing_balance": account.closing_balance,
})
d[prefix + "opening_balance"] += (-1 if minus else 1) * accounts_by_num.get(
account_number, {}).get("opening_balance", 0.0)
d[prefix + "closing_balance"] += (-1 if minus else 1) * accounts_by_num.get(
account_number, {}).get("closing_balance", 0.0)
rows_map[prefix + cstr(d.idx)].update({
"opening_balance": d[prefix + "opening_balance"],
"closing_balance": d[prefix + "closing_balance"],
})
for d in self.data:
for prefix in ["lft_", "rgt_"]:
if not d[prefix + "empty"] and d[prefix + "calc_type"] and d[prefix + "calc_sources"] and d[prefix + "calc_type"] == "Calculate Rows":
d[prefix + "opening_balance"] = d[prefix + "closing_balance"] = 0.0
d[prefix + "calc_sources"] = list(filter(None,
d[prefix + "calc_sources"].split(",")))
d[prefix + "rows"] = []
for idx in d[prefix + "calc_sources"]:
minus = False
if idx.startswith("-"):
idx = idx[1:]
minus = True
idx = cint(idx)
row = rows_map.get(prefix + cstr(idx), {})
d[prefix + "opening_balance"] += (-1 if minus else 1) * row.get("opening_balance", 0.0)
d[prefix + "closing_balance"] += (-1 if minus else 1) * \
row.get("closing_balance", 0.0)
d[prefix + "rows"].append({
"direction": (-1 if minus else 1),
"idx": idx,
"name": row.get("name"),
"opening_balance": row.get("opening_balance"),
"closing_balance": row.get("closing_balance"),
})
rows_map[prefix + cstr(d.idx)].update({
"opening_balance": d[prefix + "opening_balance"],
"closing_balance": d[prefix + "closing_balance"],
})
return self.data
def get_columns(self):
self.columns = [
{
"label": "资产",
"fieldname": "lft_name",
"fieldtype": "Data",
"width": 160,
},
{
"label": "行次",
"fieldname": "idx",
"fieldtype": "Int",
"width": 60,
},
{
"label": "期初数",
"fieldname": "lft_opening_balance",
"fieldtype": "Currency",
"width": 140,
},
{
"label": "期末数",
"fieldname": "lft_closing_balance",
"fieldtype": "Currency",
"width": 140,
},
{
"label": "负债和权益",
"fieldname": "rgt_name",
"fieldtype": "Data",
"width": 160,
},
{
"label": "行次",
"fieldname": "idx",
"fieldtype": "Int",
"width": 60,
},
{
"label": "期初数",
"fieldname": "rgt_opening_balance",
"fieldtype": "Currency",
"width": 140,
},
{
"label": "期末数",
"fieldname": "rgt_closing_balance",
"fieldtype": "Currency",
"width": 140,
},
]
return self.columns
class BalanceSheetSingleColumn(BalanceSheetDoubleColumns):
def run(self):
self.get_columns()
settings = frappe.get_single("Balance Sheet Settings")
self.data = list(map(lambda d: frappe._dict({
"idx": d.idx,
"lft_empty": d.lft_empty,
"lft_bold": d.lft_bold,
"lft_name": d.lft_name,
"lft_indent": d.lft_indent,
"lft_calc_type": d.lft_calc_type,
"lft_calc_sources": d.lft_calc_sources,
"rgt_empty": d.rgt_empty,
"rgt_bold": d.rgt_bold,
"rgt_name": d.rgt_name,
"rgt_indent": d.rgt_indent,
"rgt_calc_type": d.rgt_calc_type,
"rgt_calc_sources": d.rgt_calc_sources,
}), settings.items))
self.get_data()
currency = self.filters.presentation_currency or frappe.db.get_value(
"Company", self.filters.company, "default_currency"
)
report_summary = self.get_report_summary(
asset_row=settings.asset_row,
liability_row=settings.liability_row,
equity_row=settings.equity_row,
currency=currency,
)
chart = self.get_chart_data(
asset_row=settings.asset_row,
liability_row=settings.liability_row,
equity_row=settings.equity_row,
)
self.data = self.to_single_column()
return self.columns, self.data, None, chart, report_summary
def get_data(self):
accounts = frappe.db.sql(
"""select acc.name, acc.account_number, parent.account_number as parent_number,
acc.parent_account, acc.account_name,
acc.root_type, acc.report_type, acc.lft, acc.rgt
from `tabAccount` acc
left join `tabAccount` parent on parent.name = acc.parent_account
where acc.company=%s and acc.root_type in ('Asset', 'Liability', 'Equity') order by acc.lft""",
self.company,
as_dict=True,
)
if not accounts:
return None
accounts, accounts_by_num, parent_children_map = self.filter_accounts(accounts)
for i in range(13):
key = "balance_" + cstr(i)
# 当月期末为下一个月的期初
year = frappe.db.get_value('Fiscal Year', self.fiscal_year,'year_start_date').year
from_date = get_first_day(datetime.date(year=year, month=1, day=1))
if i > 0 and i < 12:
from_date = get_first_day(datetime.date(year=year, month=cint(i + 1), day=1))
elif i == 12:
from_date = get_first_day(datetime.date(year=year + 1, month=1, day=1))
opening_balances = get_opening_balances(self.filters.copy().update({
"from_date": from_date,
"to_date": get_last_day(from_date),
}))
for d in accounts:
if d.root_type == 'Asset':
d[key] = opening_balances.get(d.name, {}).get(
"opening_debit", 0) - opening_balances.get(d.name, {}).get("opening_credit", 0)
else:
d[key] = opening_balances.get(d.name, {}).get(
"opening_credit", 0) - opening_balances.get(d.name, {}).get("opening_debit", 0)
for d in reversed(accounts):
if d.parent_number and accounts_by_num[d.parent_number]:
accounts_by_num[d.parent_number][key] += d[key]
rows_map = {}
for d in self.data:
for prefix in ["lft_", "rgt_"]:
rows_map[prefix + cstr(d.idx)] = {
"name": d[prefix + "name"],
key: 0.0,
}
if not d[prefix + "empty"] and d[prefix + "calc_type"] and d[prefix + "calc_sources"] and d[prefix + "calc_type"] == "Closing Balance":
d[prefix + key] = 0.0
if not isinstance(d[prefix + "calc_sources"], list):
d[prefix + "calc_sources"] = list(filter(None,
d[prefix + "calc_sources"].split(",")))
d[prefix + "accounts"] = []
for account_number in d[prefix + "calc_sources"]:
minus = False
if account_number.startswith("-"):
account_number = account_number[1:]
minus = True
account = accounts_by_num.get(account_number)
if account:
d[prefix + "accounts"].append({
"direction": (-1 if minus else 1),
"name": account.name,
"account_number": account.account_number,
"account_name": account.account_name,
key: account[key],
})
d[prefix + key] += (-1 if minus else 1) * accounts_by_num.get(
account_number, {}).get(key, 0.0)
rows_map[prefix + cstr(d.idx)].update({
key: d[prefix + key],
})
for d in self.data:
for prefix in ["lft_", "rgt_"]:
if not d[prefix + "empty"] and d[prefix + "calc_type"] and d[prefix + "calc_sources"] and d[prefix + "calc_type"] == "Calculate Rows":
d[prefix + key] = d[prefix + key] = 0.0
if not isinstance(d[prefix + "calc_sources"], list):
d[prefix + "calc_sources"] = list(filter(None,
d[prefix + "calc_sources"].split(",")))
d[prefix + "rows"] = []
for idx in d[prefix + "calc_sources"]:
minus = False
if idx.startswith("-"):
idx = idx[1:]
minus = True
idx = cint(idx)
row = rows_map.get(prefix + cstr(idx))
d[prefix + key] += (-1 if minus else 1) * row.get(key, 0.0)
d[prefix + "rows"].append({
"direction": (-1 if minus else 1),
"idx": idx,
"name": row.get("name"),
key: row.get(key),
})
rows_map[prefix + cstr(d.idx)].update({
key: d[prefix + key],
})
return self.data
def to_single_column(self):
lft_data = []
rgt_data = []
for d in self.data:
for prefix in ["lft_", "rgt_"]:
r = frappe._dict({
"name": d.get(prefix + "name"),
"idx": d.get("idx"),
"empty": d.get(prefix + "empty"),
"bold": d.get(prefix + "bold"),
"indent": d.get(prefix + "indent"),
"calc_type": d.get(prefix + "calc_type"),
"calc_sources": d.get(prefix + "calc_sources"),
"rows": d.get(prefix + "rows"),
})
r.update({
"balance_" + cstr(i): d.get(prefix + "balance_" + cstr(i)) for i in range(13)
})
if prefix == "lft_":
lft_data.append(r)
else:
rgt_data.append(r)
self.data = lft_data
self.data.extend(
[
{
"name": None,
"empty": 1,
"indent": 0,
}
]
)
self.data.extend(rgt_data)
return self.data
def get_report_summary(
self,
asset_row,
liability_row,
equity_row,
currency,
):
if not self.data:
return None
row_map = {}
for d in self.data:
row_map[d.idx] = d
net_asset = row_map.get(asset_row, {}).get("lft_balance_12", 0.0)
net_liability = row_map.get(liability_row, {}).get("rgt_balance_12", 0.0)
net_equity = row_map.get(equity_row, {}).get("rgt_balance_12", 0.0)
net_provisional_profit_loss = net_asset - net_liability - net_equity
return [
{
"value": net_asset,
"label": _("Total Asset"),
"datatype": "Currency",
"currency": currency,
},
{
"value": net_liability,
"label": _("Total Liability"),
"datatype": "Currency",
"currency": currency,
},
{
"value": net_equity,
"label": _("Total Equity"),
"datatype": "Currency",
"currency": currency,
},
{
"value": net_provisional_profit_loss,
"label": _("Provisional Profit / Loss (Credit)"),
"indicator": "Green" if net_provisional_profit_loss > 0 else "Red",
"datatype": "Currency",
"currency": currency,
},
]
def get_chart_data(
self,
asset_row,
liability_row,
equity_row,
):
labels = [d.get("label") for d in self.columns[2:]]
row_map = {}
for d in self.data:
if d.get("idx"):
row_map[d.get("idx")] = d
asset_data, liability_data, equity_data = [], [], []
for p in self.columns[2:]:
if asset_row:
asset_data.append(row_map.get(asset_row, {}).get("lft_" + p.get("fieldname"), 0.0))
if liability_row:
liability_data.append(row_map.get(liability_row, {}).get("rgt_" + p.get("fieldname"), 0.0))
if equity_row:
equity_data.append(row_map.get(equity_row, {}).get("rgt_" + p.get("fieldname"), 0.0))
datasets = []
if asset_data:
datasets.append({"name": _("Assets"), "values": asset_data})
if liability_data:
datasets.append({"name": _("Liabilities"), "values": liability_data})
if equity_data:
datasets.append({"name": _("Equity"), "values": equity_data})
chart = {"data": {"labels": labels, "datasets": datasets}}
if not self.filters.accumulated_values:
chart["type"] = "bar"
else:
chart["type"] = "line"
return chart
def get_columns(self):
self.columns = [
{
"label": "科目",
"fieldname": "name",
"fieldtype": "Data",
"width": 160,
},
]
for i in range(13):
self.columns.append({
"label": "期初数" if i == 0 else cstr(i) + "",
"fieldname": "balance_" + cstr(i),
"fieldtype": "Currency",
"width": 140,
})
return self.columns
def get_opening_balances(filters):
#15.5x 升级后加了第二个参数 ignore_is_opening
sig = inspect.signature(original_get_opening_balances)
parameters = sig.parameters
num_params = len(parameters)
if num_params == 2:
return original_get_opening_balances(filters, 0)
else:
return original_get_opening_balances(filters)

View File

@@ -0,0 +1,71 @@
// Copyright (c) 2023, 杨嘉祥 and contributors
// For license information, please see license.txt
/* eslint-disable */
frappe.query_reports["Fin Profit and Loss Statement"] = $.extend(
{},
erpnext.financial_statements
);
frappe.query_reports["Fin Profit and Loss Statement"] = {
"filters": [
{
"fieldname": "company",
"label": __("Company"),
"fieldtype": "Link",
"options": "Company",
"default": frappe.defaults.get_user_default("Company"),
"reqd": 1
},
{
"fieldname": "fiscal_year",
"label": __("Fiscal Year"),
"fieldtype": "Link",
"options": "Fiscal Year",
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
"reqd": 1,
"on_change": function (query_report) {
}
},
{
"fieldname": "month",
"label": "月份",
"fieldtype": "Select",
"options": ["", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
"default": new Date().getMonth() + 1,
},
],
formatter: function (value, row, column, data, default_formatter) {
value = default_formatter(value, row, column, data);
if (!data) {
return value;
}
if (column.fieldname === 'label' && data.indent > 0) {
value = `<span style="padding-left: ${data.indent * 20}px;">${value}</span>`;
}
return value;
},
onload: function(report) {
const views_menu = report.page.add_custom_button_group(__('Financial Statements'));
report.page.add_custom_menu_item(views_menu, __("Fin Balance Sheet"), function() {
var filters = report.get_values();
frappe.set_route('query-report', 'Fin Balance Sheet', {company: filters.company});
});
report.page.add_custom_menu_item(views_menu, __("Fin Profit and Loss Statement"), function() {
var filters = report.get_values();
frappe.set_route('query-report', 'Fin Profit and Loss Statement', {company: filters.company});
});
},
set_breadcrumb() {
if (!this.report_doc || this.report_doc.ref_doctype) return;
const ref_doctype = frappe.get_meta(this.report_doc.ref_doctype);
frappe.breadcrumbs.add(ref_doctype.module);
}
}

View File

@@ -0,0 +1,29 @@
{
"add_total_row": 0,
"columns": [],
"creation": "2023-12-08 09:58:44.880821",
"disable_prepared_report": 0,
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"filters": [],
"idx": 0,
"is_standard": "Yes",
"modified": "2026-01-07 17:28:43.555159",
"modified_by": "Administrator",
"module": "ERPNext China",
"name": "Fin Profit and Loss Statement",
"owner": "Administrator",
"prepared_report": 0,
"ref_doctype": "GL Entry",
"report_name": "Fin Profit and Loss Statement",
"report_type": "Script Report",
"roles": [
{
"role": "Accounts User"
},
{
"role": "Accounts Manager"
}
]
}

View File

@@ -0,0 +1,408 @@
# Copyright (c) 2023, 杨嘉祥 and contributors
# For license information, please see license.txt
import frappe
from frappe import _
from frappe.utils import cint, cstr, flt, getdate, datetime, get_first_day, get_last_day, formatdate, nowdate
from erpnext.accounts.utils import FiscalYearError,get_fiscal_year,get_currency_precision
def execute(filters=None):
validate_filters(filters)
columns = get_columns(filters)
settings = frappe.get_single("Profit and Loss Statement Settings")
fields = ['idx','label','indent','calc_type','calc_sources','amount_from']
#globals().update(locals())
if not settings.items:
form_link = frappe.utils.get_link_to_form("Profit and Loss Statement Settings",'')
frappe.msgprint(_("请先进行利润表{0}设置").format(form_link))
return
data = [frappe._dict({f:d.get(f) for f in fields}) for d in settings.items]
data = get_data(data, filters)
return columns, data
def validate_filters(filters):
if not filters.fiscal_year:
frappe.throw(_("Fiscal Year {0} is required").format(filters.fiscal_year))
fiscal_year = frappe.db.get_value(
"Fiscal Year", filters.fiscal_year, ["year_start_date", "year_end_date"], as_dict=True
)
if not fiscal_year:
frappe.throw(
_("Fiscal Year {0} does not exist").format(filters.fiscal_year))
else:
filters.year_start_date = getdate(fiscal_year.year_start_date)
filters.year_end_date = getdate(fiscal_year.year_end_date)
if filters.month:
year = frappe.db.get_value('Fiscal Year', fiscal_year,'year_start_date').year
filters.from_date = get_first_day(datetime.date(
year=year, month=cint(filters.month), day=1))
filters.to_date = get_last_day(datetime.date(
year=year, month=cint(filters.month), day=1))
else:
filters.from_date = filters.year_start_date
filters.to_date = filters.year_end_date
filters.from_date = getdate(filters.from_date)
filters.to_date = getdate(filters.to_date)
if filters.from_date > filters.to_date:
frappe.throw(_("From Date cannot be greater than To Date"))
if (filters.from_date < filters.year_start_date) or (filters.from_date > filters.year_end_date):
frappe.msgprint(
_("From Date should be within the Fiscal Year. Assuming From Date = {0}").format(
formatdate(filters.year_start_date)
)
)
filters.from_date = filters.year_start_date
if (filters.to_date < filters.year_start_date) or (filters.to_date > filters.year_end_date):
frappe.msgprint(
_("To Date should be within the Fiscal Year. Assuming To Date = {0}").format(
formatdate(filters.year_end_date)
)
)
filters.to_date = filters.year_end_date
def get_acc_nums(filters, data):
"""
# source: -5001,5051=> {acc_nums:[5001,5051], minus_factor:[-1,1]}
"""
accounts = frappe.get_all('Account',
fields = ['account_number','lft','rgt','is_group','parent_account', 'root_type'],
filters = {
'company': filters.company
#'root_type': ('in',('Income', 'Expense'))
}
)
account_number_map = {acc.account_number:acc for acc in accounts}
acc_nums = []
for d in data:
if d.calc_type and d.calc_sources and d.calc_type == "Closing Balance":
splitted_nums = list(filter(None, d.calc_sources.split(",")))
#globals().update(locals())
d.acc_nums = [f[1:] if f and f[0] == '-' else f for f in splitted_nums]
d.minus_factor = [-1 if f and f[0] == '-' else 1 for f in splitted_nums]
acc_nums.extend(d.acc_nums)
d.accounts = [account_number_map.get(acc_num) for acc_num in d.acc_nums]
parent_children_map = {}
non_group_acc_nums = []
for acc_num in acc_nums:
account = account_number_map.get(acc_num)
if account and account.is_group:
child_account_nums = [acc.account_number for acc in accounts
if acc.is_group == 0 and acc.lft > account.lft and acc.rgt < account.rgt]
parent_children_map[acc_num] = child_account_nums
non_group_acc_nums.extend(child_account_nums)
else:
non_group_acc_nums.append(acc_num)
return non_group_acc_nums, parent_children_map
def get_data(data, filters):
"""
1. 获取利润表设置明细中计算方式为期末余额的科目号清单
1.1 获取每个父科目号的下层记账科目号清单
1.2 剔除科目号负号前缀
2. 获取当月以及年初到当月底记帐科目会计凭证小计金额
3. 获取利润表设置明细行计算方式为期末余额的汇总金额
3.1 获取记账科目小计
3.2 获取父科目小计(下层记账科目汇总)
3.3 处理科目号负号前缀
3.4 收入科目自动*-1
4. 获取利润表设置明细行计算方式为计算公式的汇总金额
"""
def get_amount(amount_map, acc_num, amount_from=None):
return amount_map.get(acc_num, {}).get(amount_from or 'balance', 0)
acc_nums, parent_children_map = get_acc_nums(filters, data)
monthly_amount_map = get_balance_on(company=filters.company, date=filters.to_date,
start_date = filters.from_date,account_numbers = acc_nums)
if filters.year_start_date == filters.from_date:
month_end_amount_map = monthly_amount_map
else:
month_end_amount_map = get_balance_on(company=filters.company, date=filters.to_date,
start_date = filters.year_start_date,account_numbers = acc_nums)
rows_map = {}
for d in data:
d.amount, d.month_end_amount = 0, 0
if d.calc_type and d.calc_sources and d.calc_type == "Closing Balance":
row_monthly_amount, row_month_end_amount = 0, 0
for (i,account) in enumerate(d.accounts):
if not account: continue
acc_num = account.account_number
minus_factor = d.minus_factor[i]
children = parent_children_map.get(acc_num)
monthly_amount, month_end_amount = 0, 0
if not account.is_group:
monthly_amount = get_amount(monthly_amount_map, acc_num, d.amount_from) * minus_factor
month_end_amount = get_amount(month_end_amount_map, acc_num, d.amount_from) * minus_factor
elif children:
for child in children:
monthly_amount += get_amount(monthly_amount_map,child, d.amount_from) * minus_factor
month_end_amount += get_amount(month_end_amount_map, child, d.amount_from) * minus_factor
if account.root_type == "Income":
monthly_amount *= -1
month_end_amount *= -1
row_monthly_amount += monthly_amount
row_month_end_amount += month_end_amount
d.amount = row_monthly_amount
d.month_end_amount = row_month_end_amount
rows_map[cstr(d.idx)]= {
"amount": d.amount,
"month_end_amount": d.month_end_amount
}
for d in data:
if d.calc_type and d.calc_sources and d.calc_type == "Calculate Rows":
splitted_rows = list(filter(None, d.calc_sources.split(",")))
#globals().update(locals())
d.acc_nums = [f[1:] if f and f[0] == '-' else f for f in splitted_rows]
d.minus_factor = [-1 if f and f[0] == '-' else 1 for f in splitted_rows]
monthly_amount, month_end_amount = 0, 0
for (i, row_num) in enumerate(d.acc_nums):
minus_factor = d.minus_factor[i]
row = rows_map.get(cstr(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
d.amount = monthly_amount
d.month_end_amount = month_end_amount
rows_map[cstr(d.idx)] = {
"amount": d.amount,
"month_end_amount": d.month_end_amount
}
return data
def get_columns(filters):
columns = [
{
"label": "项目",
"fieldname": "label",
"fieldtype": "Data",
"width": 300,
},
{
"label": "行次",
"fieldname": "idx",
"fieldtype": "Int",
"width": 60,
},
{
"label": "金额",
"fieldname": "amount",
"fieldtype": "Currency",
"width": 120,
}
]
if filters.month:
columns.extend([
{
"label": "月底累计数",
"fieldname": "month_end_amount",
"fieldtype": "Currency",
"width": 120,
}
])
return columns
@frappe.whitelist()
def get_balance_on(
account=None,
date=None,
party_type=None,
party=None,
company=None,
in_account_currency=False,
cost_center=None,
ignore_account_permission=True,
account_type=None,
start_date=None,
account_numbers=[],
with_period_closing_entry=None,
debug = False
):
"""
基于 from erpnext.accounts.utils import get_balance_on
增加了
with_period_closing_entry
account_numbers
ignore_account_permission 默认为True
返回
{科目编号:{credit: 1, debit:2, balance:1}}
字典而不是一个金额数字
"""
if not account and frappe.form_dict.get("account"):
account = frappe.form_dict.get("account")
if not date and frappe.form_dict.get("date"):
date = frappe.form_dict.get("date")
if not party_type and frappe.form_dict.get("party_type"):
party_type = frappe.form_dict.get("party_type")
if not party and frappe.form_dict.get("party"):
party = frappe.form_dict.get("party")
if not cost_center and frappe.form_dict.get("cost_center"):
cost_center = frappe.form_dict.get("cost_center")
cond = ["is_cancelled=0"]
if not with_period_closing_entry:
cond.append('voucher_type != "Period Closing Voucher"')
if start_date:
cond.append("posting_date >= %s" % frappe.db.escape(cstr(start_date)))
if date:
cond.append("posting_date <= %s" % frappe.db.escape(cstr(date)))
else:
# get balance of all entries that exist
date = nowdate()
if account:
acc = frappe.get_doc("Account", account)
try:
year_start_date = get_fiscal_year(date, company=company, verbose=0)[1]
except FiscalYearError:
if getdate(date) > getdate(nowdate()):
# if fiscal year not found and the date is greater than today
# get fiscal year for today's date and its corresponding year start date
year_start_date = get_fiscal_year(nowdate(), verbose=1)[1]
else:
# this indicates that it is a date older than any existing fiscal year.
# hence, assuming balance as 0.0
return 0.0
if account:
report_type = acc.report_type
else:
report_type = ""
if cost_center and report_type == "Profit and Loss":
cc = frappe.get_doc("Cost Center", cost_center)
if cc.is_group:
cond.append(
""" exists (
select 1 from `tabCost Center` cc where cc.name = gle.cost_center
and cc.lft >= %s and cc.rgt <= %s
)"""
% (cc.lft, cc.rgt)
)
else:
cond.append("""gle.cost_center = %s """ % (frappe.db.escape(cost_center, percent=False),))
if account:
if not (frappe.flags.ignore_account_permission or ignore_account_permission):
acc.check_permission("read")
# different filter for group and ledger - improved performance
if acc.is_group:
cond.append(
"""exists (
select name from `tabAccount` ac where ac.name = gle.account
and ac.lft >= %s and ac.rgt <= %s
)"""
% (acc.lft, acc.rgt)
)
# If group and currency same as company,
# always return balance based on debit and credit in company currency
if acc.account_currency == frappe.get_cached_value("Company", acc.company, "default_currency"):
in_account_currency = False
else:
cond.append("""gle.account = %s """ % (frappe.db.escape(account, percent=False),))
if account_type:
accounts = frappe.db.get_all(
"Account",
filters={"company": company, "account_type": account_type, "is_group": 0},
pluck="name",
order_by="lft",
)
cond.append(
"""
gle.account in (%s)
"""
% (", ".join([frappe.db.escape(account) for account in accounts]))
)
if party_type and party:
cond.append(
"""gle.party_type = %s and gle.party = %s """
% (frappe.db.escape(party_type), frappe.db.escape(party, percent=False))
)
if company:
cond.append("""gle.company = %s """ % (frappe.db.escape(company, percent=False)))
if account_numbers or account or (party_type and party) or account_type:
precision = get_currency_precision()
if in_account_currency:
select_field = (
"sum(round(debit_in_account_currency, %s)) - sum(round(credit_in_account_currency, %s))"
)
else:
select_field = "sum(round(debit, %s)) - sum(round(credit, %s))"
join_str = ""
if account_numbers:
join_str =" INNER JOIN `tabAccount` acct on gle.account = acct.name"
select_field = "acct.account_number, sum(round(debit, %s)), sum(round(credit, %s)) "
cond.append(
"""
account_number in (%s)
"""
% (", ".join([frappe.db.escape(account_number) for account_number in account_numbers]))
)
group_by = " group by acct.account_number "
else:
group_by = ""
bal = frappe.db.sql(
"""
SELECT {0}
FROM `tabGL Entry` gle
{1}
WHERE {2}
{3}
""".format(
select_field, join_str, " and ".join(cond), group_by
),
(precision, precision),
debug = debug
)
# if bal is None, return 0
if bal:
result = frappe._dict({
b[0]:{'Debit':b[1],
'Credit': b[2],
'Balance': b[1] - b[2]
}
for b in bal
})
return result if bal else {}
"""
for testing
from erpnext_china.erpnext_chinacounting.report.fin_profit_and_loss_statement.fin_profit_and_loss_statement import *
filters = frappe._dict({"company":"则霖信息技术(深圳)有限公司","fiscal_year":"2024","month":"2"})
validate_filters(filters)
columns = get_columns(filters)
settings = frappe.get_single("Profit and Loss Statement Settings")
fields = ['idx','label','indent','calc_type','calc_sources','amount_from']
globals().update(locals())
data = [frappe._dict({f:d.get(f) for f in fields}) for d in settings.items]
data = get_data(data, filters)
"""

View File

@@ -0,0 +1,227 @@
{
"charts": [],
"content": "[{\"id\":\"xc4AQjjzJL\",\"type\":\"header\",\"data\":{\"text\":\"<b>\u4e3b\u6570\u636e\u3001\u4e1a\u52a1\u4ea4\u6613\u3001\u62a5\u8868</b><br>\",\"col\":12}},{\"id\":\"pq69Iywr6X\",\"type\":\"card\",\"data\":{\"card_name\":\"\u62a5\u8868\",\"col\":4}},{\"id\":\"mlkknfg8cR\",\"type\":\"card\",\"data\":{\"card_name\":\"\u8bbe\u7f6e\",\"col\":4}}]",
"creation": "2026-01-07 08:09:01.394204",
"custom_blocks": [],
"docstatus": 0,
"doctype": "Workspace",
"for_user": "",
"hide_custom": 0,
"icon": "",
"idx": 0,
"indicator_color": "green",
"is_hidden": 0,
"label": "ERPNext China",
"links": [
{
"hidden": 0,
"is_query_report": 0,
"label": "Cash Flow Code",
"link_count": 0,
"link_to": "Cash Flow Code",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Material Move Reason Code",
"link_count": 0,
"link_to": "Material Move Reason Code",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Cash Flow",
"link_count": 0,
"link_to": "Cash Flow",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "\u6708\u7ed3",
"link_count": 7,
"link_type": "DocType",
"onboard": 0,
"type": "Card Break"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "\u5236\u9020\u8d39\u7528\u5dee\u5f02\u7ed3\u7b97",
"link_count": 0,
"link_to": "Order Settlement",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 1,
"label": "\u4f9b\u5e94\u5546\u5bf9\u8d26\u62a5\u8868",
"link_count": 0,
"link_to": "Supplier Reconciliation Report",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "\u4f9b\u5e94\u5546\u5bf9\u8d26\u5355",
"link_count": 0,
"link_to": "Supplier Reconciliation Statement",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "\u7a0e\u52a1\u53d1\u7968",
"link_count": 0,
"link_to": "Tax Invoice",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Tax Invoice Import",
"link_count": 0,
"link_to": "Tax Invoice Import",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "\u6279\u91cf\u4ed8\u6b3e\u7533\u8bf7",
"link_count": 0,
"link_to": "Batch Payment Request",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "\u8d22\u52a1\u7ed3\u8d26",
"link_count": 0,
"link_to": "Month End Tracking",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "\u62a5\u8868",
"link_count": 3,
"link_type": "DocType",
"onboard": 0,
"type": "Card Break"
},
{
"hidden": 0,
"is_query_report": 1,
"label": "\u8d44\u4ea7\u8d1f\u503a\u8868\uff08\u4e24\u680f\u5f0f\uff09",
"link_count": 0,
"link_to": "Fin Balance Sheet",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 1,
"label": "\u5229\u6da6\u8868",
"link_count": 0,
"link_to": "Fin Profit and Loss Statement",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "\u73b0\u91d1\u6d41\u91cf\u8868",
"link_count": 0,
"link_to": "Cash Flow",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "\u8bbe\u7f6e",
"link_count": 4,
"link_type": "DocType",
"onboard": 0,
"type": "Card Break"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "\u8d44\u4ea7\u8d1f\u503a\u8868\uff08\u4e24\u680f\u5f0f\uff09\u8bbe\u7f6e",
"link_count": 0,
"link_to": "Balance Sheet Settings",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "\u5229\u6da6\u8868\u8bbe\u7f6e",
"link_count": 0,
"link_to": "Profit and Loss Statement Settings",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 1,
"label": "\u8d44\u4ea7\u8d1f\u503a\u4e0e\u5229\u6da6\u8868\u8bbe\u7f6e\u9057\u6f0f\u79d1\u76ee\u68c0\u67e5\u62a5\u8868",
"link_count": 0,
"link_to": "BS and PL Missing Account",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "\u73b0\u91d1\u6d41\u7f16\u7801",
"link_count": 0,
"link_to": "Cash Flow Code",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
}
],
"modified": "2026-01-07 08:26:08.435677",
"modified_by": "fisher@abc.com",
"module": "ERPNext China",
"name": "中国财务报表",
"number_cards": [],
"owner": "fisher@abc.com",
"parent_page": "",
"public": 1,
"quick_lists": [],
"roles": [],
"sequence_id": 8.0,
"shortcuts": [],
"title": "中国财务报表"
}

View File

@@ -0,0 +1,288 @@
[
{
"cash_flow_name": "\u6536\u5230\u7684\u5176\u4ed6\u4e0e\u7ecf\u8425\u6d3b\u52a8\u6709\u5173\u7684\u73b0\u91d1",
"cash_flow_type": "\u4e00\u3001\u7ecf\u8425\u6d3b\u52a8\u4ea7\u751f\u7684\u73b0\u91d1\u6d41\u91cf:",
"code": "2",
"docstatus": 0,
"doctype": "Cash Flow Code",
"formula": null,
"is_outflow": 0,
"modified": "2023-12-16 16:39:50.153030",
"name": "2",
"party_type": null,
"report_sequence": 2
},
{
"cash_flow_name": "\u652f\u4ed8\u7684\u804c\u5de5\u85aa\u916c",
"cash_flow_type": "\u4e00\u3001\u7ecf\u8425\u6d3b\u52a8\u4ea7\u751f\u7684\u73b0\u91d1\u6d41\u91cf:",
"code": "4",
"docstatus": 0,
"doctype": "Cash Flow Code",
"formula": null,
"is_outflow": 1,
"modified": "2023-12-16 17:40:57.061064",
"name": "4",
"party_type": "Employee",
"report_sequence": 4
},
{
"cash_flow_name": "\u652f\u4ed8\u7684\u7a0e\u8d39",
"cash_flow_type": "\u4e00\u3001\u7ecf\u8425\u6d3b\u52a8\u4ea7\u751f\u7684\u73b0\u91d1\u6d41\u91cf:",
"code": "5",
"docstatus": 0,
"doctype": "Cash Flow Code",
"formula": null,
"is_outflow": 1,
"modified": "2023-12-14 10:07:07.132109",
"name": "5",
"party_type": null,
"report_sequence": 5
},
{
"cash_flow_name": "\u652f\u4ed8\u7684\u5176\u4ed6\u4e0e\u7ecf\u8425\u6d3b\u52a8\u6709\u5173\u7684\u73b0\u91d1",
"cash_flow_type": "\u4e00\u3001\u7ecf\u8425\u6d3b\u52a8\u4ea7\u751f\u7684\u73b0\u91d1\u6d41\u91cf:",
"code": "6",
"docstatus": 0,
"doctype": "Cash Flow Code",
"formula": null,
"is_outflow": 1,
"modified": "2023-12-14 10:07:04.571196",
"name": "6",
"party_type": null,
"report_sequence": 6
},
{
"cash_flow_name": "\u7ecf\u8425\u6d3b\u52a8\u4ea7\u751f\u7684\u73b0\u91d1\u6d41\u91cf\u51c0\u989d",
"cash_flow_type": "\u4e00\u3001\u7ecf\u8425\u6d3b\u52a8\u4ea7\u751f\u7684\u73b0\u91d1\u6d41\u91cf:",
"code": "7",
"docstatus": 0,
"doctype": "Cash Flow Code",
"formula": "type_subtotal",
"is_outflow": 0,
"modified": "2023-12-14 10:06:27.037641",
"name": "7",
"party_type": null,
"report_sequence": 7
},
{
"cash_flow_name": "\u6536\u56de\u77ed\u671f\u6295\u8d44\u3001\u957f\u671f\u503a\u5238\u6295\u8d44\u548c\u957f\u671f\u80a1\u6743\u6295\u8d44\u6536\u5230\u7684\u73b0\u91d1",
"cash_flow_type": "\u4e8c\u3001\u6295\u8d44\u6d3b\u52a8\u4ea7\u751f\u7684\u73b0\u91d1\u6d41\u91cf:",
"code": "8",
"docstatus": 0,
"doctype": "Cash Flow Code",
"formula": null,
"is_outflow": 0,
"modified": "2023-12-14 10:07:01.909503",
"name": "8",
"party_type": null,
"report_sequence": 8
},
{
"cash_flow_name": "\u53d6\u5f97\u6295\u8d44\u6536\u76ca\u6536\u5230\u7684\u73b0\u91d1",
"cash_flow_type": "\u4e8c\u3001\u6295\u8d44\u6d3b\u52a8\u4ea7\u751f\u7684\u73b0\u91d1\u6d41\u91cf:",
"code": "9",
"docstatus": 0,
"doctype": "Cash Flow Code",
"formula": null,
"is_outflow": 0,
"modified": "2023-12-14 10:06:58.869881",
"name": "9",
"party_type": null,
"report_sequence": 9
},
{
"cash_flow_name": "\u507f\u8fd8\u501f\u6b3e\u5229\u606f\u652f\u4ed8\u7684\u73b0\u91d1",
"cash_flow_type": "\u4e09\u3001\u7b79\u8d44\u6d3b\u52a8\u4ea7\u751f\u7684\u73b0\u91d1\u6d41\u91cf:",
"code": "17",
"docstatus": 0,
"doctype": "Cash Flow Code",
"formula": null,
"is_outflow": 1,
"modified": "2023-12-14 10:06:33.803026",
"name": "17",
"party_type": null,
"report_sequence": 17
},
{
"cash_flow_name": "\u5206\u914d\u5229\u6da6\u652f\u4ed8\u7684\u73b0\u91d1",
"cash_flow_type": "\u4e09\u3001\u7b79\u8d44\u6d3b\u52a8\u4ea7\u751f\u7684\u73b0\u91d1\u6d41\u91cf:",
"code": "18",
"docstatus": 0,
"doctype": "Cash Flow Code",
"formula": null,
"is_outflow": 1,
"modified": "2023-12-14 10:06:30.369901",
"name": "18",
"party_type": null,
"report_sequence": 18
},
{
"cash_flow_name": "\u7b79\u8d44\u6d3b\u52a8\u4ea7\u751f\u7684\u73b0\u91d1\u6d41\u91cf\u51c0\u989d",
"cash_flow_type": "\u4e09\u3001\u7b79\u8d44\u6d3b\u52a8\u4ea7\u751f\u7684\u73b0\u91d1\u6d41\u91cf:",
"code": "19",
"docstatus": 0,
"doctype": "Cash Flow Code",
"formula": "type_subtotal",
"is_outflow": 0,
"modified": "2023-12-14 10:06:17.467676",
"name": "19",
"party_type": null,
"report_sequence": 19
},
{
"cash_flow_name": null,
"cash_flow_type": "\u56db\u3001\u73b0\u91d1\u51c0\u589e\u52a0\u989d",
"code": "20",
"docstatus": 0,
"doctype": "Cash Flow Code",
"formula": "above_subtotal",
"is_outflow": 0,
"modified": "2023-12-14 10:06:14.310750",
"name": "20",
"party_type": null,
"report_sequence": 20
},
{
"cash_flow_name": "\u52a0:\u671f\u521d\u73b0\u91d1\u4f59\u989d",
"cash_flow_type": "",
"code": "21",
"docstatus": 0,
"doctype": "Cash Flow Code",
"formula": "last_period_balance",
"is_outflow": 0,
"modified": "2023-12-14 10:06:09.082144",
"name": "21",
"party_type": null,
"report_sequence": 21
},
{
"cash_flow_name": null,
"cash_flow_type": "\u4e94\u3001\u671f\u672b\u73b0\u91d1\u4f59\u989d",
"code": "22",
"docstatus": 0,
"doctype": "Cash Flow Code",
"formula": "above_subtotal",
"is_outflow": 0,
"modified": "2023-12-14 10:06:11.256054",
"name": "22",
"party_type": null,
"report_sequence": 22
},
{
"cash_flow_name": "\u9500\u552e\u4ea7\u6210\u54c1\u3001\u5546\u54c1\u3001\u63d0\u4f9b\u52b3\u52a1\u6536\u5230\u7684\u73b0\u91d1",
"cash_flow_type": "\u4e00\u3001\u7ecf\u8425\u6d3b\u52a8\u4ea7\u751f\u7684\u73b0\u91d1\u6d41\u91cf:",
"code": "1",
"docstatus": 0,
"doctype": "Cash Flow Code",
"formula": null,
"is_outflow": 0,
"modified": "2023-12-16 16:41:35.432645",
"name": "1",
"party_type": "Customer",
"report_sequence": 1
},
{
"cash_flow_name": "\u5904\u7f6e\u56fa\u5b9a\u8d44\u4ea7\u3001\u65e0\u5f62\u8d44\u4ea7\u548c\u5176\u4ed6\u975e\u6d41\u52a8\u8d44\u4ea7\u6536\u56de\u7684\u73b0\u91d1\u51c0\u989d",
"cash_flow_type": "\u4e8c\u3001\u6295\u8d44\u6d3b\u52a8\u4ea7\u751f\u7684\u73b0\u91d1\u6d41\u91cf:",
"code": "10",
"docstatus": 0,
"doctype": "Cash Flow Code",
"formula": null,
"is_outflow": 0,
"modified": "2023-12-14 10:06:56.414698",
"name": "10",
"party_type": null,
"report_sequence": 10
},
{
"cash_flow_name": "\u6295\u8d44\u6d3b\u52a8\u4ea7\u751f\u7684\u73b0\u91d1\u6d41\u91cf\u51c0\u989d",
"cash_flow_type": "\u4e8c\u3001\u6295\u8d44\u6d3b\u52a8\u4ea7\u751f\u7684\u73b0\u91d1\u6d41\u91cf:",
"code": "13",
"docstatus": 0,
"doctype": "Cash Flow Code",
"formula": "type_subtotal",
"is_outflow": 0,
"modified": "2023-12-14 10:06:24.150124",
"name": "13",
"party_type": null,
"report_sequence": 13
},
{
"cash_flow_name": "\u507f\u8fd8\u501f\u6b3e\u672c\u91d1\u652f\u4ed8\u7684\u73b0\u91d1",
"cash_flow_type": "\u4e09\u3001\u7b79\u8d44\u6d3b\u52a8\u4ea7\u751f\u7684\u73b0\u91d1\u6d41\u91cf:",
"code": "16",
"docstatus": 0,
"doctype": "Cash Flow Code",
"formula": null,
"is_outflow": 1,
"modified": "2023-12-14 10:06:37.873421",
"name": "16",
"party_type": null,
"report_sequence": 16
},
{
"cash_flow_name": "\u77ed\u671f\u6295\u8d44\u3001\u957f\u671f\u503a\u5238\u6295\u8d44\u548c\u957f\u671f\u80a1\u6743\u6295\u8d44\u652f\u4ed8\u7684\u73b0\u91d1",
"cash_flow_type": "\u4e8c\u3001\u6295\u8d44\u6d3b\u52a8\u4ea7\u751f\u7684\u73b0\u91d1\u6d41\u91cf:",
"code": "11",
"docstatus": 0,
"doctype": "Cash Flow Code",
"formula": null,
"is_outflow": 1,
"modified": "2023-12-14 10:06:53.421118",
"name": "11",
"party_type": null,
"report_sequence": 11
},
{
"cash_flow_name": "\u8d2d\u5efa\u56fa\u5b9a\u8d44\u4ea7\u3001\u65e0\u5f62\u8d44\u4ea7\u548c\u5176\u4ed6\u975e\u6d41\u52a8\u8d44\u4ea7\u652f\u4ed8\u7684\u73b0\u91d1",
"cash_flow_type": "\u4e8c\u3001\u6295\u8d44\u6d3b\u52a8\u4ea7\u751f\u7684\u73b0\u91d1\u6d41\u91cf:",
"code": "12",
"docstatus": 0,
"doctype": "Cash Flow Code",
"formula": null,
"is_outflow": 1,
"modified": "2023-12-14 10:06:47.653975",
"name": "12",
"party_type": null,
"report_sequence": 12
},
{
"cash_flow_name": "\u53d6\u5f97\u501f\u6b3e\u6536\u5230\u7684\u73b0\u91d1",
"cash_flow_type": "\u4e09\u3001\u7b79\u8d44\u6d3b\u52a8\u4ea7\u751f\u7684\u73b0\u91d1\u6d41\u91cf:",
"code": "14",
"docstatus": 0,
"doctype": "Cash Flow Code",
"formula": null,
"is_outflow": 0,
"modified": "2023-12-14 10:06:43.491071",
"name": "14",
"party_type": null,
"report_sequence": 14
},
{
"cash_flow_name": "\u5438\u6536\u6295\u8d44\u8005\u6295\u8d44\u6536\u5230\u7684\u73b0\u91d1",
"cash_flow_type": "\u4e09\u3001\u7b79\u8d44\u6d3b\u52a8\u4ea7\u751f\u7684\u73b0\u91d1\u6d41\u91cf:",
"code": "15",
"docstatus": 0,
"doctype": "Cash Flow Code",
"formula": null,
"is_outflow": 0,
"modified": "2023-12-14 10:06:40.560277",
"name": "15",
"party_type": null,
"report_sequence": 15
},
{
"cash_flow_name": "\u8d2d\u4e70\u539f\u6750\u6599\u3001\u5546\u54c1\u3001\u63a5\u53d7\u52b3\u52a1\u652f\u4ed8\u7684\u73b0\u91d1",
"cash_flow_type": "\u4e00\u3001\u7ecf\u8425\u6d3b\u52a8\u4ea7\u751f\u7684\u73b0\u91d1\u6d41\u91cf:",
"code": "3",
"docstatus": 0,
"doctype": "Cash Flow Code",
"formula": null,
"is_outflow": 1,
"modified": "2023-12-16 12:28:03.139035",
"name": "3",
"party_type": "Supplier",
"report_sequence": 3
}
]

View File

@@ -0,0 +1,197 @@
[
{
"doctype": "Custom Field",
"dt": "Account",
"fieldname": "cash_flow_code",
"fieldtype": "Link",
"options":"Cash Flow Code",
"insert_after": "balance_must_be",
"label": "Default Cash Flow Code",
"modified": "2023-12-10 22:15:02.049023",
"name": "Account-cash_flow_code"
},
{
"doctype": "Custom Field",
"dt": "Account",
"fieldname": "allow_all_party_type",
"fieldtype": "Check",
"insert_after": "cash_flow_code",
"label": "Allow All Party Type",
"modified": "2025-01-14 22:15:02.049023",
"name": "Account-allow_all_party_type"
},
{
"doctype": "Custom Field",
"dt": "Customer",
"fieldname": "cash_flow_code",
"fieldtype": "Link",
"options":"Cash Flow Code",
"insert_after": "default_bank_account",
"label": "Default Cash Flow Code",
"modified": "2023-12-16 22:15:02.049023",
"name": "Customer-cash_flow_code"
},
{
"doctype": "Custom Field",
"dt": "Supplier",
"fieldname": "cash_flow_code",
"fieldtype": "Link",
"options":"Cash Flow Code",
"insert_after": "default_bank_account",
"label": "Default Cash Flow Code",
"modified": "2023-12-16 22:15:02.049023",
"name": "Supplier-cash_flow_code"
},
{
"doctype": "Custom Field",
"dt": "Company",
"fieldname": "sb_production_cost_account",
"fieldtype": "Section Break",
"insert_after": "expenses_included_in_valuation",
"label": "Production Cost Account",
"modified": "2023-12-30 22:15:02.049023",
"name": "Company-sb_production_cost_account"
},
{
"doctype": "Custom Field",
"dt": "Company",
"fieldname": "production_input_account",
"fieldtype": "Link",
"options":"Account",
"insert_after": "sb_production_cost_account",
"label": "Production Input Account",
"modified": "2023-12-30 22:15:02.049023",
"name": "Company-production_input_account"
},
{
"doctype": "Custom Field",
"dt": "Company",
"fieldname": "production_output_account",
"fieldtype": "Link",
"options":"Account",
"insert_after": "production_input_account",
"label": "Production Output Account",
"modified": "2023-12-30 22:15:02.049023",
"name": "Company-production_output_account"
},
{
"doctype": "Custom Field",
"dt": "Purchase Taxes and Charges",
"fieldname": "actual_tax_amount",
"fieldtype": "Float",
"depends_on":"eval:doc.charge_type !=='Actual'",
"insert_after": "tax_amount",
"label": "Actual Tax Amount",
"modified": "2023-12-30 22:18:02.049026",
"name": "Purchase Taxes and Charges-actual_tax_amount"
},
{
"doctype": "Custom Field",
"dt": "Sales Taxes and Charges",
"fieldname": "actual_tax_amount",
"fieldtype": "Float",
"depends_on":"eval:doc.charge_type !=='Actual'",
"insert_after": "tax_amount",
"label": "Actual Tax Amount",
"modified": "2023-12-30 22:18:02.049025",
"name": "Sales Taxes and Charges-actual_tax_amount"
},
{
"doctype": "Custom Field",
"dt": "Cost Center",
"modified": "2025-01-15 22:18:02.049025",
"name": "Cost Center-depreciation_expense_account",
"fieldname": "depreciation_expense_account",
"fieldtype": "Link",
"label": "Depreciation Expense Account",
"options":"Account",
"link_filters": "[[\"Account\",\"is_group\",\"=\",0],[\"Account\",\"company\",\"=\",\"eval: doc.company\"],[\"Account\",\"account_type\",\"=\",\"Depreciation\"],[\"Account\",\"root_type\",\"in\", [\"Expense\", \"Income\"]]]",
"insert_after": "company"
},
{
"doctype": "Custom Field",
"dt": "Repost Item Valuation",
"modified": "2025-05-12 22:18:02.049025",
"name": "Repost Item Valuation-order_settlement",
"fieldname": "order_settlement",
"fieldtype": "Data",
"label": "Order Settlement",
"depends_on": "eval:doc.order_settlement",
"read_only": 1,
"no_copy": 1,
"insert_after": "allow_zero_rate"
},
{
"doctype": "Custom Field",
"dt": "Purchase Invoice",
"modified": "2025-12-30 22:18:02.049025",
"name": "Purchase Invoice-supplier_reconciliation_statement",
"fieldname": "supplier_reconciliation_statement",
"fieldtype": "Link",
"label": "Supplier Reconciliation Statement",
"options": "Supplier Reconciliation Statement",
"read_only": 1,
"no_copy": 1,
"insert_after": "bill_no"
},
{
"doctype": "Custom Field",
"dt": "Print Format",
"fieldname": "condition_for_default",
"fieldtype": "Code",
"options": "PythonExpression",
"insert_after": "disabled",
"label": "Condition for Default",
"description": "Context variable: doc and frappe.session.user, function get_roles can be used",
"modified": "2025-10-27 22:12:02.049023",
"name": "print-format_condition_for_default"
},
{
"doctype": "Custom Field",
"dt": "Print Format",
"fieldname": "priority",
"fieldtype": "Int",
"insert_after": "condition_for_default",
"default": 100,
"label": "Priority",
"description":"Smaller number higher priority",
"modified": "2025-10-27 22:12:02.049023",
"name": "print-format_priority"
},
{
"doctype": "Custom Field",
"dt": "Payment Entry",
"fieldname": "matched_bank_receipt",
"fieldtype": "Check",
"insert_after": "title",
"label": "Matched Bank Receipt",
"modified": "2025-11-29 22:15:02.049023",
"name": "Payment Entry-matched_bank_receipt",
"read_only": 1,
"no_copy": 1,
"allow_on_submit":1
},
{
"doctype": "Custom Field",
"dt": "Bank",
"fieldname": "bank_receipt_mapping",
"fieldtype": "Table",
"options": "Bank Receipt Field Mapping",
"insert_after": "bank_transaction_mapping",
"label": "Bank Receipt Field Mapping",
"modified": "2025-11-29 22:15:02.049023",
"name": "Bank-bank_receipt_mapping"
},
{
"doctype": "Custom Field",
"dt": "Expense Claim Account",
"fieldname": "cost_center_category",
"fieldtype": "Link",
"options": "Cost Center Category",
"insert_after": "company",
"in_list_view": 1,
"label": "Cost Center Category",
"modified": "2025-12-03 22:15:02.049023",
"name": "Expense Claim Account-cost_center_category"
}
]

View File

@@ -0,0 +1,504 @@
[
{
"doc_type": "Account",
"doctype": "Property Setter",
"doctype_or_field": "DocField",
"field_name": "root_type",
"modified": "2025-08-17 19:42:47.535662",
"name": "Account-root_type_options",
"property": "options",
"value": "\nAsset\nLiability\nIncome\nExpense\nEquity\nCommon Accounts"
},
{
"doc_type": "Expense Claim",
"doctype": "Property Setter",
"doctype_or_field": "DocField",
"field_name": "expenses",
"modified": "2024-08-27 19:42:46.943779",
"name": "Expense Claim-expenses-reqd",
"property": "reqd",
"property_type": "Check",
"value": 0
},
{
"doc_type": "Vehicle",
"doctype": "Property Setter",
"doctype_or_field": "DocField",
"field_name": "make",
"modified": "2022-02-01 19:42:46.943779",
"name": "Vihicle-make-label",
"property": "label",
"value": "Vihicle Maker"
},
{
"doc_type": "Contact Phone",
"doctype": "Property Setter",
"doctype_or_field": "DocField",
"field_name": "phone",
"modified": "2022-02-01 19:42:46.943779",
"name": "Contact Phone-phone-label",
"property": "label",
"value": "Phone Number"
},
{
"doc_type": "ToDo",
"doctype": "Property Setter",
"doctype_or_field": "DocField",
"field_name": "owner",
"modified": "2022-02-01 19:42:46.943779",
"name": "ToDo-owner-label",
"property": "label",
"value": "Completed By"
},
{
"doc_type": "Quality Meeting Minutes",
"doctype": "Property Setter",
"doctype_or_field": "DocField",
"field_name": "minute",
"modified": "2021-10-27 19:42:46.943779",
"name": "Quality Meeting Minutes-minute-label",
"property": "label",
"value": "Minutes"
},
{
"doc_type": "Account",
"doctype": "Property Setter",
"doctype_or_field": "DocField",
"field_name": "tax_rate",
"modified": "2021-10-27 19:42:46.943779",
"name": "Account-tax_rate-label",
"property": "label",
"value": "Tax Rate"
},
{
"doc_type": "Sales Taxes and Charges",
"doctype": "Property Setter",
"doctype_or_field": "DocField",
"field_name": "rate",
"modified": "2021-08-07 19:42:46.943779",
"name": "Sales Taxes and Charges-rate-label",
"property": "label",
"value": "Tax Rate"
},
{
"doc_type": "Purchase Taxes and Charges",
"doctype": "Property Setter",
"doctype_or_field": "DocField",
"field_name": "rate",
"modified": "2021-08-07 19:42:46.977888",
"name": "Purchase Taxes and Charges-rate-label",
"property": "label",
"value": "Tax Rate"
},
{
"doc_type": "Advance Taxes and Charges",
"doctype": "Property Setter",
"doctype_or_field": "DocField",
"field_name": "rate",
"modified": "2021-08-07 19:42:47.005043",
"name": "Advance Taxes and Charges-rate-label",
"property": "label",
"value": "Tax Rate"
},
{
"doc_type": "Task",
"doctype": "Property Setter",
"doctype_or_field": "DocField",
"field_name": "task_weight",
"modified": "2021-08-07 19:42:47.029981",
"name": "Task-weight-label",
"property": "label",
"value": "Weightage"
},
{
"doc_type": "Task Type",
"doctype": "Property Setter",
"doctype_or_field": "DocField",
"field_name": "weight",
"modified": "2022-02-01 19:42:47.029981",
"name": "Task-Type-weight-label",
"property": "label",
"value": "Weightage"
},
{
"doc_type": "Project",
"doctype": "Property Setter",
"doctype_or_field": "DocField",
"field_name": "margin",
"modified": "2022-02-01 19:42:47.029981",
"name": "Project-margin-label",
"property": "label",
"value": "Gross Margin"
},
{
"doc_type": "Purchase Invoice Item",
"doctype": "Property Setter",
"doctype_or_field": "DocField",
"field_name": "manufacture_details",
"modified": "2021-08-07 19:42:47.054384",
"name": "Purchase Invoice Item-manufacture_details-label",
"property": "label",
"value": "Manufacturing"
},
{
"doc_type": "Purchase Order Item",
"doctype": "Property Setter",
"doctype_or_field": "DocField",
"field_name": "manufacture_details",
"modified": "2021-08-07 19:42:47.079343",
"name": "Purchase Order Item-manufacture_details-label",
"property": "label",
"value": "Manufacturing"
},
{
"default_value": null,
"doc_type": "Supplier Quotation Item",
"doctype": "Property Setter",
"doctype_or_field": "DocField",
"field_name": "manufacture_details",
"modified": "2021-08-07 19:42:47.103802",
"name": "Supplier Quotation Item-manufacture_details-label",
"property": "label",
"value": "Manufacturing"
},
{
"doc_type": "Material Request Item",
"doctype": "Property Setter",
"doctype_or_field": "DocField",
"field_name": "manufacture_details",
"modified": "2021-08-07 19:42:47.128046",
"name": "Material Request Item-manufacture_details-label",
"property": "label",
"value": "Manufacturing"
},
{
"doc_type": "Purchase Receipt Item",
"doctype": "Property Setter",
"doctype_or_field": "DocField",
"field_name": "manufacture_details",
"modified": "2021-08-07 19:42:47.153034",
"name": "Purchase Receipt Item-manufacture_details-label",
"property": "label",
"value": "Manufacturing"
},
{
"doc_type": "Bank Account",
"doctype": "Property Setter",
"doctype_or_field": "DocField",
"field_name": "account_name",
"modified": "2021-08-07 19:42:47.177693",
"name": "Bank Account-account_name-label",
"property": "label",
"value": "Bank Account Name"
},
{
"doc_type": "Bank Account",
"doctype": "Property Setter",
"doctype_or_field": "DocField",
"field_name": "account_type",
"modified": "2021-08-07 19:42:47.202452",
"name": "Bank Account-account_type-label",
"property": "label",
"value": "Bank Account Type"
},
{
"doc_type": "Bank Account",
"doctype": "Property Setter",
"doctype_or_field": "DocField",
"field_name": "account_subtype",
"modified": "2021-08-07 19:42:47.226413",
"name": "Bank Account-account_subtype-label",
"property": "label",
"value": "Bank Account Subtype"
},
{
"doc_type": "DocPerm",
"doctype": "Property Setter",
"doctype_or_field": "DocField",
"field_name": "select",
"modified": "2021-08-07 19:42:47.250230",
"name": "DocPerm-select-label",
"property": "label",
"value": "SELECT"
},
{
"doc_type": "DocPerm",
"doctype": "Property Setter",
"doctype_or_field": "DocField",
"field_name": "permlevel",
"modified": "2022-01-08 19:42:47.250230",
"name": "DocPerm-permlevel-label",
"property": "label",
"value": "Perm Level"
},
{
"doc_type": "Item Customer Detail",
"doctype": "Property Setter",
"doctype_or_field": "DocField",
"field_name": "ref_code",
"modified": "2021-08-07 19:42:47.250230",
"name": "Item Customer Detail-ref_code-label",
"property": "label",
"value": "Customer Part Number"
},
{
"doc_type": "Sales Order",
"doctype": "Property Setter",
"doctype_or_field": "DocField",
"field_name": "set_warehouse",
"modified": "2021-08-07 19:42:47.274986",
"name": "Sales Order-set_warehouse-label",
"property": "label",
"value": "Delivery Warehouse"
},
{
"doc_type": "Supplier",
"doctype": "Property Setter",
"doctype_or_field": "DocField",
"field_name": "pan",
"modified": "2021-08-07 19:42:47.298343",
"name": "Supplier-pan-hidden",
"property": "hidden",
"value": "1"
},
{
"doc_type": "Customer",
"doctype": "Property Setter",
"doctype_or_field": "DocField",
"field_name": "pan",
"modified": "2021-08-07 19:42:47.321681",
"name": "Customer-pan-hidden",
"property": "hidden",
"value": "1"
},
{
"doc_type": "Purchase Order",
"doctype": "Property Setter",
"doctype_or_field": "DocField",
"field_name": "subscription_section",
"modified": "2021-08-07 19:42:47.392636",
"name": "Purchase Order-subscription_section-hidden",
"property": "hidden",
"value": "1"
},
{
"doc_type": "Contact",
"doctype": "Property Setter",
"doctype_or_field": "DocField",
"field_name": "middle_name",
"modified": "2021-08-07 19:42:47.416368",
"name": "Contact-middle_name-hidden",
"property": "hidden",
"value": "1"
},
{
"doc_type": "User",
"doctype": "Property Setter",
"doctype_or_field": "DocField",
"field_name": "middle_name",
"modified": "2021-08-07 19:42:47.464339",
"name": "User-middle_name-hidden",
"property": "hidden",
"value": "1"
},
{
"doc_type": "User",
"doctype": "Property Setter",
"doctype_or_field": "DocField",
"field_name": "last_name",
"modified": "2021-08-07 19:42:47.488049",
"name": "User-last_name-hidden",
"property": "hidden",
"value": "1"
},
{
"doc_type": "User",
"doctype": "Property Setter",
"doctype_or_field": "DocField",
"field_name": "full_name",
"modified": "2021-08-07 19:42:47.511779",
"name": "User-full_name-hidden",
"property": "hidden",
"value": "1"
},
{
"doc_type": "Contact",
"doctype": "Property Setter",
"doctype_or_field": "DocField",
"field_name": "last_name",
"modified": "2021-08-07 19:42:47.535662",
"name": "Contact-last_name-hidden",
"property": "hidden",
"value": "1"
},
{
"doc_type": "Asset Movement",
"doctype": "Property Setter",
"doctype_or_field": "DocField",
"field_name": "purpose",
"modified": "2022-01-22 19:42:47.535662",
"name": "Asset Movement-purpose-in_list_view",
"property": "in_list_view",
"value": "1"
},
{
"doc_type": "Asset Movement",
"doctype": "Property Setter",
"doctype_or_field": "DocField",
"field_name": "purpose",
"modified": "2022-01-22 19:42:47.535662",
"name": "Asset Movement-purpose_in_standard_filter",
"property": "in_standard_filter",
"value": "1"
},
{
"doc_type": "Opportunity",
"doctype": "Property Setter",
"doctype_or_field": "DocField",
"field_name": "opportunity_type",
"modified": "2022-03-10 19:42:47.535662",
"name": "Opportunity-opportunity_type",
"property": "default",
"value": "销售"
},
{
"doc_type": "Opportunity",
"doctype": "Property Setter",
"doctype_or_field": "DocField",
"field_name": "sales_stage",
"modified": "2022-03-10 19:42:47.535662",
"name": "Opportunity-sales_stage",
"property": "default",
"value": "有意向"
},
{
"doc_type": "Company",
"doctype": "Property Setter",
"doctype_or_field": "DocField",
"field_name": "default_currency",
"modified": "2022-12-26 19:42:47.535662",
"name": "Company-default_currency",
"property": "default",
"value": "CNY"
},
{
"doc_type": "Workstation",
"doctype": "Property Setter",
"doctype_or_field": "DocField",
"field_name": "production_capacity",
"modified": "2023-05-02 19:42:47.535662",
"name": "Workstation-description_production_capacity",
"property": "description",
"value": "for equipment pool case, number of individual equipment"
},
{
"doc_type": "BOM Operation",
"doctype": "Property Setter",
"doctype_or_field": "DocField",
"field_name": "sequence_id",
"modified": "2023-05-02 19:42:47.535662",
"name": "BOM Operation-description_sequence_id",
"property": "description",
"value": "for same sequence ID operations, no preceeding operation to be finished validation check"
},
{
"doc_type": "BOM Item",
"doctype": "Property Setter",
"doctype_or_field": "DocField",
"field_name": "do_not_explode",
"modified": "2023-05-02 19:42:47.535662",
"name": "BOM Operation-description_do_not_explode",
"property": "description",
"value": "for physical SFG to be always produced in-house"
},
{
"doc_type": "BOM Item",
"doctype": "Property Setter",
"doctype_or_field": "DocField",
"field_name": "sourced_by_supplier",
"modified": "2023-05-02 19:42:47.535662",
"name": "BOM Operation-description_sourced_by_supplier",
"property": "description",
"value": "for subcontract case, ticked means this item excluded from the to-be sent to supplier raw material list"
},
{
"doc_type": "Item",
"doctype": "Property Setter",
"doctype_or_field": "DocField",
"field_name": "include_item_in_manufacturing",
"modified": "2023-05-03 19:42:47.535662",
"name": "Item-description_sourced_by_supplier",
"property": "description",
"value": "if not checked, it will be excluded from send to manufacture,manual transfer it to WIP warehouse without refer to work order instead"
},
{
"doc_type": "BOM Item",
"doctype": "Property Setter",
"doctype_or_field": "DocField",
"field_name": "include_item_in_manufacturing",
"modified": "2023-05-03 19:42:47.535662",
"name": "BOM Item-description_sourced_by_supplier",
"property": "description",
"value": "if not checked, it will be excluded from send to manufacture,manual transfer it to WIP warehouse without refer to work order instead"
},
{
"doc_type": "Work Order Item",
"doctype": "Property Setter",
"doctype_or_field": "DocField",
"field_name": "include_item_in_manufacturing",
"modified": "2023-05-03 19:42:47.535662",
"name": "Work Order Item-description_sourced_by_supplier",
"property": "description",
"value": "if not checked, it will be excluded from send to manufacture,manual transfer it to WIP warehouse without refer to work order instead"
},
{
"doc_type": "Account",
"doctype": "Property Setter",
"doctype_or_field": "DocField",
"field_name": "root_type",
"modified": "2023-08-22 19:42:47.535662",
"name": "Account-root_type_options",
"property": "options",
"value": "\nAsset\nLiability\nIncome\nExpense\nEquity\nCommon Accounts"
},
{
"doc_type": "Role Profile",
"doctype": "Property Setter",
"doctype_or_field": "DocField",
"field_name": "role_profile",
"modified": "2023-09-10 19:42:46.943779",
"name": "Role Profile-role_profile-label",
"property": "label",
"value": "Role Profile"
},
{
"doc_type": "Customer",
"doctype": "Property Setter",
"doctype_or_field": "DocField",
"field_name": "customer_name",
"modified": "2023-12-31 19:42:46.943779",
"name": "Customer-customer_name-no_copy",
"property": "no_copy",
"value": 0
},
{
"doc_type": "Supplier",
"doctype": "Property Setter",
"doctype_or_field": "DocField",
"field_name": "supplier_name",
"modified": "2023-12-31 19:42:46.943779",
"name": "Customer-supplier_name-no_copy",
"property": "no_copy",
"value": 0
},
{
"doc_type": "Bank Reconciliation Tool",
"doctype": "Property Setter",
"doctype_or_field": "DocField",
"field_name": "bank_statement_closing_balance",
"modified": "2025-05-17 19:42:46.943780",
"name": "Bank Reconciliation Tool-bank_statement_closing_balance-label",
"property": "label",
"value": "Closing Balance as per Bank Statement"
}
]

View File

@@ -1,249 +1,21 @@
from . import __version__ as app_version
app_name = "erpnext_china" app_name = "erpnext_china"
app_title = "ERPNext China" app_title = "ERPNext China"
app_publisher = "yuxinyong" app_publisher = "yuxinyong"
app_description = "ERPNext China" app_description = "ERPNext China"
app_email = "yuxinyong@163.com" app_email = "yuxinyong@163.com"
app_license = "mit" app_license = "MIT"
# Apps after_install = "erpnext_china.setup.install.after_install"
# ------------------
# required_apps = [] setup_wizard_requires = "assets/erpnext_china/js/setup_wizard.js"
# Each item in the list will be shown as an app in the apps page
# add_to_apps_screen = [
# {
# "name": "erpnext_china",
# "logo": "/assets/erpnext_china/logo.png",
# "title": "ERPNext China",
# "route": "/erpnext_china",
# "has_permission": "erpnext_china.api.permission.has_app_permission"
# }
# ]
# Includes in <head>
# ------------------
# include js, css files in header of desk.html
# app_include_css = "/assets/erpnext_china/css/erpnext_china.css"
# app_include_js = "/assets/erpnext_china/js/erpnext_china.js"
# include js, css files in header of web template
# web_include_css = "/assets/erpnext_china/css/erpnext_china.css"
# web_include_js = "/assets/erpnext_china/js/erpnext_china.js"
# include custom scss in every website theme (without file extension ".scss")
# website_theme_scss = "erpnext_china/public/scss/website"
# include js, css files in header of web form
# webform_include_js = {"doctype": "public/js/doctype.js"}
# webform_include_css = {"doctype": "public/css/doctype.css"}
# include js in page
# page_js = {"page" : "public/js/file.js"}
# include js in doctype views
# doctype_js = {"doctype" : "public/js/doctype.js"}
# doctype_list_js = {"doctype" : "public/js/doctype_list.js"}
# doctype_tree_js = {"doctype" : "public/js/doctype_tree.js"}
# doctype_calendar_js = {"doctype" : "public/js/doctype_calendar.js"}
# Svg Icons
# ------------------
# include app icons in desk
# app_include_icons = "erpnext_china/public/icons.svg"
# Home Pages
# ----------
# application home page (will override Website Settings)
# home_page = "login"
# website user home page (by Role)
# role_home_page = {
# "Role": "home_page"
# }
# Generators
# ----------
# automatically create page for each record of this doctype
# website_generators = ["Web Page"]
# Jinja
# ----------
# add methods and filters to jinja environment
# jinja = {
# "methods": "erpnext_china.utils.jinja_methods",
# "filters": "erpnext_china.utils.jinja_filters"
# }
# Installation
# ------------
# before_install = "erpnext_china.install.before_install"
# after_install = "erpnext_china.install.after_install"
# Uninstallation
# ------------
# before_uninstall = "erpnext_china.uninstall.before_uninstall"
# after_uninstall = "erpnext_china.uninstall.after_uninstall"
# Integration Setup
# ------------------
# To set up dependencies/integrations with other apps
# Name of the app being installed is passed as an argument
# before_app_install = "erpnext_china.utils.before_app_install"
# after_app_install = "erpnext_china.utils.after_app_install"
# Integration Cleanup
# -------------------
# To clean up dependencies/integrations with other apps
# Name of the app being uninstalled is passed as an argument
# before_app_uninstall = "erpnext_china.utils.before_app_uninstall"
# after_app_uninstall = "erpnext_china.utils.after_app_uninstall"
# Desk Notifications
# ------------------
# See frappe.core.notifications.get_notification_config
# notification_config = "erpnext_china.notifications.get_notification_config"
# Permissions
# -----------
# Permissions evaluated in scripted ways
# permission_query_conditions = {
# "Event": "frappe.desk.doctype.event.event.get_permission_query_conditions",
# }
#
# has_permission = {
# "Event": "frappe.desk.doctype.event.event.has_permission",
# }
# DocType Class
# ---------------
# Override standard doctype classes
# override_doctype_class = {
# "ToDo": "custom_app.overrides.CustomToDo"
# }
# Document Events
# ---------------
# Hook on document methods and events
# doc_events = {
# "*": {
# "on_update": "method",
# "on_cancel": "method",
# "on_trash": "method"
# }
# }
# Scheduled Tasks
# ---------------
# scheduler_events = {
# "all": [
# "erpnext_china.tasks.all"
# ],
# "daily": [
# "erpnext_china.tasks.daily"
# ],
# "hourly": [
# "erpnext_china.tasks.hourly"
# ],
# "weekly": [
# "erpnext_china.tasks.weekly"
# ],
# "monthly": [
# "erpnext_china.tasks.monthly"
# ],
# }
# Testing
# -------
# before_tests = "erpnext_china.install.before_tests"
# Overriding Methods
# ------------------------------
#
# override_whitelisted_methods = {
# "frappe.desk.doctype.event.event.get_events": "erpnext_china.event.get_events"
# }
#
# each overriding function accepts a `data` argument;
# generated from the base implementation of the doctype dashboard,
# along with any modifications made in other Frappe apps
# override_doctype_dashboards = {
# "Task": "erpnext_china.task.get_dashboard_data"
# }
# exempt linked doctypes from being automatically cancelled
#
# auto_cancel_exempted_doctypes = ["Auto Repeat"]
# Ignore links to specified DocTypes when deleting documents
# -----------------------------------------------------------
# ignore_links_on_delete = ["Communication", "ToDo"]
# Request Events
# ----------------
# before_request = ["erpnext_china.utils.before_request"]
# after_request = ["erpnext_china.utils.after_request"]
# Job Events
# ----------
# before_job = ["erpnext_china.utils.before_job"]
# after_job = ["erpnext_china.utils.after_job"]
# User Data Protection
# --------------------
# user_data_fields = [
# {
# "doctype": "{doctype_1}",
# "filter_by": "{filter_by}",
# "redact_fields": ["{field_1}", "{field_2}"],
# "partial": 1,
# },
# {
# "doctype": "{doctype_2}",
# "filter_by": "{filter_by}",
# "partial": 1,
# },
# {
# "doctype": "{doctype_3}",
# "strict": False,
# },
# {
# "doctype": "{doctype_4}"
# }
# ]
# Authentication and authorization
# --------------------------------
# auth_hooks = [
# "erpnext_china.auth.validate"
# ]
# Automatically update python controller files with type annotations for this app.
# export_python_type_annotations = True
# default_log_clearing_doctypes = {
# "Logging DocType Name": 30 # days to retain logs
# }
# Translation
# ------------
# List of apps whose translatable strings should be excluded from this app's translations.
# ignore_translatable_strings_from = []
override_whitelisted_methods = {
"erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts.get_charts_for_country":
"erpnext_china.chart_of_accounts.custom_accounts.custom_account.get_charts_for_country",
"erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts.get_chart":
"erpnext_china.chart_of_accounts.custom_accounts.custom_account.get_chart",
"erpnext.accounts.utils.get_coa": "erpnext_china.chart_of_accounts.custom_accounts.custom_account.get_coa",
"frappe.desk.treeview.get_all_nodes": "erpnext_china.chart_of_accounts.custom_accounts.custom_account.get_all_nodes",
}

179
erpnext_china/locale/zh.po Normal file
View File

@@ -0,0 +1,179 @@
#. Header text in the Overview Workspace
#: hrms/hr/workspace/overview/overview.json
msgid "<span class=\"h4\"><b>Reports &amp; Masters</b></span>"
msgstr "<span class=\"h4\"><b>报表 &amp; 主数据</b></span>"
#. Header text in the Salary Payout Workspace
#: hrms/payroll/workspace/salary_payout/salary_payout.json
msgid "<span class=\"h4\"><b>Transactions &amp; Reports</b></span>"
msgstr "<span class=\"h4\"><b>交易 &amp; 报表</b></span>"
msgid "Confirm before resetting posting date"
msgstr "重置记账日期提醒"
msgid "If enabled, user will be alerted before resetting posting date to current date in relevant transactions"
msgstr "如勾选,重置记账日期为当日会弹出提醒给用户"
msgid "Upon enabling this, the JV will be submitted for a different exchange rate."
msgstr "勾选后,日记账凭证提交时会使用不同的汇率"
msgid "Set Valuation Rate for Rejected Materials"
msgstr "设置拒收物料成本价"
msgid "If enabled, the system will generate an accounting entry for materials rejected in the Purchase Receipt."
msgstr "如勾选,系统会为采购入库中的拒收物料生成会计凭证"
msgid "Fixed Outgoing Email Account"
msgstr "固定发件箱"
msgid "If set, the system does not use the user's Email or the standard outgoing Email account for sending request for quotations."
msgstr "如勾选,系统固定邮箱而非当前用户邮箱发出询价单"
msgid "Show Absolute Datetime in Timeline"
msgstr "单据时间线日志显示完整时间"
msgid "Max signups allowed per hour"
msgstr "每小时最大允许注册数"
msgid "Allow Consecutive Login Attempts"
msgstr "允许连续登录尝试次数"
msgid "Max Report Rows"
msgstr "最大报表行数"
msgid "This value specifies the max number of rows that can be rendered in report view."
msgstr "报表界面最大允许显示的行数"
msgid "Last Scanned Warehouse"
msgstr "最后已扫仓库代码"
msgid "Workflow Task"
msgstr "工作流任务"
msgid "Operating Costs (Per Hour)"
msgstr "工费成本(每小时)"
msgid "Operating Components Cost"
msgstr "工费成本"
msgid "Operating Component"
msgstr "工费成本构成"
msgid "Workstation Operating Component"
msgstr "工站工费成本构成"
msgid "Component Name"
msgstr "成本构成"
msgid "Component Expense Account"
msgstr "成本构成费用科目"
msgid "Serial No and Batch Traceability"
msgstr "序列号与批号追溯"
msgid "Traceability Direction"
msgstr "追溯方向"
msgid "Backward"
msgstr "向后"
msgid "Receipt Items"
msgstr "采购入库明细"
msgid "Vendor Invoices"
msgstr "供应商发票明细"
msgid "Vendor Invoice"
msgstr "供应商发票"
msgid "Landed Cost"
msgstr "到岸成本"
#. Label of the has_corrective_cost (Check) field in DocType 'Landed Cost Taxes
#. and Charges'
#: erpnext/stock/doctype/landed_cost_taxes_and_charges/landed_cost_taxes_and_charges.json
msgid "Has Corrective Cost"
msgstr "有返工成本"
#. Label of the claimed_landed_cost_amount (Currency) field in DocType
#. 'Purchase Invoice'
#: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
msgid "Claimed Landed Cost Amount (Company Currency)"
msgstr "已结转到岸成本(公司币)"
msgid "Top Customers"
msgstr "大客户"
msgid "Item-wise Annual Sales"
msgstr "年度单品销售额"
msgid "Delivery Trends"
msgstr "销售出货趋势"
msgid "Oldest Items"
msgstr "呆滞物料"
msgid "Item Shortage Summary"
msgstr "缺货清单"
msgid "Material Request Analysis"
msgstr "物料需求分析"
msgid "Top Suppliers"
msgstr "核心供应商"
msgid "New Lead (Last 1 Month)"
msgstr "新线索(上个月)"
msgid "New Opportunity (Last 1 Month)"
msgstr "新商机(上个月)"
msgid "Won Opportunity (Last 1 Month)"
msgstr "已转化商机(上个月)"
msgid "Open Opportunity"
msgstr "在手商机"
msgid "Incoming Leads"
msgstr "收到的线索"
msgid "Opportunity Trends"
msgstr "商机趋势"
msgid "Won Opportunities"
msgstr "已转化商机"
msgid "Territory Wise Opportunity Count"
msgstr "区域商机数"
msgid "Opportunities via Campaigns"
msgstr "营销转化的商机"
msgid "Advance Voucher No"
msgstr "预付单据号"
msgid "Advance Voucher Type"
msgstr "预付单据类型"
msgid "Show Group Accounts"
msgstr "显示上层(组)科目"
#: erpnext/accounts/report/general_ledger/general_ledger.js:202
msgid "Show Credit / Debit in Company Currency"
msgstr "以本币显示借贷金额"
msgid "API Logging"
msgstr "API调用日志"
msgid "Log API Requests"
msgstr "记录API调用日志"
msgid "FG / Semi FG Item"
msgstr "成品/半成品物料号"
msgid "Is Sub Assembly Item"
msgstr "是子装配件"
msgid "Is Composite Component"
msgstr "是子资产"

View File

@@ -0,0 +1,14 @@
from erpnext.accounts.doctype.account.chart_of_accounts import chart_of_accounts
from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import (
get_chart as original_get_chart
)
from erpnext_china.chart_of_accounts.custom_accounts.custom_account import get_chart
def erpnext_china_get_chart(chart_template, existing_company=None):
# 标准科目表直接调用源方法
if chart_template in ["Standard", "Standard with Numbers"]:
return original_get_chart(chart_template, existing_company)
return get_chart(chart_template, existing_company)
chart_of_accounts.get_chart = erpnext_china_get_chart

View File

@@ -0,0 +1,21 @@
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
# License: MIT. See LICENSE
import frappe
from frappe import _
from frappe.utils.csvutils import build_csv_response
from frappe.utils.xlsxutils import build_xlsx_response
from frappe.core.doctype.data_import.exporter import Exporter
def build_response(self):
from urllib.parse import quote
filename = _(self.doctype)
if self.file_type == "CSV":
filename = f'{quote(filename)}'
build_csv_response(self.get_csv_array_for_export(), filename)
elif self.file_type == "Excel":
build_xlsx_response(self.get_csv_array_for_export(), filename)
Exporter.build_response = build_response #转到csvutils里去处理

View File

@@ -0,0 +1,146 @@
import frappe
from frappe import _
from frappe.model.document import Document
from frappe.core.doctype.data_import import importer
from frappe.core.doctype.data_import.importer import ImportFile,INVALID_VALUES,MAX_ROWS_IN_PREVIEW
from frappe.core.doctype.data_import.importer import build_fields_dict_for_column_matching as old_build_fields_dict_for_column_matching
from frappe.core.doctype.data_import.exporter import Exporter
from frappe.core.doctype import data_import
def new_build_fields_dict_for_column_matching(parent_doctype):
"""支持中文字段标签"""
out = old_build_fields_dict_for_column_matching(parent_doctype)
#将dict转为df对象避免导入DocType时importer Header 740行 list(sort(doctypes)报 TypeError: unhashable type: '_dict'错误
child_table_df_map = {}
for label, field in out.items():
if hasattr(field, "child_table_df") and field.child_table_df and not isinstance(field.child_table_df, Document):
fieldname = field.child_table_df.get('fieldname')
if not fieldname in child_table_df_map:
child_table_df_map[fieldname] = frappe.get_doc('DocField', field.child_table_df)
field.child_table_df = child_table_df_map.get(fieldname)
if frappe.translate.get_user_lang != 'en':
to_be_trans = {label: field for (label, field) in out.items() if '.' not in label}
for label, field in to_be_trans.items():
if ' (' in label:
child_field, child_table = label.split(' (', 1)
chield_table = child_table[:-1]
trans_label = f"{_(child_field)} ({_(chield_table)})"
else:
trans_label = _(label)
if trans_label != label:
out[trans_label] = field
return out
importer.build_fields_dict_for_column_matching = new_build_fields_dict_for_column_matching
def new_add_header(self):
header = []
for df in self.fields:
is_parent = not df.is_child_table_field
if is_parent:
label = _(df.label)
else:
label = "{0} ({1})".format(_(df.label), _(df.child_table_df.label))
if label in header:
# this label is already in the header,
# which means two fields with the same label
# add the fieldname to avoid clash
if is_parent:
label = "{0}".format(df.fieldname)
else:
label = "{0}.{1}".format(df.child_table_df.fieldname, df.fieldname)
header.append(label)
self.csv_array.append(header)
Exporter.add_header = new_add_header
def parse_next_row_for_import(self, data):
"""
Parses rows that make up a doc. A doc maybe built from a single row or multiple rows.
Returns the doc, rows, and data without the rows.
"""
doctypes = self.header.doctypes
# first row is included by default
first_row = data[0]
rows = [first_row]
# if there are child doctypes, find the subsequent rows
if len(doctypes) > 1:
# subsequent rows that have blank values in parent columns
# are considered as child rows
parent_column_indexes = self.header.get_column_indexes(self.doctype)
parent_row_values = first_row.get_values(parent_column_indexes)
data_without_first_row = data[1:]
for row in data_without_first_row:
row_values = row.get_values(parent_column_indexes)
# if the row is blank, it's a child row doc fisher 下一行与上一行主单据内容相同时也作为同一个单据
if all([v in INVALID_VALUES for v in row_values]) or row_values == parent_row_values:
rows.append(row)
continue
# if we encounter a row which has values in parent columns,
# then it is the next doc
break
parent_doc = None
for row in rows:
for doctype, table_df in doctypes:
if doctype == self.doctype and not parent_doc:
parent_doc = row.parse_doc(doctype)
if doctype != self.doctype and table_df:
child_doc = row.parse_doc(doctype, parent_doc, table_df)
if child_doc is None:
continue
parent_doc[table_df.fieldname] = parent_doc.get(table_df.fieldname, [])
parent_doc[table_df.fieldname].append(child_doc)
doc = parent_doc
return doc, rows, data[len(rows) :]
ImportFile.parse_next_row_for_import = parse_next_row_for_import
def get_data_for_import_preview(self):
"""Adds a serial number column as the first column"""
#翻译第一个序号字段
columns = [frappe._dict({"header_title": _("Row #"), "skip_import": True})]
columns += [col.as_dict() for col in self.columns]
for col in columns:
# only pick useful fields in docfields to minimise the payload
if col.df:
col.df = {
"fieldtype": col.df.fieldtype,
"fieldname": col.df.fieldname,
"label": col.df.label,
"options": col.df.options,
"parent": col.df.parent,
"reqd": col.df.reqd,
"default": col.df.default,
"read_only": col.df.read_only,
}
data = [[row.row_number] + row.as_list() for row in self.data]
warnings = self.get_warnings()
out = frappe._dict()
out.data = data
out.columns = columns
out.warnings = warnings
total_number_of_rows = len(out.data)
if total_number_of_rows > MAX_ROWS_IN_PREVIEW:
out.data = out.data[:MAX_ROWS_IN_PREVIEW]
out.max_rows_exceeded = True
out.max_rows_in_preview = MAX_ROWS_IN_PREVIEW
out.total_number_of_rows = total_number_of_rows
return out
ImportFile.get_data_for_import_preview = get_data_for_import_preview

View File

@@ -0,0 +1,179 @@
from decimal import Decimal
import warnings
import frappe
from frappe.utils.data import *
# 这个金额转大写的,把它原来的方法贴过来了,就是在中间加了一段,判断如果是中文环境,就调用下面的金额转大写方法
def money_in_words_zh(number, main_currency = None, fraction_currency=None):
"""
Returns string in words with currency and fraction currency.
"""
from frappe.utils import get_defaults
_ = frappe._
try:
# note: `flt` returns 0 for invalid input and we don't want that
number = float(number)
except ValueError:
return ""
number = flt(number)
if number < 0:
return ""
d = get_defaults()
if not main_currency:
main_currency = d.get('currency', 'INR')
if not fraction_currency:
fraction_currency = frappe.db.get_value("Currency", main_currency, "fraction", cache=True) or _("Cent")
number_format = frappe.db.get_value("Currency", main_currency, "number_format", cache=True) or \
frappe.db.get_default("number_format") or "#,###.##"
fraction_length = get_number_format_info(number_format)[2]
n = "%.{0}f".format(fraction_length) % number
numbers = n.split('.')
main, fraction = numbers if len(numbers) > 1 else [n, '00']
if len(fraction) < fraction_length:
zeros = '0' * (fraction_length - len(fraction))
fraction += zeros
in_million = True
if number_format == "#,##,###.##": in_million = False
# 如果是中文,调用中文的金额转大写
if (frappe.local.lang == 'zh'):
return _(main_currency) + cncurrency(n, prefix=True)
# 0.00
if main == '0' and fraction in ['00', '000']:
out = "{0} {1}".format(main_currency, _('Zero'))
# 0.XX
elif main == '0':
out = _(in_words(fraction, in_million).title()) + ' ' + fraction_currency
else:
out = main_currency + ' ' + _(in_words(main, in_million).title())
if cint(fraction):
out = out + ' ' + _('and') + ' ' + _(in_words(fraction, in_million).title()) + ' ' + fraction_currency
return out + ' ' + _('only.')
def cncurrency(value, capital=True, prefix=False, classical=None):
'''
参数:
capital: True 大写汉字金额
False 一般汉字金额
classical: True 元
False 圆
prefix: True 以'人民币'开头
False, 无开头
'''
if not isinstance(value, (Decimal, str, int)):
msg = '''
由于浮点数精度问题,请考虑使用字符串,或者 decimal.Decimal 类。
因使用浮点数造成误差而带来的可能风险和损失作者概不负责。
'''
warnings.warn(msg, UserWarning)
# 默认大写金额用圆,一般汉字金额用元
if classical is None:
classical = True if capital else False
# 汉字金额前缀
prefix = ''
# 汉字金额字符定义
dunit = ('', '')
if capital:
num = ('', '', '', '', '', '', '', '', '', '')
iunit = [None, '', '', '', '', '', '', '', '亿', '', '', '', '', '', '', '']
else:
num = ('', '', '', '', '', '', '', '', '', '')
iunit = [None, '', '', '', '', '', '', '', '亿', '', '', '', '', '', '', '']
if classical:
iunit[0] = '' if classical else ''
# 转换为Decimal并截断多余小数
if not isinstance(value, Decimal):
value = Decimal(value).quantize(Decimal('0.01'))
# 处理负数
if value < 0:
prefix += '' # 输出前缀,加负
value = - value # 取正数部分,无须过多考虑正负数舍入
# assert - value + value == 0
# 转化为字符串
s = str(value)
if len(s) > 19:
raise ValueError('金额太大了,不知道该怎么表达。')
istr, dstr = s.split('.') # 小数部分和整数部分分别处理
istr = istr[::-1] # 翻转整数部分字符串
so = [] # 用于记录转换结果
# 零
if value == 0:
return prefix + num[0] + iunit[0]
haszero = False # 用于标记零的使用
if dstr == '00':
haszero = True # 如果无小数部分,则标记加过零,避免出现“圆零整”
# 处理小数部分
# 分
if dstr[1] != '0':
so.append(dunit[1])
so.append(num[int(dstr[1])])
else:
so.append('') # 无分,则加“整”
# 角
if dstr[0] != '0':
so.append(dunit[0])
so.append(num[int(dstr[0])])
elif dstr[1] != '0':
so.append(num[0]) # 无角有分,添加“零”
haszero = True # 标记加过零了
pass
# 无整数部分
if istr == '0':
if haszero: # 既然无整数部分,那么去掉角位置上的零
so.pop()
so.append(prefix) # 加前缀
so.reverse() # 翻转
return ''.join(so)
# 处理整数部分
for i, n in enumerate(istr):
n = int(n)
if i % 4 == 0: # 在圆、万、亿等位上,即使是零,也必须有单位
if i == 8 and so[-1] == iunit[4]: # 亿和万之间全部为零的情况
so.pop() # 去掉万
so.append(iunit[i])
if n == 0: # 处理这些位上为零的情况
if not haszero: # 如果以前没有加过零
so.insert(-1, num[0]) # 则在单位后面加零
haszero = True # 标记加过零了
pass
else: # 处理不为零的情况
so.append(num[n])
haszero = False # 重新开始标记加零的情况
else: # 在其他位置上
if n != 0: # 不为零的情况
so.append(iunit[i])
so.append(num[n])
haszero = False # 重新开始标记加零的情况
else: # 处理为零的情况
if not haszero: # 如果以前没有加过零
so.append(num[0])
haszero = True
pass
# 最终结果
so.append(prefix)
so.reverse()
return ''.join(so)
frappe.utils.data.money_in_words = money_in_words_zh
frappe.utils.money_in_words = money_in_words_zh

View File

@@ -0,0 +1,21 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
import json
import os
import frappe
from erpnext.setup.setup_wizard.operations import taxes_setup
from erpnext.setup.setup_wizard.operations.taxes_setup import setup_taxes_and_charges
def erpnext_china_setup_taxes_and_charges(company_name: str, country: str):
chart_of_accounts = frappe.db.get_value("Company", company_name, "chart_of_accounts")
china_coa = ["一般企业会计准则(2024)", "小企业会计准则(2024)", "小企业会计准则", "民间非营利组织会计制度(2025)"]
if chart_of_accounts and chart_of_accounts in china_coa and country == 'China':
return
setup_taxes_and_charges(company_name, country)
taxes_setup.setup_taxes_and_charges = erpnext_china_setup_taxes_and_charges

View File

@@ -0,0 +1,32 @@
frappe.setup.utils.load_prefilled_data = function (slide, callback) {
frappe.db
.get_value("System Settings", "System Settings", [
"country",
"timezone",
"currency",
"language",
])
.then((r) => {
if (r.message) {
frappe.wizard.values.currency = r.message.currency;
frappe.wizard.values.country = r.message.country;
frappe.wizard.values.timezone = r.message.time_zone;
// fisher 转成显示值
const language = frappe.setup.utils.get_language_name_from_code(r.message.language);
frappe.wizard.values.language = language;
frappe.db.get_value(
"User",
{ name: ["not in", ["Administrator", "Guest"]] },
["full_name", "email"],
(r) => {
if (r) {
frappe.wizard.values.full_name = r.full_name;
frappe.wizard.values.email = r.email;
}
}
);
}
callback(slide);
});
}

View File

View File

@@ -0,0 +1,36 @@
Sales Order,naming_series,options,SO-.YY.-
Purchase Order,naming_series,options,PO-.YY.-
Sales Invoice,naming_series,options,SI-.YY.-
Purchase Invoice,naming_series,options,PI-.YY.-
Delivery Note,naming_series,options,DN-.YY.-
Purchase Receipt,naming_series,options,PR-.YY.-
Stock Entry,naming_series,options,SE-.YY.-
Journal Entry,naming_series,options,JE-.YY.-
Work Order,naming_series,options,WO-.YY.-
Pick List,naming_series,options,PL-.YY.-
Quality Inspection,naming_series,options,QI-.YY.-
Production Plan,naming_series,options,PP-.YY.-
Material Request,naming_series,options,MR-.YY.-
Stock Reconciliation,naming_series,options,SRE-.YY.-
Payment Entry,naming_series,options,PE-.YY.-
Payment Request,naming_series,options,PYR-.YY.-
Job Card,naming_series,options,JC-.YY.-
Job Card,naming_series,default,JC-.YY.-
GL Entry,naming_series,options,GLE-.YY.-
Stock Ledger Entry,naming_series,options,SLE-.YY.-
Task,naming_series,options,TK-.YY.-
Landed Cost Voucher,naming_series,options,LCV-.YY.-
Period Closing Voucher,naming_series,options,PCV-.YY.-
Supplier Quotation,naming_series,options,SQ-.YY.-
Quotation,naming_series,options,QUO-.YY.-
Blanket Order,naming_series,options,BLO-.YY.-
Asset,naming_series,options,AST-.YY.-
Asset Repair,naming_series,options,ASR-.YY.-
Asset Movement,naming_series,options,ASM-.YY.-
Subcontracting Order,naming_series,options,SCO-.YY.-
Subcontracting Receipt,naming_series,options,SCR-.YY.-
Packing Slip,naming_series,options,PS-.YY.-
Installation Note,naming_series,options,INS-.YY.-
Delivery Trip,naming_series,options,DELT-.YY.-
Request for Quotation,naming_series,options,RFQ-.YY.-
Downtime Entry,naming_series,options,DTE-.YY.-
1 Sales Order naming_series options SO-.YY.-
2 Purchase Order naming_series options PO-.YY.-
3 Sales Invoice naming_series options SI-.YY.-
4 Purchase Invoice naming_series options PI-.YY.-
5 Delivery Note naming_series options DN-.YY.-
6 Purchase Receipt naming_series options PR-.YY.-
7 Stock Entry naming_series options SE-.YY.-
8 Journal Entry naming_series options JE-.YY.-
9 Work Order naming_series options WO-.YY.-
10 Pick List naming_series options PL-.YY.-
11 Quality Inspection naming_series options QI-.YY.-
12 Production Plan naming_series options PP-.YY.-
13 Material Request naming_series options MR-.YY.-
14 Stock Reconciliation naming_series options SRE-.YY.-
15 Payment Entry naming_series options PE-.YY.-
16 Payment Request naming_series options PYR-.YY.-
17 Job Card naming_series options JC-.YY.-
18 Job Card naming_series default JC-.YY.-
19 GL Entry naming_series options GLE-.YY.-
20 Stock Ledger Entry naming_series options SLE-.YY.-
21 Task naming_series options TK-.YY.-
22 Landed Cost Voucher naming_series options LCV-.YY.-
23 Period Closing Voucher naming_series options PCV-.YY.-
24 Supplier Quotation naming_series options SQ-.YY.-
25 Quotation naming_series options QUO-.YY.-
26 Blanket Order naming_series options BLO-.YY.-
27 Asset naming_series options AST-.YY.-
28 Asset Repair naming_series options ASR-.YY.-
29 Asset Movement naming_series options ASM-.YY.-
30 Subcontracting Order naming_series options SCO-.YY.-
31 Subcontracting Receipt naming_series options SCR-.YY.-
32 Packing Slip naming_series options PS-.YY.-
33 Installation Note naming_series options INS-.YY.-
34 Delivery Trip naming_series options DELT-.YY.-
35 Request for Quotation naming_series options RFQ-.YY.-
36 Downtime Entry naming_series options DTE-.YY.-

View File

@@ -0,0 +1,114 @@
import frappe, csv, os, json
from frappe import _
uom_list = [
'',
'',
'',
'',
'',
'',
'',
'',
'',
'',
'',
'',
'',
'',
'',
'千米',
'',
'分米',
'厘米',
'毫米',
'毫克',
'',
'千克',
'',
'立方厘米',
'立方分米',
'立方米',
'平方米',
'平方厘米',
'平方分米',
'平方毫米',
'',
'毫升',
'',
'',
'',
'',
'小时',
'分钟',
'',
'',
'',
'公斤',
'摄氏度',
'华氏度'
]
def after_install():
if not frappe.is_setup_complete():
set_china_default()
def set_china_default():
try:
existing_uom_list = frappe.get_all('UOM', pluck ='name')
existing_uom_set = {uom for uom in existing_uom_list}
new_uom_list = [uom for uom in uom_list if uom not in existing_uom_set]
for uom in new_uom_list:
frappe.get_doc({
'doctype': 'UOM',
'uom_name': uom,
'enabled': 1}).insert(ignore_permissions = 1, ignore_if_duplicate=1)
frappe.db.set_value('UOM',{'name': ('not in', uom_list)}, 'enabled', 0)
frappe.db.set_value('Language',{'name': 'zh'}, 'enabled', 1)
set_global_defaults()
set_system_settings()
change_field_property()
except:
frappe.log_error("erpnext_china set_china_default failed")
def set_global_defaults():
frappe.db.set_single_value('Global Defaults',
{
'disable_rounded_total':1,
'disable_in_words':1
}
)
def set_system_settings():
system_settings = frappe.get_doc('System Settings')
system_settings.enable_onboarding = 0
system_settings.country = 'China'
system_settings.language = 'zh'
system_settings.currency = 'CNY'
system_settings.time_zone = 'Asia/Chongqing'
system_settings.rounding_method = 'Commercial Rounding'
system_settings.allow_login_using_user_name = 1
system_settings.allow_login_using_mobile_number = 1
system_settings.currency_precision = 2
system_settings.float_precision = 5
system_settings.date_format = 'yyyy-mm-dd'
system_settings.save(ignore_permissions=True)
def change_field_property():
try:
file_path = os.path.join(os.path.dirname(__file__), 'field_property.csv')
with open(file_path, 'r', encoding='utf-8') as in_file:
data = list(csv.reader(in_file))
for (doctype, field_name, prop, value) in data:
frappe.get_doc({
'doctype': 'Property Setter',
'doctype_or_field': 'DocField',
'doc_type': doctype,
'field_name': field_name,
'property': prop,
'value': value
}).insert(ignore_permissions=1, ignore_if_duplicate=1)
except:
frappe.log_error("erpnext_china change_field_property failed")

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff