feat: phantom bom (#50351)

* feat: add phantom bom settings in bom doctype

* feat: new explosion logic for production plan, subcontracting and work order/manufacturing

* feat: modify explosion logic in reports and bom creator

* fix: failing test

* feat: add convert to phantom item support in bom creator

* test: added test cases

* fix: always fetch rm rate if phantom bom

* refactor: PP phantom explosion logic

* fix: report test cases

* feat: add phantom item in description of item if phantom item in bom tree

* fix: hide create button if bom is phantom

* fix: bugs found by coderabbit
This commit is contained in:
Mihir Kandoi
2025-11-16 15:08:08 +05:30
committed by GitHub
parent 9b303a2272
commit e5e26cd92a
22 changed files with 383 additions and 59 deletions

View File

@@ -140,6 +140,17 @@ class BOMConfigurator {
},
btnClass: "hidden-xs",
},
{
label: __(frappe.utils.icon("add", "sm") + " Phantom Item"),
click: function (node) {
let view = frappe.views.trees["BOM Configurator"];
view.events.add_sub_assembly(node, view, true);
},
condition: function (node) {
return node.expandable;
},
btnClass: "hidden-xs",
},
{
label: __("Collapse All"),
click: function (node) {
@@ -170,6 +181,17 @@ class BOMConfigurator {
},
btnClass: "hidden-xs",
},
{
label: __(frappe.utils.icon("move", "sm") + " Phantom Item"),
click: function (node) {
let view = frappe.views.trees["BOM Configurator"];
view.events.convert_to_sub_assembly(node, view, true);
},
condition: function (node) {
return !node.expandable;
},
btnClass: "hidden-xs",
},
{
label: __(frappe.utils.icon("delete", "sm") + " Item"),
click: function (node) {
@@ -253,10 +275,10 @@ class BOMConfigurator {
}
}
add_sub_assembly(node, view) {
add_sub_assembly(node, view, phantom = false) {
let dialog = new frappe.ui.Dialog({
fields: view.events.get_sub_assembly_modal_fields(view, node.is_root),
title: __("Add Sub Assembly"),
fields: view.events.get_sub_assembly_modal_fields(view, node.is_root, false, phantom),
title: phantom ? __("Add Phantom Item") : __("Add Sub Assembly"),
});
view.events.set_query_for_workstation(dialog);
@@ -282,6 +304,7 @@ class BOMConfigurator {
operation: node.data.operation,
workstation_type: node.data.workstation_type,
operation_time: node.data.operation_time,
phantom: phantom,
},
callback: (r) => {
view.events.load_tree(r, node);
@@ -292,15 +315,18 @@ class BOMConfigurator {
});
}
get_sub_assembly_modal_fields(view, is_root = false, read_only = false) {
get_sub_assembly_modal_fields(view, is_root = false, read_only = false, phantom = false) {
let fields = [
{
label: __("Sub Assembly Item"),
label: phantom ? __("Phantom Item") : __("Sub Assembly Item"),
fieldname: "item_code",
fieldtype: "Link",
options: "Item",
reqd: 1,
read_only: read_only,
filters: {
is_stock_item: !phantom,
},
},
{ fieldtype: "Column Break" },
{
@@ -320,7 +346,7 @@ class BOMConfigurator {
},
];
if (is_root) {
if (is_root && !phantom) {
fields.push(
...[
{ fieldtype: "Section Break" },
@@ -384,10 +410,10 @@ class BOMConfigurator {
return fields;
}
convert_to_sub_assembly(node, view) {
convert_to_sub_assembly(node, view, phantom = false) {
let dialog = new frappe.ui.Dialog({
fields: view.events.get_sub_assembly_modal_fields(view, node.is_root, true),
title: __("Add Sub Assembly"),
fields: view.events.get_sub_assembly_modal_fields(view, node.is_root, true, phantom),
title: phantom ? __("Add Phantom Item") : __("Add Sub Assembly"),
});
dialog.set_values({
@@ -400,7 +426,9 @@ class BOMConfigurator {
let bom_item = dialog.get_values();
if (!bom_item.item_code) {
frappe.throw(__("Sub Assembly Item is mandatory"));
frappe.throw(
phantom ? __("Phantom Item is mandatory") : __("Sub Assembly Item is mandatory")
);
}
bom_item.items.forEach((d) => {
@@ -425,6 +453,7 @@ class BOMConfigurator {
workstation_type: node.data.workstation_type,
operation_time: node.data.operation_time,
workstation: node.data.workstation,
phantom: phantom,
},
callback: (r) => {
node.expandable = true;