diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index 307981f4294..3e67fd08033 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -2501,6 +2501,53 @@ class TestWorkOrder(FrappeTestCase): manufacture_entry = frappe.get_doc(make_stock_entry(wo.name, "Manufacture", 10)) self.assertEqual(manufacture_entry.items[0].s_warehouse, "Stores - _TC") + def test_serial_no_status_for_stock_entry(self): + items = { + "Finished Good Test Item 1": {"is_stock_item": 1}, + "_Test RM Item with Serial No": { + "is_stock_item": 1, + "has_serial_no": 1, + "serial_no_series": "SN-FCG-NO-.####", + }, + } + for item, properties in items.items(): + make_item(item, properties) + + fg_item = "Finished Good Test Item 1" + rec_se = test_stock_entry.make_stock_entry( + item_code="_Test RM Item with Serial No", target="_Test Warehouse - _TC", qty=4, basic_rate=100 + ) + + if not frappe.db.get_value("BOM", {"item": fg_item, "docstatus": 1}): + bom = make_bom( + item=fg_item, + rate=1000, + raw_materials=["_Test RM Item with Serial No"], + do_not_save=True, + ) + bom.rm_cost_as_per = "Price List" # non stock item won't have valuation rate + bom.buying_price_list = "_Test Price List India" + bom.currency = "INR" + bom.save() + + wo = make_wo_order_test_record( + production_item=fg_item, skip_transfer=1, source_warehouse="_Test Warehouse - _TC" + ) + + ste = frappe.get_doc(make_stock_entry(wo.name, "Manufacture", 4)) + ste.items[0].use_serial_batch_fields = 1 + ste.items[0].serial_no = "\n".join( + get_serial_nos_from_bundle(rec_se.items[0].serial_and_batch_bundle) + ) + ste.insert() + ste.submit() + + ste.reload() + serial_nos = get_serial_nos_from_bundle(ste.items[0].serial_and_batch_bundle) + for row in serial_nos: + status = frappe.db.get_value("Serial No", row, "status") + self.assertEqual(status, "Consumed") + def make_operation(**kwargs): kwargs = frappe._dict(kwargs) diff --git a/erpnext/stock/doctype/serial_no/serial_no.json b/erpnext/stock/doctype/serial_no/serial_no.json index 2d7fcac89a0..89c77d46b1e 100644 --- a/erpnext/stock/doctype/serial_no/serial_no.json +++ b/erpnext/stock/doctype/serial_no/serial_no.json @@ -254,7 +254,7 @@ "in_list_view": 1, "in_standard_filter": 1, "label": "Status", - "options": "\nActive\nInactive\nDelivered\nExpired", + "options": "\nActive\nInactive\nConsumed\nDelivered\nExpired", "read_only": 1 }, { @@ -272,7 +272,7 @@ "icon": "fa fa-barcode", "idx": 1, "links": [], - "modified": "2023-12-17 10:52:55.767839", + "modified": "2025-01-15 16:22:49.873889", "modified_by": "Administrator", "module": "Stock", "name": "Serial No", @@ -316,4 +316,4 @@ "sort_order": "DESC", "states": [], "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py index 54f96fb7b10..274ce0f0d9d 100644 --- a/erpnext/stock/doctype/serial_no/serial_no.py +++ b/erpnext/stock/doctype/serial_no/serial_no.py @@ -50,7 +50,7 @@ class SerialNo(StockController): purchase_document_no: DF.Data | None purchase_rate: DF.Float serial_no: DF.Data - status: DF.Literal["", "Active", "Inactive", "Delivered", "Expired"] + status: DF.Literal["", "Active", "Inactive", "Consumed", "Delivered", "Expired"] warehouse: DF.Link | None warranty_expiry_date: DF.Date | None warranty_period: DF.Int diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index 05661d3a83a..fea807264f8 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -1802,7 +1802,7 @@ class TestStockEntry(FrappeTestCase): for serial_no in serial_nos: self.assertTrue(frappe.db.exists("Serial No", serial_no)) - self.assertEqual(frappe.db.get_value("Serial No", serial_no, "status"), "Delivered") + self.assertEqual(frappe.db.get_value("Serial No", serial_no, "status"), "Consumed") def test_serial_batch_bundle_type_of_transaction(self): item = make_item( diff --git a/erpnext/stock/serial_batch_bundle.py b/erpnext/stock/serial_batch_bundle.py index c450bf1a086..59c299de352 100644 --- a/erpnext/stock/serial_batch_bundle.py +++ b/erpnext/stock/serial_batch_bundle.py @@ -351,6 +351,15 @@ class SerialBatchBundle: status = "Inactive" if self.sle.actual_qty < 0: status = "Delivered" + if self.sle.voucher_type == "Stock Entry": + purpose = frappe.get_cached_value("Stock Entry", self.sle.voucher_no, "purpose") + if purpose in [ + "Manufacture", + "Material Issue", + "Repack", + "Material Consumption for Manufacture", + ]: + status = "Consumed" sn_table = frappe.qb.DocType("Serial No") diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 37e91080d5d..91d10b3a9ac 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -622,15 +622,12 @@ class update_entries_after: if sle.dependant_sle_voucher_detail_no: entries_to_fix = self.get_dependent_entries_to_fix(entries_to_fix, sle) - if self.has_stock_reco_with_serial_batch(sle): - break - if self.exceptions: self.raise_exceptions() def has_stock_reco_with_serial_batch(self, sle): if ( - sle.vocher_type == "Stock Reconciliation" + sle.voucher_type == "Stock Reconciliation" and frappe.db.get_value(sle.voucher_type, sle.voucher_no, "set_posting_time") == 1 ): return not (sle.batch_no or sle.serial_no or sle.serial_and_batch_bundle)