人财事物信息化 - period_closing_voucher.py
1. period_closing_voucher.py
文件总结
1.1 整体功能概述
该文件定义了 PeriodClosingVoucher
类,用于处理 ERPNext 系统中的会计期间结账凭证相关业务逻辑。会计期间结账是财务工作中的重要环节,它能确保在特定会计期间内的财务数据得到准确结算和归档,为生成财务报表提供基础。
1.2 类属性
属性名 | 类型 | 描述 |
---|---|---|
company |
DF.Link |
关联的公司,明确该结账凭证所属的公司主体,不同公司可能有不同的财务政策和科目设置。 |
posting_date |
DF.Date |
结账凭证的过账日期,即该结账操作在财务系统中生效的日期,影响财务报表的期间划分。 |
closing_account_head |
DF.Link |
结账账户头,用于指定结账时资金的结转方向和对应的会计科目。 |
remarks |
DF.Text |
结账备注信息,可记录结账的原因、特殊情况说明等内容,方便后续查询和审计。 |
1.3 关键方法
方法名 | 功能 | 业务逻辑 |
---|---|---|
validate |
在保存结账凭证前进行验证 | 检查必填字段是否填写,如公司、过账日期、结账账户头等;验证过账日期是否在合理范围内,确保结账操作符合会计期间的规定;检查结账账户头是否有效,避免因错误的账户设置导致财务数据混乱。 |
on_submit |
在提交结账凭证时执行 | 调用 make_closing_entries 方法生成结账分录,将本期的损益类科目余额结转到指定的结账账户头;更新相关会计科目的余额,确保财务数据的准确性;记录结账操作的日志,方便后续审计和追溯。 |
make_closing_entries |
生成结账分录 | 遍历本期的损益类科目,计算每个科目的余额;根据结账账户头的设置,将损益类科目的余额结转到相应的账户;生成日记账分录文档,记录结账分录的详细信息。 |
1.4 关键问题解答
- 问题 1:为什么要在
validate
方法中检查必填字段和过账日期?- 答案:必填字段是结账凭证的关键信息,缺少这些信息会导致结账操作无法准确完成。过账日期的合理性直接影响财务报表的期间划分,如果日期设置错误,会导致财务数据在错误的期间进行结算,影响财务报表的准确性。
- 问题 2:
on_submit
方法中生成结账分录的作用是什么?- 答案:生成结账分录的目的是将本期的损益类科目余额结转到指定的结账账户头,从而完成会计期间的结账操作。这有助于准确计算本期的利润或亏损,并为下一个会计期间的财务核算做好准备。
- 问题 3:
make_closing_entries
方法是如何生成结账分录的?- 答案:该方法首先遍历本期的损益类科目,计算每个科目的余额。然后,根据结账账户头的设置,将损益类科目的余额结转到相应的账户。最后,生成日记账分录文档,记录结账分录的详细信息,包括科目、金额、摘要等。
2. period_closing_voucher.js
文件总结
2.1 整体功能概述
此文件是与 PeriodClosingVoucher
类对应的前端 JavaScript 文件,主要负责处理会计期间结账凭证在用户界面上的交互逻辑,包括表单的初始化、数据验证、提交操作等。
2.2 关键函数
函数名 | 功能 | 业务逻辑 |
---|---|---|
frappe.ui.form.on("Period Closing Voucher", "refresh", function(frm) {... }) |
在表单刷新时执行 | 初始化表单界面,如设置按钮状态、显示默认值等;根据当前结账凭证的状态(草稿、已提交等)显示不同的提示信息和操作按钮。 |
frappe.ui.form.on("Period Closing Voucher", "posting_date", function(frm) {... }) |
当过账日期发生变化时执行 | 验证过账日期的合法性,确保其在合理的会计期间范围内;根据过账日期更新相关的显示信息,如提示用户该日期对应的会计期间。 |
frappe.ui.form.on("Period Closing Voucher", "validate", function(frm) {... }) |
在表单验证时执行 | 调用后端的 validate 方法进行数据验证;若验证失败,显示相应的错误提示信息,阻止表单提交。 |
frappe.ui.form.on("Period Closing Voucher", "on_submit", function(frm) {... }) |
在表单提交时执行 | 调用后端的 on_submit 方法生成结账分录;若提交成功,显示成功提示信息,并跳转到相关的财务报表页面;若失败,显示错误提示信息。 |
2.3 关键问题解答
- 问题 1:为什么要在
refresh
函数中根据结账凭证状态显示不同的提示信息和操作按钮?- 答案:不同状态的结账凭证具有不同的操作权限和显示需求。例如,草稿状态的凭证可以进行编辑和修改,而已提交的凭证则不允许修改。通过根据状态显示不同的提示信息和操作按钮,可以提高用户体验,避免用户进行无效操作。
- 问题 2:
posting_date
函数中验证过账日期的目的是什么?- 答案:验证过账日期的目的是确保结账操作在正确的会计期间内进行。如果过账日期设置错误,会导致财务数据在错误的期间进行结算,影响财务报表的准确性。通过在前端进行验证,可以及时发现并纠正日期设置错误,提高数据的准确性。
- 问题 3:
validate
和on_submit
函数分别在什么情况下调用后端方法?- 答案:
validate
函数在表单验证时调用后端的validate
方法,用于检查数据的合法性和完整性。只有在验证通过后,表单才能提交。on_submit
函数在表单提交时调用后端的on_submit
方法,用于生成结账分录和完成结账操作。这样可以确保在用户提交表单前,数据已经经过了严格的验证,避免因数据错误导致的结账失败。
- 答案:
在ERPNext系统里,会计期间结账是一个复杂且关键的财务流程,涉及众多文件和模块的协同工作。除了period_closing_voucher.py
文件,
下面这些文件也和会计期间结账密切相关:
1. 会计科目和账户设置类
account.js
和account.py
:- 这两个文件负责会计科目的管理。在会计期间结账时,需要明确各个会计科目的余额和属性,比如资产、负债、权益、收入和费用等科目的分类。
- 结账过程中要对这些科目进行结算,像将收入和费用类科目结转到权益类科目。所以准确的科目设置和管理对结账操作的准确性至关重要。
chart_of_accounts.py
:- 该文件定义了会计科目表的逻辑。会计科目表是结账操作的基础,它规定了所有会计科目的层级结构和编码规则。
- 在结账时,系统会依据科目表来汇总和分类财务数据,确保各个科目的数据能够正确地归集和结算。
2. 财务报表生成类
general_ledger.py
:- 此文件用于生成总账报表。总账是会计核算的核心,它记录了所有会计科目的借贷发生额和余额。
- 在会计期间结账时,需要依据总账的数据来编制资产负债表、利润表等财务报表,以反映企业在该期间的财务状况和经营成果。
balance_sheet.py
和profit_and_loss.py
:- 分别负责生成资产负债表和利润表。这两个报表是会计期间结账的重要输出结果,能够直观地展示企业的财务状况和经营业绩。
- 结账时,系统会根据总账数据和相关的会计规则来计算和填充报表中的各项数据,确保报表的准确性和合规性。
3. 税务和合规类
tax_rule.py
:- 该文件处理税务规则的设置和应用。在会计期间结账时,需要计算和缴纳各种税费,如增值税、所得税等。
tax_rule.py
会根据企业的业务情况和税务政策,确定应纳税额,并将其反映在财务报表中。
fiscal_year.py
:- 定义了会计年度的设置和管理逻辑。会计年度是会计期间结账的时间范围,它规定了结账的起始和结束日期。
- 系统会根据会计年度的设置来确定每个会计期间的结账时间和相关操作,确保财务数据的期间划分准确无误。
4. 凭证和交易记录类
journal_entry.py
:- 用于处理日记账分录。在会计期间结账时,会生成一些调整分录,如计提折旧、摊销费用等,这些分录都需要通过
journal_entry.py
来记录和处理。 - 准确的日记账分录是保证结账数据准确性的关键。
- 用于处理日记账分录。在会计期间结账时,会生成一些调整分录,如计提折旧、摊销费用等,这些分录都需要通过
payment_entry.py
:- 处理付款和收款记录。在结账时,需要核对和结算所有的付款和收款交易,确保资金的流动和账户余额的准确性。
payment_entry.py
会记录每一笔付款和收款的详细信息,为结账操作提供数据支持。
5. 审计和日志类
audit_trail.py
:- 该文件记录了系统中所有的操作和变更历史。在会计期间结账时,审计追踪功能可以帮助审计人员和财务人员检查和验证结账过程的合规性和准确性。
- 通过查看审计日志,可以发现和纠正可能存在的错误和异常情况。
log_settings.py
:- 用于设置系统的日志记录规则。在结账过程中,详细的日志记录可以帮助排查问题和分析财务数据的变化情况。
log_settings.py
可以控制日志的级别、存储位置和保留时间等参数。
以下是对这些和会计期间结账相关文件的具体代码实现细节的详细阐述,并给出部分示例代码:
1. 会计科目和账户设置类
account.js
和 account.py
account.js
:主要负责前端交互,像会计科目表单的初始化、验证以及提交等操作。
// account.js
frappe.ui.form.on('Account', {
refresh: function(frm) {
// 表单刷新时的操作
if (frm.is_new()) {
// 设置默认值
frm.set_value('account_type', 'Asset');
}
},
account_name: function(frm) {
// 当账户名称改变时,更新账户编号
if (frm.doc.account_name) {
// 简单示例,实际需根据规则生成
frm.set_value('account_number', '100' + frm.doc.account_name.slice(0, 3));
}
}
});
account.py
:处理后端逻辑,包括账户的创建、验证和保存等。
# account.py
import frappe
from frappe.model.document import Document
class Account(Document):
def validate(self):
# 验证账户名称是否唯一
if frappe.db.exists('Account', {'account_name': self.account_name, 'name': ('!=', self.name)}):
frappe.throw('账户名称必须唯一')
def on_update(self):
# 账户更新时更新相关设置
pass
chart_of_accounts.py
此文件用于管理会计科目表的层级结构和编码规则。
# chart_of_accounts.py
import frappe
from frappe.model.document import Document
class ChartOfAccounts(Document):
def build_tree(self):
# 构建会计科目树
accounts = frappe.get_all('Account', fields=['name', 'parent_account'])
tree = {}
for account in accounts:
if account.parent_account:
if account.parent_account not in tree:
tree[account.parent_account] = []
tree[account.parent_account].append(account.name)
else:
if 'root' not in tree:
tree['root'] = []
tree['root'].append(account.name)
return tree
2. 财务报表生成类
general_ledger.py
负责生成总账报表,从数据库中查询所有会计科目的借贷发生额和余额。
# general_ledger.py
import frappe
def get_general_ledger(fiscal_year, company):
# 查询总账数据
entries = frappe.db.sql("""
SELECT
account,
SUM(debit) as debit_total,
SUM(credit) as credit_total
FROM `tabJournal Entry Account`
WHERE fiscal_year = %s AND company = %s
GROUP BY account
""", (fiscal_year, company), as_dict=1)
return entries
balance_sheet.py
和 profit_and_loss.py
balance_sheet.py
:生成资产负债表。
# balance_sheet.py
import frappe
def get_balance_sheet(fiscal_year, company):
# 获取资产、负债和权益类科目的余额
assets = frappe.db.sql("""
SELECT
account,
SUM(debit) - SUM(credit) as balance
FROM `tabJournal Entry Account`
WHERE fiscal_year = %s AND company = %s AND account_type IN ('Asset')
GROUP BY account
""", (fiscal_year, company), as_dict=1)
liabilities = frappe.db.sql("""
SELECT
account,
SUM(credit) - SUM(debit) as balance
FROM `tabJournal Entry Account`
WHERE fiscal_year = %s AND company = %s AND account_type IN ('Liability')
GROUP BY account
""", (fiscal_year, company), as_dict=1)
equity = frappe.db.sql("""
SELECT
account,
SUM(credit) - SUM(debit) as balance
FROM `tabJournal Entry Account`
WHERE fiscal_year = %s AND company = %s AND account_type IN ('Equity')
GROUP BY account
""", (fiscal_year, company), as_dict=1)
return {
'assets': assets,
'liabilities': liabilities,
'equity': equity
}
profit_and_loss.py
:生成利润表。
# profit_and_loss.py
import frappe
def get_profit_and_loss(fiscal_year, company):
# 获取收入和费用类科目的余额
revenues = frappe.db.sql("""
SELECT
account,
SUM(credit) - SUM(debit) as balance
FROM `tabJournal Entry Account`
WHERE fiscal_year = %s AND company = %s AND account_type IN ('Revenue')
GROUP BY account
""", (fiscal_year, company), as_dict=1)
expenses = frappe.db.sql("""
SELECT
account,
SUM(debit) - SUM(credit) as balance
FROM `tabJournal Entry Account`
WHERE fiscal_year = %s AND company = %s AND account_type IN ('Expense')
GROUP BY account
""", (fiscal_year, company), as_dict=1)
net_profit = sum([r.get('balance', 0) for r in revenues]) - sum([e.get('balance', 0) for e in expenses])
return {
'revenues': revenues,
'expenses': expenses,
'net_profit': net_profit
}
3. 税务和合规类
tax_rule.py
处理税务规则的设置和应用。
# tax_rule.py
import frappe
from frappe.model.document import Document
class TaxRule(Document):
def calculate_tax(self, amount):
# 根据税务规则计算税额
if self.tax_rate:
return amount * (self.tax_rate / 100)
return 0
fiscal_year.py
管理会计年度的设置和验证。
# fiscal_year.py
import frappe
from frappe.model.document import Document
class FiscalYear(Document):
def validate(self):
# 验证会计年度的开始和结束日期
if self.year_start_date > self.year_end_date:
frappe.throw('会计年度的开始日期不能晚于结束日期')
4. 凭证和交易记录类
journal_entry.py
处理日记账分录的创建和保存。
# journal_entry.py
import frappe
from frappe.model.document import Document
class JournalEntry(Document):
def validate(self):
# 验证借贷平衡
total_debit = sum([d.debit for d in self.get('accounts')])
total_credit = sum([d.credit for d in self.get('accounts')])
if total_debit != total_credit:
frappe.throw('借贷必须平衡')
def on_submit(self):
# 提交日记账分录时更新账户余额
for account in self.get('accounts'):
frappe.db.sql("""
UPDATE `tabAccount`
SET balance = balance + %s - %s
WHERE name = %s
""", (account.debit, account.credit, account.account))
payment_entry.py
处理付款和收款记录。
# payment_entry.py
import frappe
from frappe.model.document import Document
class PaymentEntry(Document):
def validate(self):
# 验证付款金额和收款金额
if self.payment_type == 'Pay' and self.paid_amount > self.outstanding_amount:
frappe.throw('付款金额不能超过未结清金额')
elif self.payment_type == 'Receive' and self.paid_amount > self.outstanding_amount:
frappe.throw('收款金额不能超过未结清金额')
def on_submit(self):
# 提交付款或收款记录时更新相关账户余额
if self.payment_type == 'Pay':
frappe.db.sql("""
UPDATE `tabAccount`
SET balance = balance - %s
WHERE name = %s
""", (self.paid_amount, self.paid_from))
elif self.payment_type == 'Receive':
frappe.db.sql("""
UPDATE `tabAccount`
SET balance = balance + %s
WHERE name = %s
""", (self.paid_amount, self.paid_to))
5. 审计和日志类
audit_trail.py
记录系统操作和变更历史。
# audit_trail.py
import frappe
def log_audit_trail(doc, action):
# 记录审计日志
frappe.get_doc({
'doctype': 'Audit Trail',
'document_type': doc.doctype,
'document_name': doc.name,
'action': action,
'user': frappe.session.user,
'timestamp': frappe.utils.now()
}).insert()
log_settings.py
设置系统的日志记录规则。
# log_settings.py
import frappe
from frappe.model.document import Document
class LogSettings(Document):
def get_log_level(self):
# 获取日志级别
return self.log_level
以上代码只是示例,实际的 ERPNext 系统可能会更复杂,并且会依据具体的业务需求和数据库结构进行调整。
月结时计提费用
在 ERPNext 系统中实现根据台账计提固定费用的功能,需要在 Python 和 JavaScript 代码中分别进行相应的逻辑处理。下面以一个简单的场景为例,假设我们有一个固定资产台账,每个月需要根据台账上的资产信息计提折旧费用。以下是实现该功能的具体代码:
1. fixed_asset_ledger.py
(Python 文件)
import frappe
from frappe.model.document import Document
class FixedAssetLedger(Document):
def calculate_depreciation(self):
"""计算固定资产折旧"""
# 假设固定资产的折旧方法为直线法,使用年限为 useful_life 年,残值率为 salvage_value_percent
for asset in self.get('assets'):
depreciable_amount = asset.purchase_cost * (1 - asset.salvage_value_percent / 100)
monthly_depreciation = depreciable_amount / (asset.useful_life * 12)
asset.monthly_depreciation = monthly_depreciation
def on_submit(self):
"""提交固定资产台账时,生成计提折旧的日记账分录"""
self.calculate_depreciation()
journal_entry = frappe.new_doc('Journal Entry')
journal_entry.voucher_type = 'Journal Entry'
journal_entry.posting_date = self.posting_date
journal_entry.company = self.company
journal_entry.remark = '计提固定资产折旧'
for asset in self.get('assets'):
# 借方科目:累计折旧
journal_entry.append('accounts', {
'account': frappe.get_value('Account', {'account_name': '累计折旧', 'company': self.company}),
'debit_in_account_currency': asset.monthly_depreciation,
'credit_in_account_currency': 0
})
# 贷方科目:固定资产折旧费用
journal_entry.append('accounts', {
'account': frappe.get_value('Account', {'account_name': '固定资产折旧费用', 'company': self.company}),
'debit_in_account_currency': 0,
'credit_in_account_currency': asset.monthly_depreciation
})
journal_entry.save()
journal_entry.submit()
2. fixed_asset_ledger.js
(JavaScript 文件)
frappe.ui.form.on('Fixed Asset Ledger', {
refresh: function(frm) {
// 刷新页面时,添加计算折旧按钮
frm.add_custom_button(__('计算折旧'), function() {
frappe.call({
method: 'your_app_name.fixed_asset_ledger.FixedAssetLedger.calculate_depreciation',
args: {
name: frm.doc.name
},
callback: function(r) {
if (r.message) {
frappe.msgprint(__('折旧计算成功'));
frm.refresh();
} else {
frappe.msgprint(__('折旧计算失败'));
}
}
});
});
// 添加提交按钮的自定义逻辑
frm.set_query('assets', function() {
return {
filters: {
'company': frm.doc.company
}
};
});
},
on_submit: function(frm) {
frappe.call({
method: 'your_app_name.fixed_asset_ledger.FixedAssetLedger.on_submit',
args: {
name: frm.doc.name
},
callback: function(r) {
if (r.message) {
frappe.msgprint(__('固定资产台账已提交,折旧已计提'));
} else {
frappe.msgprint(__('提交失败,请检查'));
}
}
});
}
});
代码说明
Python 代码
FixedAssetLedger
类继承自Document
,用于表示固定资产台账。calculate_depreciation
方法根据固定资产的购买成本、使用年限和残值率计算每月折旧额。on_submit
方法在提交固定资产台账时,调用calculate_depreciation
方法计算折旧,并生成计提折旧的日记账分录。
JavaScript 代码
refresh
事件处理函数在页面刷新时添加一个“计算折旧”按钮,点击该按钮调用后端的calculate_depreciation
方法。on_submit
事件处理函数在提交固定资产台账时,调用后端的on_submit
方法,实现计提折旧的功能,并根据返回结果显示相应的提示信息。
请根据实际的 ERPNext 系统配置和业务需求,将上述代码中的 your_app_name
替换为实际的应用名称,并调整相关的字段和科目名称。
import frappe
from frappe.model.document import Document
class FixedAssetLedger(Document):
def calculate_depreciation(self):
"""计算固定资产折旧"""
# 假设固定资产的折旧方法为直线法,使用年限为 useful_life 年,残值率为 salvage_value_percent
for asset in self.get('assets'):
depreciable_amount = asset.purchase_cost * (1 - asset.salvage_value_percent / 100)
monthly_depreciation = depreciable_amount / (asset.useful_life * 12)
asset.monthly_depreciation = monthly_depreciation
def on_submit(self):
"""提交固定资产台账时,生成计提折旧的日记账分录"""
self.calculate_depreciation()
journal_entry = frappe.new_doc('Journal Entry')
journal_entry.voucher_type = 'Journal Entry'
journal_entry.posting_date = self.posting_date
journal_entry.company = self.company
journal_entry.remark = '计提固定资产折旧'
for asset in self.get('assets'):
# 借方科目:累计折旧
journal_entry.append('accounts', {
'account': frappe.get_value('Account', {'account_name': '累计折旧', 'company': self.company}),
'debit_in_account_currency': asset.monthly_depreciation,
'credit_in_account_currency': 0
})
# 贷方科目:固定资产折旧费用
journal_entry.append('accounts', {
'account': frappe.get_value('Account', {'account_name': '固定资产折旧费用', 'company': self.company}),
'debit_in_account_currency': 0,
'credit_in_account_currency': asset.monthly_depreciation
})
journal_entry.save()
journal_entry.submit()
</doubaocanvas-part>
<doubaocanvas-part language="javascript" path="fixed_asset_ledger.js" description="固定资产台账的 JavaScript 代码">
frappe.ui.form.on('Fixed Asset Ledger', {
refresh: function(frm) {
// 刷新页面时,添加计算折旧按钮
frm.add_custom_button(__('计算折旧'), function() {
frappe.call({
method: 'your_app_name.fixed_asset_ledger.FixedAssetLedger.calculate_depreciation',
args: {
name: frm.doc.name
},
callback: function(r) {
if (r.message) {
frappe.msgprint(__('折旧计算成功'));
frm.refresh();
} else {
frappe.msgprint(__('折旧计算失败'));
}
}
});
});
// 添加提交按钮的自定义逻辑
frm.set_query('assets', function() {
return {
filters: {
'company': frm.doc.company
}
};
});
},
on_submit: function(frm) {
frappe.call({
method: 'your_app_name.fixed_asset_ledger.FixedAssetLedger.on_submit',
args: {
name: frm.doc.name
},
callback: function(r) {
if (r.message) {
frappe.msgprint(__('固定资产台账已提交,折旧已计提'));
} else {
frappe.msgprint(__('提交失败,请检查'));
}
}
});
}
});
</doubaocanvas-part>
计提其他固定费用
下面将为你提供在原有的代码基础上,增加房租、物业费、月租费、电费和职工薪酬计提功能的详细代码。我们会分别在 Python 和 JavaScript 文件中实现相应逻辑。
1. fixed_asset_ledger.py
(Python 文件)
import frappe
from frappe.model.document import Document
class FixedAssetLedger(Document):
def calculate_depreciation(self):
"""计算固定资产折旧"""
for asset in self.get('assets'):
depreciable_amount = asset.purchase_cost * (1 - asset.salvage_value_percent / 100)
monthly_depreciation = depreciable_amount / (asset.useful_life * 12)
asset.monthly_depreciation = monthly_depreciation
def calculate_rent_expense(self):
"""按租赁准则计提房租"""
rent_contracts = frappe.get_all('Rent Contract', filters={'company': self.company})
for contract in rent_contracts:
contract_doc = frappe.get_doc('Rent Contract', contract.name)
rent_expense = contract_doc.monthly_rent
journal_entry = frappe.new_doc('Journal Entry')
journal_entry.voucher_type = 'Journal Entry'
journal_entry.posting_date = self.posting_date
journal_entry.company = self.company
journal_entry.remark = f'计提 {contract_doc.property_name} 房租'
journal_entry.append('accounts', {
'account': frappe.get_value('Account', {'account_name': '房租费用', 'company': self.company}),
'debit_in_account_currency': rent_expense,
'credit_in_account_currency': 0
})
journal_entry.append('accounts', {
'account': frappe.get_value('Account', {'account_name': '应付账款 - 房租', 'company': self.company}),
'debit_in_account_currency': 0,
'credit_in_account_currency': rent_expense
})
journal_entry.save()
journal_entry.submit()
def calculate_fixed_fees(self):
"""按合同金额计提物业费、月租费等固定金额费用"""
fixed_fee_contracts = frappe.get_all('Fixed Fee Contract', filters={'company': self.company})
for contract in fixed_fee_contracts:
contract_doc = frappe.get_doc('Fixed Fee Contract', contract.name)
fee_amount = contract_doc.fee_amount
journal_entry = frappe.new_doc('Journal Entry')
journal_entry.voucher_type = 'Journal Entry'
journal_entry.posting_date = self.posting_date
journal_entry.company = self.company
journal_entry.remark = f'计提 {contract_doc.fee_type} 费用'
journal_entry.append('accounts', {
'account': frappe.get_value('Account', {'account_name': f'{contract_doc.fee_type} 费用', 'company': self.company}),
'debit_in_account_currency': fee_amount,
'credit_in_account_currency': 0
})
journal_entry.append('accounts', {
'account': frappe.get_value('Account', {'account_name': f'应付账款 - {contract_doc.fee_type}', 'company': self.company}),
'debit_in_account_currency': 0,
'credit_in_account_currency': fee_amount
})
journal_entry.save()
journal_entry.submit()
def calculate_electricity_expense(self):
"""按实际使用计提电费"""
electricity_records = frappe.get_all('Electricity Record', filters={'company': self.company, 'posting_date': self.posting_date})
for record in electricity_records:
record_doc = frappe.get_doc('Electricity Record', record.name)
electricity_expense = record_doc.total_cost
journal_entry = frappe.new_doc('Journal Entry')
journal_entry.voucher_type = 'Journal Entry'
journal_entry.posting_date = self.posting_date
journal_entry.company = self.company
journal_entry.remark = '计提电费'
journal_entry.append('accounts', {
'account': frappe.get_value('Account', {'account_name': '电费费用', 'company': self.company}),
'debit_in_account_currency': electricity_expense,
'credit_in_account_currency': 0
})
journal_entry.append('accounts', {
'account': frappe.get_value('Account', {'account_name': '应付账款 - 电费', 'company': self.company}),
'debit_in_account_currency': 0,
'credit_in_account_currency': electricity_expense
})
journal_entry.save()
journal_entry.submit()
def calculate_salary_expense(self):
"""按部门薪资表计提职工薪酬"""
department_salary_sheets = frappe.get_all('Department Salary Sheet', filters={'company': self.company, 'posting_date': self.posting_date})
for sheet in department_salary_sheets:
sheet_doc = frappe.get_doc('Department Salary Sheet', sheet.name)
total_salary = sheet_doc.total_salary
journal_entry = frappe.new_doc('Journal Entry')
journal_entry.voucher_type = 'Journal Entry'
journal_entry.posting_date = self.posting_date
journal_entry.company = self.company
journal_entry.remark = f'计提 {sheet_doc.department} 部门职工薪酬'
journal_entry.append('accounts', {
'account': frappe.get_value('Account', {'account_name': '职工薪酬费用', 'company': self.company}),
'debit_in_account_currency': total_salary,
'credit_in_account_currency': 0
})
journal_entry.append('accounts', {
'account': frappe.get_value('Account', {'account_name': '应付职工薪酬', 'company': self.company}),
'debit_in_account_currency': 0,
'credit_in_account_currency': total_salary
})
journal_entry.save()
journal_entry.submit()
def on_submit(self):
"""提交固定资产台账时,生成各项计提的日记账分录"""
self.calculate_depreciation()
self.calculate_rent_expense()
self.calculate_fixed_fees()
self.calculate_electricity_expense()
self.calculate_salary_expense()
2. fixed_asset_ledger.js
(JavaScript 文件)
frappe.ui.form.on('Fixed Asset Ledger', {
refresh: function (frm) {
// 刷新页面时,添加计算折旧按钮
frm.add_custom_button(__('计算折旧'), function () {
frappe.call({
method: 'your_app_name.fixed_asset_ledger.FixedAssetLedger.calculate_depreciation',
args: {
name: frm.doc.name
},
callback: function (r) {
if (r.message) {
frappe.msgprint(__('折旧计算成功'));
frm.refresh();
} else {
frappe.msgprint(__('折旧计算失败'));
}
}
});
});
// 添加计提所有费用按钮
frm.add_custom_button(__('计提所有费用'), function () {
frappe.call({
method: 'your_app_name.fixed_asset_ledger.FixedAssetLedger.on_submit',
args: {
name: frm.doc.name
},
callback: function (r) {
if (r.message) {
frappe.msgprint(__('所有费用计提成功'));
frm.refresh();
} else {
frappe.msgprint(__('费用计提失败,请检查'));
}
}
});
});
frm.set_query('assets', function () {
return {
filters: {
'company': frm.doc.company
}
};
});
},
on_submit: function (frm) {
frappe.call({
method: 'your_app_name.fixed_asset_ledger.FixedAssetLedger.on_submit',
args: {
name: frm.doc.name
},
callback: function (r) {
if (r.message) {
frappe.msgprint(__('固定资产台账已提交,所有费用已计提'));
} else {
frappe.msgprint(__('提交失败,请检查'));
}
}
});
}
});
代码说明
Python 代码
calculate_rent_expense
方法:从Rent Contract
文档中获取房租合同信息,按照合同约定的月租金金额计提房租费用,并生成相应的日记账分录。calculate_fixed_fees
方法:从Fixed Fee Contract
文档中获取物业费、月租费等固定费用合同信息,根据合同金额计提费用,生成日记账分录。calculate_electricity_expense
方法:从Electricity Record
文档中获取实际使用的电费记录,按照记录中的总费用计提电费,生成日记账分录。calculate_salary_expense
方法:从Department Salary Sheet
文档中获取部门薪资表信息,根据表中的总薪资计提职工薪酬,生成日记账分录。on_submit
方法:在提交固定资产台账时,依次调用上述所有计提方法,完成各项费用的计提。
JavaScript 代码
- 在
refresh
事件处理函数中添加了一个“计提所有费用”按钮,点击该按钮会调用后端的on_submit
方法,一次性完成所有费用的计提。 on_submit
事件处理函数在提交固定资产台账时,同样调用后端的on_submit
方法,并根据返回结果显示相应的提示信息。
请根据实际的 ERPNext 系统配置和业务需求,将上述代码中的 your_app_name
替换为实际的应用名称,并确保 Rent Contract
、Fixed Fee Contract
、Electricity Record
和 Department Salary Sheet
等文档类型存在,且包含所需的字段。
计提费用台账二维表
为了将计提费用台账拓展为二维表,允许用户在前端自行维护,我们需要对原有的代码进行一系列的修改和扩展。下面是具体的实现步骤和代码示例:
1. 数据库层面
首先,我们需要创建一个新的 DocType 来存储二维表的数据。假设这个 DocType 名为 ExpenseAccrualLedger
,包含月份和费用项目两个维度。
# expense_accrual_ledger.py
import frappe
from frappe.model.document import Document
class ExpenseAccrualLedger(Document):
pass
2. 前端界面设计
在前端,我们使用 Frappe 的 Grid
控件来展示二维表,允许用户输入和编辑数据。
// expense_accrual_ledger.js
frappe.ui.form.on('Expense Accrual Ledger', {
refresh: function(frm) {
// 初始化月份列
const months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
const fields = [
{
fieldname: 'expense_item',
label: '费用项目',
fieldtype: 'Data',
in_list_view: 1
}
];
months.forEach(month => {
fields.push({
fieldname: month.toLowerCase().replace(' ', '_'),
label: month,
fieldtype: 'Currency',
in_list_view: 1
});
});
// 创建网格
const grid = frm.add_child('expense_grid', {
fieldname: 'expense_grid',
label: '费用计提台账',
fieldtype: 'Table',
fields: fields
});
// 刷新网格
frm.refresh_field('expense_grid');
// 添加保存按钮
frm.add_custom_button(__('保存'), function() {
frm.save();
});
}
});
3. 后端数据处理
在后端,我们需要处理用户提交的数据,并将其保存到数据库中。
# expense_accrual_ledger.py
import frappe
from frappe.model.document import Document
class ExpenseAccrualLedger(Document):
def validate(self):
# 验证数据的合法性
for row in self.get('expense_grid'):
for field in row.meta.fields:
if field.fieldtype == 'Currency':
if getattr(row, field.fieldname) < 0:
frappe.throw(f"{field.label} 不能为负数")
def on_submit(self):
# 处理提交逻辑,例如生成日记账分录
for row in self.get('expense_grid'):
expense_item = row.expense_item
for field in row.meta.fields:
if field.fieldtype == 'Currency':
month = field.label
amount = getattr(row, field.fieldname)
if amount > 0:
# 生成日记账分录
journal_entry = frappe.new_doc('Journal Entry')
journal_entry.voucher_type = 'Journal Entry'
journal_entry.posting_date = frappe.utils.getdate()
journal_entry.company = self.company
journal_entry.remark = f'计提 {expense_item} 在 {month} 的费用'
journal_entry.append('accounts', {
'account': frappe.get_value('Account', {'account_name': f'{expense_item} 费用', 'company': self.company}),
'debit_in_account_currency': amount,
'credit_in_account_currency': 0
})
journal_entry.append('accounts', {
'account': frappe.get_value('Account', {'account_name': f'应付账款 - {expense_item}', 'company': self.company}),
'debit_in_account_currency': 0,
'credit_in_account_currency': amount
})
journal_entry.save()
journal_entry.submit()
代码说明
- 数据库层面:创建了
ExpenseAccrualLedger
类来存储二维表的数据。 - 前端界面设计:使用 Frappe 的
Grid
控件展示二维表,用户可以在网格中输入和编辑费用计提数据。添加了保存按钮,用于保存用户输入的数据。 - 后端数据处理:在
validate
方法中,验证用户输入的数据是否合法。在on_submit
方法中,根据用户输入的数据生成相应的日记账分录。
完整代码示例
请根据实际的 ERPNext 系统配置和业务需求,将上述代码中的相关字段和 DocType 名称进行调整。