diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index 3514daba922a..309408992d43 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -1382,8 +1382,9 @@ def test_backflushed_serial_no_raw_materials_based_on_transferred(self): # Inward raw materials in Stores warehouse ste_doc.submit() + ste_doc.reload() - serial_nos_list = sorted(get_serial_nos(ste_doc.items[0].serial_no)) + serial_nos_list = sorted(get_serial_nos_from_bundle(ste_doc.items[0].serial_and_batch_bundle)) wo_doc = make_wo_order_test_record(production_item=fg_item, qty=4) transferred_ste_doc = frappe.get_doc( @@ -1395,19 +1396,22 @@ def test_backflushed_serial_no_raw_materials_based_on_transferred(self): # First Manufacture stock entry manufacture_ste_doc1 = frappe.get_doc(make_stock_entry(wo_doc.name, "Manufacture", 1)) + manufacture_ste_doc1.submit() + manufacture_ste_doc1.reload() # Serial nos should be same as transferred Serial nos - self.assertEqual(get_serial_nos(manufacture_ste_doc1.items[0].serial_no), serial_nos_list[0:1]) + self.assertEqual( + sorted(get_serial_nos_from_bundle(manufacture_ste_doc1.items[0].serial_and_batch_bundle)), + serial_nos_list[0:1], + ) self.assertEqual(manufacture_ste_doc1.items[0].qty, 1) - manufacture_ste_doc1.submit() - # Second Manufacture stock entry - manufacture_ste_doc2 = frappe.get_doc(make_stock_entry(wo_doc.name, "Manufacture", 2)) + manufacture_ste_doc2 = frappe.get_doc(make_stock_entry(wo_doc.name, "Manufacture", 3)) # Serial nos should be same as transferred Serial nos - self.assertEqual(get_serial_nos(manufacture_ste_doc2.items[0].serial_no), serial_nos_list[1:3]) - self.assertEqual(manufacture_ste_doc2.items[0].qty, 2) + self.assertEqual(get_serial_nos(manufacture_ste_doc2.items[0].serial_no), serial_nos_list[1:4]) + self.assertEqual(manufacture_ste_doc2.items[0].qty, 3) def test_backflushed_serial_no_batch_raw_materials_based_on_transferred(self): frappe.db.set_single_value( @@ -1541,19 +1545,9 @@ def test_non_consumed_material_return_against_work_order(self): row.qty -= 2 row.transfer_qty -= 2 - if not row.serial_and_batch_bundle: - continue - - bundle_id = row.serial_and_batch_bundle - bundle_doc = frappe.get_doc("Serial and Batch Bundle", bundle_id) - if bundle_doc.has_serial_no: - bundle_doc.set("entries", bundle_doc.entries[0:5]) - else: - for bundle_row in bundle_doc.entries: - bundle_row.qty += 2 - - bundle_doc.save() - bundle_doc.load_from_db() + if row.serial_no: + serial_nos = get_serial_nos(row.serial_no) + row.serial_no = "\n".join(serial_nos[:5]) ste_doc.save() ste_doc.submit() @@ -1896,6 +1890,71 @@ def test_capcity_planning_for_workstation(self): "Manufacturing Settings", {"disable_capacity_planning": 1, "mins_between_operations": 0} ) + def test_partial_material_consumption_with_batch(self): + from erpnext.stock.doctype.stock_entry.test_stock_entry import ( + make_stock_entry as make_stock_entry_test_record, + ) + + frappe.db.set_single_value("Manufacturing Settings", "material_consumption", 1) + frappe.db.set_single_value( + "Manufacturing Settings", + "backflush_raw_materials_based_on", + "Material Transferred for Manufacture", + ) + + fg_item = make_item( + "Test FG Item For Partial Material Consumption", + {"is_stock_item": 1}, + ).name + + rm_item = make_item( + "Test RM Item For Partial Material Consumption", + { + "is_stock_item": 1, + "has_batch_no": 1, + "create_new_batch": 1, + "batch_number_series": "TST-BATCH-PMCC-.####", + }, + ).name + + make_bom( + item=fg_item, + source_warehouse="Stores - _TC", + raw_materials=[rm_item], + ) + + make_stock_entry_test_record( + purpose="Material Receipt", + item_code=rm_item, + target="Stores - _TC", + qty=10, + basic_rate=100, + ) + + wo_order = make_wo_order_test_record(item=fg_item, qty=10) + + stock_entry = frappe.get_doc( + make_stock_entry(wo_order.name, "Material Transfer for Manufacture", 10) + ) + stock_entry.submit() + stock_entry.reload() + + batch_no = get_batch_from_bundle(stock_entry.items[0].serial_and_batch_bundle) + + stock_entry = frappe.get_doc( + make_stock_entry(wo_order.name, "Material Consumption for Manufacture", 10) + ) + + self.assertEqual(stock_entry.items[0].batch_no, batch_no) + self.assertEqual(stock_entry.items[0].use_serial_batch_fields, 1) + + frappe.db.set_single_value("Manufacturing Settings", "material_consumption", 0) + frappe.db.set_single_value( + "Manufacturing Settings", + "backflush_raw_materials_based_on", + "BOM", + ) + def make_operation(**kwargs): kwargs = frappe._dict(kwargs) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index dd56ef8e7fee..759fc080acab 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -2163,23 +2163,42 @@ def update_item_in_stock_entry_detail(self, row, item, qty) -> None: if not qty: return + use_serial_batch_fields = frappe.db.get_single_value("Stock Settings", "use_serial_batch_fields") + ste_item_details = { "from_warehouse": item.warehouse, "to_warehouse": "", "qty": qty, "item_name": item.item_name, - "serial_and_batch_bundle": create_serial_and_batch_bundle(self, row, item, "Outward"), + "serial_and_batch_bundle": create_serial_and_batch_bundle(self, row, item, "Outward") + if not use_serial_batch_fields + else "", "description": item.description, "stock_uom": item.stock_uom, "expense_account": item.expense_account, "cost_center": item.buying_cost_center, "original_item": item.original_item, + "serial_no": "\n".join(row.serial_nos) + if row.serial_nos and not row.batches_to_be_consume + else "", + "use_serial_batch_fields": use_serial_batch_fields, } if self.is_return: ste_item_details["to_warehouse"] = item.s_warehouse - self.add_to_stock_entry_detail({item.item_code: ste_item_details}) + if use_serial_batch_fields and not row.serial_no and row.batches_to_be_consume: + for batch_no, batch_qty in row.batches_to_be_consume.items(): + ste_item_details.update( + { + "batch_no": batch_no, + "qty": batch_qty, + } + ) + + self.add_to_stock_entry_detail({item.item_code: ste_item_details}) + else: + self.add_to_stock_entry_detail({item.item_code: ste_item_details}) @staticmethod def get_serial_nos_based_on_transferred_batch(batch_no, serial_nos) -> list: @@ -2330,6 +2349,9 @@ def add_to_stock_entry_detail(self, item_dict, bom_no=None): "item_name", "serial_and_batch_bundle", "allow_zero_valuation_rate", + "use_serial_batch_fields", + "batch_no", + "serial_no", ]: if item_row.get(field): se_child.set(field, item_row.get(field)) @@ -2978,7 +3000,7 @@ def get_available_materials(work_order) -> dict: if row.batch_no: item_data.batch_details[row.batch_no] += row.qty - if row.batch_nos: + elif row.batch_nos: for batch_no, qty in row.batch_nos.items(): item_data.batch_details[batch_no] += qty @@ -2986,7 +3008,7 @@ def get_available_materials(work_order) -> dict: item_data.serial_nos.extend(get_serial_nos(row.serial_no)) item_data.serial_nos.sort() - if row.serial_nos: + elif row.serial_nos: item_data.serial_nos.extend(get_serial_nos(row.serial_nos)) item_data.serial_nos.sort() else: @@ -2996,7 +3018,7 @@ def get_available_materials(work_order) -> dict: if row.batch_no: item_data.batch_details[row.batch_no] -= row.qty - if row.batch_nos: + elif row.batch_nos: for batch_no, qty in row.batch_nos.items(): item_data.batch_details[batch_no] += qty @@ -3004,7 +3026,7 @@ def get_available_materials(work_order) -> dict: for serial_no in get_serial_nos(row.serial_no): item_data.serial_nos.remove(serial_no) - if row.serial_nos: + elif row.serial_nos: for serial_no in get_serial_nos(row.serial_nos): item_data.serial_nos.remove(serial_no)