diff --git a/erpnext/accounts/doctype/account/account_tree.js b/erpnext/accounts/doctype/account/account_tree.js index 0248fab4602..dd7e1d29e71 100644 --- a/erpnext/accounts/doctype/account/account_tree.js +++ b/erpnext/accounts/doctype/account/account_tree.js @@ -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( + $( + ` + ${frappe.utils.icon("lock", "sm")} + ` + )[0] + ); + } + + if (flags.length) { + const $flags = $( + '' + ); + 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" }); + }, + }); + }; diff --git a/erpnext/accounts/doctype/cost_center/cost_center_tree.js b/erpnext/accounts/doctype/cost_center/cost_center_tree.js index 3edeb8efb0b..5b134f6545f 100644 --- a/erpnext/accounts/doctype/cost_center/cost_center_tree.js +++ b/erpnext/accounts/doctype/cost_center/cost_center_tree.js @@ -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" }); + }, + }); + }; diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index c2ce8ac39f9..db7a58c08c3 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -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) diff --git a/erpnext/manufacturing/doctype/bom/bom_tree.js b/erpnext/manufacturing/doctype/bom/bom_tree.js index c4ecb853d7b..35a370e9baf 100644 --- a/erpnext/manufacturing/doctype/bom/bom_tree.js +++ b/erpnext/manufacturing/doctype/bom/bom_tree.js @@ -49,6 +49,8 @@ frappe.treeview_settings["BOM"] = { { toggle_btn: true }, { label: __("Edit"), + icon: "pencil", + inline: true, condition: function (node) { return node.expandable; }, diff --git a/erpnext/projects/doctype/task/task_tree.js b/erpnext/projects/doctype/task/task_tree.js index fba1b309260..85d7db7e39b 100644 --- a/erpnext/projects/doctype/task/task_tree.js +++ b/erpnext/projects/doctype/task/task_tree.js @@ -40,6 +40,7 @@ frappe.treeview_settings["Task"] = { toolbar: [ { label: __("Add Multiple"), + icon: "list-plus", condition: function (node) { return node.expandable; }, diff --git a/erpnext/setup/doctype/employee/employee_tree.js b/erpnext/setup/doctype/employee/employee_tree.js index 26a89719d7f..81d0cd8a94d 100644 --- a/erpnext/setup/doctype/employee/employee_tree.js +++ b/erpnext/setup/doctype/employee/employee_tree.js @@ -16,6 +16,8 @@ frappe.treeview_settings["Employee"] = { { toggle_btn: true }, { label: __("Edit"), + icon: "pencil", + inline: true, condition: function (node) { return !node.is_root; },