mirror of
https://github.com/frappe/erpnext.git
synced 2026-10-29 22:57:42 +00:00
fix(selling): reset ordered_qty when a cancelled Sales Order is unlinked from its Purchase Orders (backport #59120)
Unlinking a cancelled Sales Order from its Purchase Orders resets ordered_qty.
(cherry picked from commit ded6df3614)
This commit is contained in:
@@ -2168,32 +2168,44 @@ class AccountsController(TransactionBase):
|
||||
self.unlink_ref_doc_from_po()
|
||||
|
||||
def unlink_ref_doc_from_po(self):
|
||||
so_items = []
|
||||
for item in self.items:
|
||||
so_items.append(item.name)
|
||||
so_items = [item.name for item in self.items]
|
||||
filters = {
|
||||
"sales_order": self.name,
|
||||
"sales_order_item": ["in", so_items],
|
||||
"docstatus": ["<", 2],
|
||||
}
|
||||
|
||||
linked_po = list(
|
||||
set(
|
||||
frappe.get_all(
|
||||
"Purchase Order Item",
|
||||
filters={
|
||||
"sales_order": self.name,
|
||||
"sales_order_item": ["in", so_items],
|
||||
"docstatus": ["<", 2],
|
||||
},
|
||||
pluck="parent",
|
||||
)
|
||||
linked_po_items = frappe.get_all(
|
||||
"Purchase Order Item", filters=filters, fields=["parent", "sales_order_item"]
|
||||
)
|
||||
if not linked_po_items:
|
||||
return
|
||||
|
||||
frappe.db.set_value("Purchase Order Item", filters, {"sales_order": None, "sales_order_item": None})
|
||||
self.update_ordered_qty_in_items({item.sales_order_item for item in linked_po_items})
|
||||
|
||||
linked_po = sorted({item.parent for item in linked_po_items})
|
||||
frappe.msgprint(_("Purchase Orders {0} are un-linked").format("\n".join(linked_po)))
|
||||
|
||||
def update_ordered_qty_in_items(self, so_items: set[str]):
|
||||
purchase_order_item = frappe.qb.DocType("Purchase Order Item")
|
||||
ordered_qty = dict(
|
||||
frappe.qb.from_(purchase_order_item)
|
||||
.select(purchase_order_item.sales_order_item, Sum(purchase_order_item.stock_qty))
|
||||
.where(
|
||||
purchase_order_item.sales_order_item.isin(list(so_items))
|
||||
& (purchase_order_item.docstatus == 1)
|
||||
)
|
||||
.groupby(purchase_order_item.sales_order_item)
|
||||
.run()
|
||||
)
|
||||
|
||||
if linked_po:
|
||||
frappe.db.set_value(
|
||||
"Purchase Order Item",
|
||||
{"sales_order": self.name, "sales_order_item": ["in", so_items], "docstatus": ["<", 2]},
|
||||
{"sales_order": None, "sales_order_item": None},
|
||||
)
|
||||
items_by_ordered_qty = defaultdict(list)
|
||||
for so_item in so_items:
|
||||
items_by_ordered_qty[flt(ordered_qty.get(so_item))].append(so_item)
|
||||
|
||||
frappe.msgprint(_("Purchase Orders {0} are un-linked").format("\n".join(linked_po)))
|
||||
for qty, items in items_by_ordered_qty.items():
|
||||
frappe.db.set_value("Sales Order Item", {"name": ["in", items]}, "ordered_qty", qty)
|
||||
|
||||
def get_tax_map(self):
|
||||
tax_map = {}
|
||||
|
||||
@@ -1832,6 +1832,34 @@ class TestSalesOrder(AccountsTestMixin, FrappeTestCase):
|
||||
self.assertEqual(so.packed_items[0].ordered_qty, 2)
|
||||
self.assertEqual(so.packed_items[1].ordered_qty, 2)
|
||||
|
||||
def test_ordered_qty_is_reset_when_cancelled_sales_order_is_unlinked(self):
|
||||
"""Cancelling a Sales Order unlinks its Purchase Orders, so `ordered_qty` must be recomputed."""
|
||||
selected_items = [{"item_code": "_Test Item", "supplier": "_Test Supplier"}]
|
||||
so = make_sales_order(item_code="_Test Item", qty=10)
|
||||
|
||||
purchase_order = make_purchase_order(so.name, selected_items=selected_items)
|
||||
purchase_order.supplier = "_Test Supplier"
|
||||
purchase_order.schedule_date = add_days(nowdate(), 1)
|
||||
purchase_order.submit()
|
||||
|
||||
so.reload()
|
||||
self.assertEqual(so.items[0].ordered_qty, 10)
|
||||
|
||||
so.cancel()
|
||||
so.reload()
|
||||
self.assertEqual(so.items[0].ordered_qty, 0)
|
||||
|
||||
amended_so = frappe.copy_doc(so)
|
||||
amended_so.amended_from = so.name
|
||||
amended_so.docstatus = 0
|
||||
amended_so.insert()
|
||||
amended_so.submit()
|
||||
|
||||
self.assertEqual(amended_so.items[0].ordered_qty, 0)
|
||||
|
||||
new_purchase_order = make_purchase_order(amended_so.name, selected_items=selected_items)
|
||||
self.assertEqual(new_purchase_order.items[0].qty, 10)
|
||||
|
||||
def test_reserved_qty_for_closing_so(self):
|
||||
bin = frappe.get_all(
|
||||
"Bin",
|
||||
|
||||
Reference in New Issue
Block a user