diff --git a/erpnext/stock/doctype/pick_list/pick_list.js b/erpnext/stock/doctype/pick_list/pick_list.js index 38b1ca7f76e..8f74373ac8d 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.js +++ b/erpnext/stock/doctype/pick_list/pick_list.js @@ -2,6 +2,13 @@ // For license information, please see license.txt frappe.ui.form.on("Pick List", { + after_save(frm) { + setTimeout(() => { + // Added to fix the issue of locations table not getting updated after save + frm.reload_doc(); + }, 500); + }, + setup: (frm) => { frm.set_indicator_formatter("item_code", function (doc) { return doc.stock_qty === 0 ? "red" : "green"; diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py index a2a07e6d11e..a94370c48a5 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -25,10 +25,38 @@ from erpnext.stock.get_item_details import get_conversion_factor class PickList(Document): def validate(self): + self.validate_expired_batches() self.validate_for_qty() self.validate_stock_qty() self.check_serial_no_status() + def validate_expired_batches(self): + batches = [] + for row in self.get("locations"): + if row.get("batch_no") and row.get("picked_qty"): + batches.append(row.batch_no) + + if batches: + batch = frappe.qb.DocType("Batch") + query = ( + frappe.qb.from_(batch) + .select(batch.name) + .where( + (batch.name.isin(batches)) + & (batch.expiry_date <= frappe.utils.nowdate()) + & (batch.expiry_date.isnotnull()) + ) + ) + + expired_batches = query.run(as_dict=True) + if expired_batches: + msg = "" + + frappe.throw( + _("The following batches are expired, please restock them:
{0}").format(msg), + title=_("Expired Batches"), + ) + def before_save(self): self.update_status() if not self.pick_manually: @@ -271,6 +299,7 @@ class PickList(Document): self.remove(row) updated_locations = frappe._dict() + len_idx = len(self.get("locations")) or 0 for item_doc in items: item_code = item_doc.item_code @@ -313,6 +342,8 @@ class PickList(Document): if location.picked_qty > location.stock_qty: location.picked_qty = location.stock_qty + len_idx += 1 + location.idx = len_idx self.append("locations", location) # If table is empty on update after submit, set stock_qty, picked_qty to 0 so that indicator is red @@ -321,6 +352,9 @@ class PickList(Document): for location in locations_replica: location.stock_qty = 0 location.picked_qty = 0 + + len_idx += 1 + location.idx = len_idx self.append("locations", location) frappe.msgprint( _( @@ -430,9 +464,11 @@ class PickList(Document): pi_item.item_code, pi_item.warehouse, pi_item.batch_no, - Sum(Case().when(pi_item.picked_qty > 0, pi_item.picked_qty).else_(pi_item.stock_qty)).as_( - "picked_qty" - ), + Sum( + Case() + .when((pi_item.picked_qty > 0) & (pi_item.docstatus == 1), pi_item.picked_qty) + .else_(pi_item.stock_qty) + ).as_("picked_qty"), Replace(GROUP_CONCAT(pi_item.serial_no), ",", "\n").as_("serial_no"), ) .where( @@ -465,8 +501,32 @@ class PickList(Document): else: picked_items[item_data.item_code][key] = data + self.update_picked_item_from_current_pick_list(picked_items) + return picked_items + def update_picked_item_from_current_pick_list(self, picked_items): + for row in self.get("locations"): + if flt(row.picked_qty) > 0: + key = (row.warehouse, row.batch_no) if row.batch_no else row.warehouse + serial_no = [x for x in row.serial_no.split("\n") if x] if row.serial_no else None + if row.item_code not in picked_items: + picked_items[row.item_code] = {} + + if key not in picked_items[row.item_code]: + picked_items[row.item_code][key] = frappe._dict( + { + "picked_qty": 0, + "serial_no": [], + "batch_no": row.batch_no or "", + "warehouse": row.warehouse, + } + ) + + picked_items[row.item_code][key]["picked_qty"] += flt(row.stock_qty) or flt(row.picked_qty) + if serial_no: + picked_items[row.item_code][key]["serial_no"].extend(serial_no) + def _get_product_bundles(self) -> dict[str, str]: # Dict[so_item_row: item_code] product_bundles = {}