mirror of
https://github.com/frappe/erpnext.git
synced 2026-08-17 00:18:39 +00:00
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:
@@ -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]))
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user