人财事物信息化 - financial_statements.py

一段话总结

该Python代码文件聚焦于财务报表相关功能的实现。 通过get_period_list函数获取包含日期范围、标签等信息的期间列表;借助get_fiscal_year_data等函数处理财年数据并校验;利用get_data函数获取账户数据,经计算、汇总和整理后用于生成财务报表,在这一过程中还涉及货币处理、数据过滤等操作。

    期间列表获取
        根据财年或日期范围确定起止日期
        按年、半年、季度、月的周期生成列表
        为列表项添加关键信息
    财年数据处理
        从数据库获取财年起止日期数据
        校验财年起止日期的有效性
    账户数据处理
        获取指定公司和根类型的账户
        按条件过滤账户数据
        计算账户在各期间的余额
    财务报表生成
        整理账户数据为报表格式
        过滤零值行并添加总计行
        处理货币换算以展示报表

详细总结

  1. 期间列表获取

    • 根据filter_based_on参数,若为“Fiscal Year”,则通过get_fiscal_year_data函数获取财年数据,确定year_start_dateyear_end_date;若为“Date Range”,则直接使用传入的period_start_dateperiod_end_date
    • 根据periodicity(“Yearly”“Half-Yearly”“Quarterly”“Monthly”)计算每个期间的起止日期。例如,“Monthly”表示每个期间为1个月,通过add_monthsadd_days函数进行日期计算。
    • 为每个期间生成包含from_dateto_datekeylabel等信息的字典,并添加到period_list中。其中,keyto_date的格式化字符串组成,label根据periodicity和日期范围生成。
  2. 财年数据处理

    • get_fiscal_year_data函数通过SQL查询获取指定财年范围内的起始日期和结束日期。
    • validate_fiscal_year函数校验财年数据的有效性,确保起始日期和结束日期存在且结束日期不早于起始日期。
  3. 账户数据处理

    • get_data函数获取指定公司和根类型的账户数据,调用get_accountsfilter_accounts函数对账户数据进行初步处理。
    • 通过get_appropriate_currency函数确定公司的货币,若有presentation_currency过滤器,则使用该货币,否则使用公司默认货币。
    • calculate_values函数计算每个账户在各个期间的余额,根据accumulated_valuesignore_accumulated_values_for_fy等参数确定计算方式。
    • accumulate_values_into_parents函数将子账户的余额累加到父账户中。
  4. 财务报表生成

    • prepare_data函数将账户数据整理为报表格式,包括账户名称、余额等信息。
    • filter_out_zero_value_rows函数过滤掉余额为零的行,add_total_row函数添加总计行,使报表更简洁和完整。
函数名 功能描述 关键参数
get_period_list 获取期间列表 from_fiscal_yearto_fiscal_yearperiod_start_dateperiod_end_datefilter_based_onperiodicity
get_fiscal_year_data 获取财年数据 from_fiscal_yearto_fiscal_year
validate_fiscal_year 校验财年数据 fiscal_yearfrom_fiscal_yearto_fiscal_year
get_data 获取账户数据并处理 companyroot_typebalance_must_beperiod_listfilters
calculate_values 计算账户余额 accounts_by_namegl_entries_by_accountperiod_listaccumulated_valuesignore_accumulated_values_for_fy
accumulate_values_into_parents 累加子账户余额到父账户 accountsaccounts_by_nameperiod_list
prepare_data 整理数据为报表格式 accountsbalance_must_beperiod_listcompany_currencyaccumulated_values
filter_out_zero_value_rows 过滤零值行 outparent_children_map
add_total_row 添加总计行 outroot_typebalance_must_beperiod_listcompany_currency

关键问题

  1. get_period_list函数中reset_period_on_fy_change参数的作用是什么?

    • 该参数用于控制在财年变化时,计算期间标签的方式。当reset_period_on_fy_changeTrue时,期间标签基于每个期间的财年起始日期和结束日期计算;当为False时,则基于整个报表的起始日期和当前期间的结束日期计算。
  2. calculate_values函数如何处理不同期间的账户余额计算?

    • 该函数遍历gl_entries_by_account中的每个会计分录。对于每个分录,检查其posting_date是否在期间内。若accumulated_valuesTrueposting_date在当前期间内,且满足ignore_accumulated_values_for_fy相关条件,就将该分录的debitcredit差值累加到对应账户在当前期间的余额中。若posting_date早于第一个期间的起始日期,则累加到opening_balance中。
  3. get_appropriate_currency函数在财务报表生成中有什么重要性?

    • 它决定了财务报表中数据展示所使用的货币。如果存在presentation_currency过滤器,就使用该货币展示数据;否则,使用公司默认货币。统一的货币单位对于准确呈现财务数据、进行数据对比和分析至关重要,能避免因货币换算问题导致的数据错误解读。

第二次解读

该网页是ERPNext开源ERP系统中财务报表生成模块的核心代码文件(financial_statements.py),主要功能为处理财务数据并生成不同周期的财务报表,以下是核心内容总结:

  1. 核心功能概述
  • 财务期间管理:根据财年或日期范围生成不同周期(年度、季度、月度)的期间列表,支持累计值计算和财年切换逻辑。
  • 账户数据处理:获取账户结构,过滤无效账户,按期间汇总借贷方金额,计算期初余额和累计值。
  • 报表数据生成:将账户数据按期间聚合,支持多货币(本位币/展示货币)转换,过滤零值行并添加总计行。
  1. 关键函数解析

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”)。
  1. 技术实现细节
  • 数据结构:使用字典和列表存储期间(period_list)、账户(accounts_by_name)和分录数据(gl_entries_by_account)。
  • 日期计算:通过frappe.utils中的日期函数(如add_monthsget_first_day)处理期间边界。
  • 权限与过滤:通过root_type(如“Asset”)和财务期间过滤会计分录,支持忽略结账分录(ignore_closing_entries)。
  • 货币处理:优先使用用户指定的展示货币,否则取公司默认货币,通过convert_to_presentation_currency转换。
  1. 应用场景

该模块用于生成ERPNext中的财务报表(如资产负债表、利润表),支持:

  • 多期间对比(如按月度展示全年数据)。
  • 层级账户汇总(父账户自动累加子账户值)。
  • 灵活的过滤条件(财年、日期范围、账户类型)。

以下是针对 financial_statements.py 增加中国会计格式的功能扩展方案,结合代码逻辑和需求分步骤说明:

一、利润表:增加去年同期累计数对比

  1. 修改期间生成逻辑(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
  1. 数据查询与对比(get_data 函数)
  • 需求:查询去年同期的 GL 分录,并按期间累计。

  • 修改点:

  • set_gl_entries_by_account中增加对去年期间的过滤条件。

  • calculate_values 中区分本年与去年数据,分别累计到对应期间键(如 current_year_jan_2025last_year_jan_2024)。

二、利润表:支持中国会计准则多步式格式

  1. 账户结构适配(get_accounts 函数)
  • 需求:多步式利润表需按“营业收入→营业利润→利润总额→净利润”分步展示。

  • 修改点:

  • 定义中国准则账户类别(如“营业收入”、“营业成本”、“税金及附加”)。

  • filter_accounts 中按类别分组,生成层级结构:


def filter_accounts(accounts):
    china_income_statement_categories = {
        "营业收入": ["主营业务收入", "其他业务收入"],
        "营业成本": ["主营业务成本", "其他业务成本"],
        "营业利润": ["销售费用", "管理费用", "财务费用"],
        # 其他类别...
    }
    # 按类别重组账户列表,生成多级结构
    return grouped_accounts, accounts_by_name, parent_children_map
  1. 报表格式切换(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

三、现金流量表:支持中国准则直接法格式

  1. 直接法项目定义(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 [...]  # 其他类型账户
  1. 直接法数据汇总(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)
  1. 报表格式生成(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

  1. 支持多步式格式
# 在 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
  1. 增加去年同期对比
# 修改 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

  1. 支持直接法格式
# 修改现金流量项目定义
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
  1. 调整数据汇总逻辑
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

  1. 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
  1. 货币格式调整为人民币
def get_appropriate_currency(company, filters):
    # 新增:优先使用人民币(可配置)
    if filters.get("currency") == "CNY":
        return "CNY"
    return super().get_appropriate_currency(company, filters)

关键验证点

  1. 期间对齐:确保去年同期期间与本年期间月份一致(如 2024-01-31 对应 2025-01-31)。
  2. 多步式计算:验证营业利润 = 营业收入-营业成本-费用,净利润 = 利润总额-所得税。
  3. 直接法数据:检查“销售商品收到的现金”是否等于应收账款、预收账款贷方发生额合计。
  4. 前端展示:确认报表格式切换按钮生效,去年同期列显示正确标签和数值。

通过以上修改,系统将支持中国会计准则的报表格式及对比需求,同时保持原有功能的兼容性。

Discard
Save
Review Changes ← Back to Content
Message Status Space Raised By Last update on