mirror of
https://github.com/frappe/erpnext.git
synced 2026-03-21 07:52:13 +00:00
fix: sales / prchase return validation issue
(cherry picked from commit 59dc4a96e1)
This commit is contained in:
committed by
Mergify
parent
f2ecc1d2d5
commit
7a21997701
@@ -461,6 +461,98 @@ class TestDeliveryNote(FrappeTestCase):
|
|||||||
|
|
||||||
self.assertEqual(return_dn.items[0].incoming_rate, 150)
|
self.assertEqual(return_dn.items[0].incoming_rate, 150)
|
||||||
|
|
||||||
|
def test_sales_return_against_serial_batch_bundle(self):
|
||||||
|
frappe.db.set_single_value(
|
||||||
|
"Stock Settings", "do_not_update_serial_batch_on_creation_of_auto_bundle", 1
|
||||||
|
)
|
||||||
|
|
||||||
|
batch_item = make_item(
|
||||||
|
"Test Sales Return Against Batch Item",
|
||||||
|
properties={
|
||||||
|
"has_batch_no": 1,
|
||||||
|
"is_stock_item": 1,
|
||||||
|
"create_new_batch": 1,
|
||||||
|
"batch_number_series": "BATCH-TSRABII.#####",
|
||||||
|
},
|
||||||
|
).name
|
||||||
|
|
||||||
|
serial_item = make_item(
|
||||||
|
"Test Sales Return Against Serial NO Item",
|
||||||
|
properties={
|
||||||
|
"has_serial_no": 1,
|
||||||
|
"is_stock_item": 1,
|
||||||
|
"serial_no_series": "SN-TSRABII.#####",
|
||||||
|
},
|
||||||
|
).name
|
||||||
|
|
||||||
|
make_stock_entry(item_code=batch_item, target="_Test Warehouse - _TC", qty=5, basic_rate=100)
|
||||||
|
make_stock_entry(item_code=serial_item, target="_Test Warehouse - _TC", qty=5, basic_rate=100)
|
||||||
|
|
||||||
|
dn = create_delivery_note(
|
||||||
|
item_code=batch_item,
|
||||||
|
qty=5,
|
||||||
|
rate=500,
|
||||||
|
warehouse="_Test Warehouse - _TC",
|
||||||
|
expense_account="Cost of Goods Sold - _TC",
|
||||||
|
cost_center="Main - _TC",
|
||||||
|
use_serial_batch_fields=0,
|
||||||
|
do_not_submit=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
dn.append(
|
||||||
|
"items",
|
||||||
|
{
|
||||||
|
"item_code": serial_item,
|
||||||
|
"qty": 5,
|
||||||
|
"rate": 500,
|
||||||
|
"warehouse": "_Test Warehouse - _TC",
|
||||||
|
"expense_account": "Cost of Goods Sold - _TC",
|
||||||
|
"cost_center": "Main - _TC",
|
||||||
|
"use_serial_batch_fields": 0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
dn.save()
|
||||||
|
for row in dn.items:
|
||||||
|
self.assertFalse(row.use_serial_batch_fields)
|
||||||
|
|
||||||
|
dn.submit()
|
||||||
|
dn.reload()
|
||||||
|
for row in dn.items:
|
||||||
|
self.assertTrue(row.serial_and_batch_bundle)
|
||||||
|
self.assertFalse(row.use_serial_batch_fields)
|
||||||
|
self.assertFalse(row.serial_no)
|
||||||
|
self.assertFalse(row.batch_no)
|
||||||
|
|
||||||
|
from erpnext.controllers.sales_and_purchase_return import make_return_doc
|
||||||
|
|
||||||
|
return_dn = make_return_doc(dn.doctype, dn.name)
|
||||||
|
for row in return_dn.items:
|
||||||
|
row.qty = -2
|
||||||
|
row.use_serial_batch_fields = 0
|
||||||
|
return_dn.save().submit()
|
||||||
|
|
||||||
|
for row in return_dn.items:
|
||||||
|
total_qty = frappe.db.get_value(
|
||||||
|
"Serial and Batch Bundle", row.serial_and_batch_bundle, "total_qty"
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(total_qty, 2)
|
||||||
|
|
||||||
|
doc = frappe.get_doc("Serial and Batch Bundle", row.serial_and_batch_bundle)
|
||||||
|
if doc.has_serial_no:
|
||||||
|
self.assertEqual(len(doc.entries), 2)
|
||||||
|
|
||||||
|
for entry in doc.entries:
|
||||||
|
if doc.has_batch_no:
|
||||||
|
self.assertEqual(entry.qty, 2)
|
||||||
|
else:
|
||||||
|
self.assertEqual(entry.qty, 1)
|
||||||
|
|
||||||
|
frappe.db.set_single_value(
|
||||||
|
"Stock Settings", "do_not_update_serial_batch_on_creation_of_auto_bundle", 0
|
||||||
|
)
|
||||||
|
|
||||||
def test_return_single_item_from_bundled_items(self):
|
def test_return_single_item_from_bundled_items(self):
|
||||||
company = frappe.db.get_value("Warehouse", "Stores - TCP1", "company")
|
company = frappe.db.get_value("Warehouse", "Stores - TCP1", "company")
|
||||||
|
|
||||||
|
|||||||
@@ -384,6 +384,9 @@ class SerialandBatchBundle(Document):
|
|||||||
if self.docstatus == 0:
|
if self.docstatus == 0:
|
||||||
self.set_incoming_rate(save=True, row=row)
|
self.set_incoming_rate(save=True, row=row)
|
||||||
|
|
||||||
|
if self.docstatus == 0 and parent.get("is_return") and parent.is_new():
|
||||||
|
self.reset_qty(row, qty_field=qty_field)
|
||||||
|
|
||||||
self.calculate_qty_and_amount(save=True)
|
self.calculate_qty_and_amount(save=True)
|
||||||
self.validate_quantity(row, qty_field=qty_field)
|
self.validate_quantity(row, qty_field=qty_field)
|
||||||
self.set_warranty_expiry_date()
|
self.set_warranty_expiry_date()
|
||||||
@@ -417,7 +420,11 @@ class SerialandBatchBundle(Document):
|
|||||||
if not (self.voucher_type and self.voucher_no):
|
if not (self.voucher_type and self.voucher_no):
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.voucher_no and not frappe.db.exists(self.voucher_type, self.voucher_no):
|
if (
|
||||||
|
self.docstatus == 1
|
||||||
|
and self.voucher_no
|
||||||
|
and not frappe.db.exists(self.voucher_type, self.voucher_no)
|
||||||
|
):
|
||||||
self.throw_error_message(f"The {self.voucher_type} # {self.voucher_no} does not exist")
|
self.throw_error_message(f"The {self.voucher_type} # {self.voucher_no} does not exist")
|
||||||
|
|
||||||
if self.flags.ignore_voucher_validation:
|
if self.flags.ignore_voucher_validation:
|
||||||
@@ -481,24 +488,57 @@ class SerialandBatchBundle(Document):
|
|||||||
|
|
||||||
frappe.throw(_(msg), title=_(title), exc=SerialNoExistsInFutureTransactionError)
|
frappe.throw(_(msg), title=_(title), exc=SerialNoExistsInFutureTransactionError)
|
||||||
|
|
||||||
|
def reset_qty(self, row, qty_field=None):
|
||||||
|
qty_field = self.get_qty_field(row, qty_field=qty_field)
|
||||||
|
qty = abs(row.get(qty_field))
|
||||||
|
|
||||||
|
idx = None
|
||||||
|
while qty > 0:
|
||||||
|
for d in self.entries:
|
||||||
|
row_qty = abs(d.qty)
|
||||||
|
if row_qty >= qty:
|
||||||
|
d.db_set("qty", qty if self.type_of_transaction == "Inward" else qty * -1)
|
||||||
|
qty = 0
|
||||||
|
idx = d.idx
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
qty -= row_qty
|
||||||
|
idx = d.idx
|
||||||
|
|
||||||
|
if idx and len(self.entries) > idx:
|
||||||
|
remove_rows = []
|
||||||
|
for d in self.entries:
|
||||||
|
if d.idx > idx:
|
||||||
|
remove_rows.append(d)
|
||||||
|
|
||||||
|
for d in remove_rows:
|
||||||
|
self.entries.remove(d)
|
||||||
|
|
||||||
|
self.flags.ignore_links = True
|
||||||
|
self.save()
|
||||||
|
|
||||||
def validate_quantity(self, row, qty_field=None):
|
def validate_quantity(self, row, qty_field=None):
|
||||||
|
qty_field = self.get_qty_field(row, qty_field=qty_field)
|
||||||
|
qty = row.get(qty_field)
|
||||||
|
if qty_field == "qty" and row.get("stock_qty"):
|
||||||
|
qty = row.get("stock_qty")
|
||||||
|
|
||||||
|
precision = row.precision
|
||||||
|
if abs(abs(flt(self.total_qty, precision)) - abs(flt(qty, precision))) > 0.01:
|
||||||
|
self.throw_error_message(
|
||||||
|
f"Total quantity {abs(flt(self.total_qty))} in the Serial and Batch Bundle {bold(self.name)} does not match with the quantity {abs(flt(row.get(qty_field)))} for the Item {bold(self.item_code)} in the {self.voucher_type} # {self.voucher_no}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_qty_field(self, row, qty_field=None) -> str:
|
||||||
if not qty_field:
|
if not qty_field:
|
||||||
qty_field = "qty"
|
qty_field = "qty"
|
||||||
|
|
||||||
precision = row.precision
|
|
||||||
if row.get("doctype") == "Subcontracting Receipt Supplied Item":
|
if row.get("doctype") == "Subcontracting Receipt Supplied Item":
|
||||||
qty_field = "consumed_qty"
|
qty_field = "consumed_qty"
|
||||||
elif row.get("doctype") == "Stock Entry Detail":
|
elif row.get("doctype") == "Stock Entry Detail":
|
||||||
qty_field = "transfer_qty"
|
qty_field = "transfer_qty"
|
||||||
|
|
||||||
qty = row.get(qty_field)
|
return qty_field
|
||||||
if qty_field == "qty" and row.get("stock_qty"):
|
|
||||||
qty = row.get("stock_qty")
|
|
||||||
|
|
||||||
if abs(abs(flt(self.total_qty, precision)) - abs(flt(qty, precision))) > 0.01:
|
|
||||||
self.throw_error_message(
|
|
||||||
f"Total quantity {abs(flt(self.total_qty))} in the Serial and Batch Bundle {bold(self.name)} does not match with the quantity {abs(flt(row.get(qty_field)))} for the Item {bold(self.item_code)} in the {self.voucher_type} # {self.voucher_no}"
|
|
||||||
)
|
|
||||||
|
|
||||||
def set_is_outward(self):
|
def set_is_outward(self):
|
||||||
for row in self.entries:
|
for row in self.entries:
|
||||||
|
|||||||
Reference in New Issue
Block a user