Merge pull request #29536 from nabinhait/cost-center-allocation

feat: Cost Center Allocation
This commit is contained in:
Deepesh Garg 2022-01-31 21:27:15 +05:30 committed by GitHub
commit dcd99a82cb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 595 additions and 248 deletions

View File

@ -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()) {

View File

@ -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": []
}

View File

@ -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 @@ 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 +45,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 +62,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 +116,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

View File

@ -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:

View File

@ -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
};
});
}
});

View File

@ -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": []
}

View File

@ -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)

View File

@ -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

View File

@ -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": []
}

View File

@ -1,10 +1,9 @@
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
# 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 DistributedCostCenter(Document):
class CostCenterAllocationPercentage(Document):
pass

View File

@ -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 @@ 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:
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 @@ 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)

View File

@ -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)

View File

@ -387,42 +387,15 @@ 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
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'))

View File

@ -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'))

View File

@ -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)

View File

@ -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,

View File

@ -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
erpnext.patches.v12_0.add_company_link_to_einvoice_settings
erpnext.patches.v14_0.migrate_cost_center_allocations

View File

@ -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"):

View File

@ -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')

View File

@ -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

View File

@ -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
})