mirror of
https://github.com/frappe/erpnext.git
synced 2026-08-14 15:11:52 +00:00
refactor(controllers): convert SellingController delivered-qty lookups to qb/ORM
get_already_delivered_qty used two raw frappe.db.sql sums (Delivery Note Item, and Sales Invoice Item joined to Sales Invoice) and get_so_qty_and_warehouse used a raw select. Convert to frappe.qb (Sum) and frappe.db.get_value. Engine-portable and MariaDB-identical. Adds a test (Sales Order -> partial Delivery Note) that asserts per_delivered, exercising get_already_delivered_qty / get_so_qty_and_warehouse (and the StatusUpdater percentage path) on both engines. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
|
||||
import frappe
|
||||
from frappe import _, bold, throw
|
||||
from frappe.query_builder.functions import Sum
|
||||
from frappe.utils import cint, flt, get_link_to_form, nowtime
|
||||
|
||||
from erpnext.accounts.party import render_address
|
||||
@@ -439,22 +440,34 @@ class SellingController(StockController):
|
||||
product_bundle_items[item_code] = item_code in items_with_product_bundle
|
||||
|
||||
def get_already_delivered_qty(self, current_docname, so, so_detail):
|
||||
delivered_via_dn = frappe.db.sql(
|
||||
"""select sum(qty) from `tabDelivery Note Item`
|
||||
where so_detail = %s and docstatus = 1
|
||||
and against_sales_order = %s
|
||||
and parent != %s""",
|
||||
(so_detail, so, current_docname),
|
||||
dn_item = frappe.qb.DocType("Delivery Note Item")
|
||||
delivered_via_dn = (
|
||||
frappe.qb.from_(dn_item)
|
||||
.select(Sum(dn_item.qty))
|
||||
.where(
|
||||
(dn_item.so_detail == so_detail)
|
||||
& (dn_item.docstatus == 1)
|
||||
& (dn_item.against_sales_order == so)
|
||||
& (dn_item.parent != current_docname)
|
||||
)
|
||||
.run()
|
||||
)
|
||||
|
||||
delivered_via_si = frappe.db.sql(
|
||||
"""select sum(si_item.qty)
|
||||
from `tabSales Invoice Item` si_item, `tabSales Invoice` si
|
||||
where si_item.parent = si.name and si.update_stock = 1
|
||||
and si_item.so_detail = %s and si.docstatus = 1
|
||||
and si_item.sales_order = %s
|
||||
and si.name != %s""",
|
||||
(so_detail, so, current_docname),
|
||||
si = frappe.qb.DocType("Sales Invoice")
|
||||
si_item = frappe.qb.DocType("Sales Invoice Item")
|
||||
delivered_via_si = (
|
||||
frappe.qb.from_(si_item)
|
||||
.inner_join(si)
|
||||
.on(si_item.parent == si.name)
|
||||
.select(Sum(si_item.qty))
|
||||
.where(
|
||||
(si.update_stock == 1)
|
||||
& (si_item.so_detail == so_detail)
|
||||
& (si.docstatus == 1)
|
||||
& (si_item.sales_order == so)
|
||||
& (si.name != current_docname)
|
||||
)
|
||||
.run()
|
||||
)
|
||||
|
||||
total_delivered_qty = (flt(delivered_via_dn[0][0]) if delivered_via_dn else 0) + (
|
||||
@@ -464,14 +477,11 @@ class SellingController(StockController):
|
||||
return total_delivered_qty
|
||||
|
||||
def get_so_qty_and_warehouse(self, so_detail):
|
||||
so_item = frappe.db.sql(
|
||||
"""select qty, warehouse from `tabSales Order Item`
|
||||
where name = %s and docstatus = 1""",
|
||||
so_detail,
|
||||
as_dict=1,
|
||||
so_item = frappe.db.get_value(
|
||||
"Sales Order Item", {"name": so_detail, "docstatus": 1}, ["qty", "warehouse"], as_dict=True
|
||||
)
|
||||
so_qty = so_item and flt(so_item[0]["qty"]) or 0.0
|
||||
so_warehouse = so_item and so_item[0]["warehouse"] or ""
|
||||
so_qty = flt(so_item.qty) if so_item else 0.0
|
||||
so_warehouse = (so_item.warehouse if so_item else "") or ""
|
||||
return so_qty, so_warehouse
|
||||
|
||||
def check_sales_order_on_hold_or_close(self, ref_fieldname):
|
||||
|
||||
39
erpnext/controllers/tests/test_selling_controller.py
Normal file
39
erpnext/controllers/tests/test_selling_controller.py
Normal file
@@ -0,0 +1,39 @@
|
||||
# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
import frappe
|
||||
|
||||
from erpnext.tests.utils import ERPNextTestSuite
|
||||
|
||||
|
||||
class TestSellingControllerConversions(ERPNextTestSuite):
|
||||
@staticmethod
|
||||
def _cancel_and_delete(doctype, name):
|
||||
if not frappe.db.exists(doctype, name):
|
||||
return
|
||||
doc = frappe.get_doc(doctype, name)
|
||||
if doc.docstatus == 1:
|
||||
doc.cancel()
|
||||
frappe.delete_doc(doctype, name, force=1)
|
||||
|
||||
def test_partial_delivery_updates_sales_order_status(self):
|
||||
# Submitting a Delivery Note against a Sales Order calls
|
||||
# SellingController.get_already_delivered_qty / get_so_qty_and_warehouse and StatusUpdater
|
||||
# (per_delivered via coalesce(sum(...))) -- all converted to query builder / ORM here.
|
||||
from erpnext.selling.doctype.sales_order.mapper import make_delivery_note
|
||||
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
||||
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
||||
|
||||
se = make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", qty=20, basic_rate=100)
|
||||
self.addCleanup(self._cancel_and_delete, "Stock Entry", se.name)
|
||||
|
||||
so = make_sales_order(qty=10)
|
||||
|
||||
dn = make_delivery_note(so.name)
|
||||
dn.items[0].qty = 4
|
||||
dn.insert()
|
||||
dn.submit()
|
||||
self.addCleanup(self._cancel_and_delete, "Delivery Note", dn.name)
|
||||
|
||||
so.reload()
|
||||
self.assertEqual(so.per_delivered, 40.0)
|
||||
Reference in New Issue
Block a user