refactor(controllers): convert sales/purchase return lookups to qb/ORM

validate_returned_items used a raw frappe.db.sql with a string-built column
list (and a separate Packed Item select); get_already_returned_items used a
raw GROUP BY sum. Convert both to frappe.get_all / frappe.qb (Sum(Abs(...))
with an explicit groupby). The qb GROUP BY mirrors the original
`group by item_code, <field>`, so it is parity-preserving (not a behaviour
change) and valid on Postgres.

Surgical re-apply: develop's `is_debit_note = 0` credit-note fix in
make_return_doc is preserved (the staging branch predated and would have
reverted it).

Adds a test (Delivery Note -> sales return) exercising validate_returned_items
and get_already_returned_items on both engines.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Mihir Kandoi
2026-06-21 05:23:31 +05:30
parent 4255059846
commit 465446bb79
3 changed files with 93 additions and 53 deletions

View File

@@ -5,6 +5,8 @@ import json
import frappe
from frappe import _, bold
from frappe.query_builder import Criterion
from frappe.query_builder.functions import Count
from frappe.utils import cint, cstr, flt, get_link_to_form, getdate
import erpnext
@@ -279,11 +281,7 @@ class StockController(AccountsController):
def make_gl_entries_on_cancel(self, from_repost=False):
if not from_repost:
cancel_exchange_gain_loss_journal(frappe._dict(doctype=self.doctype, name=self.name))
if frappe.db.sql(
"""select name from `tabGL Entry` where voucher_type=%s
and voucher_no=%s""",
(self.doctype, self.name),
):
if frappe.db.exists("GL Entry", {"voucher_type": self.doctype, "voucher_no": self.name}):
self.make_gl_entries()
def validate_warehouse(self):
@@ -632,7 +630,7 @@ def check_item_quality_inspection(doctype: str, docstatus: str | int, items: str
inspection_fieldname = INSPECTION_FIELDNAME_MAP.get(doctype)
if inspection_fieldname is None:
return items if doctype == "Stock Entry" else []
return []
allow_after_transaction = cint(docstatus) == 1 and frappe.get_single_value(
"Stock Settings", "allow_to_make_quality_inspection_after_purchase_or_delivery"
@@ -725,20 +723,18 @@ def future_sle_exists(args, sl_entries=None):
args["posting_datetime"] = get_combine_datetime(args["posting_date"], args["posting_time"])
data = frappe.db.sql(
"""
select item_code, warehouse, count(name) as total_row
from `tabStock Ledger Entry`
where
({})
and posting_datetime >= %(posting_datetime)s
and voucher_no != %(voucher_no)s
and is_cancelled = 0
GROUP BY
item_code, warehouse
""".format(" or ".join(or_conditions)),
args,
as_dict=1,
sle = frappe.qb.DocType("Stock Ledger Entry")
data = (
frappe.qb.from_(sle)
.select(sle.item_code, sle.warehouse, Count(sle.name).as_("total_row"))
.where(
Criterion.any(or_conditions)
& (sle.posting_datetime >= args["posting_datetime"])
& (sle.voucher_no != args["voucher_no"])
& (sle.is_cancelled == 0)
)
.groupby(sle.item_code, sle.warehouse)
.run(as_dict=1)
)
for d in data:
@@ -792,12 +788,10 @@ def get_conditions_to_validate_future_sle(sl_entries):
warehouse_items_map[entry.warehouse].add(entry.item_code)
sle = frappe.qb.DocType("Stock Ledger Entry")
or_conditions = []
for warehouse, items in warehouse_items_map.items():
or_conditions.append(
f"""warehouse = {frappe.db.escape(warehouse)}
and item_code in ({", ".join(frappe.db.escape(item) for item in items)})"""
)
or_conditions.append((sle.warehouse == warehouse) & sle.item_code.isin(list(items)))
return or_conditions