Dimensional Accounting Distribution

There already is Cost Center Allocation to distribute cost from one cost center onto multiple others by a configurable percentage.

However, this feature is weirdly limited to the cost center accounting dimension, probably for historical reasons (cost centers were first implemented).

There is no reason this feature could not be expanded with an important limitation onto all Accounting Dimensions. The important limitation is that only ever a single distribution could be applied on a particular leg of a journal entry (i.e. a single GL Entry), otherwise factorial complexity would create too many increasingly minuscule GL Entry which could hit performance badly.

Accounting Dimension Distribution

General Requirements

  • An accounting dimension should be distributable, e.g. general sales department costs which need to be split between marketing and customer service because they use the same office space or have shared management.
  • The predicate shall be main distribution head + account head + either a certain itemized expense or (and?) cost center
    • To not distribute with factorial complexity, each potential predicate, only a single match must ever occur
    • This condition must be validated at creation and an error must not allow the user to save or enable such distribution
  • The distribution should work like and replace current cost center allocation

Payroll Fixes

  • Individual Extra Salary items (using singular salary components) must gain dimensional accounting capability
    • E.g.: a bonfication was payed for a management employee’s extraordinary effort to organize sales department.
      This effort should not be assigned the usual management department dimension but rather split into the sales department.

Dear reader, if you can, please help and share your insight, knowledge and use cases to refine this feature request prior to implementation.

1 Like

Upon further research, I found that some further improvments to de accounting dimension filter could be made:

  • The filter allows for selecting of accounting groups
  • However since Accounting Groups are no valid target, the user most likely implies that selecting a group would count as selector for all child accounts.

However, that’s not the case in

# accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.py
	def validate_applicable_accounts(self):
		accounts = frappe.db.sql(
			"""
				SELECT a.applicable_on_account as account
				FROM `tabApplicable On Account` a, `tabAccounting Dimension Filter` d
				WHERE d.name = a.parent
				and d.name != %s
				and d.accounting_dimension = %s
			""",
			(self.name, self.accounting_dimension),
			as_dict=1,
		)

		account_list = [d.account for d in accounts]

		for account in self.get("accounts"):
			if account.applicable_on_account in account_list:
				frappe.throw(
					_("Row {0}: {1} account already applied for Accounting Dimension {2}").format(
						account.idx,
						frappe.bold(account.applicable_on_account),
						frappe.bold(self.accounting_dimension),
					)
				)


def get_dimension_filter_map():
	filters = frappe.db.sql(
		"""
		SELECT
			a.applicable_on_account, d.dimension_value, p.accounting_dimension,
			p.allow_or_restrict, a.is_mandatory
		FROM
			`tabApplicable On Account` a,
			`tabAccounting Dimension Filter` p
		LEFT JOIN `tabAllowed Dimension` d ON d.parent = p.name
		WHERE
			p.name = a.parent
			AND p.disabled = 0
	""",
		as_dict=1,
	)

	dimension_filter_map = {}

	for f in filters:
		f.fieldname = scrub(f.accounting_dimension)

		build_map(
			dimension_filter_map,
			f.fieldname,
			f.applicable_on_account,
			f.dimension_value,
			f.allow_or_restrict,
			f.is_mandatory,
		)

	return dimension_filter_map


def build_map(map_object, dimension, account, filter_value, allow_or_restrict, is_mandatory):
	map_object.setdefault(
		(dimension, account),
		{"allowed_dimensions": [], "is_mandatory": is_mandatory, "allow_or_restrict": allow_or_restrict},
	)
	if filter_value:
		map_object[(dimension, account)]["allowed_dimensions"].append(filter_value)