mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-05 05:09:11 +00:00
Merge pull request #55566 from frappe/mergify/bp/version-16-hotfix/pr-55562
fix: aggregate child cost center data in Budget Variance Report (backport #55562)
This commit is contained in:
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
|
from frappe.query_builder import CustomFunction
|
||||||
from frappe.utils import add_months, flt, formatdate
|
from frappe.utils import add_months, flt, formatdate
|
||||||
|
|
||||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_dimensions
|
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_dimensions
|
||||||
@@ -19,6 +20,8 @@ def execute(filters=None):
|
|||||||
columns = get_columns(filters)
|
columns = get_columns(filters)
|
||||||
if filters.get("budget_against_filter"):
|
if filters.get("budget_against_filter"):
|
||||||
dimensions = filters.get("budget_against_filter")
|
dimensions = filters.get("budget_against_filter")
|
||||||
|
if filters.get("budget_against") == "Cost Center":
|
||||||
|
dimensions = get_cost_center_with_children(dimensions)
|
||||||
else:
|
else:
|
||||||
dimensions = get_budget_dimensions(filters)
|
dimensions = get_budget_dimensions(filters)
|
||||||
if not dimensions:
|
if not dimensions:
|
||||||
@@ -40,39 +43,29 @@ def validate_filters(filters):
|
|||||||
|
|
||||||
def get_budget_records(filters, dimensions):
|
def get_budget_records(filters, dimensions):
|
||||||
budget_against_field = frappe.scrub(filters["budget_against"])
|
budget_against_field = frappe.scrub(filters["budget_against"])
|
||||||
|
budget = frappe.qb.DocType("Budget")
|
||||||
|
|
||||||
return frappe.db.sql(
|
return (
|
||||||
f"""
|
frappe.qb.from_(budget)
|
||||||
SELECT
|
.select(
|
||||||
b.name,
|
budget.name,
|
||||||
b.account,
|
budget.account,
|
||||||
b.{budget_against_field} AS dimension,
|
budget[budget_against_field].as_("dimension"),
|
||||||
b.budget_amount,
|
budget.budget_amount,
|
||||||
b.from_fiscal_year,
|
budget.from_fiscal_year,
|
||||||
b.to_fiscal_year,
|
budget.to_fiscal_year,
|
||||||
b.budget_start_date,
|
budget.budget_start_date,
|
||||||
b.budget_end_date
|
budget.budget_end_date,
|
||||||
FROM
|
)
|
||||||
`tabBudget` b
|
.where(
|
||||||
WHERE
|
(budget.company == filters.company)
|
||||||
b.company = %s
|
& (budget.docstatus == 1)
|
||||||
AND b.docstatus = 1
|
& (budget.budget_against == filters.budget_against)
|
||||||
AND b.budget_against = %s
|
& (budget[budget_against_field].isin(dimensions))
|
||||||
AND b.{budget_against_field} IN ({", ".join(["%s"] * len(dimensions))})
|
& (budget.from_fiscal_year <= filters.to_fiscal_year)
|
||||||
AND (
|
& (budget.to_fiscal_year >= filters.from_fiscal_year)
|
||||||
b.from_fiscal_year <= %s
|
)
|
||||||
AND b.to_fiscal_year >= %s
|
).run(as_dict=True)
|
||||||
)
|
|
||||||
""",
|
|
||||||
(
|
|
||||||
filters.company,
|
|
||||||
filters.budget_against,
|
|
||||||
*dimensions,
|
|
||||||
filters.to_fiscal_year,
|
|
||||||
filters.from_fiscal_year,
|
|
||||||
),
|
|
||||||
as_dict=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def build_budget_map(budget_records, filters):
|
def build_budget_map(budget_records, filters):
|
||||||
@@ -120,50 +113,41 @@ def build_budget_map(budget_records, filters):
|
|||||||
|
|
||||||
def get_actual_transactions(dimension_name, filters):
|
def get_actual_transactions(dimension_name, filters):
|
||||||
budget_against = frappe.scrub(filters.get("budget_against"))
|
budget_against = frappe.scrub(filters.get("budget_against"))
|
||||||
cost_center_filter = ""
|
monthname = CustomFunction("MONTHNAME", ["date"])
|
||||||
|
|
||||||
|
gle = frappe.qb.DocType("GL Entry")
|
||||||
|
budget = frappe.qb.DocType("Budget")
|
||||||
|
|
||||||
|
query = (
|
||||||
|
frappe.qb.from_(gle)
|
||||||
|
.from_(budget)
|
||||||
|
.select(
|
||||||
|
gle.account,
|
||||||
|
gle.debit,
|
||||||
|
gle.credit,
|
||||||
|
gle.fiscal_year,
|
||||||
|
monthname(gle.posting_date).as_("month_name"),
|
||||||
|
budget[budget_against].as_("budget_against"),
|
||||||
|
)
|
||||||
|
.where(
|
||||||
|
(budget.docstatus == 1)
|
||||||
|
& (budget.account == gle.account)
|
||||||
|
& (gle.fiscal_year >= filters.from_fiscal_year)
|
||||||
|
& (gle.fiscal_year <= filters.to_fiscal_year)
|
||||||
|
& (gle.is_cancelled == 0)
|
||||||
|
& (budget[budget_against] == dimension_name)
|
||||||
|
)
|
||||||
|
.groupby(gle.name)
|
||||||
|
.orderby(gle.fiscal_year)
|
||||||
|
)
|
||||||
|
|
||||||
if filters.get("budget_against") == "Cost Center" and dimension_name:
|
if filters.get("budget_against") == "Cost Center" and dimension_name:
|
||||||
cc_lft, cc_rgt = frappe.db.get_value("Cost Center", dimension_name, ["lft", "rgt"])
|
cost_centers = get_cost_center_with_children([dimension_name])
|
||||||
cost_center_filter = f"""
|
query = query.where(gle.cost_center.isin(cost_centers))
|
||||||
and lft >= "{cc_lft}"
|
else:
|
||||||
and rgt <= "{cc_rgt}"
|
query = query.where(budget[budget_against] == gle[budget_against])
|
||||||
"""
|
|
||||||
|
|
||||||
actual_transactions = frappe.db.sql(
|
actual_transactions = query.run(as_dict=True)
|
||||||
f"""
|
|
||||||
select
|
|
||||||
gl.account,
|
|
||||||
gl.debit,
|
|
||||||
gl.credit,
|
|
||||||
gl.fiscal_year,
|
|
||||||
MONTHNAME(gl.posting_date) as month_name,
|
|
||||||
b.{budget_against} as budget_against
|
|
||||||
from
|
|
||||||
`tabGL Entry` gl,
|
|
||||||
`tabBudget` b
|
|
||||||
where
|
|
||||||
b.docstatus = 1
|
|
||||||
and b.account=gl.account
|
|
||||||
and b.{budget_against} = gl.{budget_against}
|
|
||||||
and gl.fiscal_year between %s and %s
|
|
||||||
and gl.is_cancelled = 0
|
|
||||||
and b.{budget_against} = %s
|
|
||||||
and exists(
|
|
||||||
select
|
|
||||||
name
|
|
||||||
from
|
|
||||||
`tab{filters.budget_against}`
|
|
||||||
where
|
|
||||||
name = gl.{budget_against}
|
|
||||||
{cost_center_filter}
|
|
||||||
)
|
|
||||||
group by
|
|
||||||
gl.name
|
|
||||||
order by gl.fiscal_year
|
|
||||||
""",
|
|
||||||
(filters.from_fiscal_year, filters.to_fiscal_year, dimension_name),
|
|
||||||
as_dict=1,
|
|
||||||
)
|
|
||||||
|
|
||||||
actual_transactions_map = {}
|
actual_transactions_map = {}
|
||||||
for transaction in actual_transactions:
|
for transaction in actual_transactions:
|
||||||
@@ -382,33 +366,37 @@ def get_fiscal_years(filters):
|
|||||||
return fiscal_year
|
return fiscal_year
|
||||||
|
|
||||||
|
|
||||||
def get_budget_dimensions(filters):
|
def get_cost_center_with_children(cost_centers):
|
||||||
order_by = ""
|
"""Expand each cost center to include itself and all its descendants."""
|
||||||
if filters.get("budget_against") == "Cost Center":
|
cc = frappe.qb.DocType("Cost Center")
|
||||||
order_by = "order by lft"
|
all_cost_centers = set()
|
||||||
|
for cost_center in cost_centers:
|
||||||
if filters.get("budget_against") in ["Cost Center", "Project"]:
|
result = frappe.db.get_value("Cost Center", cost_center, ["lft", "rgt"])
|
||||||
return frappe.db.sql_list(
|
if not result:
|
||||||
"""
|
continue
|
||||||
select
|
lft, rgt = result
|
||||||
name
|
children = (
|
||||||
from
|
frappe.qb.from_(cc).select(cc.name).where((cc.lft >= lft) & (cc.rgt <= rgt)).run(pluck="name")
|
||||||
`tab{tab}`
|
|
||||||
where
|
|
||||||
company = %s
|
|
||||||
{order_by}
|
|
||||||
""".format(tab=filters.get("budget_against"), order_by=order_by),
|
|
||||||
filters.get("company"),
|
|
||||||
)
|
)
|
||||||
|
all_cost_centers.update(children)
|
||||||
|
return list(all_cost_centers)
|
||||||
|
|
||||||
|
|
||||||
|
def get_budget_dimensions(filters):
|
||||||
|
budget_against = filters.get("budget_against")
|
||||||
|
dimension = frappe.qb.DocType(budget_against)
|
||||||
|
|
||||||
|
if budget_against in ["Cost Center", "Project"]:
|
||||||
|
query = (
|
||||||
|
frappe.qb.from_(dimension)
|
||||||
|
.select(dimension.name)
|
||||||
|
.where(dimension.company == filters.get("company"))
|
||||||
|
)
|
||||||
|
if budget_against == "Cost Center":
|
||||||
|
query = query.orderby(dimension.lft)
|
||||||
|
return query.run(pluck="name")
|
||||||
else:
|
else:
|
||||||
return frappe.db.sql_list(
|
return frappe.qb.from_(dimension).select(dimension.name).run(pluck="name")
|
||||||
"""
|
|
||||||
select
|
|
||||||
name
|
|
||||||
from
|
|
||||||
`tab{tab}`
|
|
||||||
""".format(tab=filters.get("budget_against"))
|
|
||||||
) # nosec
|
|
||||||
|
|
||||||
|
|
||||||
def validate_budget_dimensions(filters):
|
def validate_budget_dimensions(filters):
|
||||||
|
|||||||
Reference in New Issue
Block a user