From 9cf76c6a68f6ea9dd99a102f881aa4df4adb7ee9 Mon Sep 17 00:00:00 2001 From: Sudharsanan Ashok <135326972+Sudharsanan11@users.noreply.github.com> Date: Tue, 25 Aug 2026 13:20:50 +0530 Subject: [PATCH] fix(stock): keep pick list links when refetching stock entry items (#58374) --- erpnext/stock/doctype/pick_list/mapper.py | 1 + .../stock/doctype/pick_list/test_pick_list.py | 50 +++++++++++++++++++ .../stock/doctype/stock_entry/stock_entry.js | 13 ++++- .../stock/doctype/stock_entry/stock_entry.py | 3 ++ 4 files changed, 66 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/pick_list/mapper.py b/erpnext/stock/doctype/pick_list/mapper.py index 306789d39e5..08efce91dfc 100644 --- a/erpnext/stock/doctype/pick_list/mapper.py +++ b/erpnext/stock/doctype/pick_list/mapper.py @@ -287,6 +287,7 @@ def add_product_bundles_to_target(pick_list, target_doc, item_mapper, sales_orde @frappe.whitelist() def create_stock_entry(pick_list: str | dict): pick_list = frappe.get_doc(frappe.parse_json(pick_list)) + pick_list.check_permission("read") validate_item_locations(pick_list) stock_entry = frappe.new_doc("Stock Entry") diff --git a/erpnext/stock/doctype/pick_list/test_pick_list.py b/erpnext/stock/doctype/pick_list/test_pick_list.py index a2ca611aa53..0cf2e4d515d 100644 --- a/erpnext/stock/doctype/pick_list/test_pick_list.py +++ b/erpnext/stock/doctype/pick_list/test_pick_list.py @@ -1452,6 +1452,56 @@ class TestPickList(ERPNextTestSuite): self.assertEqual(pick_list.locations[0].transferred_qty, 4) self.assertEqual(pick_list.status, "Partially Transferred") + def test_get_items_keeps_pick_list_rows_on_stock_entry(self): + """Entering fg_completed_qty on a Stock Entry mapped from a Pick List triggers get_items(); + it must not refetch from the BOM, or the pick_list_item links transferred_qty rides on are + lost and the Pick List stays Open with every row offered again.""" + from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom + from erpnext.manufacturing.doctype.work_order.mapper import create_pick_list as pick_list_for_wo + from erpnext.manufacturing.doctype.work_order.work_order import make_work_order + from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse + + source_warehouse = create_warehouse("_Test Partial Transfer Source") + wip_warehouse = create_warehouse("_Test Partial Transfer WIP", company="_Test Company") + fg_warehouse = create_warehouse("_Test Partial Transfer FG", company="_Test Company") + fg_item = make_item(properties={"is_stock_item": 1}).name + rm_item = make_item(properties={"is_stock_item": 1}).name + bom = make_bom(item=fg_item, rate=100, raw_materials=[rm_item]) + make_stock_entry(item=rm_item, to_warehouse=source_warehouse, qty=100) + + wo = make_work_order(item=fg_item, qty=10, bom_no=bom.name, company="_Test Company") + wo.required_items[0].source_warehouse = source_warehouse + wo.wip_warehouse = wip_warehouse + wo.fg_warehouse = fg_warehouse + wo.submit() + + pick_list = pick_list_for_wo(wo.name, for_qty=wo.qty) + pick_list.save().submit() + self.assertEqual(pick_list.status, "Open") + + se = frappe.get_doc(create_stock_entry(pick_list.as_dict())) + self.assertTrue(all(row.pick_list_item for row in se.items)) + self.assertEqual(se.fg_completed_qty, 0) + + se.fg_completed_qty = 4 + se.get_items() + self.assertEqual(len(se.items), len(pick_list.locations)) + self.assertTrue(all(row.pick_list_item for row in se.items)) + se.fg_completed_qty = 0 + + for row in se.items: + row.qty = 4 + se.save().submit() + self.assertEqual(se.fg_completed_qty, 0) + + pick_list.reload() + self.assertEqual(pick_list.locations[0].transferred_qty, 4) + self.assertEqual(pick_list.status, "Partially Transferred") + + next_se = frappe.get_doc(create_stock_entry(pick_list.as_dict())) + self.assertEqual(len(next_se.items), 1) + self.assertEqual(next_se.items[0].qty, 6) + def test_pick_list_validation(self): warehouse = "_Test Warehouse - _TC" item = make_item("Test Non Serialized Pick List Item", properties={"is_stock_item": 1}).name diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index 6b8a2e2f321..d87b9479a96 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -176,6 +176,13 @@ frappe.ui.form.on("Stock Entry", { frm.set_df_property("fg_completed_qty", "read_only", 1); frm.set_df_property("get_items", "hidden", 1); } + + if (frm.doc.pick_list) { + frm.set_df_property("get_items", "hidden", 1); + if (!frm.doc.job_card) { + frm.set_df_property("fg_completed_qty", "read_only", 1); + } + } }, setup_quality_inspection: function (frm) { @@ -1405,10 +1412,14 @@ erpnext.stock.StockEntry = class StockEntry extends erpnext.stock.StockControlle ) { frappe.model.remove_from_locals("Work Order", this.frm.doc.work_order); } + + if (this.frm.doc.pick_list) { + frappe.model.remove_from_locals("Pick List", this.frm.doc.pick_list); + } } fg_completed_qty() { - if (!this.frm.doc.job_card) { + if (!this.frm.doc.job_card && !this.frm.doc.pick_list) { this.get_items(); } } diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 6f5ba8abb61..fc510b9b582 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -1449,6 +1449,9 @@ class StockEntry(StockController, SubcontractingInwardController): @frappe.whitelist() def get_items(self): + if self.pick_list: + return + self.set("items", []) if self.purpose_cls and hasattr(self.purpose_cls, "add_items"): self.purpose_cls(self).add_items()