Skip to content

fix: Maintain picked_qty in MR and WO Items #47599

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 7 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -118,16 +118,13 @@ frappe.ui.form.on("Material Request", {
}

if (flt(frm.doc.per_ordered, precision) < 100) {
let add_create_pick_list_button = () => {
if (frm.doc.material_request_type === "Material Transfer") {
frm.add_custom_button(
__("Pick List"),
() => frm.events.create_pick_list(frm),
__("Create")
);
};

if (frm.doc.material_request_type === "Material Transfer") {
add_create_pick_list_button();
frm.add_custom_button(
__("Material Transfer"),
() => frm.events.make_stock_entry(frm),
Expand Down
17 changes: 17 additions & 0 deletions erpnext/stock/doctype/material_request/material_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -874,6 +874,21 @@ def raise_work_orders(material_request):

@frappe.whitelist()
def create_pick_list(source_name, target_doc=None):
from erpnext.stock.doctype.packed_item.packed_item import is_product_bundle

def update_item_quantity(source, target, source_parent) -> None:
picked_qty = flt(source.picked_qty) / (flt(source.conversion_factor) or 1)
qty_to_be_picked = flt(source.qty) - max(picked_qty, flt(source.ordered_qty))

target.qty = qty_to_be_picked
target.stock_qty = qty_to_be_picked * flt(source.conversion_factor)

def should_pick_order_item(item) -> bool:
return item.qty > item.ordered_qty and not is_product_bundle(item.item_code)

if frappe.db.get_value("Material Request", source_name, "material_request_type") != "Material Transfer":
frappe.throw(_("Pick List can only be created from Material Transfer"))

doc = get_mapped_doc(
"Material Request",
source_name,
Expand All @@ -886,6 +901,8 @@ def create_pick_list(source_name, target_doc=None):
"Material Request Item": {
"doctype": "Pick List Item",
"field_map": {"name": "material_request_item", "stock_qty": "stock_qty"},
"postprocess": update_item_quantity,
"condition": should_pick_order_item,
},
},
target_doc,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"qty_info_sec_break",
"min_order_qty",
"projected_qty",
"picked_qty",
"qty_info_col_break",
"actual_qty",
"ordered_qty",
Expand Down Expand Up @@ -485,21 +486,30 @@
"options": "currency",
"print_hide": 1,
"read_only": 1
},
{
"depends_on": "eval:doc.docstatus==1",
"fieldname": "picked_qty",
"fieldtype": "Float",
"label": "Picked Qty (in Stock UOM)",
"no_copy": 1,
"read_only": 1
}
],
"idx": 1,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2024-03-27 13:10:05.224712",
"modified": "2025-05-19 05:15:34.485072",
"modified_by": "Administrator",
"module": "Stock",
"name": "Material Request Item",
"naming_rule": "Random",
"owner": "Administrator",
"permissions": [],
"row_format": "Dynamic",
"sort_field": "creation",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class MaterialRequestItem(Document):
parent: DF.Data
parentfield: DF.Data
parenttype: DF.Data
picked_qty: DF.Float
price_list_rate: DF.Currency
production_plan: DF.Link | None
project: DF.Link | None
Expand Down
1 change: 1 addition & 0 deletions erpnext/stock/doctype/pick_list/pick_list.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ frappe.ui.form.on("Pick List", {
return {
filters: {
material_request_type: ["=", frm.doc.purpose],
docstatus: 1,
},
};
});
Expand Down
57 changes: 50 additions & 7 deletions erpnext/stock/doctype/pick_list/pick_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -343,21 +343,27 @@ def update_status(self, status=None, update_modified=True):
def update_reference_qty(self):
packed_items = []
so_items = []
mr_items = []

for item in self.locations:
if item.product_bundle_item:
packed_items.append(item.sales_order_item)
elif item.sales_order_item:
so_items.append(item.sales_order_item)
elif item.material_request_item:
mr_items.append(item.material_request_item)

if packed_items:
self.update_packed_items_qty(packed_items)

if so_items:
self.update_sales_order_item_qty(so_items)

if mr_items:
self.update_material_request_item_qty(mr_items)

def update_packed_items_qty(self, packed_items):
picked_items = get_picked_items_qty(packed_items)
picked_items = get_so_picked_items_qty(packed_items)
self.validate_picked_qty(picked_items)

picked_qty = frappe._dict()
Expand All @@ -374,7 +380,7 @@ def update_packed_items_qty(self, packed_items):
)

def update_sales_order_item_qty(self, so_items):
picked_items = get_picked_items_qty(so_items)
picked_items = get_so_picked_items_qty(so_items)
self.validate_picked_qty(picked_items)

picked_qty = frappe._dict()
Expand All @@ -399,6 +405,23 @@ def update_sales_order_picking_status(self) -> None:
for sales_order in sales_orders:
frappe.get_doc("Sales Order", sales_order, for_update=True).update_picking_status()

def update_material_request_item_qty(self, mr_items):
picked_items = get_mr_picked_items_qty(mr_items)
# self.validate_picked_qty(picked_items) # TODO: how to handle this for MR?

picked_qty = frappe._dict()
for d in picked_items:
picked_qty[d.material_request_item] = d.picked_qty

for item in mr_items:
frappe.db.set_value(
"Material Request Item",
item,
"picked_qty",
flt(picked_qty.get(item)),
update_modified=False,
)

@frappe.whitelist()
def create_stock_reservation_entries(self, notify=True) -> None:
"""Creates Stock Reservation Entries for Sales Order Items against Pick List."""
Expand Down Expand Up @@ -800,7 +823,7 @@ def update_pick_list_status(pick_list):
doc.run_method("update_status")


def get_picked_items_qty(items) -> list[dict]:
def get_so_picked_items_qty(items) -> list[dict]:
pi_item = frappe.qb.DocType("Pick List Item")
return (
frappe.qb.from_(pi_item)
Expand All @@ -820,6 +843,26 @@ def get_picked_items_qty(items) -> list[dict]:
).run(as_dict=True)


def get_mr_picked_items_qty(items) -> list[dict]:
pi_item = frappe.qb.DocType("Pick List Item")
return (
frappe.qb.from_(pi_item)
.select(
pi_item.material_request_item,
pi_item.item_code,
pi_item.material_request,
Sum(pi_item.stock_qty).as_("stock_qty"),
Sum(pi_item.picked_qty).as_("picked_qty"),
)
.where((pi_item.docstatus == 1) & (pi_item.material_request_item.isin(items)))
.groupby(
pi_item.material_request_item,
pi_item.material_request,
)
.for_update()
).run(as_dict=True)


def validate_item_locations(pick_list):
if not pick_list.locations:
frappe.throw(_("Add items in the Item Locations table"))
Expand Down Expand Up @@ -977,7 +1020,7 @@ def validate_picked_materials(item_code, required_qty, locations, picked_item_de


def filter_locations_by_picked_materials(locations, picked_item_details) -> list[dict]:
filterd_locations = []
filtered_locations = []
precision = frappe.get_precision("Pick List Item", "qty")
for row in locations:
key = row.warehouse
Expand All @@ -986,7 +1029,7 @@ def filter_locations_by_picked_materials(locations, picked_item_details) -> list

picked_qty = picked_item_details.get(key, {}).get("picked_qty", 0)
if not picked_qty:
filterd_locations.append(row)
filtered_locations.append(row)
continue
if picked_qty > row.qty:
row.qty = 0
Expand All @@ -998,9 +1041,9 @@ def filter_locations_by_picked_materials(locations, picked_item_details) -> list
row.serial_nos = list(set(row.serial_nos) - set(picked_item_details[key].get("serial_no")))

if flt(row.qty, precision) > 0:
filterd_locations.append(row)
filtered_locations.append(row)

return filterd_locations
return filtered_locations


def get_available_item_locations_for_serial_and_batched_item(
Expand Down
Loading