mirror of
https://github.com/frappe/erpnext.git
synced 2026-09-01 23:53:21 +00:00
fix: enhance tree view functionality for accounts and cost centers (#58520)
This commit is contained in:
@@ -52,6 +52,47 @@ frappe.treeview_settings["Account"] = {
|
||||
],
|
||||
root_label: "Accounts",
|
||||
get_tree_nodes: "erpnext.accounts.utils.get_children",
|
||||
get_label: function (node) {
|
||||
// clean display name — the account number renders as a badge (see
|
||||
// onrender) instead of being glued into the name
|
||||
return frappe.utils.escape_html(node.data.account_name || node.title || node.label);
|
||||
},
|
||||
onrender: function (node) {
|
||||
if (node.is_root || !node.data) return;
|
||||
|
||||
const flags = [];
|
||||
if (node.data.account_number) {
|
||||
flags.push(frappe.ui.badge({ label: node.data.account_number, size: "sm" }));
|
||||
}
|
||||
|
||||
const company = frappe.treeview_settings["Account"].treeview?.page?.fields_dict?.company?.get_value();
|
||||
const company_currency = company && erpnext.get_currency(company);
|
||||
if (
|
||||
node.data.account_currency &&
|
||||
company_currency &&
|
||||
node.data.account_currency !== company_currency
|
||||
) {
|
||||
flags.push(frappe.ui.badge({ label: node.data.account_currency, theme: "blue", size: "sm" }));
|
||||
}
|
||||
|
||||
if (node.data.freeze_account === "Yes") {
|
||||
flags.push(
|
||||
$(
|
||||
`<span class="inline-flex text-ink-gray-4" title="${__("Frozen — entries restricted")}">
|
||||
${frappe.utils.icon("lock", "sm")}
|
||||
</span>`
|
||||
)[0]
|
||||
);
|
||||
}
|
||||
|
||||
if (flags.length) {
|
||||
const $flags = $(
|
||||
'<span class="tree-node-flags inline-flex items-center gap-1.5 ms-2 shrink-0"></span>'
|
||||
);
|
||||
flags.forEach((flag) => $flags.append(flag));
|
||||
$flags.insertAfter(node.$tree_link.find("a.tree-label"));
|
||||
}
|
||||
},
|
||||
on_node_render: function (node, deep) {
|
||||
const render_balances = () => {
|
||||
for (let account of cur_tree.account_balance_data) {
|
||||
@@ -232,7 +273,7 @@ frappe.treeview_settings["Account"] = {
|
||||
frappe.treeview_settings["Account"].treeview["tree"] = treeview.tree;
|
||||
if (treeview.can_create) {
|
||||
treeview.page.set_primary_action(
|
||||
__("New"),
|
||||
{ label: __("Add Account"), short_label: __("Add") },
|
||||
function () {
|
||||
let root_company = treeview.page.fields_dict.root_company.get_value();
|
||||
if (root_company) {
|
||||
@@ -243,13 +284,14 @@ frappe.treeview_settings["Account"] = {
|
||||
treeview.new_node();
|
||||
}
|
||||
},
|
||||
"add"
|
||||
"plus"
|
||||
);
|
||||
}
|
||||
},
|
||||
toolbar: [
|
||||
{
|
||||
label: __("Add Child"),
|
||||
icon: "plus",
|
||||
condition: function (node) {
|
||||
return (
|
||||
frappe.boot.user.can_create.indexOf("Account") !== -1 &&
|
||||
@@ -272,6 +314,7 @@ frappe.treeview_settings["Account"] = {
|
||||
return !node.root && frappe.boot.user.can_read.indexOf("GL Entry") !== -1;
|
||||
},
|
||||
label: __("View Ledger"),
|
||||
icon: "book-open",
|
||||
click: function (node, btn) {
|
||||
frappe.route_options = {
|
||||
from_date: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
|
||||
@@ -286,6 +329,106 @@ frappe.treeview_settings["Account"] = {
|
||||
},
|
||||
btnClass: "hidden-xs",
|
||||
},
|
||||
{
|
||||
// same label and mechanism as the Account form's Actions button:
|
||||
// NOT frappe's generic rename (Allow Rename stays off) — this is
|
||||
// ERPNext's controlled update that rebuilds the derived
|
||||
// "number - name - abbr" document name
|
||||
label: __("Update Account Name / Number"),
|
||||
icon: "text-cursor-input",
|
||||
condition: function (node) {
|
||||
return !node.is_root && frappe.model.can_write("Account");
|
||||
},
|
||||
click: function (node) {
|
||||
const dialog = new frappe.ui.Dialog({
|
||||
title: __("Update Account Number / Name"),
|
||||
fields: [
|
||||
{
|
||||
fieldtype: "Data",
|
||||
fieldname: "account_name",
|
||||
label: __("Account Name"),
|
||||
reqd: 1,
|
||||
default: node.data.account_name,
|
||||
},
|
||||
{
|
||||
fieldtype: "Data",
|
||||
fieldname: "account_number",
|
||||
label: __("Account Number"),
|
||||
default: node.data.account_number,
|
||||
},
|
||||
],
|
||||
primary_action_label: __("Update"),
|
||||
primary_action(values) {
|
||||
dialog.hide();
|
||||
frappe.dom.freeze(__("Updating {0}", [node.label]));
|
||||
frappe.call({
|
||||
method: "erpnext.accounts.doctype.account.account.update_account_number",
|
||||
args: {
|
||||
name: node.label,
|
||||
account_name: values.account_name,
|
||||
account_number: values.account_number,
|
||||
},
|
||||
callback: function (r) {
|
||||
if (r.exc) return;
|
||||
const treeview = frappe.views.trees["Account"];
|
||||
node.parent_node && treeview.tree.load_children(node.parent_node);
|
||||
},
|
||||
always: function () {
|
||||
frappe.dom.unfreeze();
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
dialog.show();
|
||||
},
|
||||
},
|
||||
{
|
||||
label: __("Convert to Group"),
|
||||
icon: "folder-tree",
|
||||
condition: function (node) {
|
||||
return !node.is_root && !node.expandable && frappe.model.can_write("Account");
|
||||
},
|
||||
click: function (node) {
|
||||
erpnext.accounts.convert_tree_node("Account", node, "convert_ledger_to_group");
|
||||
},
|
||||
},
|
||||
{
|
||||
label: __("Convert to Non-Group"),
|
||||
icon: "file-text",
|
||||
condition: function (node) {
|
||||
// only on groups the user has opened and found empty — a
|
||||
// group with children can't convert, so don't offer it
|
||||
return (
|
||||
!node.is_root &&
|
||||
node.expandable &&
|
||||
node.loaded &&
|
||||
!node.$ul.children().length &&
|
||||
frappe.model.can_write("Account")
|
||||
);
|
||||
},
|
||||
click: function (node) {
|
||||
erpnext.accounts.convert_tree_node("Account", node, "convert_group_to_ledger");
|
||||
},
|
||||
},
|
||||
],
|
||||
extend_toolbar: true,
|
||||
};
|
||||
|
||||
frappe.provide("erpnext.accounts");
|
||||
// shared by the Account and Cost Center tree views (defined in both files,
|
||||
// whichever loads first wins): run the doctype's whitelisted convert method,
|
||||
// then re-render the branch so the node's group/leaf state updates
|
||||
erpnext.accounts.convert_tree_node =
|
||||
erpnext.accounts.convert_tree_node ||
|
||||
function (doctype, node, method) {
|
||||
frappe.call({
|
||||
method: "run_doc_method",
|
||||
args: { dt: doctype, dn: node.label, method: method },
|
||||
callback: function (r) {
|
||||
if (r.exc) return;
|
||||
const treeview = frappe.views.trees[doctype];
|
||||
node.parent_node && treeview.tree.load_children(node.parent_node);
|
||||
frappe.show_alert({ message: __("{0} converted", [node.label]), indicator: "green" });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -16,6 +16,8 @@ frappe.ui.form.on("Chart of Accounts Importer", {
|
||||
() => generate_tree_preview(frm),
|
||||
() => create_import_button(frm),
|
||||
() => frm.set_df_property("chart_preview", "hidden", 0),
|
||||
// the preview is the point of this page — open it right away
|
||||
() => frm.fields_dict.chart_preview.collapse(false),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -128,7 +130,6 @@ var create_import_button = function (frm) {
|
||||
freeze_message: __("Creating Accounts..."),
|
||||
callback: function (r) {
|
||||
if (!r.exc) {
|
||||
clearInterval(frm.page["interval"]);
|
||||
frm.page.set_indicator(__("Import Successful"), "blue");
|
||||
create_reset_button(frm);
|
||||
}
|
||||
@@ -142,42 +143,95 @@ var create_reset_button = function (frm) {
|
||||
frm.page
|
||||
.set_primary_action(__("Reset"), function () {
|
||||
frm.page.clear_primary_action();
|
||||
delete frm.page["show_import_button"];
|
||||
frm.reload_doc();
|
||||
})
|
||||
.addClass("btn btn-primary");
|
||||
};
|
||||
|
||||
var validate_coa = function (frm) {
|
||||
if (frm.doc.import_file) {
|
||||
let parent = __("All Accounts");
|
||||
return frappe.call({
|
||||
method: "erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.get_coa",
|
||||
args: {
|
||||
file_name: frm.doc.import_file,
|
||||
parent: parent,
|
||||
doctype: "Chart of Accounts Importer",
|
||||
file_type: frm.doc.file_type,
|
||||
for_validate: 1,
|
||||
},
|
||||
callback: function (r) {
|
||||
if (r.message["show_import_button"]) {
|
||||
frm.page["show_import_button"] = Boolean(r.message["show_import_button"]);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
var generate_tree_preview = function (frm) {
|
||||
let parent = __("All Accounts");
|
||||
$(frm.fields_dict["chart_tree"].wrapper).empty(); // empty wrapper to load new data
|
||||
const wrapper = $(frm.fields_dict["chart_tree"].wrapper).empty(); // empty wrapper to load new data
|
||||
|
||||
// search + expand/collapse-all lean on frappe.ui.Tree helpers added with
|
||||
// row mode; when running against an older frappe that predates them, skip
|
||||
// this toolbar so the preview still renders (just without the extras)
|
||||
const has_row_helpers =
|
||||
typeof frappe.ui.Tree.prototype.get_expansion_state === "function" &&
|
||||
typeof frappe.ui.Tree.prototype.filter_nodes === "function";
|
||||
|
||||
let tree;
|
||||
let deep_loaded = false;
|
||||
let search_text = "";
|
||||
let update_buttons = () => {};
|
||||
|
||||
if (has_row_helpers) {
|
||||
// same toolbar anatomy as the tree view: search on the left,
|
||||
// expand/collapse-all on the right (three-state: fully collapsed ->
|
||||
// Expand All, fully expanded -> Collapse All, partially expanded -> both)
|
||||
const $toolbar = $('<div class="flex items-center gap-2 mb-2"></div>').appendTo(wrapper);
|
||||
|
||||
const search_control = frappe.ui.form.make_control({
|
||||
df: { fieldtype: "Data", fieldname: "preview_search", placeholder: __("Search") },
|
||||
parent: $toolbar,
|
||||
only_input: true,
|
||||
});
|
||||
search_control.refresh();
|
||||
$(search_control.wrapper).addClass("m-0").css("width", "220px");
|
||||
search_control.$input.addClass("input-xs");
|
||||
search_control.$input.on(
|
||||
"input",
|
||||
frappe.utils.debounce(() => {
|
||||
search_text = search_control.$input.val();
|
||||
const run = () => {
|
||||
// a newer keystroke superseded this one while the deep load ran
|
||||
if (search_text !== search_control.$input.val()) return;
|
||||
tree.filter_nodes(search_text);
|
||||
};
|
||||
if (!search_text || deep_loaded) {
|
||||
run();
|
||||
return;
|
||||
}
|
||||
tree.load_children(tree.root_node, true).then(() => {
|
||||
deep_loaded = true;
|
||||
run();
|
||||
});
|
||||
}, 300)
|
||||
);
|
||||
|
||||
const $actions = $('<div class="ms-auto flex items-center gap-1"></div>').appendTo($toolbar);
|
||||
update_buttons = () => {
|
||||
const state = tree.get_expansion_state();
|
||||
$expand_all.prop("disabled", !(state === "collapsed" || state === "partial"));
|
||||
$collapse_all.prop("disabled", !(state === "expanded" || state === "partial"));
|
||||
};
|
||||
// tooltip on a wrapper: a disabled es-button has pointer-events:none,
|
||||
// so hover falls through to the wrapper and the tooltip still shows
|
||||
const make_action = (icon, label, onclick) => {
|
||||
const $btn = $(
|
||||
frappe.ui.button({ icon, disabled: true, onclick, attrs: { "aria-label": label } })
|
||||
);
|
||||
const $wrapper = $('<span class="inline-flex"></span>').append($btn).appendTo($actions);
|
||||
frappe.ui.tooltip($wrapper, { text: label });
|
||||
return $btn;
|
||||
};
|
||||
var $expand_all = make_action("chevrons-up-down", __("Expand All"), () => {
|
||||
tree.load_children(tree.root_node, true).then(() => {
|
||||
deep_loaded = true;
|
||||
});
|
||||
});
|
||||
var $collapse_all = make_action("chevrons-down-up", __("Collapse All"), () => {
|
||||
tree.load_children(tree.root_node, false);
|
||||
});
|
||||
}
|
||||
|
||||
// generate tree structure based on the csv data
|
||||
return new frappe.ui.Tree({
|
||||
parent: $(frm.fields_dict["chart_tree"].wrapper),
|
||||
tree = new frappe.ui.Tree({
|
||||
parent: wrapper,
|
||||
label: parent,
|
||||
expandable: true,
|
||||
// read-only preview: row-mode visuals without actions or hover cards
|
||||
// (ignored by an older frappe, which renders the legacy tree)
|
||||
row_style: true,
|
||||
method: "erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.get_coa",
|
||||
args: {
|
||||
file_name: frm.doc.import_file,
|
||||
@@ -185,8 +239,9 @@ var generate_tree_preview = function (frm) {
|
||||
doctype: "Chart of Accounts Importer",
|
||||
file_type: frm.doc.file_type,
|
||||
},
|
||||
onclick: function (node) {
|
||||
parent = node.value;
|
||||
},
|
||||
on_node_render: () => update_buttons(),
|
||||
// expanded flips right after this callback — check on the next tick
|
||||
on_click: () => setTimeout(update_buttons, 0),
|
||||
});
|
||||
return tree;
|
||||
};
|
||||
|
||||
@@ -42,6 +42,37 @@ frappe.treeview_settings["Cost Center"] = {
|
||||
},
|
||||
],
|
||||
ignore_fields: ["parent_cost_center"],
|
||||
toolbar: [
|
||||
{
|
||||
label: __("Convert to Group"),
|
||||
icon: "folder-tree",
|
||||
condition: function (node) {
|
||||
return !node.is_root && !node.expandable && frappe.model.can_write("Cost Center");
|
||||
},
|
||||
click: function (node) {
|
||||
erpnext.accounts.convert_tree_node("Cost Center", node, "convert_ledger_to_group");
|
||||
},
|
||||
},
|
||||
{
|
||||
label: __("Convert to Non-Group"),
|
||||
icon: "file-text",
|
||||
condition: function (node) {
|
||||
// only on groups the user has opened and found empty — a
|
||||
// group with children can't convert, so don't offer it
|
||||
return (
|
||||
!node.is_root &&
|
||||
node.expandable &&
|
||||
node.loaded &&
|
||||
!node.$ul.children().length &&
|
||||
frappe.model.can_write("Cost Center")
|
||||
);
|
||||
},
|
||||
click: function (node) {
|
||||
erpnext.accounts.convert_tree_node("Cost Center", node, "convert_group_to_ledger");
|
||||
},
|
||||
},
|
||||
],
|
||||
extend_toolbar: true,
|
||||
onload: function (treeview) {
|
||||
function get_company() {
|
||||
return treeview.page.fields_dict.company.get_value();
|
||||
@@ -82,3 +113,22 @@ frappe.treeview_settings["Cost Center"] = {
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
frappe.provide("erpnext.accounts");
|
||||
// shared by the Account and Cost Center tree views (defined in both files,
|
||||
// whichever loads first wins): run the doctype's whitelisted convert method,
|
||||
// then re-render the branch so the node's group/leaf state updates
|
||||
erpnext.accounts.convert_tree_node =
|
||||
erpnext.accounts.convert_tree_node ||
|
||||
function (doctype, node, method) {
|
||||
frappe.call({
|
||||
method: "run_doc_method",
|
||||
args: { dt: doctype, dn: node.label, method: method },
|
||||
callback: function (r) {
|
||||
if (r.exc) return;
|
||||
const treeview = frappe.views.trees[doctype];
|
||||
node.parent_node && treeview.tree.load_children(node.parent_node);
|
||||
frappe.show_alert({ message: __("{0} converted", [node.label]), indicator: "green" });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -236,6 +236,8 @@ async function refresh_tree_view(dialog, account_rows) {
|
||||
parent: wrapper,
|
||||
label: company,
|
||||
root_value: company,
|
||||
// read-only preview: row-mode visuals without actions
|
||||
row_style: true,
|
||||
method: "erpnext.accounts.doctype.financial_report_template.financial_report_engine.get_children_accounts",
|
||||
args: { doctype: "Account", company: company, filtered_accounts: filtered_accounts, missed: missed },
|
||||
toolbar: [],
|
||||
|
||||
@@ -1367,12 +1367,13 @@ def get_children(
|
||||
else:
|
||||
filters.append([parent_fieldname, "=", parent])
|
||||
|
||||
account_fields = ["account_name", "account_number", "account_currency", "freeze_account"]
|
||||
if is_root:
|
||||
fields += ["root_type", "report_type", "account_currency"] if doctype == "Account" else []
|
||||
fields += ["root_type", "report_type", *account_fields] if doctype == "Account" else []
|
||||
filters.append(["company", "=", company])
|
||||
|
||||
else:
|
||||
fields += ["root_type", "account_currency"] if doctype == "Account" else []
|
||||
fields += ["root_type", *account_fields] if doctype == "Account" else []
|
||||
fields += [parent_fieldname + " as parent"]
|
||||
|
||||
acc = frappe.get_list(doctype, fields=fields, filters=filters)
|
||||
|
||||
@@ -46,9 +46,10 @@ frappe.treeview_settings["BOM"] = {
|
||||
me.make_tree();
|
||||
},
|
||||
toolbar: [
|
||||
{ toggle_btn: true },
|
||||
{
|
||||
label: __("Edit"),
|
||||
icon: "pencil",
|
||||
inline: true,
|
||||
condition: function (node) {
|
||||
return node.expandable;
|
||||
},
|
||||
|
||||
@@ -40,6 +40,7 @@ frappe.treeview_settings["Task"] = {
|
||||
toolbar: [
|
||||
{
|
||||
label: __("Add Multiple"),
|
||||
icon: "list-plus",
|
||||
condition: function (node) {
|
||||
return node.expandable;
|
||||
},
|
||||
@@ -75,7 +76,6 @@ frappe.treeview_settings["Task"] = {
|
||||
data: dialog.get_values()["multiple_tasks"],
|
||||
parent: node.data.value,
|
||||
},
|
||||
callback: function () {},
|
||||
});
|
||||
},
|
||||
primary_action_label: __("Create"),
|
||||
|
||||
@@ -42,6 +42,7 @@ class BOMConfigurator {
|
||||
doctype: "BOM Configurator",
|
||||
page: this.page,
|
||||
expandable: true,
|
||||
use_row_actions: true,
|
||||
title: __("Configure Product Assembly"),
|
||||
breadcrumb: "Manufacturing",
|
||||
get_tree_nodes: "erpnext.manufacturing.doctype.bom_creator.bom_creator.get_children",
|
||||
@@ -98,13 +99,11 @@ class BOMConfigurator {
|
||||
amount = frappe.format(amount, { fieldtype: "Currency", currency: frm_obj.frm.doc.currency });
|
||||
|
||||
$(`
|
||||
<div class="pill small pull-right bom-qty-pill"
|
||||
<div class="pill small 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>
|
||||
@@ -119,15 +118,17 @@ class BOMConfigurator {
|
||||
this.frm?.doc.docstatus === 0
|
||||
? [
|
||||
{
|
||||
label: __(frappe.utils.icon("pencil", "sm") + " BOM"),
|
||||
label: __("Edit BOM"),
|
||||
icon: "pencil",
|
||||
inline: true,
|
||||
click: function (node) {
|
||||
let view = frappe.views.trees["BOM Configurator"];
|
||||
view.events.edit_bom(node, view);
|
||||
},
|
||||
btnClass: "hidden-xs",
|
||||
},
|
||||
{
|
||||
label: __(frappe.utils.icon("plus", "sm") + " Raw Material"),
|
||||
label: __("Add Raw Material"),
|
||||
icon: "plus",
|
||||
click: function (node) {
|
||||
let view = frappe.views.trees["BOM Configurator"];
|
||||
view.events.add_item(node, view);
|
||||
@@ -135,10 +136,10 @@ class BOMConfigurator {
|
||||
condition: function (node) {
|
||||
return node.expandable;
|
||||
},
|
||||
btnClass: "hidden-xs",
|
||||
},
|
||||
{
|
||||
label: __(frappe.utils.icon("plus", "sm") + " Sub Assembly"),
|
||||
label: __("Add Sub Assembly"),
|
||||
icon: "folder-tree",
|
||||
click: function (node) {
|
||||
let view = frappe.views.trees["BOM Configurator"];
|
||||
view.events.add_sub_assembly(node, view);
|
||||
@@ -146,10 +147,10 @@ class BOMConfigurator {
|
||||
condition: function (node) {
|
||||
return node.expandable;
|
||||
},
|
||||
btnClass: "hidden-xs",
|
||||
},
|
||||
{
|
||||
label: __(frappe.utils.icon("plus", "sm") + " Phantom Item"),
|
||||
label: __("Add Phantom Item"),
|
||||
icon: "box",
|
||||
click: function (node) {
|
||||
let view = frappe.views.trees["BOM Configurator"];
|
||||
view.events.add_sub_assembly(node, view, true);
|
||||
@@ -157,29 +158,27 @@ class BOMConfigurator {
|
||||
condition: function (node) {
|
||||
return node.expandable;
|
||||
},
|
||||
btnClass: "hidden-xs",
|
||||
},
|
||||
{
|
||||
label: __("Collapse All"),
|
||||
label: __("Expand / Collapse All"),
|
||||
icon: "chevrons-down-up",
|
||||
condition: function (node) {
|
||||
return node.is_root && node.expandable;
|
||||
},
|
||||
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"));
|
||||
}
|
||||
let tree = view.tree;
|
||||
// state helper is new-frappe; fall back to the
|
||||
// root's own flag on older frappe
|
||||
let expanded = tree.get_expansion_state
|
||||
? tree.get_expansion_state() === "expanded"
|
||||
: node.expanded;
|
||||
tree.load_children(tree.root_node, !expanded);
|
||||
},
|
||||
condition: function (node) {
|
||||
return node.expandable && node.is_root;
|
||||
},
|
||||
btnClass: "hidden-xs expand-all-btn",
|
||||
},
|
||||
{
|
||||
label: __(frappe.utils.icon("move", "sm") + " Sub Assembly"),
|
||||
label: __("Convert to Sub Assembly"),
|
||||
icon: "folder-tree",
|
||||
click: function (node) {
|
||||
let view = frappe.views.trees["BOM Configurator"];
|
||||
view.events.convert_to_sub_assembly(node, view);
|
||||
@@ -187,10 +186,10 @@ class BOMConfigurator {
|
||||
condition: function (node) {
|
||||
return !node.expandable;
|
||||
},
|
||||
btnClass: "hidden-xs",
|
||||
},
|
||||
{
|
||||
label: __(frappe.utils.icon("move", "sm") + " Phantom Item"),
|
||||
label: __("Convert to Phantom Item"),
|
||||
icon: "box",
|
||||
click: function (node) {
|
||||
let view = frappe.views.trees["BOM Configurator"];
|
||||
view.events.convert_to_sub_assembly(node, view, true);
|
||||
@@ -198,10 +197,11 @@ class BOMConfigurator {
|
||||
condition: function (node) {
|
||||
return !node.expandable;
|
||||
},
|
||||
btnClass: "hidden-xs",
|
||||
},
|
||||
{
|
||||
label: __(frappe.utils.icon("delete", "sm") + " Item"),
|
||||
label: __("Delete Item"),
|
||||
icon: "trash-2",
|
||||
danger: true,
|
||||
click: function (node) {
|
||||
let view = frappe.views.trees["BOM Configurator"];
|
||||
view.events.delete_node(node, view);
|
||||
@@ -209,28 +209,25 @@ class BOMConfigurator {
|
||||
condition: function (node) {
|
||||
return !node.is_root;
|
||||
},
|
||||
btnClass: "hidden-xs",
|
||||
},
|
||||
]
|
||||
: [
|
||||
{
|
||||
label: __("Expand All"),
|
||||
label: __("Expand / Collapse All"),
|
||||
icon: "chevrons-down-up",
|
||||
condition: function (node) {
|
||||
return node.is_root && node.expandable;
|
||||
},
|
||||
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"));
|
||||
}
|
||||
let tree = view.tree;
|
||||
// state helper is new-frappe; fall back to the
|
||||
// root's own flag on older frappe
|
||||
let expanded = tree.get_expansion_state
|
||||
? tree.get_expansion_state() === "expanded"
|
||||
: node.expanded;
|
||||
tree.load_children(tree.root_node, !expanded);
|
||||
},
|
||||
condition: function (node) {
|
||||
return node.expandable && node.is_root;
|
||||
},
|
||||
btnClass: "hidden-xs expand-all-btn",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -312,6 +312,8 @@ erpnext.setup.slides_settings = [
|
||||
parent: $(dialog.body),
|
||||
label: parent,
|
||||
expandable: true,
|
||||
// read-only preview: row-mode visuals without actions
|
||||
row_style: true,
|
||||
method: "erpnext.accounts.utils.get_coa",
|
||||
args: {
|
||||
chart: chart_template,
|
||||
|
||||
@@ -626,3 +626,60 @@ body[data-route="pos"] {
|
||||
transform: translateY(-50%);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
// BOM Creator's tree. The qty/amount pill's positioning lives here (not in
|
||||
// inline styles) so it can differ by tree mode:
|
||||
//
|
||||
// * Legacy — old frappe (or any tree without new frappe's `.tree-rows`
|
||||
// class): restore the original float layout, so BOM looks unchanged when
|
||||
// running against a frappe that predates row mode.
|
||||
// * Row mode — new frappe adds `.tree-rows`: lay the pill out as the row's
|
||||
// value column (like .balance-area) and let labels stay readable.
|
||||
//
|
||||
// Both directions stay compatible: nothing here changes base markup, and the
|
||||
// legacy rule only applies where `.tree-rows` is absent.
|
||||
.tree:not(.tree-rows) .bom-qty-pill {
|
||||
float: right;
|
||||
margin-right: 40px;
|
||||
min-width: 128px;
|
||||
}
|
||||
|
||||
.tree.tree-rows {
|
||||
position: relative;
|
||||
|
||||
// child-row pill = value column, pinned to the row's right edge
|
||||
li.tree-node > .bom-qty-pill {
|
||||
float: none;
|
||||
flex: none;
|
||||
align-self: center;
|
||||
margin: 0 8px 0 auto;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
// the root row is a direct child of the wrapper (not a flex li), so its
|
||||
// pill can't margin-auto right — pin it to the wrapper's top-right, where
|
||||
// the always-first root row sits. width:max-content stops it stretching.
|
||||
> .bom-qty-pill {
|
||||
position: absolute;
|
||||
// inset shorthand sets left:auto explicitly (something else sets left,
|
||||
// which would otherwise beat a lone `right`)
|
||||
inset: 5px 8px auto auto;
|
||||
float: none;
|
||||
margin: 0;
|
||||
min-width: 0;
|
||||
width: max-content;
|
||||
}
|
||||
|
||||
// keep the label readable beside the pill
|
||||
.tree-link .tree-label {
|
||||
min-width: 6ch;
|
||||
}
|
||||
|
||||
// the click-toolbar (Edit / + Raw Material / …) wraps to its own line
|
||||
// under the row instead of fighting the value column
|
||||
.tree-node-toolbar {
|
||||
flex: 1 1 100%;
|
||||
order: 3;
|
||||
margin: 2px 0 4px 24px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,9 +13,10 @@ frappe.treeview_settings["Employee"] = {
|
||||
disable_add_node: true,
|
||||
get_tree_root: false,
|
||||
toolbar: [
|
||||
{ toggle_btn: true },
|
||||
{
|
||||
label: __("Edit"),
|
||||
icon: "pencil",
|
||||
inline: true,
|
||||
condition: function (node) {
|
||||
return !node.is_root;
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user