diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 0510b2e1d15693998fc429e7a98a9cde088647ed..f7fb5235765572f9b6abbe7ec5fde0070a94e9ee 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -338,6 +338,12 @@ class AccountsController(TransactionBase): for bundle in bundles: frappe.delete_doc("Serial and Batch Bundle", bundle.name) + batches = frappe.get_all( + "Batch", filters={"reference_doctype": self.doctype, "reference_name": self.name} + ) + for row in batches: + frappe.delete_doc("Batch", row.name) + def validate_return_against_account(self): if ( self.doctype in ["Sales Invoice", "Purchase Invoice"] and self.is_return and self.return_against diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 15eeff549eb997a133abf4efefef3feae969e0c7..a1946e8a181873aac785f71d47c95c1c723768e3 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -48,7 +48,9 @@ class StockController(AccountsController): super(StockController, self).validate() if self.docstatus == 0: - self.validate_duplicate_serial_and_batch_bundle() + for table_name in ["items", "packed_items", "supplied_items"]: + self.validate_duplicate_serial_and_batch_bundle(table_name) + if not self.get("is_return"): self.validate_inspection() self.validate_serialized_batch() @@ -58,12 +60,19 @@ class StockController(AccountsController): self.validate_internal_transfer() self.validate_putaway_capacity() - def validate_duplicate_serial_and_batch_bundle(self): - if sbb_list := [ - item.get("serial_and_batch_bundle") - for item in self.items - if item.get("serial_and_batch_bundle") - ]: + def validate_duplicate_serial_and_batch_bundle(self, table_name): + if not self.get(table_name): + return + + sbb_list = [] + for item in self.get(table_name): + if item.get("serial_and_batch_bundle"): + sbb_list.append(item.get("serial_and_batch_bundle")) + + if item.get("rejected_serial_and_batch_bundle"): + sbb_list.append(item.get("rejected_serial_and_batch_bundle")) + + if sbb_list: SLE = frappe.qb.DocType("Stock Ledger Entry") data = ( frappe.qb.from_(SLE) @@ -188,7 +197,7 @@ class StockController(AccountsController): not row.serial_and_batch_bundle and not row.get("rejected_serial_and_batch_bundle") ): bundle_details = { - "item_code": row.item_code, + "item_code": row.get("rm_item_code") or row.item_code, "posting_date": self.posting_date, "posting_time": self.posting_time, "voucher_type": self.doctype, @@ -200,7 +209,7 @@ class StockController(AccountsController): "do_not_submit": True, } - if row.qty: + if row.get("qty") or row.get("consumed_qty"): self.update_bundle_details(bundle_details, table_name, row) self.create_serial_batch_bundle(bundle_details, row) @@ -219,6 +228,12 @@ class StockController(AccountsController): type_of_transaction = "Inward" if not self.is_return: type_of_transaction = "Outward" + elif table_name == "supplied_items": + qty = row.consumed_qty + warehouse = self.supplier_warehouse + type_of_transaction = "Outward" + if self.is_return: + type_of_transaction = "Inward" else: type_of_transaction = get_type_of_transaction(self, row) @@ -550,13 +565,30 @@ class StockController(AccountsController): ) def delete_auto_created_batches(self): - for row in self.items: - if row.serial_and_batch_bundle: - frappe.db.set_value( - "Serial and Batch Bundle", row.serial_and_batch_bundle, {"is_cancelled": 1} - ) + for table_name in ["items", "packed_items", "supplied_items"]: + if not self.get(table_name): + continue + + for row in self.get(table_name): + update_values = {} + if row.get("batch_no"): + update_values["batch_no"] = None + + if row.serial_and_batch_bundle: + update_values["serial_and_batch_bundle"] = None + frappe.db.set_value( + "Serial and Batch Bundle", row.serial_and_batch_bundle, {"is_cancelled": 1} + ) + + if update_values: + row.db_set(update_values) + + if table_name == "items" and row.get("rejected_serial_and_batch_bundle"): + frappe.db.set_value( + "Serial and Batch Bundle", row.rejected_serial_and_batch_bundle, {"is_cancelled": 1} + ) - row.db_set("serial_and_batch_bundle", None) + row.db_set("rejected_serial_and_batch_bundle", None) def set_serial_and_batch_bundle(self, table_name=None, ignore_validate=False): if not table_name: diff --git a/erpnext/controllers/subcontracting_controller.py b/erpnext/controllers/subcontracting_controller.py index e66fe8bcdec27407d419a3a482800991fcd894cb..ffc7f919d9563161262d3d22c62d9df70d419920 100644 --- a/erpnext/controllers/subcontracting_controller.py +++ b/erpnext/controllers/subcontracting_controller.py @@ -379,10 +379,10 @@ class SubcontractingController(StockController): if row.serial_no: details.serial_no.extend(get_serial_nos(row.serial_no)) - if row.batch_no: + elif row.batch_no: details.batch_no[row.batch_no] += row.qty - if voucher_bundle_data: + elif voucher_bundle_data: bundle_key = (row.rm_item_code, row.main_item_code, row.t_warehouse, row.voucher_no) bundle_data = voucher_bundle_data.get(bundle_key, frappe._dict()) @@ -392,6 +392,9 @@ class SubcontractingController(StockController): if bundle_data.batch_nos: for batch_no, qty in bundle_data.batch_nos.items(): + if qty < 0: + qty = abs(qty) + if qty > 0: details.batch_no[batch_no] += qty bundle_data.batch_nos[batch_no] -= qty @@ -545,17 +548,24 @@ class SubcontractingController(StockController): rm_obj.reference_name = item_row.name + use_serial_batch_fields = frappe.db.get_single_value("Stock Settings", "use_serial_batch_fields") + if self.doctype == self.subcontract_data.order_doctype: rm_obj.required_qty = qty rm_obj.amount = rm_obj.required_qty * rm_obj.rate else: rm_obj.consumed_qty = qty rm_obj.required_qty = bom_item.required_qty or qty + rm_obj.serial_and_batch_bundle = None setattr( rm_obj, self.subcontract_data.order_field, item_row.get(self.subcontract_data.order_field) ) - if self.doctype == "Subcontracting Receipt": + if use_serial_batch_fields: + rm_obj.use_serial_batch_fields = 1 + self.__set_batch_nos(bom_item, item_row, rm_obj, qty) + + if self.doctype == "Subcontracting Receipt" and not use_serial_batch_fields: args = frappe._dict( { "item_code": rm_obj.rm_item_code, @@ -581,6 +591,68 @@ class SubcontractingController(StockController): rm_obj.rate = get_incoming_rate(args) + def __set_batch_nos(self, bom_item, item_row, rm_obj, qty): + key = (rm_obj.rm_item_code, item_row.item_code, item_row.get(self.subcontract_data.order_field)) + + if self.available_materials.get(key) and self.available_materials[key]["batch_no"]: + new_rm_obj = None + for batch_no, batch_qty in self.available_materials[key]["batch_no"].items(): + if batch_qty >= qty or ( + rm_obj.consumed_qty == 0 + and self.backflush_based_on == "BOM" + and len(self.available_materials[key]["batch_no"]) == 1 + ): + if rm_obj.consumed_qty == 0: + self.__set_consumed_qty(rm_obj, qty) + + self.__set_batch_no_as_per_qty(item_row, rm_obj, batch_no, qty) + self.available_materials[key]["batch_no"][batch_no] -= qty + return + + elif qty > 0 and batch_qty > 0: + qty -= batch_qty + new_rm_obj = self.append(self.raw_material_table, bom_item) + new_rm_obj.serial_and_batch_bundle = None + new_rm_obj.use_serial_batch_fields = 1 + new_rm_obj.reference_name = item_row.name + self.__set_batch_no_as_per_qty(item_row, new_rm_obj, batch_no, batch_qty) + self.available_materials[key]["batch_no"][batch_no] = 0 + + if new_rm_obj: + self.remove(rm_obj) + elif abs(qty) > 0: + self.__set_consumed_qty(rm_obj, qty) + + else: + self.__set_consumed_qty(rm_obj, qty, bom_item.required_qty or qty) + self.__set_serial_nos(item_row, rm_obj) + + def __set_consumed_qty(self, rm_obj, consumed_qty, required_qty=0): + rm_obj.required_qty = required_qty + rm_obj.consumed_qty = consumed_qty + + def __set_serial_nos(self, item_row, rm_obj): + key = (rm_obj.rm_item_code, item_row.item_code, item_row.get(self.subcontract_data.order_field)) + if self.available_materials.get(key) and self.available_materials[key]["serial_no"]: + used_serial_nos = self.available_materials[key]["serial_no"][0 : cint(rm_obj.consumed_qty)] + rm_obj.serial_no = "\n".join(used_serial_nos) + + # Removed the used serial nos from the list + for sn in used_serial_nos: + self.available_materials[key]["serial_no"].remove(sn) + + def __set_batch_no_as_per_qty(self, item_row, rm_obj, batch_no, qty): + rm_obj.update( + { + "consumed_qty": qty, + "batch_no": batch_no, + "required_qty": qty, + self.subcontract_data.order_field: item_row.get(self.subcontract_data.order_field), + } + ) + + self.__set_serial_nos(item_row, rm_obj) + def __get_qty_based_on_material_transfer(self, item_row, transfer_item): key = (item_row.item_code, item_row.get(self.subcontract_data.order_field)) @@ -1076,6 +1148,9 @@ def make_rm_stock_entry( "serial_and_batch_bundle": rm_item.get("serial_and_batch_bundle"), "main_item_code": fg_item_code, "allow_alternative_item": item_wh.get(rm_item_code, {}).get("allow_alternative_item"), + "use_serial_batch_fields": rm_item.get("use_serial_batch_fields"), + "serial_no": rm_item.get("serial_no") if rm_item.get("use_serial_batch_fields") else None, + "batch_no": rm_item.get("batch_no") if rm_item.get("use_serial_batch_fields") else None, } } diff --git a/erpnext/controllers/tests/test_subcontracting_controller.py b/erpnext/controllers/tests/test_subcontracting_controller.py index 6e6c2e1ec37c00758338bbbbfd23225f6a506cdf..4ee195c47c8c9dd8d12f31ec493d0b32fa3fb0d4 100644 --- a/erpnext/controllers/tests/test_subcontracting_controller.py +++ b/erpnext/controllers/tests/test_subcontracting_controller.py @@ -138,6 +138,7 @@ class TestSubcontractingController(FrappeTestCase): - Create partial SCR against the SCO and check serial nos and batch no. """ + frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 0) set_backflush_based_on("Material Transferred for Subcontract") service_items = [ { @@ -200,6 +201,8 @@ class TestSubcontractingController(FrappeTestCase): if value.get(field): self.assertEqual(value.get(field), transferred_detais.get(field)) + frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 1) + def test_subcontracting_with_same_components_different_fg(self): """ - Set backflush based on Material Transfer. @@ -209,6 +212,7 @@ class TestSubcontractingController(FrappeTestCase): - Create partial SCR against the SCO and check serial nos. """ + frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 0) set_backflush_based_on("Material Transferred for Subcontract") service_items = [ { @@ -276,6 +280,8 @@ class TestSubcontractingController(FrappeTestCase): self.assertEqual(value.qty, 6) self.assertEqual(sorted(value.serial_no), sorted(transferred_detais.get("serial_no")[6:12])) + frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 1) + def test_return_non_consumed_materials(self): """ - Set backflush based on Material Transfer. @@ -286,6 +292,7 @@ class TestSubcontractingController(FrappeTestCase): - After that return the non consumed material back to the store from supplier's warehouse. """ + frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 0) set_backflush_based_on("Material Transferred for Subcontract") service_items = [ { @@ -331,6 +338,7 @@ class TestSubcontractingController(FrappeTestCase): get_serial_nos(doc.items[0].serial_no), itemwise_details.get(doc.items[0].item_code)["serial_no"][5:6], ) + frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 1) def test_item_with_batch_based_on_bom(self): """ @@ -574,6 +582,7 @@ class TestSubcontractingController(FrappeTestCase): - Create SCR for remaining qty against the SCO and change the qty manually. """ + frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 0) set_backflush_based_on("Material Transferred for Subcontract") service_items = [ { @@ -639,6 +648,8 @@ class TestSubcontractingController(FrappeTestCase): self.assertEqual(value.qty, details.qty) self.assertEqual(sorted(value.serial_no), sorted(details.serial_no)) + frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 1) + def test_incorrect_serial_no_components_based_on_material_transfer(self): """ - Set backflush based on Material Transferred for Subcontract. @@ -648,6 +659,7 @@ class TestSubcontractingController(FrappeTestCase): - System should throw the error and not allowed to save the SCR. """ + frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 0) serial_no = "ABC" if not frappe.db.exists("Serial No", serial_no): frappe.get_doc( @@ -708,6 +720,7 @@ class TestSubcontractingController(FrappeTestCase): scr1.save() self.delete_bundle_from_scr(scr1) scr1.delete() + frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 1) @staticmethod def delete_bundle_from_scr(scr): @@ -840,6 +853,223 @@ class TestSubcontractingController(FrappeTestCase): for item in sco.get("supplied_items"): self.assertEqual(item.supplied_qty, 0.0) + def test_sco_with_material_transfer_with_use_serial_batch_fields(self): + """ + - Set backflush based on Material Transfer. + - Create SCO for the item Subcontracted Item SA1 and Subcontracted Item SA5. + - Transfer the components from Stores to Supplier warehouse with batch no and serial nos. + - Transfer extra item Subcontracted SRM Item 4 for the subcontract item Subcontracted Item SA5. + - Create partial SCR against the SCO and check serial nos and batch no. + """ + + set_backflush_based_on("Material Transferred for Subcontract") + service_items = [ + { + "warehouse": "_Test Warehouse - _TC", + "item_code": "Subcontracted Service Item 1", + "qty": 5, + "rate": 100, + "fg_item": "Subcontracted Item SA1", + "fg_item_qty": 5, + }, + { + "warehouse": "_Test Warehouse - _TC", + "item_code": "Subcontracted Service Item 5", + "qty": 6, + "rate": 100, + "fg_item": "Subcontracted Item SA5", + "fg_item_qty": 6, + }, + ] + sco = get_subcontracting_order(service_items=service_items) + rm_items = get_rm_items(sco.supplied_items) + rm_items.append( + { + "main_item_code": "Subcontracted Item SA5", + "item_code": "Subcontracted SRM Item 4", + "qty": 6, + } + ) + itemwise_details = make_stock_in_entry(rm_items=rm_items) + + for item in rm_items: + item["sco_rm_detail"] = sco.items[0].name if item.get("qty") == 5 else sco.items[1].name + + make_stock_transfer_entry( + sco_no=sco.name, + rm_items=rm_items, + itemwise_details=copy.deepcopy(itemwise_details), + ) + + scr1 = make_subcontracting_receipt(sco.name) + scr1.remove(scr1.items[1]) + scr1.save() + scr1.submit() + + for key, value in get_supplied_items(scr1).items(): + transferred_detais = itemwise_details.get(key) + + for field in ["qty", "serial_no", "batch_no"]: + if value.get(field): + data = value.get(field) + if field == "serial_no": + data = sorted(data) + + self.assertEqual(data, transferred_detais.get(field)) + + scr2 = make_subcontracting_receipt(sco.name) + scr2.save() + scr2.submit() + + for key, value in get_supplied_items(scr2).items(): + transferred_detais = itemwise_details.get(key) + + for field in ["qty", "serial_no", "batch_no"]: + if value.get(field): + data = value.get(field) + if field == "serial_no": + data = sorted(data) + + self.assertEqual(data, transferred_detais.get(field)) + + def test_subcontracting_with_same_components_different_fg_with_serial_batch_fields(self): + """ + - Set backflush based on Material Transfer. + - Create SCO for the item Subcontracted Item SA2 and Subcontracted Item SA3. + - Transfer the components from Stores to Supplier warehouse with serial nos. + - Transfer extra qty of components for the item Subcontracted Item SA2. + - Create partial SCR against the SCO and check serial nos. + """ + + set_backflush_based_on("Material Transferred for Subcontract") + service_items = [ + { + "warehouse": "_Test Warehouse - _TC", + "item_code": "Subcontracted Service Item 2", + "qty": 5, + "rate": 100, + "fg_item": "Subcontracted Item SA2", + "fg_item_qty": 5, + }, + { + "warehouse": "_Test Warehouse - _TC", + "item_code": "Subcontracted Service Item 3", + "qty": 6, + "rate": 100, + "fg_item": "Subcontracted Item SA3", + "fg_item_qty": 6, + }, + ] + sco = get_subcontracting_order(service_items=service_items) + rm_items = get_rm_items(sco.supplied_items) + rm_items[0]["qty"] += 1 + itemwise_details = make_stock_in_entry(rm_items=rm_items) + + for item in rm_items: + item["sco_rm_detail"] = sco.items[0].name if item.get("qty") == 5 else sco.items[1].name + item["use_serial_batch_fields"] = 1 + + make_stock_transfer_entry( + sco_no=sco.name, + rm_items=rm_items, + itemwise_details=copy.deepcopy(itemwise_details), + ) + + scr1 = make_subcontracting_receipt(sco.name) + scr1.items[0].qty = 3 + scr1.remove(scr1.items[1]) + scr1.save() + scr1.submit() + + for key, value in get_supplied_items(scr1).items(): + transferred_detais = itemwise_details.get(key) + + self.assertEqual(value.qty, 4) + self.assertEqual(sorted(value.serial_no), sorted(transferred_detais.get("serial_no")[0:4])) + + scr2 = make_subcontracting_receipt(sco.name) + scr2.items[0].qty = 2 + scr2.remove(scr2.items[1]) + scr2.save() + scr2.submit() + + for key, value in get_supplied_items(scr2).items(): + transferred_detais = itemwise_details.get(key) + + self.assertEqual(value.qty, 2) + self.assertEqual(sorted(value.serial_no), sorted(transferred_detais.get("serial_no")[4:6])) + + scr3 = make_subcontracting_receipt(sco.name) + scr3.save() + scr3.submit() + + for key, value in get_supplied_items(scr3).items(): + transferred_detais = itemwise_details.get(key) + + self.assertEqual(value.qty, 6) + self.assertEqual(sorted(value.serial_no), sorted(transferred_detais.get("serial_no")[6:12])) + + def test_return_non_consumed_materials_with_serial_batch_fields(self): + """ + - Set backflush based on Material Transfer. + - Create SCO for item Subcontracted Item SA2. + - Transfer the components from Stores to Supplier warehouse with serial nos. + - Transfer extra qty of component for the subcontracted item Subcontracted Item SA2. + - Create SCR for full qty against the SCO and change the qty of raw material. + - After that return the non consumed material back to the store from supplier's warehouse. + """ + + set_backflush_based_on("Material Transferred for Subcontract") + service_items = [ + { + "warehouse": "_Test Warehouse - _TC", + "item_code": "Subcontracted Service Item 2", + "qty": 5, + "rate": 100, + "fg_item": "Subcontracted Item SA2", + "fg_item_qty": 5, + }, + ] + sco = get_subcontracting_order(service_items=service_items) + rm_items = get_rm_items(sco.supplied_items) + rm_items[0]["qty"] += 1 + itemwise_details = make_stock_in_entry(rm_items=rm_items) + + for item in rm_items: + item["use_serial_batch_fields"] = 1 + item["sco_rm_detail"] = sco.items[0].name + + make_stock_transfer_entry( + sco_no=sco.name, + rm_items=rm_items, + itemwise_details=copy.deepcopy(itemwise_details), + ) + + scr1 = make_subcontracting_receipt(sco.name) + scr1.save() + scr1.supplied_items[0].consumed_qty = 5 + scr1.supplied_items[0].serial_no = "\n".join( + sorted(itemwise_details.get("Subcontracted SRM Item 2").get("serial_no")[0:5]) + ) + scr1.submit() + + for key, value in get_supplied_items(scr1).items(): + transferred_detais = itemwise_details.get(key) + self.assertTrue(value.use_serial_batch_fields) + self.assertEqual(value.qty, 5) + self.assertEqual(sorted(value.serial_no), sorted(transferred_detais.get("serial_no")[0:5])) + + sco.load_from_db() + self.assertEqual(sco.supplied_items[0].consumed_qty, 5) + doc = get_materials_from_supplier(sco.name, [d.name for d in sco.supplied_items]) + self.assertEqual(doc.items[0].qty, 1) + self.assertEqual(doc.items[0].s_warehouse, "_Test Warehouse 1 - _TC") + self.assertEqual(doc.items[0].t_warehouse, "_Test Warehouse - _TC") + self.assertEqual( + get_serial_nos(doc.items[0].serial_no), + itemwise_details.get(doc.items[0].item_code)["serial_no"][5:6], + ) + def add_second_row_in_scr(scr): item_dict = {} @@ -910,6 +1140,7 @@ def update_item_details(child_row, details): else child_row.get("consumed_qty") ) + details.use_serial_batch_fields = child_row.get("use_serial_batch_fields") if child_row.serial_and_batch_bundle: doc = frappe.get_doc("Serial and Batch Bundle", child_row.serial_and_batch_bundle) for row in doc.get("entries"): @@ -941,6 +1172,7 @@ def make_stock_transfer_entry(**args): "rate": row.rate or 100, "stock_uom": row.stock_uom or "Nos", "warehouse": row.warehouse or "_Test Warehouse - _TC", + "use_serial_batch_fields": row.get("use_serial_batch_fields"), } item_details = args.itemwise_details.get(row.item_code) @@ -956,9 +1188,12 @@ def make_stock_transfer_entry(**args): if batch_qty >= row.qty: batches[batch_no] = row.qty item_details.batch_no[batch_no] -= row.qty + if row.get("use_serial_batch_fields"): + item["batch_no"] = batch_no + break - if serial_nos or batches: + if not row.get("use_serial_batch_fields") and (serial_nos or batches): item["serial_and_batch_bundle"] = make_serial_batch_bundle( frappe._dict( { @@ -974,6 +1209,9 @@ def make_stock_transfer_entry(**args): ) ).name + if serial_nos and row.get("use_serial_batch_fields"): + item["serial_no"] = "\n".join(serial_nos) + items.append(item) ste_dict = make_rm_stock_entry(args.sco_no, items) @@ -1131,6 +1369,7 @@ def get_rm_items(supplied_items): "rate": item.rate, "stock_uom": item.stock_uom, "warehouse": item.reserve_warehouse, + "use_serial_batch_fields": 0, } ) diff --git a/erpnext/stock/deprecated_serial_batch.py b/erpnext/stock/deprecated_serial_batch.py index 7be1418823bb9ab51e78a488ee8911f99d9be95e..6e32cc2dae63fb6ae2dc2b743c57697c3a87a0a3 100644 --- a/erpnext/stock/deprecated_serial_batch.py +++ b/erpnext/stock/deprecated_serial_batch.py @@ -245,6 +245,8 @@ class DeprecatedBatchNoValuation: if self.sle.serial_and_batch_bundle: query = query.where(bundle.name != self.sle.serial_and_batch_bundle) + query = query.where(bundle.voucher_type != "Pick List") + for d in query.run(as_dict=True): self.non_batchwise_balance_value += flt(d.batch_value) self.non_batchwise_balance_qty += flt(d.batch_qty) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 7296a14b035adc63fcb86fe25afcf928aba9a623..d49ac3e33595d77be49f71379cae1350026d78c2 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -2565,7 +2565,9 @@ def make_stock_in_entry(source_name, target_doc=None): def set_missing_values(source, target): target.stock_entry_type = "Material Transfer" target.set_missing_values() - target.make_serial_and_batch_bundle_for_transfer() + + if not frappe.db.get_single_value("Stock Settings", "use_serial_batch_fields"): + target.make_serial_and_batch_bundle_for_transfer() def update_item(source_doc, target_doc, source_parent): target_doc.t_warehouse = "" diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py index 187070ed62bafe2dc6a5730af2c53630c90bd4b7..3ce6b229fe2ff6ba3cdaf288cb470b42290556f0 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py @@ -79,7 +79,9 @@ class SubcontractingReceipt(SubcontractingController): self.update_prevdoc_status() self.set_subcontracting_order_status() self.set_consumed_qty_in_subcontract_order() - self.make_bundle_using_old_serial_batch_fields() + + for table_name in ["items", "supplied_items"]: + self.make_bundle_using_old_serial_batch_fields(table_name) self.update_stock_ledger() self.make_gl_entries() self.repost_future_sle_and_gle() diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py index d69bbc57665f0ae2ddcdd03926f4ea481d3e604e..1f665260fb0a77544039884ba2738760f2d00b4d 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py @@ -291,6 +291,7 @@ class TestSubcontractingReceipt(FrappeTestCase): self.assertRaises(OverAllowanceError, make_return_subcontracting_receipt, **args) def test_subcontracting_receipt_no_gl_entry(self): + frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 0) sco = get_subcontracting_order() rm_items = get_rm_items(sco.supplied_items) itemwise_details = make_stock_in_entry(rm_items=rm_items) @@ -326,8 +327,10 @@ class TestSubcontractingReceipt(FrappeTestCase): # Service Cost(100 * 10) + Raw Materials Cost(100 * 10) + Additional Costs(10 * 10) = 2100 self.assertEqual(stock_value_difference, 2100) self.assertFalse(get_gl_entries("Subcontracting Receipt", scr.name)) + frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 1) def test_subcontracting_receipt_gl_entry(self): + frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 0) sco = get_subcontracting_order( company="_Test Company with perpetual inventory", warehouse="Stores - TCP1", @@ -386,6 +389,7 @@ class TestSubcontractingReceipt(FrappeTestCase): scr.reload() scr.cancel() self.assertTrue(get_gl_entries("Subcontracting Receipt", scr.name)) + frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 1) def test_supplied_items_consumed_qty(self): # Set Backflush Based On as "Material Transferred for Subcontracting" to transfer RM's more than the required qty @@ -663,6 +667,7 @@ class TestSubcontractingReceipt(FrappeTestCase): ) def test_subcontracting_receipt_valuation_for_fg_with_auto_created_serial_batch_bundle(self): + frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 0) set_backflush_based_on("BOM") fg_item = make_item( @@ -759,9 +764,11 @@ class TestSubcontractingReceipt(FrappeTestCase): frappe.db.set_single_value( "Stock Settings", "auto_create_serial_and_batch_bundle_for_outward", 0 ) + frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 1) def test_subcontracting_receipt_raw_material_rate(self): # Step - 1: Set Backflush Based On as "BOM" + frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 0) set_backflush_based_on("BOM") # Step - 2: Create FG and RM Items @@ -819,6 +826,8 @@ class TestSubcontractingReceipt(FrappeTestCase): self.assertEqual(rm_item.rate, 100) self.assertEqual(rm_item.amount, rm_item.consumed_qty * rm_item.rate) + frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 1) + def test_quality_inspection_for_subcontracting_receipt(self): from erpnext.stock.doctype.quality_inspection.test_quality_inspection import ( create_quality_inspection,