mirror of
https://github.com/frappe/erpnext.git
synced 2026-09-15 09:53:09 +00:00
fix(stock): recalculate billing status of returned delivery notes (#58953)
This commit is contained in:
@@ -521,3 +521,4 @@ erpnext.patches.v16_0.set_secondary_item_valuation_type
|
||||
erpnext.patches.v16_0.append_fieldname_to_pos_search_fields
|
||||
erpnext.patches.v16_0.set_supplier_quotation_order_status
|
||||
erpnext.patches.v16_0.recalculate_holiday_list_totals
|
||||
erpnext.patches.v16_0.recalculate_returned_delivery_note_billing_status
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
"""Recalculate billing status of Delivery Notes left open by a return.
|
||||
|
||||
Returning the uninvoiced qty of a Delivery Note did not recalculate the original
|
||||
Delivery Note, so it stayed "To Bill" / "Partially Billed" with nothing left to invoice.
|
||||
"""
|
||||
dn = frappe.qb.DocType("Delivery Note")
|
||||
dn_item = frappe.qb.DocType("Delivery Note Item")
|
||||
|
||||
delivery_notes = (
|
||||
frappe.qb.from_(dn)
|
||||
.inner_join(dn_item)
|
||||
.on(dn_item.parent == dn.name)
|
||||
.select(dn.name)
|
||||
.distinct()
|
||||
.where(
|
||||
(dn.docstatus == 1)
|
||||
& (dn.is_return == 0)
|
||||
& dn.status.isin(["To Bill", "Partially Billed"])
|
||||
& (dn_item.returned_qty > 0)
|
||||
)
|
||||
.run(pluck=True)
|
||||
)
|
||||
|
||||
for name in delivery_notes:
|
||||
doc = frappe.get_doc("Delivery Note", name)
|
||||
doc.update_billing_percentage(update_modified=False)
|
||||
doc.load_from_db()
|
||||
doc.set_status(update=True, update_modified=False)
|
||||
@@ -1110,6 +1110,67 @@ class TestDeliveryNote(ERPNextTestSuite):
|
||||
self.assertEqual(dn.per_billed, 50)
|
||||
self.assertEqual(dn.status, "Partially Billed")
|
||||
|
||||
def test_billing_status_repair_patch(self):
|
||||
"""Returns submitted before #58869 left the original Delivery Note's per_billed stale.
|
||||
|
||||
The repair patch recalculates such notes: a directly invoiced one whose remaining
|
||||
qty was returned becomes Completed, an uninvoiced Sales Order linked one goes back
|
||||
to To Bill.
|
||||
"""
|
||||
from erpnext.patches.v16_0 import recalculate_returned_delivery_note_billing_status as patch
|
||||
from erpnext.stock.doctype.delivery_note.mapper import make_sales_return
|
||||
|
||||
# Delivery Note invoiced for 2 of 5 qty, the remaining 3 returned -> fully billed
|
||||
make_stock_entry(target="_Test Warehouse - _TC", qty=5, basic_rate=100)
|
||||
dn = create_delivery_note(qty=5)
|
||||
|
||||
si = make_sales_invoice(dn.name)
|
||||
si.items[0].qty = 2
|
||||
si.insert()
|
||||
si.submit()
|
||||
|
||||
dn_return = make_sales_return(dn.name)
|
||||
dn_return.items[0].qty = -3
|
||||
dn_return.insert()
|
||||
# Mimic the submit request, which reconstructs the document from client data.
|
||||
frappe.get_doc(dn_return.as_dict()).submit()
|
||||
|
||||
dn.load_from_db()
|
||||
self.assertEqual(dn.items[0].returned_qty, 3)
|
||||
self.assertEqual(dn.per_billed, 100)
|
||||
|
||||
# Sales Order linked Delivery Note, nothing invoiced, partly returned -> unbilled
|
||||
so = make_sales_order(qty=10)
|
||||
so_dn = create_dn_against_so(so.name, delivered_qty=5)
|
||||
|
||||
so_dn_return = make_sales_return(so_dn.name)
|
||||
so_dn_return.items[0].qty = -2
|
||||
so_dn_return.insert()
|
||||
frappe.get_doc(so_dn_return.as_dict()).submit()
|
||||
|
||||
so_dn.load_from_db()
|
||||
self.assertEqual(so_dn.items[0].returned_qty, 2)
|
||||
self.assertEqual(so_dn.per_billed, 0)
|
||||
|
||||
# Mimic the state left behind by a return submitted before the fix
|
||||
for name, per_billed in ((dn.name, 40), (so_dn.name, 50)):
|
||||
frappe.db.set_value(
|
||||
"Delivery Note",
|
||||
name,
|
||||
{"per_billed": per_billed, "status": "Partially Billed"},
|
||||
update_modified=False,
|
||||
)
|
||||
|
||||
patch.execute()
|
||||
|
||||
dn.load_from_db()
|
||||
self.assertEqual(dn.per_billed, 100)
|
||||
self.assertEqual(dn.status, "Completed")
|
||||
|
||||
so_dn.load_from_db()
|
||||
self.assertEqual(so_dn.per_billed, 0)
|
||||
self.assertEqual(so_dn.status, "To Bill")
|
||||
|
||||
def test_dn_billing_status_case2(self):
|
||||
# SO -> SI and SO -> DN1, DN2
|
||||
from erpnext.selling.doctype.sales_order.mapper import (
|
||||
|
||||
Reference in New Issue
Block a user