From d71d3e3b3e9de01de4fa040dccea1e8f9057a932 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 31 Jan 2022 13:18:05 +0530 Subject: [PATCH 1/9] fix: Minor issues in existing patches --- erpnext/patches/v13_0/remove_bad_selling_defaults.py | 1 + .../update_maintenance_schedule_field_in_visit.py | 3 +++ erpnext/patches/v14_0/migrate_crm_settings.py | 11 ++++++----- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/erpnext/patches/v13_0/remove_bad_selling_defaults.py b/erpnext/patches/v13_0/remove_bad_selling_defaults.py index 5487a6c60c..02625396dd 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 @@ from frappe import _ 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 450c00e421..7a8c1c6135 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 @@ import frappe 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_crm_settings.py b/erpnext/patches/v14_0/migrate_crm_settings.py index 30d3ea0cb1..0c7785367c 100644 --- a/erpnext/patches/v14_0/migrate_crm_settings.py +++ b/erpnext/patches/v14_0/migrate_crm_settings.py @@ -9,8 +9,9 @@ def execute(): ], 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 + }) From ac2e19103f86d5713abf72af7bf0011c376a0862 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 31 Jan 2022 13:19:24 +0530 Subject: [PATCH 2/9] Cost Center Allocation doctype, validations and test cases --- .../cost_center_allocation/__init__.py | 0 .../cost_center_allocation.js | 19 +++ .../cost_center_allocation.json | 128 +++++++++++++++ .../cost_center_allocation.py | 87 ++++++++++ .../test_cost_center_allocation.py | 150 ++++++++++++++++++ .../__init__.py | 0 .../cost_center_allocation_percentage.json | 41 +++++ .../cost_center_allocation_percentage.py | 8 + 8 files changed, 433 insertions(+) create mode 100644 erpnext/accounts/doctype/cost_center_allocation/__init__.py create mode 100644 erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.js create mode 100644 erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.json create mode 100644 erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.py create mode 100644 erpnext/accounts/doctype/cost_center_allocation/test_cost_center_allocation.py create mode 100644 erpnext/accounts/doctype/cost_center_allocation_percentage/__init__.py create mode 100644 erpnext/accounts/doctype/cost_center_allocation_percentage/cost_center_allocation_percentage.json create mode 100644 erpnext/accounts/doctype/cost_center_allocation_percentage/cost_center_allocation_percentage.py diff --git a/erpnext/accounts/doctype/cost_center_allocation/__init__.py b/erpnext/accounts/doctype/cost_center_allocation/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 0000000000..864bef3ffd --- /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(doc) { + 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 0000000000..45ab886d33 --- /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 0000000000..37787cb529 --- /dev/null +++ b/erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.py @@ -0,0 +1,87 @@ +# 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 getdate, format_date, add_days + +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 0000000000..af318eeae3 --- /dev/null +++ b/erpnext/accounts/doctype/cost_center_allocation/test_cost_center_allocation.py @@ -0,0 +1,150 @@ +# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +import frappe +import unittest +from frappe.utils import today, add_months, add_days +from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center +from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry +from erpnext.accounts.doctype.cost_center_allocation.cost_center_allocation import (MainCostCenterCantBeChild, + InvalidMainCostCenter, InvalidChildCostCenter, WrongPercentageAllocation, InvalidDateError) + +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/cost_center_allocation_percentage/__init__.py b/erpnext/accounts/doctype/cost_center_allocation_percentage/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/accounts/doctype/cost_center_allocation_percentage/cost_center_allocation_percentage.json b/erpnext/accounts/doctype/cost_center_allocation_percentage/cost_center_allocation_percentage.json new file mode 100644 index 0000000000..4b871ae88d --- /dev/null +++ b/erpnext/accounts/doctype/cost_center_allocation_percentage/cost_center_allocation_percentage.json @@ -0,0 +1,41 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2022-01-03 18:10:11.697198", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "cost_center", + "percentage" + ], + "fields": [ + { + "fieldname": "cost_center", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Cost Center", + "options": "Cost Center", + "reqd": 1 + }, + { + "fieldname": "percentage", + "fieldtype": "Int", + "in_list_view": 1, + "label": "Percentage (%)", + "reqd": 1 + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2022-01-03 18:10:20.029821", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Cost Center Allocation Percentage", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "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 0000000000..92148846b7 --- /dev/null +++ b/erpnext/accounts/doctype/cost_center_allocation_percentage/cost_center_allocation_percentage.py @@ -0,0 +1,8 @@ +# 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 From 004c1ed09487a84190221b6e0503832c5689c9f5 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 31 Jan 2022 13:20:18 +0530 Subject: [PATCH 3/9] feat: Split Gl Entry based on cost center allocation --- erpnext/accounts/general_ledger.py | 122 ++++++++++++++++++++--------- 1 file changed, 86 insertions(+), 36 deletions(-) diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index 1836db6477..134b02396c 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -3,6 +3,7 @@ import frappe +import copy from frappe import _ from frappe.model.meta import get_field_precision from frappe.utils import cint, cstr, flt, formatdate, getdate, now @@ -51,49 +52,55 @@ def validate_accounting_period(gl_map): .format(frappe.bold(accounting_periods[0].name)), ClosedAccountingPeriod) def process_gl_map(gl_map, merge_entries=True, precision=None): + 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 +152,49 @@ def check_if_in_list(gle, gl_map, dimensions=None): 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) From 174104c0d4899b150b43779e7656511b026e65b2 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 31 Jan 2022 13:21:08 +0530 Subject: [PATCH 4/9] feat: Workspace link for Cost Center Allocation --- erpnext/accounts/workspace/accounting/accounting.json | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/erpnext/accounts/workspace/accounting/accounting.json b/erpnext/accounts/workspace/accounting/accounting.json index 203ea20882..a456c7fb57 100644 --- a/erpnext/accounts/workspace/accounting/accounting.json +++ b/erpnext/accounts/workspace/accounting/accounting.json @@ -1023,6 +1023,17 @@ "onboard": 0, "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, From ce49e6ccf74a1b7acf757ded7d79953abab06438 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 31 Jan 2022 13:25:44 +0530 Subject: [PATCH 5/9] fix: Resolved conflicts in patches --- erpnext/patches.txt | 3 +- .../v14_0/migrate_cost_center_allocations.py | 45 +++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 erpnext/patches/v14_0/migrate_cost_center_allocations.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index b04eeb85a8..c2f14aaacf 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -340,4 +340,5 @@ erpnext.patches.v14_0.rearrange_company_fields erpnext.patches.v14_0.update_leave_notification_template 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 \ No newline at end of file +erpnext.patches.v12_0.add_company_link_to_einvoice_settings +erpnext.patches.v14_0.migrate_cost_center_allocations 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 0000000000..ab8da12412 --- /dev/null +++ b/erpnext/patches/v14_0/migrate_cost_center_allocations.py @@ -0,0 +1,45 @@ +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() + 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(): + 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 From c09d5a613eb274031964724cc020ca07487333d5 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 31 Jan 2022 13:24:06 +0530 Subject: [PATCH 6/9] fix: Delete old fields from cost center related to distributed cost center --- .../doctype/cost_center/cost_center.json | 26 +++---------------- 1 file changed, 3 insertions(+), 23 deletions(-) diff --git a/erpnext/accounts/doctype/cost_center/cost_center.json b/erpnext/accounts/doctype/cost_center/cost_center.json index e7fa954e01..7cbb290947 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 From 3dadfc9048d804e097ebfe6801802f2c980e04a7 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 31 Jan 2022 15:58:40 +0530 Subject: [PATCH 7/9] patch: Removed files and code related to old distributed cost center feature --- .../doctype/cost_center/cost_center.js | 11 ----- .../doctype/cost_center/cost_center.py | 45 ++++++----------- .../doctype/cost_center/test_cost_center.py | 27 ---------- .../cost_center_allocation.js | 2 +- .../cost_center_allocation.py | 18 ++++--- .../test_cost_center_allocation.py | 2 +- .../distributed_cost_center/__init__.py | 0 .../distributed_cost_center.json | 40 --------------- .../distributed_cost_center.py | 10 ---- .../budget_variance_report.py | 12 ----- .../accounts/report/financial_statements.py | 36 ++------------ .../report/general_ledger/general_ledger.py | 49 ++----------------- .../profitability_analysis.py | 14 ------ .../v14_0/migrate_cost_center_allocations.py | 7 ++- 14 files changed, 43 insertions(+), 230 deletions(-) delete mode 100644 erpnext/accounts/doctype/distributed_cost_center/__init__.py delete mode 100644 erpnext/accounts/doctype/distributed_cost_center/distributed_cost_center.json delete mode 100644 erpnext/accounts/doctype/distributed_cost_center/distributed_cost_center.py diff --git a/erpnext/accounts/doctype/cost_center/cost_center.js b/erpnext/accounts/doctype/cost_center/cost_center.js index ee23b1be5c..632fab0197 100644 --- a/erpnext/accounts/doctype/cost_center/cost_center.js +++ b/erpnext/accounts/doctype/cost_center/cost_center.js @@ -15,17 +15,6 @@ frappe.ui.form.on('Cost Center', { } } }); - - 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.py b/erpnext/accounts/doctype/cost_center/cost_center.py index 7ae0a72e3d..558c51605d 100644 --- a/erpnext/accounts/doctype/cost_center/cost_center.py +++ b/erpnext/accounts/doctype/cost_center/cost_center.py @@ -20,24 +20,6 @@ class CostCenter(NestedSet): 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 +46,10 @@ class CostCenter(NestedSet): @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 +63,17 @@ class CostCenter(NestedSet): 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 +117,4 @@ def on_doctype_update(): 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 f8615ec03a..ff50a21124 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 @@ class TestCostCenter(unittest.TestCase): 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/cost_center_allocation/cost_center_allocation.js b/erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.js index 864bef3ffd..bdc7b1814c 100644 --- a/erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.js +++ b/erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.js @@ -10,7 +10,7 @@ frappe.ui.form.on('Cost Center Allocation', { }); } - frm.set_query('main_cost_center', function(doc) { + frm.set_query('main_cost_center', function() { return { filters: filters }; diff --git a/erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.py b/erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.py index 37787cb529..2991c73159 100644 --- a/erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.py +++ b/erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.py @@ -6,12 +6,16 @@ from frappe import _ from frappe.model.document import Document from frappe.utils import getdate, format_date, add_days -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 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): @@ -25,7 +29,7 @@ class CostCenterAllocation(Document): 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) + 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 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 index af318eeae3..d7d6578c35 100644 --- a/erpnext/accounts/doctype/cost_center_allocation/test_cost_center_allocation.py +++ b/erpnext/accounts/doctype/cost_center_allocation/test_cost_center_allocation.py @@ -3,7 +3,7 @@ import frappe import unittest -from frappe.utils import today, add_months, add_days +from frappe.utils import today, add_days from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry from erpnext.accounts.doctype.cost_center_allocation.cost_center_allocation import (MainCostCenterCantBeChild, diff --git a/erpnext/accounts/doctype/distributed_cost_center/__init__.py b/erpnext/accounts/doctype/distributed_cost_center/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/accounts/doctype/distributed_cost_center/distributed_cost_center.json b/erpnext/accounts/doctype/distributed_cost_center/distributed_cost_center.json deleted file mode 100644 index 45b0e2df9b..0000000000 --- a/erpnext/accounts/doctype/distributed_cost_center/distributed_cost_center.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "actions": [], - "creation": "2020-03-19 12:34:01.500390", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "cost_center", - "percentage_allocation" - ], - "fields": [ - { - "fieldname": "cost_center", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Cost Center", - "options": "Cost Center", - "reqd": 1 - }, - { - "fieldname": "percentage_allocation", - "fieldtype": "Float", - "in_list_view": 1, - "label": "Percentage Allocation", - "reqd": 1 - } - ], - "istable": 1, - "links": [], - "modified": "2020-03-19 12:54:43.674655", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Distributed Cost Center", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1 -} \ No newline at end of file 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 dcf0e3b99d..0000000000 --- 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/report/budget_variance_report/budget_variance_report.py b/erpnext/accounts/report/budget_variance_report/budget_variance_report.py index 3bb590a564..56ee5008cf 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 @@ def execute(filters=None): 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 1e89b650c7..416d1b0bbb 100644 --- a/erpnext/accounts/report/financial_statements.py +++ b/erpnext/accounts/report/financial_statements.py @@ -387,42 +387,14 @@ def set_gl_entries_by_account( 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 + """.format(additional_conditions=additional_conditions), gl_filters, as_dict=True) #nosec 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 7f27920547..4ff0297dba 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -176,44 +176,7 @@ def get_gl_entries(filters, accounting_dimensions): 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 @@ def get_gl_entries(filters, accounting_dimensions): 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 3dcb86267c..f4b8731ba8 100644 --- a/erpnext/accounts/report/profitability_analysis/profitability_analysis.py +++ b/erpnext/accounts/report/profitability_analysis/profitability_analysis.py @@ -109,7 +109,6 @@ def accumulate_values_into_parents(accounts, accounts_by_name): 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 @@ def prepare_data(accounts, filters, total_row, parent_children_map, based_on): "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/patches/v14_0/migrate_cost_center_allocations.py b/erpnext/patches/v14_0/migrate_cost_center_allocations.py index ab8da12412..c880796283 100644 --- a/erpnext/patches/v14_0/migrate_cost_center_allocations.py +++ b/erpnext/patches/v14_0/migrate_cost_center_allocations.py @@ -6,7 +6,8 @@ def execute(): frappe.reload_doc('accounts', 'doctype', dt) cc_allocations = get_existing_cost_center_allocations() - create_new_cost_center_allocation_records(cc_allocations) + if cc_allocations: + create_new_cost_center_allocation_records(cc_allocations) frappe.delete_doc('DocType', 'Distributed Cost Center', ignore_missing=True) @@ -25,8 +26,10 @@ def create_new_cost_center_allocation_records(cc_allocations): 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") From 6099af5d00c5a99bb2bc82425396ce102cc248cf Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 31 Jan 2022 17:24:50 +0530 Subject: [PATCH 8/9] fix: Fixed test case and sider issues --- .../cost_center_allocation.py | 9 +++--- .../test_cost_center_allocation.py | 30 +++++++++---------- erpnext/accounts/general_ledger.py | 6 ++-- .../accounts/report/financial_statements.py | 7 +++-- .../v14_0/migrate_cost_center_allocations.py | 6 ++-- 5 files changed, 31 insertions(+), 27 deletions(-) diff --git a/erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.py b/erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.py index 2991c73159..00a591aa58 100644 --- a/erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.py +++ b/erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.py @@ -4,7 +4,8 @@ import frappe from frappe import _ from frappe.model.document import Document -from frappe.utils import getdate, format_date, add_days +from frappe.utils import add_days, format_date, getdate + class MainCostCenterCantBeChild(frappe.ValidationError): pass @@ -32,7 +33,7 @@ class CostCenterAllocation(Document): 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 + # 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", @@ -47,7 +48,7 @@ class CostCenterAllocation(Document): 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), @@ -82,7 +83,7 @@ class CostCenterAllocation(Document): 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 + 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: 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 index d7d6578c35..137ab7de24 100644 --- a/erpnext/accounts/doctype/cost_center_allocation/test_cost_center_allocation.py +++ b/erpnext/accounts/doctype/cost_center_allocation/test_cost_center_allocation.py @@ -34,11 +34,11 @@ class TestCostCenterAllocation(unittest.TestCase): 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) + .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) @@ -57,8 +57,8 @@ class TestCostCenterAllocation(unittest.TestCase): { "Sub Cost Center 1 - _TC": 60, "Main Cost Center 1 - _TC": 40 - } - , save=False) + }, save=False + ) self.assertRaises(MainCostCenterCantBeChild, cca.save) @@ -75,8 +75,8 @@ class TestCostCenterAllocation(unittest.TestCase): cca2 = create_cost_center_allocation("_Test Company", "Sub Cost Center 1 - _TC", { "Sub Cost Center 2 - _TC": 100 - } - , save=False) + }, save=False + ) self.assertRaises(InvalidMainCostCenter, cca2.save) @@ -96,8 +96,8 @@ class TestCostCenterAllocation(unittest.TestCase): { "Main Cost Center 1 - _TC": 60, "Sub Cost Center 1 - _TC": 40 - } - , save=False) + }, save=False + ) self.assertRaises(InvalidChildCostCenter, cca2.save) @@ -108,8 +108,8 @@ class TestCostCenterAllocation(unittest.TestCase): { "Sub Cost Center 1 - _TC": 40, "Sub Cost Center 2 - _TC": 40 - } - , save=False) + }, save=False + ) self.assertRaises(WrongPercentageAllocation, cca.save) def test_valid_from_based_on_existing_gle(self): @@ -122,8 +122,8 @@ class TestCostCenterAllocation(unittest.TestCase): { "Sub Cost Center 1 - _TC": 60, "Sub Cost Center 2 - _TC": 40 - } - , valid_from=add_days(today(), -1), save=False) + }, valid_from=add_days(today(), -1), save=False + ) self.assertRaises(InvalidDateError, cca.save) diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index 134b02396c..6b73e3cb1c 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -52,6 +52,9 @@ def validate_accounting_period(gl_map): .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: @@ -86,8 +89,7 @@ def get_cost_center_allocation_data(company, posting_date): child = frappe.qb.DocType("Cost Center Allocation Percentage") records = ( - frappe.qb.from_(par) - .inner_join(child).on(par.name == child.parent) + 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) diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py index 416d1b0bbb..03ae0aea13 100644 --- a/erpnext/accounts/report/financial_statements.py +++ b/erpnext/accounts/report/financial_statements.py @@ -388,13 +388,14 @@ def set_gl_entries_by_account( }) gl_entries = frappe.db.sql(""" - select posting_date, account, debit, credit, is_opening, fiscal_year, + 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 - """.format(additional_conditions=additional_conditions), 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/patches/v14_0/migrate_cost_center_allocations.py b/erpnext/patches/v14_0/migrate_cost_center_allocations.py index c880796283..d101307d8b 100644 --- a/erpnext/patches/v14_0/migrate_cost_center_allocations.py +++ b/erpnext/patches/v14_0/migrate_cost_center_allocations.py @@ -35,9 +35,9 @@ def get_existing_cost_center_allocations(): 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) + .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() From 5b0ec6437b2aa0366be11a136559b5e3d23a3b6c Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 31 Jan 2022 18:07:04 +0530 Subject: [PATCH 9/9] fix: sider issues --- .../doctype/cost_center/cost_center.py | 1 - .../cost_center_allocation.js | 2 +- .../cost_center_allocation.py | 8 ++---- .../test_cost_center_allocation.py | 28 +++++++++++-------- .../cost_center_allocation_percentage.py | 1 + erpnext/accounts/general_ledger.py | 5 ++-- .../v14_0/migrate_cost_center_allocations.py | 2 +- 7 files changed, 26 insertions(+), 21 deletions(-) diff --git a/erpnext/accounts/doctype/cost_center/cost_center.py b/erpnext/accounts/doctype/cost_center/cost_center.py index 558c51605d..07cc0764e3 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 diff --git a/erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.js b/erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.js index bdc7b1814c..ab0baab24a 100644 --- a/erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.js +++ b/erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.js @@ -9,7 +9,7 @@ frappe.ui.form.on('Cost Center Allocation', { "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.py b/erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.py index 00a591aa58..bad3fb4f96 100644 --- a/erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.py +++ b/erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.py @@ -58,11 +58,9 @@ class CostCenterAllocation(Document): 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 + .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): 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 index 137ab7de24..9cf4c00217 100644 --- a/erpnext/accounts/doctype/cost_center_allocation/test_cost_center_allocation.py +++ b/erpnext/accounts/doctype/cost_center_allocation/test_cost_center_allocation.py @@ -1,13 +1,21 @@ # Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -import frappe import unittest -from frappe.utils import today, add_days + +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 -from erpnext.accounts.doctype.cost_center_allocation.cost_center_allocation import (MainCostCenterCantBeChild, - InvalidMainCostCenter, InvalidChildCostCenter, WrongPercentageAllocation, InvalidDateError) + class TestCostCenterAllocation(unittest.TestCase): def setUp(self): @@ -22,7 +30,7 @@ class TestCostCenterAllocation(unittest.TestCase): "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) @@ -77,14 +85,13 @@ class TestCostCenterAllocation(unittest.TestCase): "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 + # 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, @@ -98,7 +105,7 @@ class TestCostCenterAllocation(unittest.TestCase): "Sub Cost Center 1 - _TC": 40 }, save=False ) - + self.assertRaises(InvalidChildCostCenter, cca2.save) cca1.cancel() @@ -128,7 +135,6 @@ class TestCostCenterAllocation(unittest.TestCase): 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): 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 index 92148846b7..7d20efb6d5 100644 --- 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 @@ -4,5 +4,6 @@ # import frappe from frappe.model.document import Document + class CostCenterAllocationPercentage(Document): pass diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index 6b73e3cb1c..55bc9673c1 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -2,8 +2,9 @@ # License: GNU General Public License v3. See license.txt -import frappe import copy + +import frappe from frappe import _ from frappe.model.meta import get_field_precision from frappe.utils import cint, cstr, flt, formatdate, getdate, now @@ -101,7 +102,7 @@ def get_cost_center_allocation_data(company, posting_date): 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): diff --git a/erpnext/patches/v14_0/migrate_cost_center_allocations.py b/erpnext/patches/v14_0/migrate_cost_center_allocations.py index d101307d8b..3d217d89e2 100644 --- a/erpnext/patches/v14_0/migrate_cost_center_allocations.py +++ b/erpnext/patches/v14_0/migrate_cost_center_allocations.py @@ -1,6 +1,7 @@ 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) @@ -22,7 +23,6 @@ def create_new_cost_center_allocation_records(cc_allocations): "cost_center": child_cc, "percentage": percentage })) - cca.save() cca.submit()