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 b63066ed44)
This commit is contained in:
R-Jayaraman
2026-07-31 13:31:01 +05:30
committed by Mergify
parent 0f6c6d4df7
commit 8676add875

View File

@@ -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"))