From 7bb19c89b133f1d1041af9b54d2aa065933cbffd 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) (cherry picked from commit f864333afab6c3b1dec7519c2e66b0bb17e548e6) # Conflicts: # erpnext/stock/doctype/delivery_note/services/billing_status.py --- .../doctype/delivery_note/delivery_note.py | 6 ++- .../delivery_note/test_delivery_note.py | 50 +++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index facbcebac8a..8a8c540bacd 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -642,6 +642,9 @@ class DeliveryNote(SellingController): def update_billing_status(self, update_modified=True): updated_delivery_notes = [self.name] + if self.is_return and self.return_against: + updated_delivery_notes.append(self.return_against) + for d in self.get("items"): if d.si_detail and not d.so_detail: d.db_set("billed_amt", d.amount, update_modified=update_modified) @@ -650,7 +653,8 @@ class DeliveryNote(SellingController): for dn in set(updated_delivery_notes): dn_doc = self if (dn == self.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 != self.return_against + dn_doc.update_billing_percentage(update_modified=update_dn_modified) self.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 9b2907b7dc1..2dd39734916 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -1052,6 +1052,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.delivery_note 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.sales_order import (