Merge branch 'develop' into e-commerce-refactor-develop
diff --git a/erpnext/accounts/doctype/cost_center/cost_center.js b/erpnext/accounts/doctype/cost_center/cost_center.js
index ee23b1b..632fab0 100644
--- a/erpnext/accounts/doctype/cost_center/cost_center.js
+++ b/erpnext/accounts/doctype/cost_center/cost_center.js
@@ -15,17 +15,6 @@
}
}
});
-
- frm.set_query("cost_center", "distributed_cost_center", function() {
- return {
- filters: {
- company: frm.doc.company,
- is_group: 0,
- enable_distributed_cost_center: 0,
- name: ['!=', frm.doc.name]
- }
- };
- });
},
refresh: function(frm) {
if (!frm.is_new()) {
diff --git a/erpnext/accounts/doctype/cost_center/cost_center.json b/erpnext/accounts/doctype/cost_center/cost_center.json
index e7fa954..7cbb290 100644
--- a/erpnext/accounts/doctype/cost_center/cost_center.json
+++ b/erpnext/accounts/doctype/cost_center/cost_center.json
@@ -16,9 +16,6 @@
"cb0",
"is_group",
"disabled",
- "section_break_9",
- "enable_distributed_cost_center",
- "distributed_cost_center",
"lft",
"rgt",
"old_parent"
@@ -122,31 +119,13 @@
"fieldname": "disabled",
"fieldtype": "Check",
"label": "Disabled"
- },
- {
- "default": "0",
- "fieldname": "enable_distributed_cost_center",
- "fieldtype": "Check",
- "label": "Enable Distributed Cost Center"
- },
- {
- "depends_on": "eval:doc.is_group==0",
- "fieldname": "section_break_9",
- "fieldtype": "Section Break"
- },
- {
- "depends_on": "enable_distributed_cost_center",
- "fieldname": "distributed_cost_center",
- "fieldtype": "Table",
- "label": "Distributed Cost Center",
- "options": "Distributed Cost Center"
}
],
"icon": "fa fa-money",
"idx": 1,
"is_tree": 1,
"links": [],
- "modified": "2020-06-17 16:09:30.025214",
+ "modified": "2022-01-31 13:22:58.916273",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Cost Center",
@@ -189,5 +168,6 @@
"search_fields": "parent_cost_center, is_group",
"show_name_in_global_search": 1,
"sort_field": "modified",
- "sort_order": "ASC"
+ "sort_order": "ASC",
+ "states": []
}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/cost_center/cost_center.py b/erpnext/accounts/doctype/cost_center/cost_center.py
index 7ae0a72..07cc076 100644
--- a/erpnext/accounts/doctype/cost_center/cost_center.py
+++ b/erpnext/accounts/doctype/cost_center/cost_center.py
@@ -4,7 +4,6 @@
import frappe
from frappe import _
-from frappe.utils import cint
from frappe.utils.nestedset import NestedSet
from erpnext.accounts.utils import validate_field_number
@@ -20,24 +19,6 @@
def validate(self):
self.validate_mandatory()
self.validate_parent_cost_center()
- self.validate_distributed_cost_center()
-
- def validate_distributed_cost_center(self):
- if cint(self.enable_distributed_cost_center):
- if not self.distributed_cost_center:
- frappe.throw(_("Please enter distributed cost center"))
- if sum(x.percentage_allocation for x in self.distributed_cost_center) != 100:
- frappe.throw(_("Total percentage allocation for distributed cost center should be equal to 100"))
- if not self.get('__islocal'):
- if not cint(frappe.get_cached_value("Cost Center", {"name": self.name}, "enable_distributed_cost_center")) \
- and self.check_if_part_of_distributed_cost_center():
- frappe.throw(_("Cannot enable Distributed Cost Center for a Cost Center already allocated in another Distributed Cost Center"))
- if next((True for x in self.distributed_cost_center if x.cost_center == x.parent), False):
- frappe.throw(_("Parent Cost Center cannot be added in Distributed Cost Center"))
- if check_if_distributed_cost_center_enabled(list(x.cost_center for x in self.distributed_cost_center)):
- frappe.throw(_("A Distributed Cost Center cannot be added in the Distributed Cost Center allocation table."))
- else:
- self.distributed_cost_center = []
def validate_mandatory(self):
if self.cost_center_name != self.company and not self.parent_cost_center:
@@ -64,10 +45,10 @@
@frappe.whitelist()
def convert_ledger_to_group(self):
- if cint(self.enable_distributed_cost_center):
- frappe.throw(_("Cost Center with enabled distributed cost center can not be converted to group"))
- if self.check_if_part_of_distributed_cost_center():
- frappe.throw(_("Cost Center Already Allocated in a Distributed Cost Center cannot be converted to group"))
+ if self.if_allocation_exists_against_cost_center():
+ frappe.throw(_("Cost Center with Allocation records can not be converted to a group"))
+ if self.check_if_part_of_cost_center_allocation():
+ frappe.throw(_("Cost Center is a part of Cost Center Allocation, hence cannot be converted to a group"))
if self.check_gle_exists():
frappe.throw(_("Cost Center with existing transactions can not be converted to group"))
self.is_group = 1
@@ -81,8 +62,17 @@
return frappe.db.sql("select name from `tabCost Center` where \
parent_cost_center = %s and docstatus != 2", self.name)
- def check_if_part_of_distributed_cost_center(self):
- return frappe.db.get_value("Distributed Cost Center", {"cost_center": self.name})
+ def if_allocation_exists_against_cost_center(self):
+ return frappe.db.get_value("Cost Center Allocation", filters = {
+ "main_cost_center": self.name,
+ "docstatus": 1
+ })
+
+ def check_if_part_of_cost_center_allocation(self):
+ return frappe.db.get_value("Cost Center Allocation Percentage", filters = {
+ "cost_center": self.name,
+ "docstatus": 1
+ })
def before_rename(self, olddn, newdn, merge=False):
# Add company abbr if not provided
@@ -126,8 +116,4 @@
def get_name_with_number(new_account, account_number):
if account_number and not new_account[0].isdigit():
new_account = account_number + " - " + new_account
- return new_account
-
-def check_if_distributed_cost_center_enabled(cost_center_list):
- value_list = frappe.get_list("Cost Center", {"name": ["in", cost_center_list]}, "enable_distributed_cost_center", as_list=1)
- return next((True for x in value_list if x[0]), False)
+ return new_account
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/cost_center/test_cost_center.py b/erpnext/accounts/doctype/cost_center/test_cost_center.py
index f8615ec..ff50a21 100644
--- a/erpnext/accounts/doctype/cost_center/test_cost_center.py
+++ b/erpnext/accounts/doctype/cost_center/test_cost_center.py
@@ -23,33 +23,6 @@
self.assertRaises(frappe.ValidationError, cost_center.save)
- def test_validate_distributed_cost_center(self):
-
- if not frappe.db.get_value('Cost Center', {'name': '_Test Cost Center - _TC'}):
- frappe.get_doc(test_records[0]).insert()
-
- if not frappe.db.get_value('Cost Center', {'name': '_Test Cost Center 2 - _TC'}):
- frappe.get_doc(test_records[1]).insert()
-
- invalid_distributed_cost_center = frappe.get_doc({
- "company": "_Test Company",
- "cost_center_name": "_Test Distributed Cost Center",
- "doctype": "Cost Center",
- "is_group": 0,
- "parent_cost_center": "_Test Company - _TC",
- "enable_distributed_cost_center": 1,
- "distributed_cost_center": [{
- "cost_center": "_Test Cost Center - _TC",
- "percentage_allocation": 40
- }, {
- "cost_center": "_Test Cost Center 2 - _TC",
- "percentage_allocation": 50
- }
- ]
- })
-
- self.assertRaises(frappe.ValidationError, invalid_distributed_cost_center.save)
-
def create_cost_center(**args):
args = frappe._dict(args)
if args.cost_center_name:
diff --git a/erpnext/accounts/doctype/distributed_cost_center/__init__.py b/erpnext/accounts/doctype/cost_center_allocation/__init__.py
similarity index 100%
rename from erpnext/accounts/doctype/distributed_cost_center/__init__.py
rename to erpnext/accounts/doctype/cost_center_allocation/__init__.py
diff --git a/erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.js b/erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.js
new file mode 100644
index 0000000..ab0baab
--- /dev/null
+++ b/erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.js
@@ -0,0 +1,19 @@
+// Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Cost Center Allocation', {
+ setup: function(frm) {
+ let filters = {"is_group": 0};
+ if (frm.doc.company) {
+ $.extend(filters, {
+ "company": frm.doc.company
+ });
+ }
+
+ frm.set_query('main_cost_center', function() {
+ return {
+ filters: filters
+ };
+ });
+ }
+});
diff --git a/erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.json b/erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.json
new file mode 100644
index 0000000..45ab886
--- /dev/null
+++ b/erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.json
@@ -0,0 +1,128 @@
+{
+ "actions": [],
+ "allow_rename": 1,
+ "autoname": "CC-ALLOC-.#####",
+ "creation": "2022-01-13 20:07:29.871109",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "main_cost_center",
+ "company",
+ "column_break_2",
+ "valid_from",
+ "section_break_5",
+ "allocation_percentages",
+ "amended_from"
+ ],
+ "fields": [
+ {
+ "fieldname": "main_cost_center",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Main Cost Center",
+ "options": "Cost Center",
+ "reqd": 1
+ },
+ {
+ "default": "Today",
+ "fieldname": "valid_from",
+ "fieldtype": "Date",
+ "in_list_view": 1,
+ "label": "Valid From",
+ "reqd": 1
+ },
+ {
+ "fieldname": "column_break_2",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "section_break_5",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fetch_from": "main_cost_center.company",
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "label": "Company",
+ "options": "Company",
+ "reqd": 1
+ },
+ {
+ "fieldname": "allocation_percentages",
+ "fieldtype": "Table",
+ "label": "Cost Center Allocation Percentages",
+ "options": "Cost Center Allocation Percentage",
+ "reqd": 1
+ },
+ {
+ "fieldname": "amended_from",
+ "fieldtype": "Link",
+ "label": "Amended From",
+ "no_copy": 1,
+ "options": "Cost Center Allocation",
+ "print_hide": 1,
+ "read_only": 1
+ }
+ ],
+ "index_web_pages_for_search": 1,
+ "is_submittable": 1,
+ "links": [],
+ "modified": "2022-01-31 11:47:12.086253",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Cost Center Allocation",
+ "name_case": "UPPER CASE",
+ "naming_rule": "Expression (old style)",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "amend": 1,
+ "cancel": 1,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "share": 1,
+ "submit": 1,
+ "write": 1
+ },
+ {
+ "amend": 1,
+ "cancel": 1,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Accounts Manager",
+ "share": 1,
+ "submit": 1,
+ "write": 1
+ },
+ {
+ "amend": 1,
+ "cancel": 1,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Accounts User",
+ "share": 1,
+ "submit": 1,
+ "write": 1
+ }
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.py b/erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.py
new file mode 100644
index 0000000..bad3fb4
--- /dev/null
+++ b/erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.py
@@ -0,0 +1,90 @@
+# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+import frappe
+from frappe import _
+from frappe.model.document import Document
+from frappe.utils import add_days, format_date, getdate
+
+
+class MainCostCenterCantBeChild(frappe.ValidationError):
+ pass
+class InvalidMainCostCenter(frappe.ValidationError):
+ pass
+class InvalidChildCostCenter(frappe.ValidationError):
+ pass
+class WrongPercentageAllocation(frappe.ValidationError):
+ pass
+class InvalidDateError(frappe.ValidationError):
+ pass
+
+class CostCenterAllocation(Document):
+ def validate(self):
+ self.validate_total_allocation_percentage()
+ self.validate_from_date_based_on_existing_gle()
+ self.validate_backdated_allocation()
+ self.validate_main_cost_center()
+ self.validate_child_cost_centers()
+
+ def validate_total_allocation_percentage(self):
+ total_percentage = sum([d.percentage for d in self.get("allocation_percentages", [])])
+
+ if total_percentage != 100:
+ frappe.throw(_("Total percentage against cost centers should be 100"), WrongPercentageAllocation)
+
+ def validate_from_date_based_on_existing_gle(self):
+ # Check if GLE exists against the main cost center
+ # If exists ensure from date is set after posting date of last GLE
+
+ last_gle_date = frappe.db.get_value("GL Entry",
+ {"cost_center": self.main_cost_center, "is_cancelled": 0},
+ "posting_date", order_by="posting_date desc")
+
+ if last_gle_date:
+ if getdate(self.valid_from) <= getdate(last_gle_date):
+ frappe.throw(_("Valid From must be after {0} as last GL Entry against the cost center {1} posted on this date")
+ .format(last_gle_date, self.main_cost_center), InvalidDateError)
+
+ def validate_backdated_allocation(self):
+ # Check if there are any future existing allocation records against the main cost center
+ # If exists, warn the user about it
+
+ future_allocation = frappe.db.get_value("Cost Center Allocation", filters = {
+ "main_cost_center": self.main_cost_center,
+ "valid_from": (">=", self.valid_from),
+ "name": ("!=", self.name),
+ "docstatus": 1
+ }, fieldname=['valid_from', 'name'], order_by='valid_from', as_dict=1)
+
+ if future_allocation:
+ frappe.msgprint(_("Another Cost Center Allocation record {0} applicable from {1}, hence this allocation will be applicable upto {2}")
+ .format(frappe.bold(future_allocation.name), frappe.bold(format_date(future_allocation.valid_from)),
+ frappe.bold(format_date(add_days(future_allocation.valid_from, -1)))),
+ title=_("Warning!"), indicator="orange", alert=1
+ )
+
+ def validate_main_cost_center(self):
+ # Main cost center itself cannot be entered in child table
+ if self.main_cost_center in [d.cost_center for d in self.allocation_percentages]:
+ frappe.throw(_("Main Cost Center {0} cannot be entered in the child table")
+ .format(self.main_cost_center), MainCostCenterCantBeChild)
+
+ # If main cost center is used for allocation under any other cost center,
+ # allocation cannot be done against it
+ parent = frappe.db.get_value("Cost Center Allocation Percentage", filters = {
+ "cost_center": self.main_cost_center,
+ "docstatus": 1
+ }, fieldname='parent')
+ if parent:
+ frappe.throw(_("{0} cannot be used as a Main Cost Center because it has been used as child in Cost Center Allocation {1}")
+ .format(self.main_cost_center, parent), InvalidMainCostCenter)
+
+ def validate_child_cost_centers(self):
+ # Check if child cost center is used as main cost center in any existing allocation
+ main_cost_centers = [d.main_cost_center for d in
+ frappe.get_all("Cost Center Allocation", {'docstatus': 1}, 'main_cost_center')]
+
+ for d in self.allocation_percentages:
+ if d.cost_center in main_cost_centers:
+ frappe.throw(_("Cost Center {0} cannot be used for allocation as it is used as main cost center in other allocation record.")
+ .format(d.cost_center), InvalidChildCostCenter)
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/cost_center_allocation/test_cost_center_allocation.py b/erpnext/accounts/doctype/cost_center_allocation/test_cost_center_allocation.py
new file mode 100644
index 0000000..9cf4c00
--- /dev/null
+++ b/erpnext/accounts/doctype/cost_center_allocation/test_cost_center_allocation.py
@@ -0,0 +1,156 @@
+# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+
+import unittest
+
+import frappe
+from frappe.utils import add_days, today
+
+from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
+from erpnext.accounts.doctype.cost_center_allocation.cost_center_allocation import (
+ InvalidChildCostCenter,
+ InvalidDateError,
+ InvalidMainCostCenter,
+ MainCostCenterCantBeChild,
+ WrongPercentageAllocation,
+)
+from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
+
+
+class TestCostCenterAllocation(unittest.TestCase):
+ def setUp(self):
+ cost_centers = ["Main Cost Center 1", "Main Cost Center 2", "Sub Cost Center 1", "Sub Cost Center 2"]
+ for cc in cost_centers:
+ create_cost_center(cost_center_name=cc, company="_Test Company")
+
+ def test_gle_based_on_cost_center_allocation(self):
+ cca = create_cost_center_allocation("_Test Company", "Main Cost Center 1 - _TC",
+ {
+ "Sub Cost Center 1 - _TC": 60,
+ "Sub Cost Center 2 - _TC": 40
+ }
+ )
+
+ jv = make_journal_entry("_Test Cash - _TC", "Sales - _TC", 100,
+ cost_center = "Main Cost Center 1 - _TC", submit=True)
+
+ expected_values = [
+ ["Sub Cost Center 1 - _TC", 0.0, 60],
+ ["Sub Cost Center 2 - _TC", 0.0, 40]
+ ]
+
+ gle = frappe.qb.DocType("GL Entry")
+ gl_entries = (
+ frappe.qb.from_(gle)
+ .select(gle.cost_center, gle.debit, gle.credit)
+ .where(gle.voucher_type == 'Journal Entry')
+ .where(gle.voucher_no == jv.name)
+ .where(gle.account == 'Sales - _TC')
+ .orderby(gle.cost_center)
+ ).run(as_dict=1)
+
+ self.assertTrue(gl_entries)
+
+ for i, gle in enumerate(gl_entries):
+ self.assertEqual(expected_values[i][0], gle.cost_center)
+ self.assertEqual(expected_values[i][1], gle.debit)
+ self.assertEqual(expected_values[i][2], gle.credit)
+
+ cca.cancel()
+ jv.cancel()
+
+ def test_main_cost_center_cant_be_child(self):
+ # Main cost center itself cannot be entered in child table
+ cca = create_cost_center_allocation("_Test Company", "Main Cost Center 1 - _TC",
+ {
+ "Sub Cost Center 1 - _TC": 60,
+ "Main Cost Center 1 - _TC": 40
+ }, save=False
+ )
+
+ self.assertRaises(MainCostCenterCantBeChild, cca.save)
+
+ def test_invalid_main_cost_center(self):
+ # If main cost center is used for allocation under any other cost center,
+ # allocation cannot be done against it
+ cca1 = create_cost_center_allocation("_Test Company", "Main Cost Center 1 - _TC",
+ {
+ "Sub Cost Center 1 - _TC": 60,
+ "Sub Cost Center 2 - _TC": 40
+ }
+ )
+
+ cca2 = create_cost_center_allocation("_Test Company", "Sub Cost Center 1 - _TC",
+ {
+ "Sub Cost Center 2 - _TC": 100
+ }, save=False
+ )
+
+ self.assertRaises(InvalidMainCostCenter, cca2.save)
+
+ cca1.cancel()
+
+ def test_if_child_cost_center_has_any_allocation_record(self):
+ # Check if any child cost center is used as main cost center in any other existing allocation
+ cca1 = create_cost_center_allocation("_Test Company", "Main Cost Center 1 - _TC",
+ {
+ "Sub Cost Center 1 - _TC": 60,
+ "Sub Cost Center 2 - _TC": 40
+ }
+ )
+
+ cca2 = create_cost_center_allocation("_Test Company", "Main Cost Center 2 - _TC",
+ {
+ "Main Cost Center 1 - _TC": 60,
+ "Sub Cost Center 1 - _TC": 40
+ }, save=False
+ )
+
+ self.assertRaises(InvalidChildCostCenter, cca2.save)
+
+ cca1.cancel()
+
+ def test_total_percentage(self):
+ cca = create_cost_center_allocation("_Test Company", "Main Cost Center 1 - _TC",
+ {
+ "Sub Cost Center 1 - _TC": 40,
+ "Sub Cost Center 2 - _TC": 40
+ }, save=False
+ )
+ self.assertRaises(WrongPercentageAllocation, cca.save)
+
+ def test_valid_from_based_on_existing_gle(self):
+ # GLE posted against Sub Cost Center 1 on today
+ jv = make_journal_entry("_Test Cash - _TC", "Sales - _TC", 100,
+ cost_center = "Main Cost Center 1 - _TC", posting_date=today(), submit=True)
+
+ # try to set valid from as yesterday
+ cca = create_cost_center_allocation("_Test Company", "Main Cost Center 1 - _TC",
+ {
+ "Sub Cost Center 1 - _TC": 60,
+ "Sub Cost Center 2 - _TC": 40
+ }, valid_from=add_days(today(), -1), save=False
+ )
+
+ self.assertRaises(InvalidDateError, cca.save)
+
+ jv.cancel()
+
+def create_cost_center_allocation(company, main_cost_center, allocation_percentages,
+ valid_from=None, valid_upto=None, save=True, submit=True):
+ doc = frappe.new_doc("Cost Center Allocation")
+ doc.main_cost_center = main_cost_center
+ doc.company = company
+ doc.valid_from = valid_from or today()
+ doc.valid_upto = valid_upto
+ for cc, percentage in allocation_percentages.items():
+ doc.append("allocation_percentages", {
+ "cost_center": cc,
+ "percentage": percentage
+ })
+ if save:
+ doc.save()
+ if submit:
+ doc.submit()
+
+ return doc
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/distributed_cost_center/__init__.py b/erpnext/accounts/doctype/cost_center_allocation_percentage/__init__.py
similarity index 100%
copy from erpnext/accounts/doctype/distributed_cost_center/__init__.py
copy to erpnext/accounts/doctype/cost_center_allocation_percentage/__init__.py
diff --git a/erpnext/accounts/doctype/distributed_cost_center/distributed_cost_center.json b/erpnext/accounts/doctype/cost_center_allocation_percentage/cost_center_allocation_percentage.json
similarity index 63%
rename from erpnext/accounts/doctype/distributed_cost_center/distributed_cost_center.json
rename to erpnext/accounts/doctype/cost_center_allocation_percentage/cost_center_allocation_percentage.json
index 45b0e2d..4b871ae 100644
--- a/erpnext/accounts/doctype/distributed_cost_center/distributed_cost_center.json
+++ b/erpnext/accounts/doctype/cost_center_allocation_percentage/cost_center_allocation_percentage.json
@@ -1,12 +1,13 @@
{
"actions": [],
- "creation": "2020-03-19 12:34:01.500390",
+ "allow_rename": 1,
+ "creation": "2022-01-03 18:10:11.697198",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"cost_center",
- "percentage_allocation"
+ "percentage"
],
"fields": [
{
@@ -18,23 +19,23 @@
"reqd": 1
},
{
- "fieldname": "percentage_allocation",
- "fieldtype": "Float",
+ "fieldname": "percentage",
+ "fieldtype": "Int",
"in_list_view": 1,
- "label": "Percentage Allocation",
+ "label": "Percentage (%)",
"reqd": 1
}
],
+ "index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2020-03-19 12:54:43.674655",
+ "modified": "2022-01-03 18:10:20.029821",
"modified_by": "Administrator",
"module": "Accounts",
- "name": "Distributed Cost Center",
+ "name": "Cost Center Allocation Percentage",
"owner": "Administrator",
"permissions": [],
- "quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
- "track_changes": 1
+ "states": []
}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/cost_center_allocation_percentage/cost_center_allocation_percentage.py b/erpnext/accounts/doctype/cost_center_allocation_percentage/cost_center_allocation_percentage.py
new file mode 100644
index 0000000..7d20efb
--- /dev/null
+++ b/erpnext/accounts/doctype/cost_center_allocation_percentage/cost_center_allocation_percentage.py
@@ -0,0 +1,9 @@
+# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+# import frappe
+from frappe.model.document import Document
+
+
+class CostCenterAllocationPercentage(Document):
+ pass
diff --git a/erpnext/accounts/doctype/distributed_cost_center/distributed_cost_center.py b/erpnext/accounts/doctype/distributed_cost_center/distributed_cost_center.py
deleted file mode 100644
index dcf0e3b..0000000
--- a/erpnext/accounts/doctype/distributed_cost_center/distributed_cost_center.py
+++ /dev/null
@@ -1,10 +0,0 @@
-# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-
-# import frappe
-from frappe.model.document import Document
-
-
-class DistributedCostCenter(Document):
- pass
diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py
index 1836db6..55bc967 100644
--- a/erpnext/accounts/general_ledger.py
+++ b/erpnext/accounts/general_ledger.py
@@ -2,6 +2,8 @@
# License: GNU General Public License v3. See license.txt
+import copy
+
import frappe
from frappe import _
from frappe.model.meta import get_field_precision
@@ -51,49 +53,57 @@
.format(frappe.bold(accounting_periods[0].name)), ClosedAccountingPeriod)
def process_gl_map(gl_map, merge_entries=True, precision=None):
+ if not gl_map:
+ return []
+
+ gl_map = distribute_gl_based_on_cost_center_allocation(gl_map, precision)
+
if merge_entries:
gl_map = merge_similar_entries(gl_map, precision)
- for entry in gl_map:
- # toggle debit, credit if negative entry
- if flt(entry.debit) < 0:
- entry.credit = flt(entry.credit) - flt(entry.debit)
- entry.debit = 0.0
- if flt(entry.debit_in_account_currency) < 0:
- entry.credit_in_account_currency = \
- flt(entry.credit_in_account_currency) - flt(entry.debit_in_account_currency)
- entry.debit_in_account_currency = 0.0
-
- if flt(entry.credit) < 0:
- entry.debit = flt(entry.debit) - flt(entry.credit)
- entry.credit = 0.0
-
- if flt(entry.credit_in_account_currency) < 0:
- entry.debit_in_account_currency = \
- flt(entry.debit_in_account_currency) - flt(entry.credit_in_account_currency)
- entry.credit_in_account_currency = 0.0
-
- update_net_values(entry)
+ gl_map = toggle_debit_credit_if_negative(gl_map)
return gl_map
-def update_net_values(entry):
- # In some scenarios net value needs to be shown in the ledger
- # This method updates net values as debit or credit
- if entry.post_net_value and entry.debit and entry.credit:
- if entry.debit > entry.credit:
- entry.debit = entry.debit - entry.credit
- entry.debit_in_account_currency = entry.debit_in_account_currency \
- - entry.credit_in_account_currency
- entry.credit = 0
- entry.credit_in_account_currency = 0
- else:
- entry.credit = entry.credit - entry.debit
- entry.credit_in_account_currency = entry.credit_in_account_currency \
- - entry.debit_in_account_currency
+def distribute_gl_based_on_cost_center_allocation(gl_map, precision=None):
+ cost_center_allocation = get_cost_center_allocation_data(gl_map[0]["company"], gl_map[0]["posting_date"])
+ if not cost_center_allocation:
+ return gl_map
- entry.debit = 0
- entry.debit_in_account_currency = 0
+ new_gl_map = []
+ for d in gl_map:
+ cost_center = d.get("cost_center")
+ if cost_center and cost_center_allocation.get(cost_center):
+ for sub_cost_center, percentage in cost_center_allocation.get(cost_center, {}).items():
+ gle = copy.deepcopy(d)
+ gle.cost_center = sub_cost_center
+ for field in ("debit", "credit", "debit_in_account_currency", "credit_in_company_currency"):
+ gle[field] = flt(flt(d.get(field)) * percentage / 100, precision)
+ new_gl_map.append(gle)
+ else:
+ new_gl_map.append(d)
+
+ return new_gl_map
+
+def get_cost_center_allocation_data(company, posting_date):
+ par = frappe.qb.DocType("Cost Center Allocation")
+ child = frappe.qb.DocType("Cost Center Allocation Percentage")
+
+ records = (
+ frappe.qb.from_(par).inner_join(child).on(par.name == child.parent)
+ .select(par.main_cost_center, child.cost_center, child.percentage)
+ .where(par.docstatus == 1)
+ .where(par.company == company)
+ .where(par.valid_from <= posting_date)
+ .orderby(par.valid_from, order=frappe.qb.desc)
+ ).run(as_dict=True)
+
+ cc_allocation = frappe._dict()
+ for d in records:
+ cc_allocation.setdefault(d.main_cost_center, frappe._dict())\
+ .setdefault(d.cost_center, d.percentage)
+
+ return cc_allocation
def merge_similar_entries(gl_map, precision=None):
merged_gl_map = []
@@ -145,6 +155,49 @@
if same_head:
return e
+def toggle_debit_credit_if_negative(gl_map):
+ for entry in gl_map:
+ # toggle debit, credit if negative entry
+ if flt(entry.debit) < 0:
+ entry.credit = flt(entry.credit) - flt(entry.debit)
+ entry.debit = 0.0
+
+ if flt(entry.debit_in_account_currency) < 0:
+ entry.credit_in_account_currency = \
+ flt(entry.credit_in_account_currency) - flt(entry.debit_in_account_currency)
+ entry.debit_in_account_currency = 0.0
+
+ if flt(entry.credit) < 0:
+ entry.debit = flt(entry.debit) - flt(entry.credit)
+ entry.credit = 0.0
+
+ if flt(entry.credit_in_account_currency) < 0:
+ entry.debit_in_account_currency = \
+ flt(entry.debit_in_account_currency) - flt(entry.credit_in_account_currency)
+ entry.credit_in_account_currency = 0.0
+
+ update_net_values(entry)
+
+ return gl_map
+
+def update_net_values(entry):
+ # In some scenarios net value needs to be shown in the ledger
+ # This method updates net values as debit or credit
+ if entry.post_net_value and entry.debit and entry.credit:
+ if entry.debit > entry.credit:
+ entry.debit = entry.debit - entry.credit
+ entry.debit_in_account_currency = entry.debit_in_account_currency \
+ - entry.credit_in_account_currency
+ entry.credit = 0
+ entry.credit_in_account_currency = 0
+ else:
+ entry.credit = entry.credit - entry.debit
+ entry.credit_in_account_currency = entry.credit_in_account_currency \
+ - entry.debit_in_account_currency
+
+ entry.debit = 0
+ entry.debit_in_account_currency = 0
+
def save_entries(gl_map, adv_adj, update_outstanding, from_repost=False):
if not from_repost:
validate_cwip_accounts(gl_map)
diff --git a/erpnext/accounts/report/budget_variance_report/budget_variance_report.py b/erpnext/accounts/report/budget_variance_report/budget_variance_report.py
index 3bb590a..56ee500 100644
--- a/erpnext/accounts/report/budget_variance_report/budget_variance_report.py
+++ b/erpnext/accounts/report/budget_variance_report/budget_variance_report.py
@@ -29,18 +29,6 @@
dimension_items = cam_map.get(dimension)
if dimension_items:
data = get_final_data(dimension, dimension_items, filters, period_month_ranges, data, 0)
- else:
- DCC_allocation = frappe.db.sql('''SELECT parent, sum(percentage_allocation) as percentage_allocation
- FROM `tabDistributed Cost Center`
- WHERE cost_center IN %(dimension)s
- AND parent NOT IN %(dimension)s
- GROUP BY parent''',{'dimension':[dimension]})
- if DCC_allocation:
- filters['budget_against_filter'] = [DCC_allocation[0][0]]
- ddc_cam_map = get_dimension_account_month_map(filters)
- dimension_items = ddc_cam_map.get(DCC_allocation[0][0])
- if dimension_items:
- data = get_final_data(dimension, dimension_items, filters, period_month_ranges, data, DCC_allocation[0][1])
chart = get_chart_data(filters, columns, data)
diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py
index 1e89b65..03ae0ae 100644
--- a/erpnext/accounts/report/financial_statements.py
+++ b/erpnext/accounts/report/financial_statements.py
@@ -387,42 +387,15 @@
key: value
})
- distributed_cost_center_query = ""
- if filters and filters.get('cost_center'):
- distributed_cost_center_query = """
- UNION ALL
- SELECT posting_date,
- account,
- debit*(DCC_allocation.percentage_allocation/100) as debit,
- credit*(DCC_allocation.percentage_allocation/100) as credit,
- is_opening,
- fiscal_year,
- debit_in_account_currency*(DCC_allocation.percentage_allocation/100) as debit_in_account_currency,
- credit_in_account_currency*(DCC_allocation.percentage_allocation/100) as credit_in_account_currency,
- account_currency
- FROM `tabGL Entry`,
- (
- SELECT parent, sum(percentage_allocation) as percentage_allocation
- FROM `tabDistributed Cost Center`
- WHERE cost_center IN %(cost_center)s
- AND parent NOT IN %(cost_center)s
- GROUP BY parent
- ) as DCC_allocation
- WHERE company=%(company)s
- {additional_conditions}
- AND posting_date <= %(to_date)s
- AND is_cancelled = 0
- AND cost_center = DCC_allocation.parent
- """.format(additional_conditions=additional_conditions.replace("and cost_center in %(cost_center)s ", ''))
-
- gl_entries = frappe.db.sql("""select posting_date, account, debit, credit, is_opening, fiscal_year, debit_in_account_currency, credit_in_account_currency, account_currency from `tabGL Entry`
+ gl_entries = frappe.db.sql("""
+ select posting_date, account, debit, credit, is_opening, fiscal_year,
+ debit_in_account_currency, credit_in_account_currency, account_currency from `tabGL Entry`
where company=%(company)s
{additional_conditions}
and posting_date <= %(to_date)s
- and is_cancelled = 0
- {distributed_cost_center_query}""".format(
- additional_conditions=additional_conditions,
- distributed_cost_center_query=distributed_cost_center_query), gl_filters, as_dict=True) #nosec
+ and is_cancelled = 0""".format(
+ additional_conditions=additional_conditions), gl_filters, as_dict=True
+ )
if filters and filters.get('presentation_currency'):
convert_to_presentation_currency(gl_entries, get_currency(filters), filters.get('company'))
diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py
index 7f27920..4ff0297 100644
--- a/erpnext/accounts/report/general_ledger/general_ledger.py
+++ b/erpnext/accounts/report/general_ledger/general_ledger.py
@@ -176,44 +176,7 @@
if accounting_dimensions:
dimension_fields = ', '.join(accounting_dimensions) + ','
- distributed_cost_center_query = ""
- if filters and filters.get('cost_center'):
- select_fields_with_percentage = """, debit*(DCC_allocation.percentage_allocation/100) as debit,
- credit*(DCC_allocation.percentage_allocation/100) as credit,
- debit_in_account_currency*(DCC_allocation.percentage_allocation/100) as debit_in_account_currency,
- credit_in_account_currency*(DCC_allocation.percentage_allocation/100) as credit_in_account_currency """
-
- distributed_cost_center_query = """
- UNION ALL
- SELECT name as gl_entry,
- posting_date,
- account,
- party_type,
- party,
- voucher_type,
- voucher_no, {dimension_fields}
- cost_center, project,
- against_voucher_type,
- against_voucher,
- account_currency,
- remarks, against,
- is_opening, `tabGL Entry`.creation {select_fields_with_percentage}
- FROM `tabGL Entry`,
- (
- SELECT parent, sum(percentage_allocation) as percentage_allocation
- FROM `tabDistributed Cost Center`
- WHERE cost_center IN %(cost_center)s
- AND parent NOT IN %(cost_center)s
- GROUP BY parent
- ) as DCC_allocation
- WHERE company=%(company)s
- {conditions}
- AND posting_date <= %(to_date)s
- AND cost_center = DCC_allocation.parent
- """.format(dimension_fields=dimension_fields,select_fields_with_percentage=select_fields_with_percentage, conditions=get_conditions(filters).replace("and cost_center in %(cost_center)s ", ''))
-
- gl_entries = frappe.db.sql(
- """
+ gl_entries = frappe.db.sql("""
select
name as gl_entry, posting_date, account, party_type, party,
voucher_type, voucher_no, {dimension_fields}
@@ -222,13 +185,11 @@
remarks, against, is_opening, creation {select_fields}
from `tabGL Entry`
where company=%(company)s {conditions}
- {distributed_cost_center_query}
{order_by_statement}
- """.format(
- dimension_fields=dimension_fields, select_fields=select_fields, conditions=get_conditions(filters), distributed_cost_center_query=distributed_cost_center_query,
- order_by_statement=order_by_statement
- ),
- filters, as_dict=1)
+ """.format(
+ dimension_fields=dimension_fields, select_fields=select_fields,
+ conditions=get_conditions(filters), order_by_statement=order_by_statement
+ ), filters, as_dict=1)
if filters.get('presentation_currency'):
return convert_to_presentation_currency(gl_entries, currency_map, filters.get('company'))
diff --git a/erpnext/accounts/report/profitability_analysis/profitability_analysis.py b/erpnext/accounts/report/profitability_analysis/profitability_analysis.py
index 3dcb862..f4b8731 100644
--- a/erpnext/accounts/report/profitability_analysis/profitability_analysis.py
+++ b/erpnext/accounts/report/profitability_analysis/profitability_analysis.py
@@ -109,7 +109,6 @@
def prepare_data(accounts, filters, total_row, parent_children_map, based_on):
data = []
- new_accounts = accounts
company_currency = frappe.get_cached_value('Company', filters.get("company"), "default_currency")
for d in accounts:
@@ -123,19 +122,6 @@
"currency": company_currency,
"based_on": based_on
}
- if based_on == 'cost_center':
- cost_center_doc = frappe.get_doc("Cost Center",d.name)
- if not cost_center_doc.enable_distributed_cost_center:
- DCC_allocation = frappe.db.sql("""SELECT parent, sum(percentage_allocation) as percentage_allocation
- FROM `tabDistributed Cost Center`
- WHERE cost_center IN %(cost_center)s
- AND parent NOT IN %(cost_center)s
- GROUP BY parent""",{'cost_center': [d.name]})
- if DCC_allocation:
- for account in new_accounts:
- if account['name'] == DCC_allocation[0][0]:
- for value in value_fields:
- d[value] += account[value]*(DCC_allocation[0][1]/100)
for key in value_fields:
row[key] = flt(d.get(key, 0.0), 3)
diff --git a/erpnext/accounts/workspace/accounting/accounting.json b/erpnext/accounts/workspace/accounting/accounting.json
index 203ea20..a456c7f 100644
--- a/erpnext/accounts/workspace/accounting/accounting.json
+++ b/erpnext/accounts/workspace/accounting/accounting.json
@@ -1024,6 +1024,17 @@
"type": "Link"
},
{
+ "dependencies": "Cost Center",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Cost Center Allocation",
+ "link_count": 0,
+ "link_to": "Cost Center Allocation",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
"dependencies": "Cost Center",
"hidden": 0,
"is_query_report": 1,
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 9317afa..7ef6b42 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -345,3 +345,4 @@
erpnext.patches.v14_0.restore_einvoice_fields
erpnext.patches.v13_0.update_sane_transfer_against
erpnext.patches.v12_0.add_company_link_to_einvoice_settings
+erpnext.patches.v14_0.migrate_cost_center_allocations
diff --git a/erpnext/patches/v13_0/remove_bad_selling_defaults.py b/erpnext/patches/v13_0/remove_bad_selling_defaults.py
index 5487a6c..0262539 100644
--- a/erpnext/patches/v13_0/remove_bad_selling_defaults.py
+++ b/erpnext/patches/v13_0/remove_bad_selling_defaults.py
@@ -3,6 +3,7 @@
def execute():
+ frappe.reload_doctype('Selling Settings')
selling_settings = frappe.get_single("Selling Settings")
if selling_settings.customer_group in (_("All Customer Groups"), "All Customer Groups"):
diff --git a/erpnext/patches/v13_0/update_maintenance_schedule_field_in_visit.py b/erpnext/patches/v13_0/update_maintenance_schedule_field_in_visit.py
index 450c00e..7a8c1c6 100644
--- a/erpnext/patches/v13_0/update_maintenance_schedule_field_in_visit.py
+++ b/erpnext/patches/v13_0/update_maintenance_schedule_field_in_visit.py
@@ -3,6 +3,9 @@
def execute():
+ frappe.reload_doctype('Maintenance Visit')
+ frappe.reload_doctype('Maintenance Visit Purpose')
+
# Updates the Maintenance Schedule link to fetch serial nos
from frappe.query_builder.functions import Coalesce
mvp = frappe.qb.DocType('Maintenance Visit Purpose')
diff --git a/erpnext/patches/v14_0/migrate_cost_center_allocations.py b/erpnext/patches/v14_0/migrate_cost_center_allocations.py
new file mode 100644
index 0000000..3d217d8
--- /dev/null
+++ b/erpnext/patches/v14_0/migrate_cost_center_allocations.py
@@ -0,0 +1,48 @@
+import frappe
+from frappe.utils import today
+
+
+def execute():
+ for dt in ("cost_center_allocation", "cost_center_allocation_percentage"):
+ frappe.reload_doc('accounts', 'doctype', dt)
+
+ cc_allocations = get_existing_cost_center_allocations()
+ if cc_allocations:
+ create_new_cost_center_allocation_records(cc_allocations)
+
+ frappe.delete_doc('DocType', 'Distributed Cost Center', ignore_missing=True)
+
+def create_new_cost_center_allocation_records(cc_allocations):
+ for main_cc, allocations in cc_allocations.items():
+ cca = frappe.new_doc("Cost Center Allocation")
+ cca.main_cost_center = main_cc
+ cca.valid_from = today()
+
+ for child_cc, percentage in allocations.items():
+ cca.append("allocation_percentages", ({
+ "cost_center": child_cc,
+ "percentage": percentage
+ }))
+ cca.save()
+ cca.submit()
+
+def get_existing_cost_center_allocations():
+ if not frappe.get_meta("Cost Center").has_field("enable_distributed_cost_center"):
+ return
+
+ par = frappe.qb.DocType("Cost Center")
+ child = frappe.qb.DocType("Distributed Cost Center")
+
+ records = (
+ frappe.qb.from_(par)
+ .inner_join(child).on(par.name == child.parent)
+ .select(par.name, child.cost_center, child.percentage_allocation)
+ .where(par.enable_distributed_cost_center == 1)
+ ).run(as_dict=True)
+
+ cc_allocations = frappe._dict()
+ for d in records:
+ cc_allocations.setdefault(d.name, frappe._dict())\
+ .setdefault(d.cost_center, d.percentage_allocation)
+
+ return cc_allocations
\ No newline at end of file
diff --git a/erpnext/patches/v14_0/migrate_crm_settings.py b/erpnext/patches/v14_0/migrate_crm_settings.py
index 30d3ea0..0c77853 100644
--- a/erpnext/patches/v14_0/migrate_crm_settings.py
+++ b/erpnext/patches/v14_0/migrate_crm_settings.py
@@ -9,8 +9,9 @@
], as_dict=True)
frappe.reload_doc('crm', 'doctype', 'crm_settings')
- frappe.db.set_value('CRM Settings', 'CRM Settings', {
- 'campaign_naming_by': settings.campaign_naming_by,
- 'close_opportunity_after_days': settings.close_opportunity_after_days,
- 'default_valid_till': settings.default_valid_till
- })
+ if settings:
+ frappe.db.set_value('CRM Settings', 'CRM Settings', {
+ 'campaign_naming_by': settings.campaign_naming_by,
+ 'close_opportunity_after_days': settings.close_opportunity_after_days,
+ 'default_valid_till': settings.default_valid_till
+ })