Skip to content

Commit 33273fa

Browse files
fix: validate returned serial nos and batches (backport frappe#44669) (frappe#44674)
fix: validate returned serial nos and batches (frappe#44669) (cherry picked from commit 4385349) Co-authored-by: rohitwaghchaure <[email protected]>
1 parent bd9c84d commit 33273fa

File tree

2 files changed

+90
-9
lines changed

2 files changed

+90
-9
lines changed

erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py

+63
Original file line numberDiff line numberDiff line change
@@ -3984,6 +3984,69 @@ def test_do_not_allow_to_inward_same_serial_no_multiple_times(self):
39843984

39853985
frappe.db.set_single_value("Stock Settings", "allow_existing_serial_no", 1)
39863986

3987+
def test_seral_no_return_validation(self):
3988+
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import (
3989+
make_purchase_return,
3990+
)
3991+
3992+
sn_item_code = make_item(
3993+
"Test Serial No for Validation", {"has_serial_no": 1, "serial_no_series": "SN-TSNFVAL-.#####"}
3994+
).name
3995+
3996+
pr1 = make_purchase_receipt(item_code=sn_item_code, qty=5, rate=100, use_serial_batch_fields=1)
3997+
pr1_serial_nos = get_serial_nos_from_bundle(pr1.items[0].serial_and_batch_bundle)
3998+
3999+
serial_no_pr = make_purchase_receipt(
4000+
item_code=sn_item_code, qty=5, rate=100, use_serial_batch_fields=1
4001+
)
4002+
serial_no_pr_serial_nos = get_serial_nos_from_bundle(serial_no_pr.items[0].serial_and_batch_bundle)
4003+
4004+
sn_return = make_purchase_return(serial_no_pr.name)
4005+
sn_return.items[0].qty = -1
4006+
sn_return.items[0].received_qty = -1
4007+
sn_return.items[0].serial_no = pr1_serial_nos[0]
4008+
sn_return.save()
4009+
self.assertRaises(frappe.ValidationError, sn_return.submit)
4010+
4011+
sn_return = make_purchase_return(serial_no_pr.name)
4012+
sn_return.items[0].qty = -1
4013+
sn_return.items[0].received_qty = -1
4014+
sn_return.items[0].serial_no = serial_no_pr_serial_nos[0]
4015+
sn_return.save()
4016+
sn_return.submit()
4017+
4018+
def test_batch_no_return_validation(self):
4019+
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import (
4020+
make_purchase_return,
4021+
)
4022+
4023+
batch_item_code = make_item(
4024+
"Test Batch No for Validation",
4025+
{"has_batch_no": 1, "batch_number_series": "BT-TSNFVAL-.#####", "create_new_batch": 1},
4026+
).name
4027+
4028+
pr1 = make_purchase_receipt(item_code=batch_item_code, qty=5, rate=100, use_serial_batch_fields=1)
4029+
batch_no = get_batch_from_bundle(pr1.items[0].serial_and_batch_bundle)
4030+
4031+
batch_no_pr = make_purchase_receipt(
4032+
item_code=batch_item_code, qty=5, rate=100, use_serial_batch_fields=1
4033+
)
4034+
original_batch_no = get_batch_from_bundle(batch_no_pr.items[0].serial_and_batch_bundle)
4035+
4036+
batch_return = make_purchase_return(batch_no_pr.name)
4037+
batch_return.items[0].qty = -1
4038+
batch_return.items[0].received_qty = -1
4039+
batch_return.items[0].batch_no = batch_no
4040+
batch_return.save()
4041+
self.assertRaises(frappe.ValidationError, batch_return.submit)
4042+
4043+
batch_return = make_purchase_return(batch_no_pr.name)
4044+
batch_return.items[0].qty = -1
4045+
batch_return.items[0].received_qty = -1
4046+
batch_return.items[0].batch_no = original_batch_no
4047+
batch_return.save()
4048+
batch_return.submit()
4049+
39874050

39884051
def prepare_data_for_internal_transfer():
39894052
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier

erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py

+27-9
Original file line numberDiff line numberDiff line change
@@ -252,18 +252,21 @@ def set_incoming_rate(self, parent=None, row=None, save=False, allow_negative_st
252252
]:
253253
return
254254

255-
if return_aginst := self.get_return_aginst(parent=parent):
256-
self.set_valuation_rate_for_return_entry(return_aginst, save)
255+
if return_against := self.get_return_against(parent=parent):
256+
self.set_valuation_rate_for_return_entry(return_against, save)
257257
elif self.type_of_transaction == "Outward":
258258
self.set_incoming_rate_for_outward_transaction(
259259
row, save, allow_negative_stock=allow_negative_stock
260260
)
261261
else:
262262
self.set_incoming_rate_for_inward_transaction(row, save)
263263

264-
def set_valuation_rate_for_return_entry(self, return_aginst, save=False):
265-
if valuation_details := self.get_valuation_rate_for_return_entry(return_aginst):
264+
def set_valuation_rate_for_return_entry(self, return_against, save=False):
265+
if valuation_details := self.get_valuation_rate_for_return_entry(return_against):
266266
for row in self.entries:
267+
if valuation_details:
268+
self.validate_returned_serial_batch_no(return_against, row, valuation_details)
269+
267270
if row.serial_no:
268271
valuation_rate = valuation_details["serial_nos"].get(row.serial_no)
269272
else:
@@ -280,7 +283,22 @@ def set_valuation_rate_for_return_entry(self, return_aginst, save=False):
280283
}
281284
)
282285

283-
def get_valuation_rate_for_return_entry(self, return_aginst):
286+
def validate_returned_serial_batch_no(self, return_against, row, original_inv_details):
287+
if row.serial_no and row.serial_no not in original_inv_details["serial_nos"]:
288+
self.throw_error_message(
289+
_(
290+
"Serial No {0} is not present in the {1} {2}, hence you can't return it against the {1} {2}"
291+
).format(bold(row.serial_no), self.voucher_type, bold(return_against))
292+
)
293+
294+
if row.batch_no and row.batch_no not in original_inv_details["batches"]:
295+
self.throw_error_message(
296+
_(
297+
"Batch No {0} is not present in the original {1} {2}, hence you can't return it against the {1} {2}"
298+
).format(bold(row.batch_no), self.voucher_type, bold(return_against))
299+
)
300+
301+
def get_valuation_rate_for_return_entry(self, return_against):
284302
valuation_details = frappe._dict(
285303
{
286304
"serial_nos": defaultdict(float),
@@ -296,7 +314,7 @@ def get_valuation_rate_for_return_entry(self, return_aginst):
296314
"`tabSerial and Batch Entry`.`incoming_rate`",
297315
],
298316
filters=[
299-
["Serial and Batch Bundle", "voucher_no", "=", return_aginst],
317+
["Serial and Batch Bundle", "voucher_no", "=", return_against],
300318
["Serial and Batch Entry", "docstatus", "=", 1],
301319
["Serial and Batch Bundle", "is_cancelled", "=", 0],
302320
["Serial and Batch Bundle", "item_code", "=", self.item_code],
@@ -430,8 +448,8 @@ def get_sle_for_outward_transaction(self):
430448

431449
return sle
432450

433-
def get_return_aginst(self, parent=None):
434-
return_aginst = None
451+
def get_return_against(self, parent=None):
452+
return_against = None
435453

436454
if parent and parent.get("is_return") and parent.get("return_against"):
437455
return parent.get("return_against")
@@ -455,7 +473,7 @@ def get_return_aginst(self, parent=None):
455473
if voucher_details and voucher_details.get("is_return") and voucher_details.get("return_against"):
456474
return voucher_details.get("return_against")
457475

458-
return return_aginst
476+
return return_against
459477

460478
def set_incoming_rate_for_inward_transaction(self, row=None, save=False):
461479
valuation_field = "valuation_rate"

0 commit comments

Comments
 (0)