diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py index e572540a04c..7b75bb0ffd3 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -181,7 +181,11 @@ class PickList(Document): self.item_location_map.setdefault( item_code, get_available_item_locations( - item_code, from_warehouses, self.item_count_map.get(item_code), self.company + item_code, + from_warehouses, + self.item_count_map.get(item_code), + self.company, + picked_item_details=picked_items_details.get(item_code), ), ) @@ -473,31 +477,38 @@ def get_items_with_location_and_quantity(item_doc, item_location_map, docstatus) def get_available_item_locations( - item_code, from_warehouses, required_qty, company, ignore_validation=False + item_code, + from_warehouses, + required_qty, + company, + ignore_validation=False, + picked_item_details=None, ): locations = [] + total_picked_qty = ( + sum([v.get("picked_qty") for k, v in picked_item_details.items()]) if picked_item_details else 0 + ) has_serial_no = frappe.get_cached_value("Item", item_code, "has_serial_no") has_batch_no = frappe.get_cached_value("Item", item_code, "has_batch_no") if has_batch_no and has_serial_no: locations = get_available_item_locations_for_serial_and_batched_item( - item_code, from_warehouses, required_qty, company + item_code, from_warehouses, required_qty, company, total_picked_qty ) elif has_serial_no: locations = get_available_item_locations_for_serialized_item( - item_code, from_warehouses, required_qty, company + item_code, from_warehouses, required_qty, company, total_picked_qty ) elif has_batch_no: locations = get_available_item_locations_for_batched_item( - item_code, from_warehouses, required_qty, company + item_code, from_warehouses, required_qty, company, total_picked_qty ) else: locations = get_available_item_locations_for_other_item( - item_code, from_warehouses, required_qty, company + item_code, from_warehouses, required_qty, company, total_picked_qty ) total_qty_available = sum(location.get("qty") for location in locations) - remaining_qty = required_qty - total_qty_available if remaining_qty > 0 and not ignore_validation: @@ -508,11 +519,44 @@ def get_available_item_locations( title=_("Insufficient Stock"), ) + if picked_item_details: + for location in list(locations): + key = ( + (location["warehouse"], location["batch_no"]) + if location.get("batch_no") + else location["warehouse"] + ) + + if key in picked_item_details: + picked_detail = picked_item_details[key] + + if picked_detail.get("serial_no") and location.get("serial_no"): + location["serial_no"] = list( + set(location["serial_no"]).difference(set(picked_detail["serial_no"])) + ) + location["qty"] = len(location["serial_no"]) + else: + location["qty"] -= picked_detail.get("picked_qty") + + if location["qty"] < 1: + locations.remove(location) + + total_qty_available = sum(location.get("qty") for location in locations) + remaining_qty = required_qty - total_qty_available + + if remaining_qty > 0 and not ignore_validation: + frappe.msgprint( + _("{0} units of Item {1} is picked in another Pick List.").format( + remaining_qty, frappe.get_desk_link("Item", item_code) + ), + title=_("Already Picked"), + ) + return locations def get_available_item_locations_for_serialized_item( - item_code, from_warehouses, required_qty, company + item_code, from_warehouses, required_qty, company, total_picked_qty=0 ): sn = frappe.qb.DocType("Serial No") query = ( @@ -520,7 +564,7 @@ def get_available_item_locations_for_serialized_item( .select(sn.name, sn.warehouse) .where((sn.item_code == item_code) & (sn.company == company)) .orderby(sn.purchase_date) - .limit(cint(required_qty)) + .limit(cint(required_qty + total_picked_qty)) ) if from_warehouses: @@ -542,7 +586,7 @@ def get_available_item_locations_for_serialized_item( def get_available_item_locations_for_batched_item( - item_code, from_warehouses, required_qty, company + item_code, from_warehouses, required_qty, company, total_picked_qty=0 ): sle = frappe.qb.DocType("Stock Ledger Entry") batch = frappe.qb.DocType("Batch") @@ -562,6 +606,7 @@ def get_available_item_locations_for_batched_item( .groupby(sle.warehouse, sle.batch_no, sle.item_code) .having(Sum(sle.actual_qty) > 0) .orderby(IfNull(batch.expiry_date, "2200-01-01"), batch.creation, sle.batch_no, sle.warehouse) + .limit(cint(required_qty + total_picked_qty)) ) if from_warehouses: @@ -571,7 +616,7 @@ def get_available_item_locations_for_batched_item( def get_available_item_locations_for_serial_and_batched_item( - item_code, from_warehouses, required_qty, company + item_code, from_warehouses, required_qty, company, total_picked_qty=0 ): # Get batch nos by FIFO locations = get_available_item_locations_for_batched_item( @@ -594,23 +639,26 @@ def get_available_item_locations_for_serial_and_batched_item( (conditions) & (sn.batch_no == location.batch_no) & (sn.warehouse == location.warehouse) ) .orderby(sn.purchase_date) - .limit(cint(location.qty)) + .limit(cint(location.qty + total_picked_qty)) ).run(as_dict=True) serial_nos = [sn.name for sn in serial_nos] location.serial_no = serial_nos + location.qty = len(serial_nos) return locations -def get_available_item_locations_for_other_item(item_code, from_warehouses, required_qty, company): +def get_available_item_locations_for_other_item( + item_code, from_warehouses, required_qty, company, total_picked_qty=0 +): bin = frappe.qb.DocType("Bin") query = ( frappe.qb.from_(bin) .select(bin.warehouse, bin.actual_qty.as_("qty")) .where((bin.item_code == item_code) & (bin.actual_qty > 0)) .orderby(bin.creation) - .limit(cint(required_qty)) + .limit(cint(required_qty + total_picked_qty)) ) if from_warehouses: