diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py index 5d504b94be6..427f3dba20d 100644 --- a/erpnext/accounts/doctype/account/account.py +++ b/erpnext/accounts/doctype/account/account.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import frappe from frappe.utils import cint, cstr from frappe import throw, _ -from frappe.utils.nestedset import NestedSet +from frappe.utils.nestedset import NestedSet, get_ancestors_of, get_descendants_of class RootNotEditable(frappe.ValidationError): pass class BalanceMismatchError(frappe.ValidationError): pass @@ -41,6 +41,7 @@ class Account(NestedSet): self.validate_frozen_accounts_modifier() self.validate_balance_must_be_debit_or_credit() self.validate_account_currency() + self.validate_root_company_and_sync_account_to_children() def validate_parent(self): """Fetch Parent Details and validate parent account""" @@ -90,6 +91,34 @@ class Account(NestedSet): if not self.parent_account and not self.is_group: frappe.throw(_("Root Account must be a group")) + def validate_root_company_and_sync_account_to_children(self): + # ignore validation while creating new compnay or while syncing to child companies + if frappe.local.flags.ignore_root_company_validation or self.flags.ignore_root_company_validation: + return + + ancestors = get_root_company(self.company) + if ancestors: + frappe.throw(_("Please add the account to root level Company - %s" % ancestors[0])) + else: + descendants = get_descendants_of('Company', self.company) + if not descendants: return + + acc_name_map = {} + acc_name = frappe.db.get_value('Account', self.parent_account, "account_name") + for d in frappe.db.get_values('Account', + {"company": ["in", descendants], "account_name": acc_name}, + ["company", "name"], as_dict=True): + acc_name_map[d["company"]] = d["name"] + + for company in descendants: + doc = frappe.copy_doc(self) + doc.flags.ignore_root_company_validation = True + doc.update({"company": company, "account_currency": None, + "parent": acc_name_map[company], "parent_account": acc_name_map[company]}) + doc.save() + frappe.msgprint(_("Account {0} is added in the child company {1}") + .format(doc.name, company)) + def validate_group_or_ledger(self): if self.get("__islocal"): return @@ -250,3 +279,9 @@ def merge_account(old, new, is_group, root_type, company): frappe.rename_doc("Account", old, new, merge=1, ignore_permissions=1) return new + +@frappe.whitelist() +def get_root_company(company): + # return the topmost company in the hierarchy + ancestors = get_ancestors_of('Company', company, "lft asc") + return [ancestors[0]] if ancestors else [] diff --git a/erpnext/accounts/doctype/account/account_tree.js b/erpnext/accounts/doctype/account/account_tree.js index a9cbdd5dee7..df0486cc271 100644 --- a/erpnext/accounts/doctype/account/account_tree.js +++ b/erpnext/accounts/doctype/account/account_tree.js @@ -4,13 +4,38 @@ frappe.treeview_settings["Account"] = { breadcrumbs: "Accounts", title: __("Chart Of Accounts"), get_tree_root: false, - filters: [{ - fieldname: "company", - fieldtype:"Select", - options: erpnext.utils.get_tree_options("company"), - label: __("Company"), - default: erpnext.utils.get_tree_default("company") - }], + filters: [ + { + fieldname: "company", + fieldtype:"Select", + options: erpnext.utils.get_tree_options("company"), + label: __("Company"), + default: erpnext.utils.get_tree_default("company"), + on_change: function() { + var me = frappe.treeview_settings['Account'].treeview; + var company = me.page.fields_dict.company.get_value(); + frappe.call({ + method: "erpnext.accounts.doctype.account.account.get_root_company", + args: { + company: company, + }, + callback: function(r) { + if(r.message) { + let root_company = r.message.length ? r.message[0] : ""; + me.page.fields_dict.root_company.set_value(root_company); + } + } + }); + } + }, + { + fieldname: "root_company", + fieldtype:"Data", + label: __("Root Company"), + hidden: true, + disable_onchange: true + } + ], root_label: "Accounts", get_tree_nodes: 'erpnext.accounts.utils.get_children', add_tree_node: 'erpnext.accounts.utils.add_ac', @@ -42,8 +67,8 @@ frappe.treeview_settings["Account"] = { ], ignore_fields:["parent_account"], onload: function(treeview) { - frappe.treeview_settings['Account'].page = {}; - $.extend(frappe.treeview_settings['Account'].page, treeview.page); + frappe.treeview_settings['Account'].treeview = {}; + $.extend(frappe.treeview_settings['Account'].treeview, treeview); function get_company() { return treeview.page.fields_dict.company.get_value(); } @@ -78,6 +103,18 @@ frappe.treeview_settings["Account"] = { } }, + post_render: function(treeview) { + frappe.treeview_settings['Account'].treeview["tree"] = treeview.tree; + treeview.page.set_primary_action(__("New"), function() { + let root_company = treeview.page.fields_dict.root_company.get_value(); + + if(root_company) { + frappe.throw(__("Please add the account to root level Company - ") + root_company); + } else { + treeview.new_node(); + } + }, "octicon octicon-plus"); + }, onrender: function(node) { if(frappe.boot.user.can_read.indexOf("GL Entry") !== -1){ var dr_or_cr = node.data.balance < 0 ? "Cr" : "Dr"; @@ -93,6 +130,19 @@ frappe.treeview_settings["Account"] = { } }, toolbar: [ + { + label:__("Add Child"), + condition: function(node) { + return frappe.boot.user.can_create.indexOf("Account") !== -1 && + !frappe.treeview_settings['Account'].treeview.page.fields_dict.root_company.get_value() && + node.expandable && !node.hide_add; + }, + click: function() { + var me = frappe.treeview_settings['Account'].treeview; + me.new_node(); + }, + btnClass: "hidden-xs" + }, { condition: function(node) { return !node.root && frappe.boot.user.can_read.indexOf("GL Entry") !== -1 @@ -103,7 +153,7 @@ frappe.treeview_settings["Account"] = { "account": node.label, "from_date": frappe.sys_defaults.year_start_date, "to_date": frappe.sys_defaults.year_end_date, - "company": frappe.treeview_settings['Account'].page.fields_dict.company.get_value() + "company": frappe.treeview_settings['Account'].treeview.page.fields_dict.company.get_value() }; frappe.set_route("query-report", "General Ledger"); }, diff --git a/erpnext/accounts/doctype/account/test_account.py b/erpnext/accounts/doctype/account/test_account.py index acaa0966a20..4c057d95492 100644 --- a/erpnext/accounts/doctype/account/test_account.py +++ b/erpnext/accounts/doctype/account/test_account.py @@ -97,6 +97,19 @@ class TestAccount(unittest.TestCase): self.assertRaises(frappe.ValidationError, merge_account, "Capital Stock - _TC",\ "Softwares - _TC", doc.is_group, doc.root_type, doc.company) + def test_account_sync(self): + del frappe.local.flags["ignore_root_company_validation"] + acc = frappe.new_doc("Account") + acc.account_name = "Test Sync Account" + acc.parent_account = "Temporary Accounts - _TC3" + acc.company = "_Test Company 3" + acc.insert() + + acc_tc_4 = frappe.db.get_value('Account', {'account_name': "Test Sync Account", "company": "_Test Company 4"}) + acc_tc_5 = frappe.db.get_value('Account', {'account_name': "Test Sync Account", "company": "_Test Company 5"}) + self.assertEqual(acc_tc_4, "Test Sync Account - _TC4") + self.assertEqual(acc_tc_5, "Test Sync Account - _TC5") + def _make_test_records(verbose): from frappe.test_runner import make_test_objects diff --git a/erpnext/hr/doctype/staffing_plan/staffing_plan.py b/erpnext/hr/doctype/staffing_plan/staffing_plan.py index 70e185cde56..83e53135ef4 100644 --- a/erpnext/hr/doctype/staffing_plan/staffing_plan.py +++ b/erpnext/hr/doctype/staffing_plan/staffing_plan.py @@ -20,6 +20,7 @@ class StaffingPlan(Document): self.total_estimated_budget = 0 for detail in self.get("staffing_details"): + self.set_vacancies(detail) self.validate_overlap(detail) self.validate_with_subsidiary_plans(detail) self.validate_with_parent_plan(detail) @@ -39,6 +40,15 @@ class StaffingPlan(Document): else: detail.vacancies = detail.number_of_positions = detail.total_estimated_cost = 0 self.total_estimated_budget += detail.total_estimated_cost + def set_vacancies(self, row): + if not row.vacancies: + current_openings = 0 + for field in ['current_count', 'current_openings']: + if row.get(field): + current_openings += row.get(field) + + row.vacancies = row.number_of_positions - current_openings + def validate_overlap(self, staffing_plan_detail): # Validate if any submitted Staffing Plan exist for any Designations in this plan # and spd.vacancies>0 ? diff --git a/erpnext/hr/doctype/staffing_plan/test_staffing_plan.py b/erpnext/hr/doctype/staffing_plan/test_staffing_plan.py index 66d9cdd07a5..22dba99af00 100644 --- a/erpnext/hr/doctype/staffing_plan/test_staffing_plan.py +++ b/erpnext/hr/doctype/staffing_plan/test_staffing_plan.py @@ -18,7 +18,7 @@ class TestStaffingPlan(unittest.TestCase): if frappe.db.exists("Staffing Plan", "Test"): return staffing_plan = frappe.new_doc("Staffing Plan") - staffing_plan.company = "_Test Company 3" + staffing_plan.company = "_Test Company 10" staffing_plan.name = "Test" staffing_plan.from_date = nowdate() staffing_plan.to_date = add_days(nowdate(), 10) @@ -67,7 +67,7 @@ class TestStaffingPlan(unittest.TestCase): if frappe.db.exists("Staffing Plan", "Test 1"): return staffing_plan = frappe.new_doc("Staffing Plan") - staffing_plan.company = "_Test Company 3" + staffing_plan.company = "_Test Company 10" staffing_plan.name = "Test 1" staffing_plan.from_date = nowdate() staffing_plan.to_date = add_days(nowdate(), 10) @@ -85,11 +85,11 @@ def _set_up(): make_company() def make_company(): - if frappe.db.exists("Company", "_Test Company 3"): + if frappe.db.exists("Company", "_Test Company 10"): return company = frappe.new_doc("Company") - company.company_name = "_Test Company 3" - company.abbr = "_TC3" + company.company_name = "_Test Company 10" + company.abbr = "_TC10" company.parent_company = "_Test Company" company.default_currency = "INR" company.country = "India" diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js index 70e047a4e80..aff4baf9314 100644 --- a/erpnext/setup/doctype/company/company.js +++ b/erpnext/setup/doctype/company/company.js @@ -16,6 +16,12 @@ frappe.ui.form.on("Company", { filters: {"is_additional_component": 1} } }); + + frm.set_query("parent_company", function() { + return { + filters: {"is_group": 1} + } + }); }, company_name: function(frm) { @@ -28,6 +34,13 @@ frappe.ui.form.on("Company", { } }, + parent_company: function(frm) { + var bool = frm.doc.parent_company ? true : false; + frm.set_value('create_chart_of_accounts_based_on', bool ? "Existing Company" : ""); + frm.set_value('existing_company', bool ? frm.doc.parent_company : ""); + disbale_coa_fields(frm, bool); + }, + date_of_commencement: function(frm) { if(frm.doc.date_of_commencement