diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js index 5ab291bb56a..63ead3d4e26 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.js +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js @@ -207,9 +207,9 @@ frappe.ui.form.on("Production Plan", { set_field_options("projected_qty_formula", projected_qty_formula); }, - has_unreserved_stock(frm, table) { + has_unreserved_stock(frm, table, qty_field = "required_qty") { let has_unreserved_stock = frm.doc[table].some( - (item) => flt(item.qty) > flt(item.stock_reserved_qty) + (item) => flt(item[qty_field]) > flt(item.stock_reserved_qty) ); return has_unreserved_stock; @@ -249,7 +249,7 @@ frappe.ui.form.on("Production Plan", { setup_stock_reservation_for_raw_materials(frm) { if (frm.doc.docstatus === 1 && frm.doc.reserve_stock) { - if (frm.events.has_unreserved_stock(frm, "mr_items")) { + if (frm.events.has_unreserved_stock(frm, "mr_items", "required_bom_qty")) { frm.add_custom_button( __("Reserve for Raw Materials"), () => erpnext.stock_reservation.make_entries(frm, "mr_items"), diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 899e28e0a1e..8e40804e761 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -1619,18 +1619,20 @@ def get_items_for_material_requests(doc, warehouses=None, get_parent_warehouse_d frappe.throw(_("For row {0}: Enter Planned Qty").format(data.get("idx"))) if bom_no: - if data.get("include_exploded_items") and doc.get("skip_available_sub_assembly_item"): - item_details = {} - if doc.get("sub_assembly_items"): - item_details = get_raw_materials_of_sub_assembly_items( - so_item_details[doc.get("sales_order")].keys() if so_item_details else [], - item_details, - company, - bom_no, - include_non_stock_items, - sub_assembly_items, - planned_qty=planned_qty, - ) + if ( + data.get("include_exploded_items") + and doc.get("skip_available_sub_assembly_item") + and doc.get("sub_assembly_items") + ): + item_details = get_raw_materials_of_sub_assembly_items( + so_item_details[doc.get("sales_order")].keys() if so_item_details else [], + item_details, + company, + bom_no, + include_non_stock_items, + sub_assembly_items, + planned_qty=planned_qty, + ) elif data.get("include_exploded_items") and include_subcontracted_items: # fetch exploded items from BOM @@ -2089,7 +2091,7 @@ def get_reserved_qty_for_sub_assembly(item_code, warehouse): def make_stock_reservation_entries(doc, items=None, table_name=None, notify=False): if isinstance(doc, str): doc = parse_json(doc) - doc = frappe.get_doc("Work Order", doc.get("name")) + doc = frappe.get_doc("Production Plan", doc.get("name")) if items and isinstance(items, str): items = parse_json(items) @@ -2113,9 +2115,22 @@ def make_stock_reservation_entries(doc, items=None, table_name=None, notify=Fals sre = StockReservation(doc, items=items, kwargs=mapper[child_table_name], notify=notify) if doc.docstatus == 1: - sre.make_stock_reservation_entries() - frappe.msgprint(_("Stock Reservation Entries Created"), alert=True) + sre_created = sre.make_stock_reservation_entries() + if sre_created: + frappe.msgprint(_("Stock Reservation Entries Created"), alert=True) elif doc.docstatus == 2: sre.cancel_stock_reservation_entries() doc.reload() + + +@frappe.whitelist() +def cancel_stock_reservation_entries(doc, sre_list): + if isinstance(doc, str): + doc = parse_json(doc) + doc = frappe.get_doc("Production Plan", doc.get("name")) + + sre = StockReservation(doc) + sre.cancel_stock_reservation_entries(sre_list) + + doc.reload() diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index 15342f9d6be..2010161170b 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -1415,8 +1415,12 @@ class TestProductionPlan(IntegrationTestCase): do_not_submit=1, skip_available_sub_assembly_item=1, warehouse="_Test Warehouse - _TC", + sub_assembly_warehouse="_Test Warehouse - _TC", ) + plan.get_sub_assembly_items() + plan.save() + items = get_items_for_material_requests( plan.as_dict(), warehouses=[{"warehouse": "_Test Warehouse - _TC"}] ) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 5a0af932bf0..10254256026 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -1426,7 +1426,7 @@ class WorkOrder(Document): return item_list = list(items.values()) - make_stock_reservation_entries(self, item_list, notify=True) + make_stock_reservation_entries(self, item_list, is_transfer=False, notify=True) def get_list_of_materials_for_reservation(self, stock_entry): items = frappe._dict() @@ -1648,7 +1648,7 @@ class WorkOrder(Document): @frappe.whitelist() -def make_stock_reservation_entries(doc, items=None, table_name=None, notify=False): +def make_stock_reservation_entries(doc, items=None, table_name=None, is_transfer=True, notify=False): if isinstance(doc, str): doc = parse_json(doc) doc = frappe.get_doc("Work Order", doc.get("name")) @@ -1658,14 +1658,15 @@ def make_stock_reservation_entries(doc, items=None, table_name=None, notify=Fals sre = StockReservation(doc, items=items, notify=notify) if doc.docstatus == 1: - if doc.production_plan: + if doc.production_plan and is_transfer: sre.transfer_reservation_entries_to( doc.production_plan, from_doctype="Production Plan", to_doctype="Work Order" ) else: - sre.make_stock_reservation_entries() + sre_created = sre.make_stock_reservation_entries() + if sre_created: + frappe.msgprint(_("Stock Reservation Entries Created"), alert=True) - frappe.msgprint(_("Stock Reservation Entries Created"), alert=True) elif doc.docstatus == 2: sre.cancel_stock_reservation_entries() diff --git a/erpnext/public/js/stock_reservation.js b/erpnext/public/js/stock_reservation.js index 854212ee5ae..4af158a132b 100644 --- a/erpnext/public/js/stock_reservation.js +++ b/erpnext/public/js/stock_reservation.js @@ -30,15 +30,15 @@ $.extend(erpnext.stock_reservation, { params["qty_field"] = { "Sales Order": "stock_qty", "Work Order": "required_qty", + "Production Plan": "required_qty", }[frm.doc.doctype]; if (frm.doc.doctype === "Production Plan") { if (table_name === "sub_assembly_items") { - params["qty_field"] = "qty"; params["item_code_field"] = "production_item"; params["warehouse_field"] = "fg_warehouse"; } else { - params["qty_field"] = "quantity"; + params["qty_field"] = "required_bom_qty"; } } @@ -50,6 +50,8 @@ $.extend(erpnext.stock_reservation, { params["method"] = { "Sales Order": "delivered_qty", + "Production Plan": + "erpnext.manufacturing.doctype.production_plan.production_plan.make_stock_reservation_entries", "Work Order": "erpnext.manufacturing.doctype.work_order.work_order.make_stock_reservation_entries", }[frm.doc.doctype]; @@ -141,6 +143,10 @@ $.extend(erpnext.stock_reservation, { }, render_items(frm, parms) { + if (!frm.doc.reserve_stock) { + return; + } + let dialog = erpnext.stock_reservation.dialog; let field = frappe.scrub(parms.child_doctype); @@ -155,25 +161,23 @@ $.extend(erpnext.stock_reservation, { let warehouse_field = parms.warehouse_field || "warehouse"; frm.doc[parms.table_name].forEach((item) => { - if (frm.doc.reserve_stock) { - let unreserved_qty = - (flt(item[qty_field]) - - (item.stock_reserved_qty - ? flt(item.stock_reserved_qty) - : flt(item[dispatch_qty_field]) * flt(item.conversion_factor || 1))) / - flt(item.conversion_factor || 1); + let unreserved_qty = + (flt(item[qty_field]) - + (item.stock_reserved_qty + ? flt(item.stock_reserved_qty) + : flt(item[dispatch_qty_field]) * flt(item.conversion_factor || 1))) / + flt(item.conversion_factor || 1); - if (unreserved_qty > 0) { - let args = { - __checked: 1, - item_code: item[item_code_field] || item.item_code, - warehouse: item[warehouse_field] || item.warehouse || item.source_warehouse, - }; + if (unreserved_qty > 0) { + let args = { + __checked: 1, + item_code: item[item_code_field] || item.item_code, + warehouse: item[warehouse_field] || item.warehouse || item.source_warehouse, + }; - args[field] = item.name; - args[qty_field] = unreserved_qty; - dialog.fields_dict.items.df.data.push(args); - } + args[field] = item.name; + args[qty_field] = unreserved_qty; + dialog.fields_dict.items.df.data.push(args); } }); @@ -257,11 +261,17 @@ $.extend(erpnext.stock_reservation, { }, cancel_stock_reservation(dialog, frm) { - var data = { sr_entries: dialog.fields_dict.sr_entries.grid.get_selected_children() }; + let data = { sr_entries: dialog.fields_dict.sr_entries.grid.get_selected_children() }; + let method = "erpnext.manufacturing.doctype.work_order.work_order.cancel_stock_reservation_entries"; + + if (frm.doc.doctype === "Production Plan") { + method = + "erpnext.manufacturing.doctype.production_plan.production_plan.cancel_stock_reservation_entries"; + } if (data.sr_entries?.length > 0) { frappe.call({ - method: "erpnext.manufacturing.doctype.work_order.work_order.cancel_stock_reservation_entries", + method: method, args: { doc: frm.doc, sre_list: data.sr_entries.map((item) => item.sre), diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js index a764a006b0c..68be8499ad2 100644 --- a/erpnext/stock/doctype/item/item.js +++ b/erpnext/stock/doctype/item/item.js @@ -16,11 +16,11 @@ frappe.ui.form.on("Item", { let msg = __( "Changing the valuation method to Moving Average will affect new transactions. If backdated entries are added, earlier FIFO-based entries will be reposted, which may change closing balances." ); - msg += "
"; + msg += "

"; msg += __( "Also you can't switch back to FIFO after setting the valuation method to Moving Average for this item." ); - msg += "
"; + msg += "

"; msg += __("Do you want to change valuation method?"); frappe.confirm( diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py index 11f9ee932ee..eb1658509bd 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py +++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py @@ -1908,19 +1908,33 @@ def get_reserved_voucher_details(kwargs): value = { "Delivery Note": ["Delivery Note Item", "against_sales_order"], - }.get(kwargs.get("voucher_type")) + "Stock Entry": ["Stock Entry", "work_order"], + "Work Order": ["Work Order", "production_plan"], + }.get(kwargs.get("sabb_voucher_type")) if not value or not kwargs.get("sabb_voucher_no"): return reserved_voucher_details - reserved_voucher_details = frappe.get_all( - value[0], - pluck=value[1], - filters={ + voucher_based_filters = { + "Delivery Note": { "name": kwargs.get("sabb_voucher_detail_no"), "parent": kwargs.get("sabb_voucher_no"), "docstatus": 1, }, + "Stock Entry": { + "name": kwargs.get("sabb_voucher_no"), + "docstatus": 1, + }, + "Work Order": { + "name": kwargs.get("sabb_voucher_no"), + "docstatus": 1, + }, + }.get(kwargs.get("sabb_voucher_type")) + + reserved_voucher_details = frappe.get_all( + value[0], + pluck=value[1], + filters=voucher_based_filters, ) return reserved_voucher_details diff --git a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py index b9a5593afc1..81047bd6d09 100644 --- a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py +++ b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py @@ -1044,6 +1044,7 @@ class StockReservation: child_doctype = frappe.scrub(self.doc.doctype + " Item") + is_sre_created = False for item in items: sre = frappe.new_doc("Stock Reservation Entry") if isinstance(item, dict): @@ -1106,6 +1107,9 @@ class StockReservation: self.set_serial_batch(sre, item.serial_and_batch_bundles) sre.submit() + is_sre_created = True + + return is_sre_created def set_serial_batch(self, sre, serial_batch_bundles): bundle_details = frappe.get_all(