mirror of
https://github.com/frappe/erpnext.git
synced 2026-02-15 15:45:01 +00:00
fix(invoice):validate return invoice qty (#46451)
* fix(invoice): validate return quantity when update stock is unchecked
* test: add unit test for validating fully returned invoice quantity
(cherry picked from commit ba96c86576)
Co-authored-by: Bhavansathru <122002510+Bhavan23@users.noreply.github.com>
This commit is contained in:
@@ -2669,6 +2669,20 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
|
||||
)
|
||||
self.assertEqual(actual, expected)
|
||||
|
||||
def test_prevents_fully_returned_invoice_with_zero_quantity(self):
|
||||
from erpnext.controllers.sales_and_purchase_return import StockOverReturnError, make_return_doc
|
||||
|
||||
invoice = make_purchase_invoice(qty=10)
|
||||
|
||||
return_doc = make_return_doc(invoice.doctype, invoice.name)
|
||||
return_doc.items[0].qty = -10
|
||||
return_doc.save().submit()
|
||||
|
||||
return_doc = make_return_doc(invoice.doctype, invoice.name)
|
||||
return_doc.items[0].qty = 0
|
||||
|
||||
self.assertRaises(StockOverReturnError, return_doc.save)
|
||||
|
||||
|
||||
def set_advance_flag(company, flag, default_account):
|
||||
frappe.db.set_value(
|
||||
|
||||
@@ -4295,6 +4295,20 @@ class TestSalesInvoice(FrappeTestCase):
|
||||
pos_return = make_sales_return(pos.name)
|
||||
self.assertEqual(abs(pos_return.payments[0].amount), pos.payments[0].amount)
|
||||
|
||||
def test_prevents_fully_returned_invoice_with_zero_quantity(self):
|
||||
from erpnext.controllers.sales_and_purchase_return import StockOverReturnError, make_return_doc
|
||||
|
||||
invoice = create_sales_invoice(qty=10)
|
||||
|
||||
return_doc = make_return_doc(invoice.doctype, invoice.name)
|
||||
return_doc.items[0].qty = -10
|
||||
return_doc.save().submit()
|
||||
|
||||
return_doc = make_return_doc(invoice.doctype, invoice.name)
|
||||
return_doc.items[0].qty = 0
|
||||
|
||||
self.assertRaises(StockOverReturnError, return_doc.save)
|
||||
|
||||
|
||||
def set_advance_flag(company, flag, default_account):
|
||||
frappe.db.set_value(
|
||||
|
||||
@@ -25,9 +25,6 @@ def validate_return(doc):
|
||||
if doc.return_against:
|
||||
validate_return_against(doc)
|
||||
|
||||
if doc.doctype in ("Sales Invoice", "Purchase Invoice") and not doc.update_stock:
|
||||
return
|
||||
|
||||
validate_returned_items(doc)
|
||||
|
||||
|
||||
@@ -118,7 +115,7 @@ def validate_returned_items(doc):
|
||||
elif doc.doctype == "Delivery Note":
|
||||
key = (d.item_code, d.get("dn_detail"))
|
||||
|
||||
if d.item_code and (flt(d.qty) < 0 or flt(d.get("received_qty")) < 0):
|
||||
if d.item_code and (flt(d.qty) <= 0 or flt(d.get("received_qty")) <= 0):
|
||||
if key not in valid_items:
|
||||
frappe.msgprint(
|
||||
_("Row # {0}: Returned Item {1} does not exist in {2} {3}").format(
|
||||
@@ -160,6 +157,9 @@ def validate_returned_items(doc):
|
||||
|
||||
def validate_quantity(doc, key, args, ref, valid_items, already_returned_items):
|
||||
fields = ["stock_qty"]
|
||||
if (doc.doctype == "Purchase Invoice" or doc.doctype == "Sales Invoice") and not doc.update_stock:
|
||||
fields = ["qty"]
|
||||
|
||||
if doc.doctype in ["Purchase Receipt", "Purchase Invoice", "Subcontracting Receipt"]:
|
||||
if not args.get("return_qty_from_rejected_warehouse"):
|
||||
fields.extend(["received_qty", "rejected_qty"])
|
||||
@@ -169,13 +169,16 @@ def validate_quantity(doc, key, args, ref, valid_items, already_returned_items):
|
||||
already_returned_data = already_returned_items.get(key) or {}
|
||||
|
||||
company_currency = erpnext.get_company_currency(doc.company)
|
||||
stock_qty_precision = get_field_precision(
|
||||
frappe.get_meta(doc.doctype + " Item").get_field("stock_qty"), company_currency
|
||||
field_precision = get_field_precision(
|
||||
frappe.get_meta(doc.doctype + " Item").get_field(
|
||||
"stock_qty" if doc.get("update_stock", "") else "qty"
|
||||
),
|
||||
company_currency,
|
||||
)
|
||||
|
||||
for column in fields:
|
||||
returned_qty = (
|
||||
flt(already_returned_data.get(column, 0), stock_qty_precision)
|
||||
flt(already_returned_data.get(column, 0), field_precision)
|
||||
if len(already_returned_data) > 0
|
||||
else 0
|
||||
)
|
||||
@@ -190,17 +193,17 @@ def validate_quantity(doc, key, args, ref, valid_items, already_returned_items):
|
||||
reference_qty = ref.get(column) * ref.get("conversion_factor", 1.0)
|
||||
current_stock_qty = args.get(column) * args.get("conversion_factor", 1.0)
|
||||
|
||||
max_returnable_qty = flt(flt(reference_qty, stock_qty_precision) - returned_qty, stock_qty_precision)
|
||||
max_returnable_qty = flt(flt(reference_qty, field_precision) - returned_qty, field_precision)
|
||||
label = column.replace("_", " ").title()
|
||||
|
||||
if reference_qty:
|
||||
if flt(args.get(column)) > 0:
|
||||
frappe.throw(_("{0} must be negative in return document").format(label))
|
||||
elif returned_qty >= reference_qty and args.get(column):
|
||||
elif returned_qty >= reference_qty and args.get(column) >= 0:
|
||||
frappe.throw(
|
||||
_("Item {0} has already been returned").format(args.item_code), StockOverReturnError
|
||||
)
|
||||
elif abs(flt(current_stock_qty, stock_qty_precision)) > max_returnable_qty:
|
||||
elif abs(flt(current_stock_qty, field_precision)) > max_returnable_qty:
|
||||
frappe.throw(
|
||||
_("Row # {0}: Cannot return more than {1} for Item {2}").format(
|
||||
args.idx, max_returnable_qty, args.item_code
|
||||
|
||||
Reference in New Issue
Block a user