refactor(stock): convert Delivery Note raw SQL to ORM

set_actual_qty Bin lookup -> frappe.db.get_value; validate_proj_cust raw
"customer=%s OR ifnull(customer,'')=''" -> get_all or_filters with
[customer, is, not set] (correct PG empty-string/NULL handling); the two
check_next_docstatus implicit comma-joins -> get_all on the child table
(Sales Invoice Item / Installation Note Item, docstatus=1). Same result on
MariaDB; valid under Postgres.

Tests: validate_proj_cust mismatch + no-customer (the or_filters branch), and
check_next_docstatus blocking cancel when a submitted Sales Invoice draws from
the DN.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Mihir Kandoi
2026-06-19 23:19:34 +05:30
parent 884f57d5f6
commit ff4adce91b
2 changed files with 79 additions and 20 deletions

View File

@@ -261,12 +261,10 @@ class DeliveryNote(SellingController):
def set_actual_qty(self):
for d in self.get("items"):
if d.item_code and d.warehouse:
actual_qty = frappe.db.sql(
"""select actual_qty from `tabBin`
where item_code = %s and warehouse = %s""",
(d.item_code, d.warehouse),
actual_qty = frappe.db.get_value(
"Bin", {"item_code": d.item_code, "warehouse": d.warehouse}, "actual_qty"
)
d.actual_qty = actual_qty and flt(actual_qty[0][0]) or 0
d.actual_qty = flt(actual_qty) or 0
def so_required(self):
"""check in manage account if sales order required or not"""
@@ -385,11 +383,10 @@ class DeliveryNote(SellingController):
def validate_proj_cust(self):
"""check for does customer belong to same project as entered.."""
if self.project and self.customer:
res = frappe.db.sql(
"""select name from `tabProject`
where name = %s and (customer = %s or
ifnull(customer,'')='')""",
(self.project, self.customer),
res = frappe.get_all(
"Project",
filters={"name": self.project},
or_filters=[["customer", "=", self.customer], ["customer", "is", "not set"]],
)
if not res:
frappe.throw(
@@ -604,20 +601,20 @@ class DeliveryNote(SellingController):
PackingService(self).validate_packed_qty()
def check_next_docstatus(self):
submit_rv = frappe.db.sql(
"""select t1.name
from `tabSales Invoice` t1,`tabSales Invoice Item` t2
where t1.name = t2.parent and t2.delivery_note = %s and t1.docstatus = 1""",
(self.name),
submit_rv = frappe.get_all(
"Sales Invoice Item",
filters={"delivery_note": self.name, "docstatus": 1},
fields=["parent"],
as_list=True,
)
if submit_rv:
frappe.throw(_("Sales Invoice {0} has already been submitted").format(submit_rv[0][0]))
submit_in = frappe.db.sql(
"""select t1.name
from `tabInstallation Note` t1, `tabInstallation Note Item` t2
where t1.name = t2.parent and t2.prevdoc_docname = %s and t1.docstatus = 1""",
(self.name),
submit_in = frappe.get_all(
"Installation Note Item",
filters={"prevdoc_docname": self.name, "docstatus": 1},
fields=["parent"],
as_list=True,
)
if submit_in:
frappe.throw(_("Installation Note {0} has already been submitted").format(submit_in[0][0]))

View File

@@ -3376,6 +3376,68 @@ class TestDeliveryNote(ERPNextTestSuite):
dn.items[0].stock_qty = 2
dn.save()
def test_validate_proj_cust_matches_project_customer(self):
"""validate_proj_cust must reject a DN whose customer differs from the project's customer,
and accept one when the project has no customer (the ifnull(customer,'')='' / `is not set`
branch of the converted or_filters)."""
mismatch_project = frappe.get_doc(
{
"doctype": "Project",
"project_name": "_Test DN Project Mismatch",
"company": "_Test Company",
"customer": "_Test Customer 1",
}
).insert()
dn = create_delivery_note(customer="_Test Customer", do_not_save=True)
dn.project = mismatch_project.name
with self.assertRaises(frappe.ValidationError) as cm:
dn.insert()
self.assertIn("does not belong to project", str(cm.exception))
# A project with no customer must pass via the empty-string/NULL or_filters branch.
open_project = frappe.get_doc(
{
"doctype": "Project",
"project_name": "_Test DN Project No Customer",
"company": "_Test Company",
}
).insert()
self.assertFalse(open_project.customer)
dn2 = create_delivery_note(customer="_Test Customer", do_not_save=True)
dn2.project = open_project.name
dn2.insert() # must not raise
self.assertTrue(dn2.name)
def test_check_next_docstatus_blocks_cancel_with_submitted_invoice(self):
"""check_next_docstatus must block cancelling a DN once a submitted Sales Invoice draws from
it — covers the converted child-table get_all (Sales Invoice Item, docstatus=1)."""
dn = create_delivery_note() # submitted, simple _Test Item
si = make_sales_invoice(dn.name)
si.insert()
si.submit()
dn.load_from_db()
with self.assertRaises(frappe.ValidationError) as cm:
dn.cancel()
self.assertIn("has already been submitted", str(cm.exception))
def test_cancel_packing_slips_cancels_submitted_slips(self):
"""cancel_packing_slips must cancel the DN's submitted Packing Slips — covers the converted
get_all(pluck=name) lookup and the pluck-aware iteration."""
from erpnext.stock.doctype.delivery_note.mapper import make_packing_slip
from erpnext.stock.doctype.delivery_note.services.packing import PackingService
dn = create_delivery_note(do_not_submit=True) # draft, so a Packing Slip can be mapped
ps = make_packing_slip(dn.name)
ps.save()
ps.submit()
dn.submit()
self.assertEqual(frappe.db.get_value("Packing Slip", ps.name, "docstatus"), 1)
PackingService(dn).cancel_packing_slips()
self.assertEqual(frappe.db.get_value("Packing Slip", ps.name, "docstatus"), 2)
def create_delivery_note(**args):
dn = frappe.new_doc("Delivery Note")