From 8c2c90f77a42acbfa21fd5ca5391770d101ed6ff Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 22 Dec 2023 15:50:07 +0530 Subject: [PATCH] feat: provision to add items in Stock Reservation dialog (backport #38558) (#38920) * feat: provision to update items in Stock Reservation dialog (cherry picked from commit 9471d8fff94f84a627957c5ff2c7f8f1ed53a098) * fix(ux): show row index and field label while selecting the Sales Order Item (cherry picked from commit 00261094c8ce0663f953b73ae17a5e7f3f494b59) * feat: provision to add items in Stock Reservation dialog (cherry picked from commit 8d5045ef4caf695dbfdc087b78c8f68a79976813) --------- Co-authored-by: s-aga-r --- erpnext/controllers/queries.py | 28 +++++++ .../doctype/sales_order/sales_order.js | 78 +++++++++++++++++-- 2 files changed, 101 insertions(+), 5 deletions(-) diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index 199732b152..e858820965 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -891,3 +891,31 @@ def get_payment_terms_for_references(doctype, txt, searchfield, start, page_len, as_list=1, ) return terms + + +@frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs +def get_filtered_child_rows(doctype, txt, searchfield, start, page_len, filters) -> list: + table = frappe.qb.DocType(doctype) + query = ( + frappe.qb.from_(table) + .select( + table.name, + Concat("#", table.idx, ", ", table.item_code), + ) + .orderby(table.idx) + .offset(start) + .limit(page_len) + ) + + if filters: + for field, value in filters.items(): + query = query.where(table[field] == value) + + if txt: + txt += "%" + query = query.where( + ((table.idx.like(txt.replace("#", ""))) | (table.item_code.like(txt))) | (table.name.like(txt)) + ) + + return query.run(as_dict=False) diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js index 97b214e33e..b206e3fe33 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.js +++ b/erpnext/selling/doctype/sales_order/sales_order.js @@ -182,7 +182,7 @@ frappe.ui.form.on("Sales Order", { create_stock_reservation_entries(frm) { const dialog = new frappe.ui.Dialog({ title: __("Stock Reservation"), - size: "large", + size: "extra-large", fields: [ { fieldname: "set_warehouse", @@ -207,6 +207,50 @@ frappe.ui.form.on("Sales Order", { }, }, {fieldtype: "Column Break"}, + { + fieldname: "add_item", + fieldtype: "Link", + label: __("Add Item"), + options: "Sales Order Item", + get_query: () => { + return { + query: "erpnext.controllers.queries.get_filtered_child_rows", + filters: { + "parenttype": frm.doc.doctype, + "parent": frm.doc.name, + "reserve_stock": 1, + } + } + }, + onchange: () => { + let sales_order_item = dialog.get_value("add_item"); + + if (sales_order_item) { + frm.doc.items.forEach(item => { + if (item.name === sales_order_item) { + let unreserved_qty = (flt(item.stock_qty) - (item.stock_reserved_qty ? flt(item.stock_reserved_qty) : (flt(item.delivered_qty) * flt(item.conversion_factor)))) / flt(item.conversion_factor); + + if (unreserved_qty > 0) { + dialog.fields_dict.items.df.data.forEach((row) => { + if (row.sales_order_item === sales_order_item) { + unreserved_qty -= row.qty_to_reserve; + } + }); + } + + dialog.fields_dict.items.df.data.push({ + 'sales_order_item': item.name, + 'item_code': item.item_code, + 'warehouse': dialog.get_value("set_warehouse") || item.warehouse, + 'qty_to_reserve': Math.max(unreserved_qty, 0) + }); + dialog.fields_dict.items.grid.refresh(); + dialog.set_value("add_item", undefined); + } + }); + } + }, + }, {fieldtype: "Section Break"}, { fieldname: "items", @@ -218,10 +262,34 @@ frappe.ui.form.on("Sales Order", { fields: [ { fieldname: "sales_order_item", - fieldtype: "Data", + fieldtype: "Link", label: __("Sales Order Item"), + options: "Sales Order Item", reqd: 1, - read_only: 1, + in_list_view: 1, + get_query: () => { + return { + query: "erpnext.controllers.queries.get_filtered_child_rows", + filters: { + "parenttype": frm.doc.doctype, + "parent": frm.doc.name, + "reserve_stock": 1, + } + } + }, + onchange: (event) => { + if (event) { + let name = $(event.currentTarget).closest(".grid-row").attr("data-name"); + let item_row = dialog.fields_dict.items.grid.grid_rows_by_docname[name].doc; + + frm.doc.items.forEach(item => { + if (item.name === item_row.sales_order_item) { + item_row.item_code = item.item_code; + } + }); + dialog.fields_dict.items.grid.refresh(); + } + } }, { fieldname: "item_code", @@ -284,14 +352,14 @@ frappe.ui.form.on("Sales Order", { frm.doc.items.forEach(item => { if (item.reserve_stock) { - let unreserved_qty = (flt(item.stock_qty) - (item.stock_reserved_qty ? flt(item.stock_reserved_qty) : (flt(item.delivered_qty) * flt(item.conversion_factor)))) + let unreserved_qty = (flt(item.stock_qty) - (item.stock_reserved_qty ? flt(item.stock_reserved_qty) : (flt(item.delivered_qty) * flt(item.conversion_factor)))) / flt(item.conversion_factor); if (unreserved_qty > 0) { dialog.fields_dict.items.df.data.push({ 'sales_order_item': item.name, 'item_code': item.item_code, 'warehouse': item.warehouse, - 'qty_to_reserve': (unreserved_qty / flt(item.conversion_factor)) + 'qty_to_reserve': unreserved_qty }); } }