fix: parent warehouse checks in the production plan for sub-assemblies (#40150)
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js
index c9c474d..667ece2 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.js
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js
@@ -518,6 +518,12 @@
}
});
+frappe.ui.form.on("Production Plan Sub Assembly Item", {
+ fg_warehouse(frm, cdt, cdn) {
+ erpnext.utils.copy_value_in_all_rows(frm.doc, cdt, cdn, "sub_assembly_items", "fg_warehouse");
+ },
+})
+
frappe.tour['Production Plan'] = [
{
fieldname: "get_items_from",
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.json b/erpnext/manufacturing/doctype/production_plan/production_plan.json
index 54c3893..84bbad5 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.json
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.json
@@ -421,9 +421,11 @@
"fieldtype": "Column Break"
},
{
+ "description": "When a parent warehouse is chosen, the system conducts stock checks against the associated child warehouses",
"fieldname": "sub_assembly_warehouse",
"fieldtype": "Link",
"label": "Sub Assembly Warehouse",
+ "mandatory_depends_on": "eval:doc.skip_available_sub_assembly_item === 1",
"options": "Warehouse"
},
{
@@ -437,7 +439,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2024-02-11 15:42:47.642481",
+ "modified": "2024-02-27 13:34:20.692211",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Production Plan",
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py
index 517b2b0..c852f84 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py
@@ -894,8 +894,8 @@
sub_assembly_items_store = [] # temporary store to process all subassembly items
for row in self.po_items:
- if self.skip_available_sub_assembly_item and not row.warehouse:
- frappe.throw(_("Row #{0}: Please select the FG Warehouse in Assembly Items").format(row.idx))
+ if self.skip_available_sub_assembly_item and not self.sub_assembly_warehouse:
+ frappe.throw(_("Row #{0}: Please select the Sub Assembly Warehouse").format(row.idx))
if not row.item_code:
frappe.throw(_("Row #{0}: Please select Item Code in Assembly Items").format(row.idx))
@@ -905,15 +905,24 @@
bom_data = []
- warehouse = (
- (self.sub_assembly_warehouse or row.warehouse)
- if self.skip_available_sub_assembly_item
- else None
- )
+ warehouse = (self.sub_assembly_warehouse) if self.skip_available_sub_assembly_item else None
get_sub_assembly_items(row.bom_no, bom_data, row.planned_qty, self.company, warehouse=warehouse)
self.set_sub_assembly_items_based_on_level(row, bom_data, manufacturing_type)
sub_assembly_items_store.extend(bom_data)
+ if not sub_assembly_items_store and self.skip_available_sub_assembly_item:
+ message = (
+ _(
+ "As there are sufficient Sub Assembly Items, Work Order is not required for Warehouse {0}."
+ ).format(self.sub_assembly_warehouse)
+ + "<br><br>"
+ )
+ message += _(
+ "If you still want to proceed, please disable 'Skip Available Sub Assembly Items' checkbox."
+ )
+
+ frappe.msgprint(message, title=_("Note"))
+
if self.combine_sub_items:
# Combine subassembly items
sub_assembly_items_store = self.combine_subassembly_items(sub_assembly_items_store)
@@ -926,15 +935,19 @@
def set_sub_assembly_items_based_on_level(self, row, bom_data, manufacturing_type=None):
"Modify bom_data, set additional details."
+ is_group_warehouse = frappe.db.get_value("Warehouse", self.sub_assembly_warehouse, "is_group")
+
for data in bom_data:
data.qty = data.stock_qty
data.production_plan_item = row.name
- data.fg_warehouse = self.sub_assembly_warehouse or row.warehouse
data.schedule_date = row.planned_start_date
data.type_of_manufacturing = manufacturing_type or (
"Subcontract" if data.is_sub_contracted_item else "In House"
)
+ if not is_group_warehouse:
+ data.fg_warehouse = self.sub_assembly_warehouse
+
def set_default_supplier_for_subcontracting_order(self):
items = [
d.production_item for d in self.sub_assembly_items if d.type_of_manufacturing == "Subcontract"
@@ -1478,7 +1491,7 @@
so_item_details = frappe._dict()
sub_assembly_items = {}
- if doc.get("skip_available_sub_assembly_item"):
+ if doc.get("skip_available_sub_assembly_item") and doc.get("sub_assembly_items"):
for d in doc.get("sub_assembly_items"):
sub_assembly_items.setdefault((d.get("production_item"), d.get("bom_no")), d.get("qty"))
@@ -1690,34 +1703,37 @@
stock_qty = (d.stock_qty / d.parent_bom_qty) * flt(to_produce_qty)
if warehouse:
- bin_dict = get_bin_details(d, company, for_warehouse=warehouse)
+ bin_details = get_bin_details(d, company, for_warehouse=warehouse)
- if bin_dict and bin_dict[0].projected_qty > 0:
- if bin_dict[0].projected_qty > stock_qty:
- continue
- else:
- stock_qty = stock_qty - bin_dict[0].projected_qty
+ for _bin_dict in bin_details:
+ if _bin_dict.projected_qty > 0:
+ if _bin_dict.projected_qty > stock_qty:
+ stock_qty = 0
+ continue
+ else:
+ stock_qty = stock_qty - _bin_dict.projected_qty
- bom_data.append(
- frappe._dict(
- {
- "parent_item_code": parent_item_code,
- "description": d.description,
- "production_item": d.item_code,
- "item_name": d.item_name,
- "stock_uom": d.stock_uom,
- "uom": d.stock_uom,
- "bom_no": d.value,
- "is_sub_contracted_item": d.is_sub_contracted_item,
- "bom_level": indent,
- "indent": indent,
- "stock_qty": stock_qty,
- }
+ if stock_qty > 0:
+ bom_data.append(
+ frappe._dict(
+ {
+ "parent_item_code": parent_item_code,
+ "description": d.description,
+ "production_item": d.item_code,
+ "item_name": d.item_name,
+ "stock_uom": d.stock_uom,
+ "uom": d.stock_uom,
+ "bom_no": d.value,
+ "is_sub_contracted_item": d.is_sub_contracted_item,
+ "bom_level": indent,
+ "indent": indent,
+ "stock_qty": stock_qty,
+ }
+ )
)
- )
- if d.value:
- get_sub_assembly_items(d.value, bom_data, stock_qty, company, warehouse, indent=indent + 1)
+ if d.value:
+ get_sub_assembly_items(d.value, bom_data, stock_qty, company, warehouse, indent=indent + 1)
def set_default_warehouses(row, default_warehouses):
diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
index 53537f9..0bf3705 100644
--- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
@@ -1205,6 +1205,7 @@
ignore_existing_ordered_qty=1,
do_not_submit=1,
skip_available_sub_assembly_item=1,
+ sub_assembly_warehouse="_Test Warehouse - _TC",
warehouse="_Test Warehouse - _TC",
)
@@ -1338,6 +1339,7 @@
ignore_existing_ordered_qty=1,
do_not_submit=1,
skip_available_sub_assembly_item=1,
+ sub_assembly_warehouse="_Test Warehouse - _TC",
warehouse="_Test Warehouse - _TC",
)
@@ -1590,6 +1592,48 @@
for row in work_orders:
self.assertEqual(row.qty, wo_qty[row.name])
+ def test_parent_warehouse_for_sub_assembly_items(self):
+ from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom
+ from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
+
+ parent_warehouse = "_Test Warehouse Group - _TC"
+ sub_warehouse = create_warehouse("Sub Warehouse", company="_Test Company")
+
+ fg_item = make_item(properties={"is_stock_item": 1}).name
+ sf_item = make_item(properties={"is_stock_item": 1}).name
+ rm_item = make_item(properties={"is_stock_item": 1}).name
+
+ bom_tree = {fg_item: {sf_item: {rm_item: {}}}}
+ create_nested_bom(bom_tree, prefix="")
+
+ pln = create_production_plan(
+ item_code=fg_item,
+ planned_qty=10,
+ warehouse="_Test Warehouse - _TC",
+ sub_assembly_warehouse=parent_warehouse,
+ skip_available_sub_assembly_item=1,
+ do_not_submit=1,
+ skip_getting_mr_items=1,
+ )
+
+ pln.get_sub_assembly_items()
+
+ for row in pln.sub_assembly_items:
+ self.assertFalse(row.fg_warehouse)
+ self.assertEqual(row.production_item, sf_item)
+ self.assertEqual(row.qty, 10.0)
+
+ make_stock_entry(item_code=sf_item, qty=5, target=sub_warehouse, rate=100)
+
+ pln.sub_assembly_items = []
+ pln.get_sub_assembly_items()
+
+ self.assertEqual(pln.sub_assembly_warehouse, parent_warehouse)
+ for row in pln.sub_assembly_items:
+ self.assertFalse(row.fg_warehouse)
+ self.assertEqual(row.production_item, sf_item)
+ self.assertEqual(row.qty, 5.0)
+
def create_production_plan(**args):
"""
diff --git a/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json b/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json
index 0688278..78a3897 100644
--- a/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json
+++ b/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json
@@ -11,6 +11,7 @@
"bom_no",
"column_break_6",
"planned_qty",
+ "stock_uom",
"warehouse",
"planned_start_date",
"section_break_9",
@@ -18,7 +19,6 @@
"ordered_qty",
"column_break_17",
"description",
- "stock_uom",
"produced_qty",
"reference_section",
"sales_order",
@@ -65,6 +65,7 @@
"width": "100px"
},
{
+ "columns": 1,
"fieldname": "planned_qty",
"fieldtype": "Float",
"in_list_view": 1,
@@ -80,6 +81,7 @@
"fieldtype": "Column Break"
},
{
+ "columns": 2,
"fieldname": "warehouse",
"fieldtype": "Link",
"in_list_view": 1,
@@ -141,8 +143,10 @@
"width": "200px"
},
{
+ "columns": 1,
"fieldname": "stock_uom",
"fieldtype": "Link",
+ "in_list_view": 1,
"label": "UOM",
"oldfieldname": "stock_uom",
"oldfieldtype": "Data",
@@ -216,7 +220,7 @@
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2022-11-25 14:15:40.061514",
+ "modified": "2024-02-27 13:24:43.571844",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Production Plan Item",
diff --git a/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json b/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json
index aff740b..7965965 100644
--- a/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json
+++ b/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json
@@ -101,7 +101,6 @@
"columns": 1,
"fieldname": "bom_level",
"fieldtype": "Int",
- "in_list_view": 1,
"label": "Level (BOM)",
"read_only": 1
},
@@ -149,8 +148,10 @@
"label": "Indent"
},
{
+ "columns": 2,
"fieldname": "fg_warehouse",
"fieldtype": "Link",
+ "in_list_view": 1,
"label": "Target Warehouse",
"options": "Warehouse"
},
@@ -170,6 +171,7 @@
"options": "Supplier"
},
{
+ "columns": 1,
"fieldname": "schedule_date",
"fieldtype": "Datetime",
"in_list_view": 1,
@@ -207,7 +209,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2023-11-03 13:33:42.959387",
+ "modified": "2024-02-27 13:45:17.422435",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Production Plan Sub Assembly Item",