mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-24 07:29:22 +00:00
725 lines
19 KiB
JavaScript
725 lines
19 KiB
JavaScript
class BOMConfigurator {
|
|
constructor({ wrapper, page, frm, bom_configurator }) {
|
|
this.$wrapper = $(wrapper);
|
|
this.page = page;
|
|
this.bom_configurator = bom_configurator;
|
|
this.frm = frm;
|
|
|
|
this.make();
|
|
this.prepare_layout();
|
|
this.bind_events();
|
|
}
|
|
|
|
add_boms() {
|
|
this.frm.call({
|
|
method: "add_boms",
|
|
freeze: true,
|
|
doc: this.frm.doc,
|
|
});
|
|
}
|
|
|
|
make() {
|
|
let options = {
|
|
...this.tree_options(),
|
|
...this.tree_methods(),
|
|
};
|
|
|
|
frappe.views.trees["BOM Configurator"] = new frappe.views.TreeView(options);
|
|
let node = frappe.views.trees["BOM Configurator"].tree.root_node;
|
|
frappe.views.trees["BOM Configurator"].tree.show_toolbar(node);
|
|
frappe.views.trees["BOM Configurator"].tree.load_children(node, true);
|
|
this.tree_view = frappe.views.trees["BOM Configurator"];
|
|
}
|
|
|
|
bind_events() {
|
|
frappe.views.trees["BOM Configurator"].events = {
|
|
frm: this.frm,
|
|
add_item: this.add_item,
|
|
add_sub_assembly: this.add_sub_assembly,
|
|
set_query_for_workstation: this.set_query_for_workstation,
|
|
get_sub_assembly_modal_fields: this.get_sub_assembly_modal_fields,
|
|
convert_to_sub_assembly: this.convert_to_sub_assembly,
|
|
delete_node: this.delete_node,
|
|
edit_bom: this.edit_bom,
|
|
load_tree: this.load_tree,
|
|
set_default_qty: this.set_default_qty,
|
|
};
|
|
}
|
|
|
|
tree_options() {
|
|
return {
|
|
parent: this.$wrapper.get(0),
|
|
body: this.$wrapper.get(0),
|
|
doctype: "BOM Configurator",
|
|
page: this.page,
|
|
expandable: true,
|
|
title: __("Configure Product Assembly"),
|
|
breadcrumb: "Manufacturing",
|
|
get_tree_nodes: "erpnext.manufacturing.doctype.bom_creator.bom_creator.get_children",
|
|
root_label: this.frm.doc.item_code,
|
|
disable_add_node: true,
|
|
get_tree_root: false,
|
|
show_expand_all: false,
|
|
extend_toolbar: false,
|
|
do_not_make_page: true,
|
|
do_not_setup_menu: true,
|
|
};
|
|
}
|
|
|
|
tree_methods() {
|
|
let frm_obj = this;
|
|
let view = frappe.views.trees["BOM Configurator"];
|
|
|
|
return {
|
|
onload: function (me) {
|
|
me.args["parent_id"] = frm_obj.frm.doc.name;
|
|
me.args["parent"] = frm_obj.frm.doc.item_code;
|
|
me.parent = frm_obj.$wrapper.get(0);
|
|
me.body = frm_obj.$wrapper.get(0);
|
|
me.make_tree();
|
|
},
|
|
onrender(node) {
|
|
const qty = node.data.qty || frm_obj.frm.doc.qty;
|
|
const uom = node.data.uom || frm_obj.frm.doc.uom;
|
|
const docname = node.data.name || frm_obj.frm.doc.name;
|
|
let amount = node.data.amount;
|
|
if (node.data.value === frm_obj.frm.doc.item_code) {
|
|
amount = frm_obj.frm.doc.raw_material_cost;
|
|
}
|
|
|
|
amount = frappe.format(amount, { fieldtype: "Currency", currency: frm_obj.frm.doc.currency });
|
|
|
|
$(`
|
|
<div class="pill small pull-right bom-qty-pill"
|
|
style="background-color: var(--bg-white);
|
|
color: var(--text-on-gray);
|
|
font-weight:450;
|
|
margin-right: 40px;
|
|
display: inline-flex;
|
|
min-width: 128px;
|
|
border: 1px solid var(--bg-gray);
|
|
">
|
|
<div style="padding-right:5px" data-bom-qty-docname="${docname}">${qty} ${uom}</div>
|
|
<div class="fg-item-amt" style="padding-left:12px; border-left:1px solid var(--bg-gray)">
|
|
${amount}
|
|
</div>
|
|
</div>
|
|
|
|
`).insertBefore(node.$ul);
|
|
},
|
|
toolbar:
|
|
this.frm?.doc.docstatus === 0
|
|
? [
|
|
{
|
|
label: __(frappe.utils.icon("edit", "sm") + " BOM"),
|
|
click: function (node) {
|
|
let view = frappe.views.trees["BOM Configurator"];
|
|
view.events.edit_bom(node, view);
|
|
},
|
|
btnClass: "hidden-xs",
|
|
},
|
|
{
|
|
label: __(frappe.utils.icon("add", "sm") + " Raw Material"),
|
|
click: function (node) {
|
|
let view = frappe.views.trees["BOM Configurator"];
|
|
view.events.add_item(node, view);
|
|
},
|
|
condition: function (node) {
|
|
return node.expandable;
|
|
},
|
|
btnClass: "hidden-xs",
|
|
},
|
|
{
|
|
label: __(frappe.utils.icon("add", "sm") + " Sub Assembly"),
|
|
click: function (node) {
|
|
let view = frappe.views.trees["BOM Configurator"];
|
|
view.events.add_sub_assembly(node, view);
|
|
},
|
|
condition: function (node) {
|
|
return node.expandable;
|
|
},
|
|
btnClass: "hidden-xs",
|
|
},
|
|
{
|
|
label: __("Collapse All"),
|
|
click: function (node) {
|
|
let view = frappe.views.trees["BOM Configurator"];
|
|
|
|
if (!node.expanded) {
|
|
view.tree.load_children(node, true);
|
|
$(node.parent[0]).find(".tree-children").show();
|
|
node.$toolbar.find(".expand-all-btn").html("Collapse All");
|
|
} else {
|
|
node.$tree_link.trigger("click");
|
|
node.$toolbar.find(".expand-all-btn").html("Expand All");
|
|
}
|
|
},
|
|
condition: function (node) {
|
|
return node.expandable && node.is_root;
|
|
},
|
|
btnClass: "hidden-xs expand-all-btn",
|
|
},
|
|
{
|
|
label: __(frappe.utils.icon("move", "sm") + " Sub Assembly"),
|
|
click: function (node) {
|
|
let view = frappe.views.trees["BOM Configurator"];
|
|
view.events.convert_to_sub_assembly(node, view);
|
|
},
|
|
condition: function (node) {
|
|
return !node.expandable;
|
|
},
|
|
btnClass: "hidden-xs",
|
|
},
|
|
{
|
|
label: __(frappe.utils.icon("delete", "sm") + " Item"),
|
|
click: function (node) {
|
|
let view = frappe.views.trees["BOM Configurator"];
|
|
view.events.delete_node(node, view);
|
|
},
|
|
condition: function (node) {
|
|
return !node.is_root;
|
|
},
|
|
btnClass: "hidden-xs",
|
|
},
|
|
]
|
|
: [
|
|
{
|
|
label: __("Expand All"),
|
|
click: function (node) {
|
|
let view = frappe.views.trees["BOM Configurator"];
|
|
|
|
if (!node.expanded) {
|
|
view.tree.load_children(node, true);
|
|
$(node.parent[0]).find(".tree-children").show();
|
|
node.$toolbar.find(".expand-all-btn").html("Collapse All");
|
|
} else {
|
|
node.$tree_link.trigger("click");
|
|
node.$toolbar.find(".expand-all-btn").html("Expand All");
|
|
}
|
|
},
|
|
condition: function (node) {
|
|
return node.expandable && node.is_root;
|
|
},
|
|
btnClass: "hidden-xs expand-all-btn",
|
|
},
|
|
],
|
|
};
|
|
}
|
|
|
|
add_item(node, view) {
|
|
frappe.prompt(
|
|
[
|
|
{ label: __("Item"), fieldname: "item_code", fieldtype: "Link", options: "Item", reqd: 1 },
|
|
{ label: __("Qty"), fieldname: "qty", default: 1.0, fieldtype: "Float", reqd: 1 },
|
|
],
|
|
(data) => {
|
|
if (!node.data.parent_id) {
|
|
node.data.parent_id = this.frm.doc.name;
|
|
}
|
|
|
|
frappe.call({
|
|
method: "erpnext.manufacturing.doctype.bom_creator.bom_creator.add_item",
|
|
args: {
|
|
parent: node.data.parent_id,
|
|
fg_item: node.data.value,
|
|
item_code: data.item_code,
|
|
fg_reference_id: node.data.name || this.frm.doc.name,
|
|
qty: data.qty,
|
|
},
|
|
callback: (r) => {
|
|
view.events.load_tree(r, node);
|
|
},
|
|
});
|
|
},
|
|
__("Add Item"),
|
|
__("Add")
|
|
);
|
|
}
|
|
|
|
set_query_for_workstation(dialog) {
|
|
let workstation = dialog.fields.filter((field) => field.fieldname === "workstation");
|
|
if (workstation.length) {
|
|
workstation[0].get_query = function () {
|
|
let workstation_type = dialog.get_value("workstation_type");
|
|
|
|
if (workstation_type) {
|
|
return {
|
|
filters: {
|
|
workstation_type: dialog.get_value("workstation_type"),
|
|
},
|
|
};
|
|
}
|
|
};
|
|
}
|
|
}
|
|
|
|
add_sub_assembly(node, view) {
|
|
let dialog = new frappe.ui.Dialog({
|
|
fields: view.events.get_sub_assembly_modal_fields(view, node.is_root),
|
|
title: __("Add Sub Assembly"),
|
|
});
|
|
view.events.set_query_for_workstation(dialog);
|
|
|
|
dialog.show();
|
|
dialog.set_primary_action(__("Add"), () => {
|
|
let bom_item = dialog.get_values();
|
|
|
|
if (dialog.operation && !dialog.workstation_type && !dialog.workstation) {
|
|
frappe.throw(__("Either Workstation or Workstation Type is mandatory"));
|
|
}
|
|
|
|
if (!node.data?.parent_id) {
|
|
node.data.parent_id = this.frm.doc.name;
|
|
}
|
|
|
|
frappe.call({
|
|
method: "erpnext.manufacturing.doctype.bom_creator.bom_creator.add_sub_assembly",
|
|
args: {
|
|
parent: node.data.parent_id,
|
|
fg_item: node.data.value,
|
|
fg_reference_id: node.data.name || this.frm.doc.name,
|
|
bom_item: bom_item,
|
|
operation: node.data.operation,
|
|
workstation_type: node.data.workstation_type,
|
|
operation_time: node.data.operation_time,
|
|
},
|
|
callback: (r) => {
|
|
view.events.load_tree(r, node);
|
|
},
|
|
});
|
|
|
|
dialog.hide();
|
|
});
|
|
}
|
|
|
|
get_sub_assembly_modal_fields(view, is_root = false, read_only = false, show_operations_fields = false) {
|
|
let fields = [
|
|
{
|
|
label: __("Sub Assembly Item"),
|
|
fieldname: "item_code",
|
|
fieldtype: "Link",
|
|
options: "Item",
|
|
reqd: 1,
|
|
read_only: read_only,
|
|
},
|
|
{ fieldtype: "Column Break" },
|
|
{
|
|
label: __("Qty"),
|
|
fieldname: "qty",
|
|
default: 1.0,
|
|
fieldtype: "Float",
|
|
reqd: 1,
|
|
read_only: read_only,
|
|
change() {
|
|
this.layout.fields_dict.items.grid.data.forEach((row) => {
|
|
row.qty = flt(this.value);
|
|
});
|
|
|
|
this.layout.fields_dict.items.grid.refresh();
|
|
},
|
|
},
|
|
];
|
|
|
|
if (this.frm.doc.track_operations && (is_root || show_operations_fields)) {
|
|
fields.push(
|
|
...[
|
|
{ fieldtype: "Section Break" },
|
|
{
|
|
label: __("Operation"),
|
|
fieldname: "operation",
|
|
fieldtype: "Link",
|
|
options: "Operation",
|
|
reqd: 1,
|
|
},
|
|
{
|
|
label: __("Operation Time"),
|
|
fieldname: "operation_time",
|
|
fieldtype: "Int",
|
|
reqd: 1,
|
|
},
|
|
{
|
|
label: __("Is Subcontracted"),
|
|
fieldname: "is_subcontracted",
|
|
fieldtype: "Check",
|
|
},
|
|
{ fieldtype: "Column Break" },
|
|
{
|
|
label: __("Workstation Type"),
|
|
fieldname: "workstation_type",
|
|
fieldtype: "Link",
|
|
options: "Workstation Type",
|
|
},
|
|
{
|
|
label: __("Workstation"),
|
|
fieldname: "workstation",
|
|
fieldtype: "Link",
|
|
options: "Workstation",
|
|
},
|
|
]
|
|
);
|
|
|
|
if (this.frm.doc.track_semi_finished_goods) {
|
|
fields.push(
|
|
...[
|
|
{ label: __("Default Warehouse"), fieldtype: "Section Break", collapsible: 1 },
|
|
{
|
|
label: __("Skip Material Transfer"),
|
|
fieldname: "skip_material_transfer",
|
|
fieldtype: "Check",
|
|
},
|
|
{
|
|
label: __("Backflush Materials From WIP"),
|
|
fieldname: "backflush_from_wip_warehouse",
|
|
fieldtype: "Check",
|
|
depends_on: "eval:doc.skip_material_transfer",
|
|
},
|
|
{
|
|
label: __("Source Warehouse"),
|
|
fieldname: "source_warehouse",
|
|
fieldtype: "Link",
|
|
options: "Warehouse",
|
|
depends_on: "eval:!doc.backflush_from_wip_warehouse",
|
|
get_query() {
|
|
return {
|
|
filters: {
|
|
company: view.events.frm.doc.company,
|
|
},
|
|
};
|
|
},
|
|
},
|
|
{ fieldtype: "Column Break" },
|
|
{
|
|
label: __("Work In Progress Warehouse"),
|
|
fieldname: "wip_warehouse",
|
|
fieldtype: "Link",
|
|
options: "Warehouse",
|
|
depends_on:
|
|
"eval:!doc.skip_material_transfer || doc.backflush_from_wip_warehouse",
|
|
get_query() {
|
|
return {
|
|
filters: {
|
|
company: view.events.frm.doc.company,
|
|
},
|
|
};
|
|
},
|
|
},
|
|
{
|
|
label: __("Finished Good Warehouse"),
|
|
fieldname: "fg_warehouse",
|
|
fieldtype: "Link",
|
|
options: "Warehouse",
|
|
get_query() {
|
|
return {
|
|
filters: {
|
|
company: view.events.frm.doc.company,
|
|
},
|
|
};
|
|
},
|
|
},
|
|
]
|
|
);
|
|
}
|
|
}
|
|
|
|
fields.push(
|
|
...[
|
|
{ fieldtype: "Section Break" },
|
|
{
|
|
label: __("Raw Materials"),
|
|
fieldname: "items",
|
|
fieldtype: "Table",
|
|
reqd: 1,
|
|
fields: [
|
|
{
|
|
label: __("Item"),
|
|
fieldname: "item_code",
|
|
fieldtype: "Link",
|
|
options: "Item",
|
|
reqd: 1,
|
|
in_list_view: 1,
|
|
change() {
|
|
let doc = this.doc;
|
|
doc.qty = 1.0;
|
|
this.grid.set_value("qty", 1.0, doc);
|
|
},
|
|
},
|
|
{
|
|
label: __("Qty"),
|
|
fieldname: "qty",
|
|
default: 1.0,
|
|
fieldtype: "Float",
|
|
reqd: 1,
|
|
in_list_view: 1,
|
|
},
|
|
],
|
|
},
|
|
]
|
|
);
|
|
|
|
return fields;
|
|
}
|
|
|
|
convert_to_sub_assembly(node, view) {
|
|
let dialog = new frappe.ui.Dialog({
|
|
fields: view.events.get_sub_assembly_modal_fields(view, node.is_root, true, true),
|
|
title: __("Add Sub Assembly"),
|
|
});
|
|
|
|
dialog.set_values({
|
|
item_code: node.data.value,
|
|
qty: node.data.qty,
|
|
});
|
|
|
|
dialog.show();
|
|
dialog.set_primary_action(__("Add"), () => {
|
|
let bom_item = dialog.get_values();
|
|
|
|
if (dialog.operation && !dialog.workstation_type && !dialog.workstation) {
|
|
frappe.throw(__("Either Workstation or Workstation Type is mandatory"));
|
|
}
|
|
|
|
frappe.call({
|
|
method: "erpnext.manufacturing.doctype.bom_creator.bom_creator.add_sub_assembly",
|
|
args: {
|
|
parent: node.data.parent_id,
|
|
fg_item: node.data.value,
|
|
bom_item: bom_item,
|
|
fg_reference_id: node.data.name || this.frm.doc.name,
|
|
convert_to_sub_assembly: true,
|
|
operation: node.data.operation,
|
|
workstation_type: node.data.workstation_type,
|
|
operation_time: node.data.operation_time,
|
|
workstation: node.data.workstation,
|
|
},
|
|
callback: (r) => {
|
|
node.expandable = true;
|
|
view.events.load_tree(r, node.parent_node);
|
|
},
|
|
});
|
|
|
|
dialog.hide();
|
|
});
|
|
}
|
|
|
|
set_default_qty(dialog) {
|
|
dialog.fields_dict.items.grid.fields_map.item_code.onchange = function (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;
|
|
item_row.qty = 1;
|
|
dialog.fields_dict.items.grid.refresh();
|
|
}
|
|
};
|
|
}
|
|
|
|
delete_node(node, view) {
|
|
frappe.confirm(__("Are you sure you want to delete this Item?"), () => {
|
|
frappe.call({
|
|
method: "erpnext.manufacturing.doctype.bom_creator.bom_creator.delete_node",
|
|
args: {
|
|
parent: node.data.parent_id,
|
|
fg_item: node.data.value,
|
|
doctype: node.data.doctype,
|
|
docname: node.data.name,
|
|
},
|
|
callback: (r) => {
|
|
view.events.load_tree(r, node.parent_node);
|
|
},
|
|
});
|
|
});
|
|
}
|
|
|
|
edit_bom(node, view) {
|
|
let me = this;
|
|
let qty = node.data.qty || this.frm.doc.qty;
|
|
let fields = [{ label: __("Qty"), fieldname: "qty", default: qty, fieldtype: "Float", reqd: 1 }];
|
|
|
|
if (node.expandable && this.frm.doc.track_operations) {
|
|
let data = node.data.operation ? node.data : this.frm.doc;
|
|
|
|
fields = [
|
|
...fields,
|
|
...[
|
|
{ fieldtype: "Section Break" },
|
|
{
|
|
label: __("Operation"),
|
|
fieldname: "operation",
|
|
fieldtype: "Link",
|
|
options: "Operation",
|
|
default: data.operation,
|
|
},
|
|
{
|
|
label: __("Operation Time"),
|
|
fieldname: "operation_time",
|
|
fieldtype: "Float",
|
|
default: data.operation_time,
|
|
},
|
|
{
|
|
label: __("Is Subcontracted"),
|
|
fieldname: "is_subcontracted",
|
|
fieldtype: "Check",
|
|
hidden: node?.is_root || 0,
|
|
default: data.is_subcontracted,
|
|
},
|
|
{ fieldtype: "Column Break" },
|
|
{
|
|
label: __("Workstation Type"),
|
|
fieldname: "workstation_type",
|
|
fieldtype: "Link",
|
|
options: "Workstation Type",
|
|
default: data.workstation_type,
|
|
},
|
|
{
|
|
label: __("Workstation"),
|
|
fieldname: "workstation",
|
|
fieldtype: "Link",
|
|
options: "Workstation",
|
|
default: data.workstation,
|
|
get_query() {
|
|
let dialog = me.frm.edit_bom_dialog;
|
|
let workstation_type = dialog.get_value("workstation_type");
|
|
|
|
if (workstation_type) {
|
|
return {
|
|
filters: {
|
|
workstation_type: dialog.get_value("workstation_type"),
|
|
},
|
|
};
|
|
}
|
|
},
|
|
},
|
|
{ fieldtype: "Section Break" },
|
|
{
|
|
label: __("Skip Material Transfer"),
|
|
fieldname: "skip_material_transfer",
|
|
fieldtype: "Check",
|
|
default: data.skip_material_transfer,
|
|
},
|
|
{
|
|
label: __("Backflush Materials From WIP"),
|
|
fieldname: "backflush_from_wip_warehouse",
|
|
fieldtype: "Check",
|
|
depends_on: "eval:doc.skip_material_transfer",
|
|
default: data.backflush_from_wip_warehouse,
|
|
},
|
|
{
|
|
label: __("Source Warehouse"),
|
|
fieldname: "source_warehouse",
|
|
fieldtype: "Link",
|
|
options: "Warehouse",
|
|
default: data.source_warehouse,
|
|
depends_on: "eval:!doc.backflush_from_wip_warehouse",
|
|
get_query() {
|
|
return {
|
|
filters: {
|
|
company: me.frm.doc.company,
|
|
},
|
|
};
|
|
},
|
|
},
|
|
{ fieldtype: "Column Break" },
|
|
{
|
|
label: __("Work In Progress Warehouse"),
|
|
fieldname: "wip_warehouse",
|
|
fieldtype: "Link",
|
|
options: "Warehouse",
|
|
default: data.wip_warehouse,
|
|
depends_on: "eval:!doc.skip_material_transfer || doc.backflush_from_wip_warehouse",
|
|
get_query() {
|
|
return {
|
|
filters: {
|
|
company: me.frm.doc.company,
|
|
},
|
|
};
|
|
},
|
|
},
|
|
{
|
|
label: __("Finished Good Warehouse"),
|
|
fieldname: "fg_warehouse",
|
|
fieldtype: "Link",
|
|
options: "Warehouse",
|
|
default: data.fg_warehouse,
|
|
get_query() {
|
|
return {
|
|
filters: {
|
|
company: me.frm.doc.company,
|
|
},
|
|
};
|
|
},
|
|
},
|
|
],
|
|
];
|
|
}
|
|
|
|
this.frm.edit_bom_dialog = frappe.prompt(
|
|
fields,
|
|
(data) => {
|
|
let doctype = node.data.doctype || this.frm.doc.doctype;
|
|
let docname = node.data.name || this.frm.doc.name;
|
|
|
|
frappe.call({
|
|
method: "erpnext.manufacturing.doctype.bom_creator.bom_creator.edit_bom_creator",
|
|
args: {
|
|
doctype: doctype,
|
|
docname: docname,
|
|
data: data,
|
|
parent: node.data.parent_id || this.frm.doc.name,
|
|
},
|
|
callback: (r) => {
|
|
for (let key in data) {
|
|
node.data[key] = data[key];
|
|
}
|
|
|
|
let uom = node.data.uom || this.frm.doc.uom;
|
|
$(node.parent.get(0))
|
|
.find(`[data-bom-qty-docname='${docname}']`)
|
|
.html(data.qty + " " + uom);
|
|
view.events.load_tree(r, node);
|
|
},
|
|
});
|
|
},
|
|
__("Edit BOM"),
|
|
__("Update")
|
|
);
|
|
}
|
|
|
|
prepare_layout() {
|
|
let main_div = $(this.page)[0];
|
|
|
|
main_div.style.marginBottom = "15px";
|
|
$(main_div).find(".tree-children")[0].style.minHeight = "370px";
|
|
$(main_div).find(".tree-children")[0].style.maxHeight = "370px";
|
|
$(main_div).find(".tree-children")[0].style.overflowY = "auto";
|
|
}
|
|
|
|
load_tree(response, node) {
|
|
let item_row = "";
|
|
let parent_dom = "";
|
|
let total_amount = response.message.raw_material_cost;
|
|
|
|
frappe.views.trees["BOM Configurator"].tree.load_children(node);
|
|
|
|
while (node) {
|
|
item_row = response.message.items.filter((item) => item.name === node.data.name);
|
|
|
|
if (item_row?.length) {
|
|
node.data.amount = item_row[0].amount;
|
|
total_amount = node.data.amount;
|
|
} else {
|
|
total_amount = response.message.raw_material_cost;
|
|
}
|
|
|
|
parent_dom = $(node.parent.get(0));
|
|
total_amount = frappe.format(total_amount, {
|
|
fieldtype: "Currency",
|
|
currency: this.frm.doc.currency,
|
|
});
|
|
|
|
$($(parent_dom).find(".fg-item-amt")[0]).html(total_amount);
|
|
node = node.parent_node;
|
|
}
|
|
}
|
|
}
|
|
|
|
frappe.ui.BOMConfigurator = BOMConfigurator;
|