mirror of
https://github.com/frappe/erpnext.git
synced 2026-09-14 01:20:41 +00:00
Merge pull request #57645 from aerele/fix/purchase-zero-qty-return-validation
fix(purchase): reject purchase returns where every item has zero quan…
This commit is contained in:
@@ -160,10 +160,21 @@ def validate_returned_items(doc):
|
|||||||
):
|
):
|
||||||
frappe.throw(_("Warehouse is mandatory"))
|
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:
|
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:
|
if not items_returned:
|
||||||
frappe.throw(_("At least one item should be entered with negative quantity in return document"))
|
frappe.throw(_("At least one item should be entered with negative quantity in return document"))
|
||||||
|
|||||||
@@ -37,3 +37,44 @@ class TestSalesAndPurchaseReturn(ERPNextTestSuite):
|
|||||||
|
|
||||||
self.assertEqual(return_dn.is_return, 1)
|
self.assertEqual(return_dn.is_return, 1)
|
||||||
self.assertEqual(return_dn.items[0].qty, -5)
|
self.assertEqual(return_dn.items[0].qty, -5)
|
||||||
|
|
||||||
|
def test_purchase_invoice_zero_qty_return_is_rejected(self):
|
||||||
|
# A return with every item at qty 0 moves no stock and no value, so it must be
|
||||||
|
# rejected the same way a return with no items at all would be.
|
||||||
|
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
|
||||||
|
|
||||||
|
pi = make_purchase_invoice(qty=10)
|
||||||
|
self.addCleanup(self._cancel_and_delete, "Purchase Invoice", pi.name)
|
||||||
|
|
||||||
|
return_pi = make_purchase_invoice(
|
||||||
|
is_return=1,
|
||||||
|
return_against=pi.name,
|
||||||
|
qty=0,
|
||||||
|
do_not_save=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertRaises(frappe.ValidationError, return_pi.save)
|
||||||
|
|
||||||
|
def test_purchase_invoice_item_name_only_zero_qty_return_is_rejected(self):
|
||||||
|
# Item Code is not mandatory on Purchase Invoice Item - a row can have only an
|
||||||
|
# item_name (e.g. a free-text/non-stock line). Such rows fall through to the
|
||||||
|
# item_name-only branch, which must also reject an all-zero-qty return instead
|
||||||
|
# of unconditionally treating the row as returned.
|
||||||
|
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
|
||||||
|
|
||||||
|
pi = make_purchase_invoice(item_name="_Test Item", qty=10, do_not_submit=True)
|
||||||
|
pi.items[0].item_code = ""
|
||||||
|
pi.save()
|
||||||
|
pi.submit()
|
||||||
|
self.addCleanup(self._cancel_and_delete, "Purchase Invoice", pi.name)
|
||||||
|
|
||||||
|
return_pi = make_purchase_invoice(
|
||||||
|
item_name="_Test Item",
|
||||||
|
is_return=1,
|
||||||
|
return_against=pi.name,
|
||||||
|
qty=0,
|
||||||
|
do_not_save=True,
|
||||||
|
)
|
||||||
|
return_pi.items[0].item_code = ""
|
||||||
|
|
||||||
|
self.assertRaises(frappe.ValidationError, return_pi.save)
|
||||||
|
|||||||
Reference in New Issue
Block a user