fix: use serial/batch field for rejected items (#40327)
This commit is contained in:
@@ -2108,6 +2108,92 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
|
||||
return_pi.submit()
|
||||
self.assertEqual(return_pi.docstatus, 1)
|
||||
|
||||
def test_purchase_invoice_with_use_serial_batch_field_for_rejected_qty(self):
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
||||
|
||||
batch_item = make_item(
|
||||
"_Test Purchase Invoice Batch Item For Rejected Qty",
|
||||
properties={"has_batch_no": 1, "create_new_batch": 1, "is_stock_item": 1},
|
||||
).name
|
||||
|
||||
serial_item = make_item(
|
||||
"_Test Purchase Invoice Serial Item for Rejected Qty",
|
||||
properties={"has_serial_no": 1, "is_stock_item": 1},
|
||||
).name
|
||||
|
||||
rej_warehouse = create_warehouse("_Test Purchase INV Warehouse For Rejected Qty")
|
||||
|
||||
batch_no = "BATCH-PI-BNU-TPRBI-0001"
|
||||
serial_nos = ["SNU-PI-TPRSI-0001", "SNU-PI-TPRSI-0002", "SNU-PI-TPRSI-0003"]
|
||||
|
||||
if not frappe.db.exists("Batch", batch_no):
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "Batch",
|
||||
"batch_id": batch_no,
|
||||
"item": batch_item,
|
||||
}
|
||||
).insert()
|
||||
|
||||
for serial_no in serial_nos:
|
||||
if not frappe.db.exists("Serial No", serial_no):
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "Serial No",
|
||||
"item_code": serial_item,
|
||||
"serial_no": serial_no,
|
||||
}
|
||||
).insert()
|
||||
|
||||
pi = make_purchase_invoice(
|
||||
item_code=batch_item,
|
||||
received_qty=10,
|
||||
qty=8,
|
||||
rejected_qty=2,
|
||||
update_stock=1,
|
||||
rejected_warehouse=rej_warehouse,
|
||||
use_serial_batch_fields=1,
|
||||
batch_no=batch_no,
|
||||
rate=100,
|
||||
do_not_submit=1,
|
||||
)
|
||||
|
||||
pi.append(
|
||||
"items",
|
||||
{
|
||||
"item_code": serial_item,
|
||||
"qty": 2,
|
||||
"rate": 100,
|
||||
"base_rate": 100,
|
||||
"item_name": serial_item,
|
||||
"uom": "Nos",
|
||||
"stock_uom": "Nos",
|
||||
"conversion_factor": 1,
|
||||
"rejected_qty": 1,
|
||||
"warehouse": pi.items[0].warehouse,
|
||||
"rejected_warehouse": rej_warehouse,
|
||||
"use_serial_batch_fields": 1,
|
||||
"serial_no": "\n".join(serial_nos[:2]),
|
||||
"rejected_serial_no": serial_nos[2],
|
||||
},
|
||||
)
|
||||
|
||||
pi.save()
|
||||
pi.submit()
|
||||
|
||||
pi.reload()
|
||||
|
||||
for row in pi.items:
|
||||
self.assertTrue(row.serial_and_batch_bundle)
|
||||
self.assertTrue(row.rejected_serial_and_batch_bundle)
|
||||
|
||||
if row.item_code == batch_item:
|
||||
self.assertEqual(row.batch_no, batch_no)
|
||||
else:
|
||||
self.assertEqual(row.serial_no, "\n".join(serial_nos[:2]))
|
||||
self.assertEqual(row.rejected_serial_no, serial_nos[2])
|
||||
|
||||
|
||||
def set_advance_flag(company, flag, default_account):
|
||||
frappe.db.set_value(
|
||||
@@ -2215,7 +2301,7 @@ def make_purchase_invoice(**args):
|
||||
pi.cost_center = args.parent_cost_center
|
||||
|
||||
bundle_id = None
|
||||
if args.get("batch_no") or args.get("serial_no"):
|
||||
if not args.use_serial_batch_fields and ((args.get("batch_no") or args.get("serial_no"))):
|
||||
batches = {}
|
||||
qty = args.qty if args.qty is not None else 5
|
||||
item_code = args.item or args.item_code or "_Test Item"
|
||||
@@ -2262,6 +2348,9 @@ def make_purchase_invoice(**args):
|
||||
"rejected_warehouse": args.rejected_warehouse or "",
|
||||
"asset_location": args.location or "",
|
||||
"allow_zero_valuation_rate": args.get("allow_zero_valuation_rate") or 0,
|
||||
"use_serial_batch_fields": args.get("use_serial_batch_fields") or 0,
|
||||
"batch_no": args.get("batch_no") if args.get("use_serial_batch_fields") else "",
|
||||
"serial_no": args.get("serial_no") if args.get("use_serial_batch_fields") else "",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -159,9 +159,6 @@ class StockController(AccountsController):
|
||||
row.serial_no = clean_serial_no_string(row.serial_no)
|
||||
|
||||
def make_bundle_using_old_serial_batch_fields(self, table_name=None):
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||
from erpnext.stock.serial_batch_bundle import SerialBatchCreation
|
||||
|
||||
if self.get("_action") == "update_after_submit":
|
||||
return
|
||||
|
||||
@@ -199,31 +196,21 @@ class StockController(AccountsController):
|
||||
"voucher_detail_no": row.name,
|
||||
"company": self.company,
|
||||
"is_rejected": 1 if row.get("rejected_warehouse") else 0,
|
||||
"serial_nos": get_serial_nos(row.serial_no) if row.serial_no else None,
|
||||
"batch_no": row.batch_no,
|
||||
"use_serial_batch_fields": row.use_serial_batch_fields,
|
||||
"do_not_submit": True,
|
||||
}
|
||||
|
||||
self.update_bundle_details(bundle_details, table_name, row)
|
||||
sn_doc = SerialBatchCreation(bundle_details).make_serial_and_batch_bundle()
|
||||
if row.qty:
|
||||
self.update_bundle_details(bundle_details, table_name, row)
|
||||
self.create_serial_batch_bundle(bundle_details, row)
|
||||
|
||||
if sn_doc.is_rejected:
|
||||
row.rejected_serial_and_batch_bundle = sn_doc.name
|
||||
row.db_set(
|
||||
{
|
||||
"rejected_serial_and_batch_bundle": sn_doc.name,
|
||||
}
|
||||
)
|
||||
else:
|
||||
row.serial_and_batch_bundle = sn_doc.name
|
||||
row.db_set(
|
||||
{
|
||||
"serial_and_batch_bundle": sn_doc.name,
|
||||
}
|
||||
)
|
||||
if row.get("rejected_qty"):
|
||||
self.update_bundle_details(bundle_details, table_name, row, is_rejected=True)
|
||||
self.create_serial_batch_bundle(bundle_details, row)
|
||||
|
||||
def update_bundle_details(self, bundle_details, table_name, row, is_rejected=False):
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||
|
||||
def update_bundle_details(self, bundle_details, table_name, row):
|
||||
# Since qty field is different for different doctypes
|
||||
qty = row.get("qty")
|
||||
warehouse = row.get("warehouse")
|
||||
@@ -242,15 +229,37 @@ class StockController(AccountsController):
|
||||
qty = row.transfer_qty
|
||||
warehouse = row.s_warehouse or row.t_warehouse
|
||||
|
||||
serial_nos = row.serial_no
|
||||
if is_rejected:
|
||||
serial_nos = row.get("rejected_serial_no")
|
||||
type_of_transaction = "Inward" if not self.is_return else "Outward"
|
||||
qty = row.get("rejected_qty")
|
||||
warehouse = row.get("rejected_warehouse")
|
||||
|
||||
bundle_details.update(
|
||||
{
|
||||
"qty": qty,
|
||||
"is_rejected": is_rejected,
|
||||
"type_of_transaction": type_of_transaction,
|
||||
"warehouse": warehouse,
|
||||
"batches": frappe._dict({row.batch_no: qty}) if row.batch_no else None,
|
||||
"serial_nos": get_serial_nos(serial_nos) if serial_nos else None,
|
||||
"batch_no": row.batch_no,
|
||||
}
|
||||
)
|
||||
|
||||
def create_serial_batch_bundle(self, bundle_details, row):
|
||||
from erpnext.stock.serial_batch_bundle import SerialBatchCreation
|
||||
|
||||
sn_doc = SerialBatchCreation(bundle_details).make_serial_and_batch_bundle()
|
||||
|
||||
field = "serial_and_batch_bundle"
|
||||
if bundle_details.get("is_rejected"):
|
||||
field = "rejected_serial_and_batch_bundle"
|
||||
|
||||
row.set(field, sn_doc.name)
|
||||
row.db_set({field: sn_doc.name})
|
||||
|
||||
def validate_serial_nos_and_batches_with_bundle(self, row):
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||
|
||||
|
||||
@@ -859,6 +859,7 @@ class SubcontractingController(StockController):
|
||||
item,
|
||||
{
|
||||
"warehouse": item.rejected_warehouse,
|
||||
"serial_and_batch_bundle": item.get("rejected_serial_and_batch_bundle"),
|
||||
"actual_qty": flt(item.rejected_qty) * flt(item.conversion_factor),
|
||||
"incoming_rate": 0.0,
|
||||
},
|
||||
|
||||
@@ -2477,6 +2477,88 @@ class TestPurchaseReceipt(FrappeTestCase):
|
||||
pr.reload()
|
||||
self.assertEqual(pr.per_billed, 100)
|
||||
|
||||
def test_purchase_receipt_with_use_serial_batch_field_for_rejected_qty(self):
|
||||
batch_item = make_item(
|
||||
"_Test Purchase Receipt Batch Item For Rejected Qty",
|
||||
properties={"has_batch_no": 1, "create_new_batch": 1, "is_stock_item": 1},
|
||||
).name
|
||||
|
||||
serial_item = make_item(
|
||||
"_Test Purchase Receipt Serial Item for Rejected Qty",
|
||||
properties={"has_serial_no": 1, "is_stock_item": 1},
|
||||
).name
|
||||
|
||||
rej_warehouse = create_warehouse("_Test Purchase Warehouse For Rejected Qty")
|
||||
|
||||
batch_no = "BATCH-BNU-TPRBI-0001"
|
||||
serial_nos = ["SNU-TPRSI-0001", "SNU-TPRSI-0002", "SNU-TPRSI-0003"]
|
||||
|
||||
if not frappe.db.exists("Batch", batch_no):
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "Batch",
|
||||
"batch_id": batch_no,
|
||||
"item": batch_item,
|
||||
}
|
||||
).insert()
|
||||
|
||||
for serial_no in serial_nos:
|
||||
if not frappe.db.exists("Serial No", serial_no):
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "Serial No",
|
||||
"item_code": serial_item,
|
||||
"serial_no": serial_no,
|
||||
}
|
||||
).insert()
|
||||
|
||||
pr = make_purchase_receipt(
|
||||
item_code=batch_item,
|
||||
received_qty=10,
|
||||
qty=8,
|
||||
rejected_qty=2,
|
||||
rejected_warehouse=rej_warehouse,
|
||||
use_serial_batch_fields=1,
|
||||
batch_no=batch_no,
|
||||
rate=100,
|
||||
do_not_submit=1,
|
||||
)
|
||||
|
||||
pr.append(
|
||||
"items",
|
||||
{
|
||||
"item_code": serial_item,
|
||||
"qty": 2,
|
||||
"rate": 100,
|
||||
"base_rate": 100,
|
||||
"item_name": serial_item,
|
||||
"uom": "Nos",
|
||||
"stock_uom": "Nos",
|
||||
"conversion_factor": 1,
|
||||
"rejected_qty": 1,
|
||||
"warehouse": pr.items[0].warehouse,
|
||||
"rejected_warehouse": rej_warehouse,
|
||||
"use_serial_batch_fields": 1,
|
||||
"serial_no": "\n".join(serial_nos[:2]),
|
||||
"rejected_serial_no": serial_nos[2],
|
||||
},
|
||||
)
|
||||
|
||||
pr.save()
|
||||
pr.submit()
|
||||
|
||||
pr.reload()
|
||||
|
||||
for row in pr.items:
|
||||
self.assertTrue(row.serial_and_batch_bundle)
|
||||
self.assertTrue(row.rejected_serial_and_batch_bundle)
|
||||
|
||||
if row.item_code == batch_item:
|
||||
self.assertEqual(row.batch_no, batch_no)
|
||||
else:
|
||||
self.assertEqual(row.serial_no, "\n".join(serial_nos[:2]))
|
||||
self.assertEqual(row.rejected_serial_no, serial_nos[2])
|
||||
|
||||
|
||||
def prepare_data_for_internal_transfer():
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier
|
||||
|
||||
@@ -893,6 +893,9 @@ class update_entries_after(object):
|
||||
query.run()
|
||||
|
||||
def calculate_valuation_for_serial_batch_bundle(self, sle):
|
||||
if not frappe.db.exists("Serial and Batch Bundle", sle.serial_and_batch_bundle):
|
||||
return
|
||||
|
||||
doc = frappe.get_cached_doc("Serial and Batch Bundle", sle.serial_and_batch_bundle)
|
||||
|
||||
doc.set_incoming_rate(save=True, allow_negative_stock=self.allow_negative_stock)
|
||||
|
||||
@@ -1132,6 +1132,86 @@ class TestSubcontractingReceipt(FrappeTestCase):
|
||||
scr.reload()
|
||||
self.assertTrue(scr.items[0].serial_and_batch_bundle)
|
||||
|
||||
def test_use_serial_batch_fields_for_subcontracting_receipt_with_rejected_qty(self):
|
||||
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
||||
|
||||
fg_item = make_item(
|
||||
"Test Subcontracted Item With Batch No for Rejected Qty",
|
||||
properties={
|
||||
"is_stock_item": 1,
|
||||
"has_batch_no": 1,
|
||||
"create_new_batch": 1,
|
||||
"batch_number_series": "BATCH-REJ-BNGS-.####",
|
||||
"is_sub_contracted_item": 1,
|
||||
},
|
||||
).name
|
||||
|
||||
make_item(
|
||||
"Test Subcontracted Item With Batch No Service Item 2",
|
||||
properties={"is_stock_item": 0},
|
||||
)
|
||||
|
||||
make_bom(
|
||||
item=fg_item,
|
||||
raw_materials=[
|
||||
make_item(
|
||||
"Test Subcontracted Item With Batch No RM Item 2",
|
||||
properties={
|
||||
"is_stock_item": 1,
|
||||
"has_batch_no": 1,
|
||||
"create_new_batch": 1,
|
||||
"batch_number_series": "BATCH-REJ-RM-BNGS-.####",
|
||||
},
|
||||
).name
|
||||
],
|
||||
)
|
||||
|
||||
service_items = [
|
||||
{
|
||||
"warehouse": "_Test Warehouse - _TC",
|
||||
"item_code": "Test Subcontracted Item With Batch No Service Item 2",
|
||||
"qty": 10,
|
||||
"rate": 100,
|
||||
"fg_item": fg_item,
|
||||
"fg_item_qty": 10,
|
||||
},
|
||||
]
|
||||
sco = get_subcontracting_order(service_items=service_items)
|
||||
rm_items = get_rm_items(sco.supplied_items)
|
||||
itemwise_details = make_stock_in_entry(rm_items=rm_items)
|
||||
make_stock_transfer_entry(
|
||||
sco_no=sco.name,
|
||||
rm_items=rm_items,
|
||||
itemwise_details=copy.deepcopy(itemwise_details),
|
||||
)
|
||||
|
||||
batch_no = "BATCH-REJ-BNGS-0001"
|
||||
if not frappe.db.exists("Batch", batch_no):
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "Batch",
|
||||
"batch_id": batch_no,
|
||||
"item": fg_item,
|
||||
}
|
||||
).insert()
|
||||
|
||||
rej_warehouse = create_warehouse("_Test Subcontract Warehouse For Rejected Qty")
|
||||
|
||||
scr = make_subcontracting_receipt(sco.name)
|
||||
self.assertFalse(scr.items[0].serial_and_batch_bundle)
|
||||
scr.items[0].use_serial_batch_fields = 1
|
||||
scr.items[0].batch_no = batch_no
|
||||
scr.items[0].received_qty = 10
|
||||
scr.items[0].rejected_qty = 2
|
||||
scr.items[0].qty = 8
|
||||
scr.items[0].rejected_warehouse = rej_warehouse
|
||||
|
||||
scr.save()
|
||||
scr.submit()
|
||||
scr.reload()
|
||||
self.assertTrue(scr.items[0].serial_and_batch_bundle)
|
||||
self.assertTrue(scr.items[0].rejected_serial_and_batch_bundle)
|
||||
|
||||
|
||||
def make_return_subcontracting_receipt(**args):
|
||||
args = frappe._dict(args)
|
||||
|
||||
@@ -335,8 +335,7 @@
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Rejected Serial No",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "subcontracting_order_item",
|
||||
@@ -569,7 +568,7 @@
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-02-04 16:23:30.374865",
|
||||
"modified": "2024-03-07 11:43:38.954262",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Subcontracting",
|
||||
"name": "Subcontracting Receipt Item",
|
||||
|
||||
Reference in New Issue
Block a user