From f864333afab6c3b1dec7519c2e66b0bb17e548e6 Mon Sep 17 00:00:00 2001 From: Pandiyan P Date: Tue, 8 Sep 2026 16:21:55 +0530 Subject: [PATCH] fix(stock): recalculate delivery note billing after return (#58869) --- .../delivery_note/services/billing_status.py | 6 ++- .../delivery_note/test_delivery_note.py | 50 +++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/delivery_note/services/billing_status.py b/erpnext/stock/doctype/delivery_note/services/billing_status.py index 831587d78df..f9748042557 100644 --- a/erpnext/stock/doctype/delivery_note/services/billing_status.py +++ b/erpnext/stock/doctype/delivery_note/services/billing_status.py @@ -29,6 +29,9 @@ class BillingStatusService: def update_billing_status(self, update_modified: bool = True) -> None: doc = self.doc updated_delivery_notes = [doc.name] + if doc.is_return and doc.return_against: + updated_delivery_notes.append(doc.return_against) + for d in doc.get("items"): if d.si_detail and not d.so_detail: d.db_set("billed_amt", d.amount, update_modified=update_modified) @@ -37,7 +40,8 @@ class BillingStatusService: for dn in set(updated_delivery_notes): dn_doc = doc if (dn == doc.name) else frappe.get_lazy_doc("Delivery Note", dn) - dn_doc.update_billing_percentage(update_modified=update_modified) + update_dn_modified = update_modified and dn != doc.return_against + dn_doc.update_billing_percentage(update_modified=update_dn_modified) doc.load_from_db() diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index df0a277dc5b..0d8b483ef6f 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -1060,6 +1060,56 @@ class TestDeliveryNote(ERPNextTestSuite): self.assertEqual(dn.per_billed, 100) self.assertEqual(dn.status, "Completed") + def test_dn_is_completed_when_unbilled_item_is_returned(self): + from erpnext.stock.doctype.delivery_note.mapper import make_sales_return + + make_stock_entry(target="_Test Warehouse - _TC", qty=1, basic_rate=100) + make_stock_entry(item_code="_Test Item 2", target="_Test Warehouse - _TC", qty=1, basic_rate=100) + + dn = create_delivery_note(do_not_submit=True) + dn.append( + "items", + { + "item_code": "_Test Item 2", + "warehouse": "_Test Warehouse - _TC", + "qty": 1, + "rate": 100, + "conversion_factor": 1, + "allow_zero_valuation_rate": 1, + "expense_account": "Cost of Goods Sold - _TC", + "cost_center": "_Test Cost Center - _TC", + }, + ) + dn.submit() + + si = make_sales_invoice(dn.name) + si.set("items", [item for item in si.items if item.item_code == "_Test Item"]) + si.insert() + si.submit() + + dn.reload() + self.assertEqual(dn.per_billed, 50) + self.assertEqual(dn.status, "Partially Billed") + + return_dn = make_sales_return(dn.name) + return_dn.set("items", [item for item in return_dn.items if item.item_code == "_Test Item 2"]) + return_dn.insert() + # Mimic the submit request, which reconstructs the document from client data. + return_dn = frappe.get_doc(return_dn.as_dict()) + return_dn.submit() + + dn.reload() + self.assertEqual(dn.items[1].returned_qty, 1) + self.assertEqual(dn.per_billed, 100) + self.assertEqual(dn.status, "Completed") + + return_dn.cancel() + + dn.reload() + self.assertEqual(dn.items[1].returned_qty, 0) + self.assertEqual(dn.per_billed, 50) + self.assertEqual(dn.status, "Partially Billed") + def test_dn_billing_status_case2(self): # SO -> SI and SO -> DN1, DN2 from erpnext.selling.doctype.sales_order.mapper import (