Frappe ERPNext:销售发票自定义总计删除子表行后不递减
问题:Frappe/ERPNext销售发票自定义总计,删除子表行后总计不更新
问题描述
在Frappe/ERPNext v14的自定义销售发票中,需计算每项数量×单价打85折后的总计。目前新增/编辑子表行时总计更新正常,但删除items子表行后,总计不会随之递减。
已尝试方案
- 客户端JS:绑定
qty、rate、items_add、items_remove、refresh、onload、after_save事件触发重新计算,刷新字段,还添加了自定义“重新计算并保存”按钮。 - 服务端Python:在
before_save/before_submit钩子中执行重新计算,用frappe.db.set_value持久化数据,添加了调试用的frappe.msgprint。 - 自定义字段:已创建
custom_g_total(货币型,归属Sales Invoice Item)和custom_grand_total_amount(货币型,归属Sales Invoice),均设置为非只读。 - 尝试过删除后手动保存、重启bench/清除缓存、执行migrate操作,无控制台或Frappe日志错误;调试信息显示重新计算已执行,但总计字段未变化。
- 简化代码避免冲突,在全新发票上测试,问题依旧。
代码片段
JavaScript(销售发票自定义表单脚本)
frappe.ui.form.on('Sales Invoice Item', { qty: function(frm, cdt, cdn) { recalc(frm); }, rate: function(frm, cdt, cdn) { recalc(frm); } }); frappe.ui.form.on('Sales Invoice', { items_add: function(frm) { recalc(frm); }, items_remove: function(frm) { recalc(frm); }, refresh: function(frm) { recalc(frm); frm.add_custom_button('Recalculate & Save', function() { recalc(frm); frm.save(); }); } }); function recalc(frm) { let total = 0; (frm.doc.items || []).forEach(row => { let qty = parseFloat(row.qty) || 0; let rate = parseFloat(row.rate) || 0; row.custom_g_total = qty * rate * 0.85; frappe.model.set_value(row.doctype, row.name, 'custom_g_total', row.custom_g_total); total += row.custom_g_total; }); frm.set_value('custom_grand_total_amount', total); frm.refresh_field('custom_grand_total_amount'); frm.refresh_field('items'); }
Python(library_management/doctype/sales_invoice/sales_invoice.py)
import frappe from frappe.model.document import Document class SalesInvoice(Document): pass def calculate_grand_total(doc, method=None): total = 0 for row in doc.get("items", []): qty = row.qty or 0 rate = row.rate or 0 row.custom_g_total = qty * rate * 0.85 total += row.custom_g_total doc.custom_grand_total_amount = total frappe.db.set_value(doc.doctype, doc.name, 'custom_grand_total_amount', total) frappe.msgprint(f"DEBUG: Grand total recalculated to {total}")
复现步骤
- 创建新销售发票。
- 添加2个商品(数量=1,单价=100 → 每项
custom_g_total=85,总计=170)。 - 删除1个商品。
- 保存或点击“重新计算并保存”按钮。
- 查看总计(仍显示170,而非预期的85)。
预期与实际行为
- 预期:删除子表行后,总计自动递减,匹配剩余商品的折扣后总额。
- 实际:总计无变化;调试信息显示重新计算得到的新总计正确,但字段未更新。
解决方法
问题根源
- 客户端:
items_remove事件执行时机:Frappe中items_remove事件触发后,frm.doc.items并未立即同步删除后的行数据,导致计算时仍使用删除前的列表。 - 服务端:冗余的
frappe.db.set_value:before_save钩子执行时,系统会自动保存doc对象的变更,手动调用set_value会引发数据覆盖冲突。
修正后的代码
客户端JS调整
给items_remove事件添加延迟执行,确保子表数据同步完成:
frappe.ui.form.on('Sales Invoice', { // 保留其他事件逻辑 items_remove: function(frm) { // 延迟100ms执行,确保子表数据已更新 setTimeout(() => recalc(frm), 100); }, // 其他代码不变 });
服务端Python调整
删除冗余的frappe.db.set_value,直接依赖doc对象的变更:
import frappe from frappe.model.document import Document class SalesInvoice(Document): pass def calculate_grand_total(doc, method=None): total = 0 for row in doc.get("items", []): qty = row.qty or 0 rate = row.rate or 0 row.custom_g_total = qty * rate * 0.85 total += row.custom_g_total doc.custom_grand_total_amount = total frappe.msgprint(f"DEBUG: Grand total recalculated to {total}")
额外验证步骤
- 清除前端缓存:执行
bench clear-cache和bench build(开发模式下)。 - 确认钩子注册:在
library_management/hooks.py中添加或检查以下配置:
doc_events = { "Sales Invoice": { "before_save": "library_management.doctype.sales_invoice.sales_invoice.calculate_grand_total", "before_submit": "library_management.doctype.sales_invoice.sales_invoice.calculate_grand_total" } }
- 测试时删除行后,可等待自动计算完成再保存,或直接保存由服务端钩子确保数据正确性。
内容的提问来源于stack exchange,提问作者Abhidha Pokharel
相关产品推荐
相关产品推荐

