From 8676add8757a6c71743acacc8ec9680244e8b0cd Mon Sep 17 00:00:00 2001 From: R-Jayaraman Date: Fri, 31 Jul 2026 13:31:01 +0530 Subject: [PATCH] fix(purchase): reject purchase returns where every item has zero quantity validate_returned_items() set items_returned=True whenever a row matched a valid item from the original document, even if its qty was 0. This let a Purchase Invoice, Purchase Receipt, or Subcontracting Receipt return be submitted with every line at qty=0 - a no-op document with no stock or financial effect that still consumed a document number and linked back to the original transaction. Scoped to the Purchase side only: items_returned now flips to True for Purchase Invoice/Purchase Receipt/Subcontracting Receipt only when qty (or received_qty) is actually negative, so an all-zero purchase return correctly hits the existing "At least one item should be entered with negative quantity" check. Sales Invoice, Delivery Note, and POS Invoice are unchanged. Also applies a corresponding check to the item_name-only fallback branch (for rows without an item_code - Item Code is not mandatory on Purchase Invoice Item), which previously bypassed this fix entirely and still set items_returned=True unconditionally regardless of quantity. For that branch specifically, only qty is checked (not received_qty): with no linked Item there's no accepted/rejected split, so received_qty carries no independent meaning and a qty=0 row must be rejected regardless of its value. (cherry picked from commit b63066ed4497a68bb6ca8ced6124caf4247e0f7c) --- erpnext/controllers/sales_and_purchase_return.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index c58580739e3..0def40d024e 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -158,10 +158,21 @@ def validate_returned_items(doc): ): frappe.throw(_("Warehouse is mandatory")) - items_returned = True + if doc.doctype in ("Purchase Invoice", "Purchase Receipt", "Subcontracting Receipt"): + if flt(d.qty) < 0 or flt(d.get("received_qty")) < 0: + items_returned = True + else: + items_returned = True elif d.item_name: - items_returned = True + if doc.doctype in ("Purchase Invoice", "Purchase Receipt", "Subcontracting Receipt"): + # No item_code here means no linked Item, so there's no accepted/rejected + # split to speak of - received_qty isn't a meaningful independent signal. + # Only a negative qty (i.e. a real negative billing amount) counts. + if flt(d.qty) < 0: + items_returned = True + else: + items_returned = True if not items_returned: frappe.throw(_("Atleast one item should be entered with negative quantity in return document"))