人财事物信息化 - financial_statements.py
一段话总结
该Python代码文件聚焦于财务报表相关功能的实现。
通过get_period_list
函数获取包含日期范围、标签等信息的期间列表;借助get_fiscal_year_data
等函数处理财年数据并校验;利用get_data
函数获取账户数据,经计算、汇总和整理后用于生成财务报表,在这一过程中还涉及货币处理、数据过滤等操作。
期间列表获取 根据财年或日期范围确定起止日期 按年、半年、季度、月的周期生成列表 为列表项添加关键信息 财年数据处理 从数据库获取财年起止日期数据 校验财年起止日期的有效性 账户数据处理 获取指定公司和根类型的账户 按条件过滤账户数据 计算账户在各期间的余额 财务报表生成 整理账户数据为报表格式 过滤零值行并添加总计行 处理货币换算以展示报表
详细总结
期间列表获取
- 根据
filter_based_on
参数,若为“Fiscal Year”,则通过get_fiscal_year_data
函数获取财年数据,确定year_start_date
和year_end_date
;若为“Date Range”,则直接使用传入的period_start_date
和period_end_date
。 - 根据
periodicity
(“Yearly”“Half-Yearly”“Quarterly”“Monthly”)计算每个期间的起止日期。例如,“Monthly”表示每个期间为1个月,通过add_months
和add_days
函数进行日期计算。 - 为每个期间生成包含
from_date
、to_date
、key
、label
等信息的字典,并添加到period_list
中。其中,key
由to_date
的格式化字符串组成,label
根据periodicity
和日期范围生成。
- 根据
财年数据处理
get_fiscal_year_data
函数通过SQL查询获取指定财年范围内的起始日期和结束日期。validate_fiscal_year
函数校验财年数据的有效性,确保起始日期和结束日期存在且结束日期不早于起始日期。
账户数据处理
get_data
函数获取指定公司和根类型的账户数据,调用get_accounts
和filter_accounts
函数对账户数据进行初步处理。- 通过
get_appropriate_currency
函数确定公司的货币,若有presentation_currency
过滤器,则使用该货币,否则使用公司默认货币。 calculate_values
函数计算每个账户在各个期间的余额,根据accumulated_values
和ignore_accumulated_values_for_fy
等参数确定计算方式。accumulate_values_into_parents
函数将子账户的余额累加到父账户中。
财务报表生成
prepare_data
函数将账户数据整理为报表格式,包括账户名称、余额等信息。filter_out_zero_value_rows
函数过滤掉余额为零的行,add_total_row
函数添加总计行,使报表更简洁和完整。
函数名 | 功能描述 | 关键参数 |
---|---|---|
get_period_list |
获取期间列表 | from_fiscal_year 、to_fiscal_year 、period_start_date 、period_end_date 、filter_based_on 、periodicity |
get_fiscal_year_data |
获取财年数据 | from_fiscal_year 、to_fiscal_year |
validate_fiscal_year |
校验财年数据 | fiscal_year 、from_fiscal_year 、to_fiscal_year |
get_data |
获取账户数据并处理 | company 、root_type 、balance_must_be 、period_list 、filters |
calculate_values |
计算账户余额 | accounts_by_name 、gl_entries_by_account 、period_list 、accumulated_values 、ignore_accumulated_values_for_fy |
accumulate_values_into_parents |
累加子账户余额到父账户 | accounts 、accounts_by_name 、period_list |
prepare_data |
整理数据为报表格式 | accounts 、balance_must_be 、period_list 、company_currency 、accumulated_values |
filter_out_zero_value_rows |
过滤零值行 | out 、parent_children_map |
add_total_row |
添加总计行 | out 、root_type 、balance_must_be 、period_list 、company_currency |
关键问题
get_period_list
函数中reset_period_on_fy_change
参数的作用是什么?- 该参数用于控制在财年变化时,计算期间标签的方式。当
reset_period_on_fy_change
为True
时,期间标签基于每个期间的财年起始日期和结束日期计算;当为False
时,则基于整个报表的起始日期和当前期间的结束日期计算。
- 该参数用于控制在财年变化时,计算期间标签的方式。当
calculate_values
函数如何处理不同期间的账户余额计算?- 该函数遍历
gl_entries_by_account
中的每个会计分录。对于每个分录,检查其posting_date
是否在期间内。若accumulated_values
为True
或posting_date
在当前期间内,且满足ignore_accumulated_values_for_fy
相关条件,就将该分录的debit
和credit
差值累加到对应账户在当前期间的余额中。若posting_date
早于第一个期间的起始日期,则累加到opening_balance
中。
- 该函数遍历
get_appropriate_currency
函数在财务报表生成中有什么重要性?- 它决定了财务报表中数据展示所使用的货币。如果存在
presentation_currency
过滤器,就使用该货币展示数据;否则,使用公司默认货币。统一的货币单位对于准确呈现财务数据、进行数据对比和分析至关重要,能避免因货币换算问题导致的数据错误解读。
- 它决定了财务报表中数据展示所使用的货币。如果存在
第二次解读
该网页是ERPNext开源ERP系统中财务报表生成模块的核心代码文件(financial_statements.py
),主要功能为处理财务数据并生成不同周期的财务报表,以下是核心内容总结:
- 核心功能概述
- 财务期间管理:根据财年或日期范围生成不同周期(年度、季度、月度)的期间列表,支持累计值计算和财年切换逻辑。
- 账户数据处理:获取账户结构,过滤无效账户,按期间汇总借贷方金额,计算期初余额和累计值。
- 报表数据生成:将账户数据按期间聚合,支持多货币(本位币/展示货币)转换,过滤零值行并添加总计行。
- 关键函数解析
get_period_list
- 功能:生成财务期间列表(如月度、季度),支持按财年或日期范围过滤。
- 逻辑:
- 根据过滤条件(财年/日期范围)验证日期有效性。
- 按周期(如每月3个月)分割时间段,生成包含起始日期、标签(如“Jan 2025”)的期间对象。
- 处理财年切换时的期间重置逻辑(
reset_period_on_fy_change
)。
get_data
- 功能:核心数据处理流程,生成财务报表所需的账户数据。
- 步骤: 1.账户获取与过滤:根据公司和账户类型(如资产、负债)获取账户结构,排除无效账户。 2.GL分录汇总:从数据库查询会计分录,按账户和期间汇总借贷方金额。 3.值计算与累计:根据期间列表计算各账户的期初余额和期间发生额,支持累计值(跨期间汇总)。 4.父账户聚合:将子账户值累加到父账户,构建层级结构(如“资产-现金-银行存款”)。 5.结果过滤与格式化:移除零值行,添加总计行,转换为目标货币(如美元)。
辅助函数
get_fiscal_year_data
:查询财年起止日期。validate_dates/fiscal_year
:验证日期有效性,确保起止日期逻辑正确。calculate_values
:逐笔处理会计分录,按期间分配金额。get_label
:生成期间标签(如“2024-2025”或“Jan-Mar 25”)。
- 技术实现细节
- 数据结构:使用字典和列表存储期间(
period_list
)、账户(accounts_by_name
)和分录数据(gl_entries_by_account
)。 - 日期计算:通过
frappe.utils
中的日期函数(如add_months
、get_first_day
)处理期间边界。 - 权限与过滤:通过
root_type
(如“Asset”)和财务期间过滤会计分录,支持忽略结账分录(ignore_closing_entries
)。 - 货币处理:优先使用用户指定的展示货币,否则取公司默认货币,通过
convert_to_presentation_currency
转换。
- 应用场景
该模块用于生成ERPNext中的财务报表(如资产负债表、利润表),支持:
- 多期间对比(如按月度展示全年数据)。
- 层级账户汇总(父账户自动累加子账户值)。
- 灵活的过滤条件(财年、日期范围、账户类型)。
以下是针对 financial_statements.py
增加中国会计格式的功能扩展方案,结合代码逻辑和需求分步骤说明:
一、利润表:增加去年同期累计数对比
- 修改期间生成逻辑(
get_period_list
函数)
需求:需同时获取“本年累计”和“去年同期累计”数据,需生成包含 去年同期期间 的列表。
修改点:
在参数中增加
include_last_year
标识(默认True
)。若启用,根据当前期间自动计算去年同期时间段(如当前期间为
2025-01
,则去年同期为2024-01
)。
def get_period_list(... include_last_year=True, ...):
period_list = [...] # 原逻辑生成本年期间
if include_last_year and not ignore_fiscal_year:
for period in period_list:
last_year_period = copy.deepcopy(period)
last_year_period.from_date = add_years(period.from_date, -1)
last_year_period.to_date = add_years(period.to_date, -1)
last_year_period.label = f"上年同期({formatdate(last_year_period.to_date, 'YYYY')})"
last_year_period.key = f"last_year_{period.key}"
period_list.append(last_year_period)
return period_list
- 数据查询与对比(
get_data
函数)
需求:查询去年同期的 GL 分录,并按期间累计。
修改点:
在
set_gl_entries_by_account
中增加对去年期间的过滤条件。在
calculate_values
中区分本年与去年数据,分别累计到对应期间键(如current_year_jan_2025
和last_year_jan_2024
)。
二、利润表:支持中国会计准则多步式格式
- 账户结构适配(
get_accounts
函数)
需求:多步式利润表需按“营业收入→营业利润→利润总额→净利润”分步展示。
修改点:
定义中国准则账户类别(如“营业收入”、“营业成本”、“税金及附加”)。
在
filter_accounts
中按类别分组,生成层级结构:
def filter_accounts(accounts):
china_income_statement_categories = {
"营业收入": ["主营业务收入", "其他业务收入"],
"营业成本": ["主营业务成本", "其他业务成本"],
"营业利润": ["销售费用", "管理费用", "财务费用"],
# 其他类别...
}
# 按类别重组账户列表,生成多级结构
return grouped_accounts, accounts_by_name, parent_children_map
- 报表格式切换(
prepare_data
函数)
需求:通过参数
report_format
选择“多步式”或“单步式”。修改点:
添加参数
report_format="single_step"
(默认单步式)。在
prepare_data
中根据格式生成不同层级的报表行:
def prepare_data(..., report_format="single_step"):
if report_format == "multi_step":
# 插入分步计算行(如营业利润=营业收入-营业成本-费用)
out.append({"account": "营业利润", "value": out["营业收入"].value - out["营业成本"].value - ...})
return out
三、现金流量表:支持中国准则直接法格式
- 直接法项目定义(
get_accounts
函数)
需求:直接法需展示“销售商品收到的现金”、“购买商品支付的现金”等具体项目。
修改点:
为现金流量表账户添加自定义字段
cash_flow_category
(如经营活动-现金流入
)。在
get_accounts
中过滤出属于直接法项目的账户:
def get_accounts(company, root_type):
if root_type == "Cash Flow":
return frappe.db.sql("""
select name, parent_account, cash_flow_category
from tabAccount
where company=%s and cash_flow_category is not null
""", company, as_dict=1)
return [...] # 其他类型账户
- 直接法数据汇总(
calculate_values
函数)
需求:按现金流量类别(如经营活动、投资活动)汇总借贷方发生额。
修改点:
在
calculate_values
中按cash_flow_category
分组累计:
def calculate_values(...):
for entry in entries:
category = accounts_by_name[entry.account].cash_flow_category
if category:
d[category][period.key] += flt(entry.debit) - flt(entry.credit)
- 报表格式生成(
prepare_data
函数)
需求:按直接法项目顺序展示(如经营活动现金流入→流出→净额)。
修改点:
定义直接法项目顺序列表:
direct_method_order = [
"经营活动-销售商品收到的现金",
"经营活动-购买商品支付的现金",
"经营活动产生的现金流量净额",
# 投资活动、筹资活动项目...
]
- 按顺序组装报表行:
def prepare_data(... format="direct_method"):
if format == "direct_method":
out = [{"account": item, "value": d[item]} for item in direct_method_order]
return out
四、参数与配置扩展
1.全局参数添加:
- 在
get_data
函数中增加参数:
def get_data(... profit_and_loss_format="single_step", cash_flow_format="indirect_method", ...):
# 处理利润表格式和现金流量表格式参数
2.前端交互适配:
在 ERPNext 前端报表界面添加下拉菜单,允许选择:
利润表格式:单步式/多步式(中国准则)。
现金流量表格式:间接法/直接法(中国准则)。
显示去年同期累计数(勾选框)。
五、测试与验证
1.用例覆盖:
验证不同期间(如跨财年)的去年同期数据准确性。
检查多步式利润表的分步计算逻辑(如营业利润、净利润是否正确)。
确保直接法现金流量表的项目金额与 GL 分录匹配(如“销售商品收到的现金”等于应收账款贷方发生额)。
2.本地化调整:
中文标签适配(如“上年同期累计”、“营业利润”)。
货币格式使用人民币符号(¥),支持四舍五入到元。
通过以上修改,financial_statements.py
将支持中国会计准则的报表格式和对比需求,适配本地化财务报告场景。
以下是针对关联文件的修改建议,以支持中国会计准则的报表格式需求:
一、财务报表模板(financial_statements.html
)
修改点:增加去年同期列渲染
<!-- 在表头中添加去年同期列 -->
<thead>
<tr>
<th style="width: {%= 100 - (report_columns.length - 1) * 13 %}%"></th>
{% for (let i=1, l=report_columns.length; i<l; i++) { %}
<th class="text-right">{%= report_columns[i].label %}</th>
<!-- 新增:判断是否存在去年同期字段 -->
{% if (report_columns[i].fieldname.includes("last_year")) { %}
<th class="text-right">上年同期</th>
{% } %}
{% endfor %}
</tr>
</thead>
<!-- 在表体中渲染去年同期数据 -->
<tbody>
{% for(let j=0, k=data.length; j<k; j++) { %}
<tr class="{%= row_class %}">
<td>...</td>
{% for(let i=1, l=report_columns.length; i<l; i++) { %}
<td class="text-right">
{%= frappe.format(row[fieldname], report_columns[i], {}, row) %}
</td>
<!-- 新增:渲染去年同期值 -->
{% if (fieldname.includes("current_year")) { %}
{% const last_year_field = fieldname.replace("current_year", "last_year"); %}
<td class="text-right">
{%= frappe.format(row[last_year_field], report_columns[i], {}, row) %}
</td>
{% } %}
{% endfor %}
</tr>
{% endfor %}
</tbody>
二、利润表逻辑(profit_and_loss_statement.py
)
- 支持多步式格式
# 在 get_data 后增加多步式组装逻辑
def execute(filters=None):
# ... 原逻辑 ...
# 新增:处理多步式格式
if filters.get("report_format") == "multi_step":
data = assemble_multi_step_income_statement(income, expense, net_profit_loss)
return columns, data, ...
def assemble_multi_step_income_statement(income, expense, net_profit):
multi_step_data = [
{"account_name": "一、营业收入", "indent": 1},
*income,
{"account_name": "减:营业成本", "indent": 1},
*expense,
{"account_name": "二、营业利润", "indent": 0, "value": income_total - expense_total},
# 后续分步项目...
]
return multi_step_data
- 增加去年同期对比
# 修改 period_list 生成逻辑
period_list = get_period_list(
filters.from_fiscal_year,
filters.to_fiscal_year,
filters.period_start_date,
filters.period_end_date,
filters.filter_based_on,
filters.periodicity,
company=filters.company,
include_last_year=True # 新增参数,控制是否生成去年同期期间
)
# 在 get_data 中获取去年数据
last_year_income = get_data(
filters.company,
"Income",
"Credit",
last_year_period_list, # 单独的去年期间列表
filters=filters,
accumulated_values=True
)
三、利润表前端配置(profit_and_loss_statement.js
)
修改点:添加格式选择参数
frappe.query_reports["Profit and Loss Statement"]["filters"].push({
fieldname: "report_format",
label: __("报表格式"),
fieldtype: "Select",
options: ["单步式", "多步式(中国准则)"],
default: "单步式"
});
// 修改列生成逻辑(与 Python 代码字段名对应)
erpnext.financial_statements.get_columns = function(periodicity, period_list, accumulated_values, company) {
let columns = [
{fieldname: "account_name", label: __("账户名称"), fieldtype: "Data", width: 200},
...
];
// 新增:为去年同期添加列
if (period_list.some(p => p.label.includes("上年同期"))) {
period_list.forEach(period => {
if (period.label.includes("上年同期")) {
columns.push({fieldname: `last_year_${period.key}`, label: period.label, ...});
}
});
}
return columns;
};
四、现金流量表逻辑(cash_flow.py
)
- 支持直接法格式
# 修改现金流量项目定义
def get_cash_flow_accounts():
direct_method_sections = [
{
"section_header": "经营活动产生的现金流量",
"account_types": [
{"account_type": "Sales", "label": "销售商品、提供劳务收到的现金"},
{"account_type": "Purchases", "label": "购买商品、接受劳务支付的现金"},
# 其他直接法项目...
]
},
# 投资活动、筹资活动...
]
return direct_method_sections if filters.format == "direct" else indirect_method_sections
- 调整数据汇总逻辑
def get_account_type_based_gl_data(company, filters):
# 直接法需按具体账户类型汇总(如 Sales 对应应收账款贷方)
if filters.account_type == "Sales":
cond += " AND account IN (SELECT name FROM tabAccount WHERE account_type='Receivable')"
return frappe.db.sql(...)[0]
五、现金流量表前端配置(cash_flow.js
)
修改点:添加格式切换参数
frappe.query_reports["Cash Flow"]["filters"].push({
fieldname: "report_format",
label: __("报表格式"),
fieldtype: "Select",
options: ["间接法", "直接法(中国准则)"],
default: "间接法"
});
// 修改数据渲染顺序(直接法项目顺序)
frappe.query_reports["Cash Flow"]["render_table"] = function(data) {
if (filters.report_format == "direct") {
data.sort((a, b) => direct_method_order.indexOf(a.section) - direct_method_order.indexOf(b.section));
}
return data;
};
六、公共函数调整(financial_statements.py
)
- period_list 增加去年同期生成
def get_period_list(... include_last_year=False, ...):
if include_last_year:
for period in original_periods:
last_year_period = {
"from_date": add_years(period.from_date, -1),
"to_date": add_years(period.to_date, -1),
"label": f"上年同期 ({formatdate(period.to_date, 'YYYY')})",
"key": f"last_year_{period.key}"
}
period_list.append(last_year_period)
return period_list
- 货币格式调整为人民币
def get_appropriate_currency(company, filters):
# 新增:优先使用人民币(可配置)
if filters.get("currency") == "CNY":
return "CNY"
return super().get_appropriate_currency(company, filters)
关键验证点
- 期间对齐:确保去年同期期间与本年期间月份一致(如 2024-01-31 对应 2025-01-31)。
- 多步式计算:验证营业利润 = 营业收入-营业成本-费用,净利润 = 利润总额-所得税。
- 直接法数据:检查“销售商品收到的现金”是否等于应收账款、预收账款贷方发生额合计。
- 前端展示:确认报表格式切换按钮生效,去年同期列显示正确标签和数值。
通过以上修改,系统将支持中国会计准则的报表格式及对比需求,同时保持原有功能的兼容性。