feat: provision to skip available sub assembly items in the production plan
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.json b/erpnext/manufacturing/doctype/production_plan/production_plan.json
index fdaa4a2..232f1cb 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.json
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.json
@@ -35,8 +35,12 @@
"section_break_25",
"prod_plan_references",
"section_break_24",
- "get_sub_assembly_items",
"combine_sub_items",
+ "section_break_ucc4",
+ "skip_available_sub_assembly_item",
+ "column_break_igxl",
+ "get_sub_assembly_items",
+ "section_break_g4ip",
"sub_assembly_items",
"download_materials_request_plan_section_section",
"download_materials_required",
@@ -351,12 +355,12 @@
{
"fieldname": "section_break_24",
"fieldtype": "Section Break",
- "hide_border": 1
+ "hide_border": 1,
+ "label": "Sub Assembly Items"
},
{
"fieldname": "sub_assembly_items",
"fieldtype": "Table",
- "label": "Sub Assembly Items",
"no_copy": 1,
"options": "Production Plan Sub Assembly Item"
},
@@ -392,13 +396,33 @@
"fieldname": "download_materials_request_plan_section_section",
"fieldtype": "Section Break",
"label": "Download Materials Request Plan Section"
+ },
+ {
+ "default": "0",
+ "description": "System consider the projected quantity to check available or will be available sub-assembly items ",
+ "fieldname": "skip_available_sub_assembly_item",
+ "fieldtype": "Check",
+ "label": "Skip Available Sub Assembly Items"
+ },
+ {
+ "fieldname": "section_break_ucc4",
+ "fieldtype": "Column Break",
+ "hide_border": 1
+ },
+ {
+ "fieldname": "section_break_g4ip",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "column_break_igxl",
+ "fieldtype": "Column Break"
}
],
"icon": "fa fa-calendar",
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2023-03-31 10:30:48.118932",
+ "modified": "2023-05-22 23:36:31.770517",
"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 f9e68b9..e40539a 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py
@@ -718,7 +718,9 @@
frappe.throw(_("Row #{0}: Please select Item Code in Assembly Items").format(row.idx))
bom_data = []
- get_sub_assembly_items(row.bom_no, bom_data, row.planned_qty)
+
+ warehouse = row.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)
@@ -894,7 +896,9 @@
build_csv_response(item_list, doc.name)
-def get_exploded_items(item_details, company, bom_no, include_non_stock_items, planned_qty=1):
+def get_exploded_items(
+ item_details, company, bom_no, include_non_stock_items, planned_qty=1, doc=None
+):
bei = frappe.qb.DocType("BOM Explosion Item")
bom = frappe.qb.DocType("BOM")
item = frappe.qb.DocType("Item")
@@ -1271,6 +1275,12 @@
include_safety_stock = doc.get("include_safety_stock")
so_item_details = frappe._dict()
+
+ sub_assembly_items = {}
+ if doc.get("skip_available_sub_assembly_item"):
+ for d in doc.get("sub_assembly_items"):
+ sub_assembly_items.setdefault((d.get("production_item"), d.get("bom_no")), d.get("qty"))
+
for data in po_items:
if not data.get("include_exploded_items") and doc.get("sub_assembly_items"):
data["include_exploded_items"] = 1
@@ -1296,10 +1306,24 @@
frappe.throw(_("For row {0}: Enter Planned Qty").format(data.get("idx")))
if bom_no:
- if data.get("include_exploded_items") and include_subcontracted_items:
+ if (
+ data.get("include_exploded_items")
+ and doc.get("sub_assembly_items")
+ and doc.get("skip_available_sub_assembly_item")
+ ):
+ item_details = get_raw_materials_of_sub_assembly_items(
+ 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
item_details = get_exploded_items(
- item_details, company, bom_no, include_non_stock_items, planned_qty=planned_qty
+ item_details, company, bom_no, include_non_stock_items, planned_qty=planned_qty, doc=doc
)
else:
item_details = get_subitems(
@@ -1456,12 +1480,22 @@
}
-def get_sub_assembly_items(bom_no, bom_data, to_produce_qty, indent=0):
+def get_sub_assembly_items(bom_no, bom_data, to_produce_qty, company, warehouse=None, indent=0):
data = get_bom_children(parent=bom_no)
for d in data:
if d.expandable:
parent_item_code = frappe.get_cached_value("BOM", bom_no, "item")
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)
+
+ 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
+
bom_data.append(
frappe._dict(
{
@@ -1481,7 +1515,7 @@
)
if d.value:
- get_sub_assembly_items(d.value, bom_data, stock_qty, indent=indent + 1)
+ get_sub_assembly_items(d.value, bom_data, stock_qty, company, warehouse, indent=indent + 1)
def set_default_warehouses(row, default_warehouses):
@@ -1519,3 +1553,68 @@
)
return reserved_qty_for_production_plan - reserved_qty_for_production
+
+
+def get_raw_materials_of_sub_assembly_items(
+ item_details, company, bom_no, include_non_stock_items, sub_assembly_items, planned_qty=1
+):
+
+ bei = frappe.qb.DocType("BOM Item")
+ bom = frappe.qb.DocType("BOM")
+ item = frappe.qb.DocType("Item")
+ item_default = frappe.qb.DocType("Item Default")
+ item_uom = frappe.qb.DocType("UOM Conversion Detail")
+
+ items = (
+ frappe.qb.from_(bei)
+ .join(bom)
+ .on(bom.name == bei.parent)
+ .join(item)
+ .on(item.name == bei.item_code)
+ .left_join(item_default)
+ .on((item_default.parent == item.name) & (item_default.company == company))
+ .left_join(item_uom)
+ .on((item.name == item_uom.parent) & (item_uom.uom == item.purchase_uom))
+ .select(
+ (IfNull(Sum(bei.stock_qty / IfNull(bom.quantity, 1)), 0) * planned_qty).as_("qty"),
+ item.item_name,
+ item.name.as_("item_code"),
+ bei.description,
+ bei.stock_uom,
+ bei.bom_no,
+ item.min_order_qty,
+ bei.source_warehouse,
+ item.default_material_request_type,
+ item.min_order_qty,
+ item_default.default_warehouse,
+ item.purchase_uom,
+ item_uom.conversion_factor,
+ item.safety_stock,
+ )
+ .where(
+ (bei.docstatus == 1)
+ & (bom.name == bom_no)
+ & (item.is_stock_item.isin([0, 1]) if include_non_stock_items else item.is_stock_item == 1)
+ )
+ .groupby(bei.item_code, bei.stock_uom)
+ ).run(as_dict=True)
+
+ for item in items:
+ key = (item.item_code, item.bom_no)
+ if item.bom_no and key in sub_assembly_items:
+ planned_qty = flt(sub_assembly_items[key])
+ get_raw_materials_of_sub_assembly_items(
+ item_details,
+ company,
+ item.bom_no,
+ include_non_stock_items,
+ sub_assembly_items,
+ planned_qty=planned_qty,
+ )
+ else:
+ if not item.conversion_factor and item.purchase_uom:
+ item.conversion_factor = get_uom_conversion_factor(item.item_code, item.purchase_uom)
+
+ item_details.setdefault(item.get("item_code"), item)
+
+ return item_details
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 4eb6bf6..fde0404 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
@@ -28,7 +28,11 @@
"uom",
"stock_uom",
"column_break_22",
- "description"
+ "description",
+ "section_break_4rxf",
+ "actual_qty",
+ "column_break_xfhm",
+ "projected_qty"
],
"fields": [
{
@@ -183,12 +187,34 @@
"fieldtype": "Datetime",
"in_list_view": 1,
"label": "Schedule Date"
+ },
+ {
+ "fieldname": "section_break_4rxf",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "actual_qty",
+ "fieldtype": "Float",
+ "label": "Actual Qty",
+ "no_copy": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "column_break_xfhm",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "projected_qty",
+ "fieldtype": "Float",
+ "label": "Projected Qty",
+ "no_copy": 1,
+ "read_only": 1
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2022-11-28 13:50:15.116082",
+ "modified": "2023-05-22 17:52:34.708879",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Production Plan Sub Assembly Item",