mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-08 15:42:52 +00:00
Merge pull request #51405 from khushi8112/fix-budget-variance-report
refactor: budget variance report
This commit is contained in:
@@ -1,17 +1,21 @@
|
|||||||
{
|
{
|
||||||
"add_total_row": 0,
|
"add_total_row": 0,
|
||||||
"apply_user_permissions": 1,
|
"add_translate_data": 0,
|
||||||
|
"columns": [],
|
||||||
"creation": "2013-06-18 12:56:36",
|
"creation": "2013-06-18 12:56:36",
|
||||||
"disabled": 0,
|
"disabled": 0,
|
||||||
"docstatus": 0,
|
"docstatus": 0,
|
||||||
"doctype": "Report",
|
"doctype": "Report",
|
||||||
|
"filters": [],
|
||||||
"idx": 3,
|
"idx": 3,
|
||||||
"is_standard": "Yes",
|
"is_standard": "Yes",
|
||||||
"modified": "2017-02-24 20:19:06.964033",
|
"letter_head": null,
|
||||||
|
"modified": "2025-12-30 14:51:02.061226",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Budget Variance Report",
|
"name": "Budget Variance Report",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
|
"prepared_report": 0,
|
||||||
"ref_doctype": "Cost Center",
|
"ref_doctype": "Cost Center",
|
||||||
"report_name": "Budget Variance Report",
|
"report_name": "Budget Variance Report",
|
||||||
"report_type": "Script Report",
|
"report_type": "Script Report",
|
||||||
@@ -31,5 +35,6 @@
|
|||||||
{
|
{
|
||||||
"role": "Purchase User"
|
"role": "Purchase User"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"timeout": 0
|
||||||
}
|
}
|
||||||
@@ -1,14 +1,12 @@
|
|||||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
# License: GNU General Public License v3. See license.txt
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
|
||||||
import datetime
|
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.utils import flt, formatdate
|
from frappe.utils import add_months, flt, formatdate
|
||||||
|
|
||||||
from erpnext.controllers.trends import get_period_date_ranges, get_period_month_ranges
|
from erpnext.accounts.utils import get_fiscal_year
|
||||||
|
from erpnext.controllers.trends import get_period_date_ranges
|
||||||
|
|
||||||
|
|
||||||
def execute(filters=None):
|
def execute(filters=None):
|
||||||
@@ -19,57 +17,282 @@ def execute(filters=None):
|
|||||||
if filters.get("budget_against_filter"):
|
if filters.get("budget_against_filter"):
|
||||||
dimensions = filters.get("budget_against_filter")
|
dimensions = filters.get("budget_against_filter")
|
||||||
else:
|
else:
|
||||||
dimensions = get_cost_centers(filters)
|
dimensions = get_budget_dimensions(filters)
|
||||||
|
|
||||||
period_month_ranges = get_period_month_ranges(filters["period"], filters["from_fiscal_year"])
|
budget_records = get_budget_records(filters, dimensions)
|
||||||
cam_map = get_dimension_account_month_map(filters)
|
budget_map = build_budget_map(budget_records, filters)
|
||||||
|
|
||||||
|
data = build_report_data(budget_map, filters)
|
||||||
|
|
||||||
|
chart_data = build_comparison_chart_data(filters, columns, data)
|
||||||
|
|
||||||
|
return columns, data, None, chart_data
|
||||||
|
|
||||||
|
|
||||||
|
def get_budget_records(filters, dimensions):
|
||||||
|
budget_against_field = frappe.scrub(filters["budget_against"])
|
||||||
|
|
||||||
|
return frappe.db.sql(
|
||||||
|
f"""
|
||||||
|
SELECT
|
||||||
|
b.name,
|
||||||
|
b.account,
|
||||||
|
b.{budget_against_field} AS dimension,
|
||||||
|
b.budget_amount,
|
||||||
|
b.from_fiscal_year,
|
||||||
|
b.to_fiscal_year,
|
||||||
|
b.budget_start_date,
|
||||||
|
b.budget_end_date
|
||||||
|
FROM
|
||||||
|
`tabBudget` b
|
||||||
|
WHERE
|
||||||
|
b.company = %s
|
||||||
|
AND b.docstatus = 1
|
||||||
|
AND b.budget_against = %s
|
||||||
|
AND b.{budget_against_field} IN ({', '.join(['%s'] * len(dimensions))})
|
||||||
|
AND (
|
||||||
|
b.from_fiscal_year <= %s
|
||||||
|
AND b.to_fiscal_year >= %s
|
||||||
|
)
|
||||||
|
""",
|
||||||
|
(
|
||||||
|
filters.company,
|
||||||
|
filters.budget_against,
|
||||||
|
*dimensions,
|
||||||
|
filters.to_fiscal_year,
|
||||||
|
filters.from_fiscal_year,
|
||||||
|
),
|
||||||
|
as_dict=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def build_budget_map(budget_records, filters):
|
||||||
|
"""
|
||||||
|
Builds a nested dictionary structure aggregating budget and actual amounts.
|
||||||
|
|
||||||
|
Structure: {dimension_name: {account_name: {fiscal_year: {month_name: {"budget": amount, "actual": amount}}}}}
|
||||||
|
"""
|
||||||
|
budget_map = {}
|
||||||
|
|
||||||
|
for budget in budget_records:
|
||||||
|
actual_amt = get_actual_transactions(budget.dimension, filters)
|
||||||
|
budget_map.setdefault(budget.dimension, {})
|
||||||
|
budget_map[budget.dimension].setdefault(budget.account, {})
|
||||||
|
|
||||||
|
budget_distributions = get_budget_distributions(budget)
|
||||||
|
|
||||||
|
for row in budget_distributions:
|
||||||
|
months = get_months_in_range(row.start_date, row.end_date)
|
||||||
|
monthly_budget = flt(row.amount) / len(months)
|
||||||
|
|
||||||
|
for month_date in months:
|
||||||
|
fiscal_year = get_fiscal_year(month_date)[0]
|
||||||
|
month = month_date.strftime("%B")
|
||||||
|
|
||||||
|
budget_map[budget.dimension][budget.account].setdefault(fiscal_year, {})
|
||||||
|
budget_map[budget.dimension][budget.account][fiscal_year].setdefault(
|
||||||
|
month,
|
||||||
|
{
|
||||||
|
"budget": 0,
|
||||||
|
"actual": 0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
budget_map[budget.dimension][budget.account][fiscal_year][month]["budget"] += monthly_budget
|
||||||
|
|
||||||
|
for ad in actual_amt.get(budget.account, []):
|
||||||
|
if ad.month_name == month and ad.fiscal_year == fiscal_year:
|
||||||
|
budget_map[budget.dimension][budget.account][fiscal_year][month]["actual"] += flt(
|
||||||
|
ad.debit
|
||||||
|
) - flt(ad.credit)
|
||||||
|
|
||||||
|
return budget_map
|
||||||
|
|
||||||
|
|
||||||
|
def get_actual_transactions(dimension_name, filters):
|
||||||
|
budget_against = frappe.scrub(filters.get("budget_against"))
|
||||||
|
cost_center_filter = ""
|
||||||
|
|
||||||
|
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_center_filter = f"""
|
||||||
|
and lft >= "{cc_lft}"
|
||||||
|
and rgt <= "{cc_rgt}"
|
||||||
|
"""
|
||||||
|
|
||||||
|
actual_transactions = frappe.db.sql(
|
||||||
|
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 = {}
|
||||||
|
for transaction in actual_transactions:
|
||||||
|
actual_transactions_map.setdefault(transaction.account, []).append(transaction)
|
||||||
|
|
||||||
|
return actual_transactions_map
|
||||||
|
|
||||||
|
|
||||||
|
def get_budget_distributions(budget):
|
||||||
|
return frappe.db.sql(
|
||||||
|
"""
|
||||||
|
SELECT start_date, end_date, amount, percent
|
||||||
|
FROM `tabBudget Distribution`
|
||||||
|
WHERE parent = %s
|
||||||
|
ORDER BY start_date ASC
|
||||||
|
""",
|
||||||
|
(budget.name,),
|
||||||
|
as_dict=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_months_in_range(start_date, end_date):
|
||||||
|
months = []
|
||||||
|
current = start_date
|
||||||
|
|
||||||
|
while current <= end_date:
|
||||||
|
months.append(current)
|
||||||
|
current = add_months(current, 1)
|
||||||
|
|
||||||
|
return months
|
||||||
|
|
||||||
|
|
||||||
|
def build_report_data(budget_map, filters):
|
||||||
data = []
|
data = []
|
||||||
for dimension in dimensions:
|
|
||||||
dimension_items = cam_map.get(dimension)
|
|
||||||
if dimension_items:
|
|
||||||
data = get_final_data(dimension, dimension_items, filters, period_month_ranges, data, 0)
|
|
||||||
|
|
||||||
chart = get_chart_data(filters, columns, data)
|
show_cumulative = filters.get("show_cumulative") and filters.get("period") != "Yearly"
|
||||||
|
periods = get_periods(filters)
|
||||||
|
|
||||||
return columns, data, None, chart
|
for dimension, accounts in budget_map.items():
|
||||||
|
for account, fiscal_year_map in accounts.items():
|
||||||
|
row = {
|
||||||
|
"budget_against": dimension,
|
||||||
|
"account": account,
|
||||||
|
}
|
||||||
|
|
||||||
|
running_budget = 0
|
||||||
|
running_actual = 0
|
||||||
|
total_budget = 0
|
||||||
|
total_actual = 0
|
||||||
|
|
||||||
def get_final_data(dimension, dimension_items, filters, period_month_ranges, data, DCC_allocation):
|
for period in periods:
|
||||||
for account, monthwise_data in dimension_items.items():
|
fiscal_year = period["fiscal_year"]
|
||||||
row = [dimension, account]
|
months = get_months_between(period["from_date"], period["to_date"])
|
||||||
totals = [0, 0, 0]
|
|
||||||
for year in get_fiscal_years(filters):
|
|
||||||
last_total = 0
|
|
||||||
for relevant_months in period_month_ranges:
|
|
||||||
period_data = [0, 0, 0]
|
|
||||||
for month in relevant_months:
|
|
||||||
if monthwise_data.get(year[0]):
|
|
||||||
month_data = monthwise_data.get(year[0]).get(month, {})
|
|
||||||
for i, fieldname in enumerate(["target", "actual", "variance"]):
|
|
||||||
value = flt(month_data.get(fieldname))
|
|
||||||
period_data[i] += value
|
|
||||||
totals[i] += value
|
|
||||||
|
|
||||||
period_data[0] += last_total
|
period_budget = 0
|
||||||
|
period_actual = 0
|
||||||
|
|
||||||
if DCC_allocation:
|
month_map = fiscal_year_map.get(fiscal_year, {})
|
||||||
period_data[0] = period_data[0] * (DCC_allocation / 100)
|
|
||||||
period_data[1] = period_data[1] * (DCC_allocation / 100)
|
|
||||||
|
|
||||||
if filters.get("show_cumulative"):
|
for month in months:
|
||||||
last_total = period_data[0] - period_data[1]
|
values = month_map.get(month)
|
||||||
|
if values:
|
||||||
|
period_budget += values.get("budget", 0)
|
||||||
|
period_actual += values.get("actual", 0)
|
||||||
|
|
||||||
|
if show_cumulative:
|
||||||
|
running_budget += period_budget
|
||||||
|
running_actual += period_actual
|
||||||
|
display_budget = running_budget
|
||||||
|
display_actual = running_actual
|
||||||
|
else:
|
||||||
|
display_budget = period_budget
|
||||||
|
display_actual = period_actual
|
||||||
|
|
||||||
|
total_budget += period_budget
|
||||||
|
total_actual += period_actual
|
||||||
|
|
||||||
|
if filters["period"] == "Yearly":
|
||||||
|
budget_label = _("Budget") + " " + fiscal_year
|
||||||
|
actual_label = _("Actual") + " " + fiscal_year
|
||||||
|
variance_label = _("Variance") + " " + fiscal_year
|
||||||
|
else:
|
||||||
|
budget_label = _("Budget") + f" ({period['label_suffix']}) {fiscal_year}"
|
||||||
|
actual_label = _("Actual") + f" ({period['label_suffix']}) {fiscal_year}"
|
||||||
|
variance_label = _("Variance") + f" ({period['label_suffix']}) {fiscal_year}"
|
||||||
|
|
||||||
|
row[frappe.scrub(budget_label)] = display_budget
|
||||||
|
row[frappe.scrub(actual_label)] = display_actual
|
||||||
|
row[frappe.scrub(variance_label)] = display_budget - display_actual
|
||||||
|
|
||||||
period_data[2] = period_data[0] - period_data[1]
|
|
||||||
row += period_data
|
|
||||||
totals[2] = totals[0] - totals[1]
|
|
||||||
if filters["period"] != "Yearly":
|
if filters["period"] != "Yearly":
|
||||||
row += totals
|
row["total_budget"] = total_budget
|
||||||
|
row["total_actual"] = total_actual
|
||||||
|
row["total_variance"] = total_budget - total_actual
|
||||||
|
|
||||||
data.append(row)
|
data.append(row)
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def get_periods(filters):
|
||||||
|
periods = []
|
||||||
|
|
||||||
|
group_months = filters["period"] != "Monthly"
|
||||||
|
|
||||||
|
for (fiscal_year,) in get_fiscal_years(filters):
|
||||||
|
for from_date, to_date in get_period_date_ranges(filters["period"], fiscal_year):
|
||||||
|
if filters["period"] == "Yearly":
|
||||||
|
label_suffix = fiscal_year
|
||||||
|
else:
|
||||||
|
if group_months:
|
||||||
|
label_suffix = formatdate(from_date, "MMM") + "-" + formatdate(to_date, "MMM")
|
||||||
|
else:
|
||||||
|
label_suffix = formatdate(from_date, "MMM")
|
||||||
|
|
||||||
|
periods.append(
|
||||||
|
{
|
||||||
|
"fiscal_year": fiscal_year,
|
||||||
|
"from_date": from_date,
|
||||||
|
"to_date": to_date,
|
||||||
|
"label_suffix": label_suffix,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return periods
|
||||||
|
|
||||||
|
|
||||||
|
def get_months_between(from_date, to_date):
|
||||||
|
months = []
|
||||||
|
current = from_date
|
||||||
|
|
||||||
|
while current <= to_date:
|
||||||
|
months.append(formatdate(current, "MMMM"))
|
||||||
|
current = add_months(current, 1)
|
||||||
|
|
||||||
|
return months
|
||||||
|
|
||||||
|
|
||||||
def get_columns(filters):
|
def get_columns(filters):
|
||||||
columns = [
|
columns = [
|
||||||
{
|
{
|
||||||
@@ -81,7 +304,7 @@ def get_columns(filters):
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": _("Account"),
|
"label": _("Account"),
|
||||||
"fieldname": "Account",
|
"fieldname": "account",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"options": "Account",
|
"options": "Account",
|
||||||
"width": 150,
|
"width": 150,
|
||||||
@@ -134,7 +357,23 @@ def get_columns(filters):
|
|||||||
return columns
|
return columns
|
||||||
|
|
||||||
|
|
||||||
def get_cost_centers(filters):
|
def get_fiscal_years(filters):
|
||||||
|
fiscal_year = frappe.db.sql(
|
||||||
|
"""
|
||||||
|
select
|
||||||
|
name
|
||||||
|
from
|
||||||
|
`tabFiscal Year`
|
||||||
|
where
|
||||||
|
name between %(from_fiscal_year)s and %(to_fiscal_year)s
|
||||||
|
""",
|
||||||
|
{"from_fiscal_year": filters["from_fiscal_year"], "to_fiscal_year": filters["to_fiscal_year"]},
|
||||||
|
)
|
||||||
|
|
||||||
|
return fiscal_year
|
||||||
|
|
||||||
|
|
||||||
|
def get_budget_dimensions(filters):
|
||||||
order_by = ""
|
order_by = ""
|
||||||
if filters.get("budget_against") == "Cost Center":
|
if filters.get("budget_against") == "Cost Center":
|
||||||
order_by = "order by lft"
|
order_by = "order by lft"
|
||||||
@@ -163,222 +402,56 @@ def get_cost_centers(filters):
|
|||||||
) # nosec
|
) # nosec
|
||||||
|
|
||||||
|
|
||||||
# Get dimension & target details
|
def build_comparison_chart_data(filters, columns, data):
|
||||||
def get_dimension_target_details(filters):
|
|
||||||
budget_against = frappe.scrub(filters.get("budget_against"))
|
|
||||||
cond = ""
|
|
||||||
if filters.get("budget_against_filter"):
|
|
||||||
cond += f""" and b.{budget_against} in (%s)""" % ", ".join(
|
|
||||||
["%s"] * len(filters.get("budget_against_filter"))
|
|
||||||
)
|
|
||||||
|
|
||||||
return frappe.db.sql(
|
|
||||||
f"""
|
|
||||||
select
|
|
||||||
b.{budget_against} as budget_against,
|
|
||||||
b.monthly_distribution,
|
|
||||||
ba.account,
|
|
||||||
ba.budget_amount,
|
|
||||||
b.fiscal_year
|
|
||||||
from
|
|
||||||
`tabBudget` b,
|
|
||||||
`tabBudget Account` ba
|
|
||||||
where
|
|
||||||
b.name = ba.parent
|
|
||||||
and b.docstatus = 1
|
|
||||||
and b.fiscal_year between %s and %s
|
|
||||||
and b.budget_against = %s
|
|
||||||
and b.company = %s
|
|
||||||
{cond}
|
|
||||||
order by
|
|
||||||
b.fiscal_year
|
|
||||||
""",
|
|
||||||
tuple(
|
|
||||||
[
|
|
||||||
filters.from_fiscal_year,
|
|
||||||
filters.to_fiscal_year,
|
|
||||||
filters.budget_against,
|
|
||||||
filters.company,
|
|
||||||
]
|
|
||||||
+ (filters.get("budget_against_filter") or [])
|
|
||||||
),
|
|
||||||
as_dict=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# Get target distribution details of accounts of cost center
|
|
||||||
def get_target_distribution_details(filters):
|
|
||||||
target_details = {}
|
|
||||||
for d in frappe.db.sql(
|
|
||||||
"""
|
|
||||||
select
|
|
||||||
md.name,
|
|
||||||
mdp.month,
|
|
||||||
mdp.percentage_allocation
|
|
||||||
from
|
|
||||||
`tabMonthly Distribution Percentage` mdp,
|
|
||||||
`tabMonthly Distribution` md
|
|
||||||
where
|
|
||||||
mdp.parent = md.name
|
|
||||||
and md.fiscal_year between %s and %s
|
|
||||||
order by
|
|
||||||
md.fiscal_year
|
|
||||||
""",
|
|
||||||
(filters.from_fiscal_year, filters.to_fiscal_year),
|
|
||||||
as_dict=1,
|
|
||||||
):
|
|
||||||
target_details.setdefault(d.name, {}).setdefault(d.month, flt(d.percentage_allocation))
|
|
||||||
|
|
||||||
return target_details
|
|
||||||
|
|
||||||
|
|
||||||
# Get actual details from gl entry
|
|
||||||
def get_actual_details(name, filters):
|
|
||||||
budget_against = frappe.scrub(filters.get("budget_against"))
|
|
||||||
cond = ""
|
|
||||||
|
|
||||||
if filters.get("budget_against") == "Cost Center":
|
|
||||||
cc_lft, cc_rgt = frappe.db.get_value("Cost Center", name, ["lft", "rgt"])
|
|
||||||
cond = f"""
|
|
||||||
and lft >= "{cc_lft}"
|
|
||||||
and rgt <= "{cc_rgt}"
|
|
||||||
"""
|
|
||||||
|
|
||||||
ac_details = frappe.db.sql(
|
|
||||||
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 Account` ba,
|
|
||||||
`tabBudget` b
|
|
||||||
where
|
|
||||||
b.name = ba.parent
|
|
||||||
and b.docstatus = 1
|
|
||||||
and ba.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}
|
|
||||||
{cond}
|
|
||||||
)
|
|
||||||
group by
|
|
||||||
gl.name
|
|
||||||
order by gl.fiscal_year
|
|
||||||
""",
|
|
||||||
(filters.from_fiscal_year, filters.to_fiscal_year, name),
|
|
||||||
as_dict=1,
|
|
||||||
)
|
|
||||||
|
|
||||||
cc_actual_details = {}
|
|
||||||
for d in ac_details:
|
|
||||||
cc_actual_details.setdefault(d.account, []).append(d)
|
|
||||||
|
|
||||||
return cc_actual_details
|
|
||||||
|
|
||||||
|
|
||||||
def get_dimension_account_month_map(filters):
|
|
||||||
dimension_target_details = get_dimension_target_details(filters)
|
|
||||||
tdd = get_target_distribution_details(filters)
|
|
||||||
|
|
||||||
cam_map = {}
|
|
||||||
|
|
||||||
for ccd in dimension_target_details:
|
|
||||||
actual_details = get_actual_details(ccd.budget_against, filters)
|
|
||||||
|
|
||||||
for month_id in range(1, 13):
|
|
||||||
month = datetime.date(2013, month_id, 1).strftime("%B")
|
|
||||||
cam_map.setdefault(ccd.budget_against, {}).setdefault(ccd.account, {}).setdefault(
|
|
||||||
ccd.fiscal_year, {}
|
|
||||||
).setdefault(month, frappe._dict({"target": 0.0, "actual": 0.0}))
|
|
||||||
|
|
||||||
tav_dict = cam_map[ccd.budget_against][ccd.account][ccd.fiscal_year][month]
|
|
||||||
month_percentage = (
|
|
||||||
tdd.get(ccd.monthly_distribution, {}).get(month, 0)
|
|
||||||
if ccd.monthly_distribution
|
|
||||||
else 100.0 / 12
|
|
||||||
)
|
|
||||||
|
|
||||||
tav_dict.target = flt(ccd.budget_amount) * month_percentage / 100
|
|
||||||
|
|
||||||
for ad in actual_details.get(ccd.account, []):
|
|
||||||
if ad.month_name == month and ad.fiscal_year == ccd.fiscal_year:
|
|
||||||
tav_dict.actual += flt(ad.debit) - flt(ad.credit)
|
|
||||||
|
|
||||||
return cam_map
|
|
||||||
|
|
||||||
|
|
||||||
def get_fiscal_years(filters):
|
|
||||||
fiscal_year = frappe.db.sql(
|
|
||||||
"""
|
|
||||||
select
|
|
||||||
name
|
|
||||||
from
|
|
||||||
`tabFiscal Year`
|
|
||||||
where
|
|
||||||
name between %(from_fiscal_year)s and %(to_fiscal_year)s
|
|
||||||
""",
|
|
||||||
{"from_fiscal_year": filters["from_fiscal_year"], "to_fiscal_year": filters["to_fiscal_year"]},
|
|
||||||
)
|
|
||||||
|
|
||||||
return fiscal_year
|
|
||||||
|
|
||||||
|
|
||||||
def get_chart_data(filters, columns, data):
|
|
||||||
if not data:
|
if not data:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
labels = []
|
budget_fields = []
|
||||||
|
actual_fields = []
|
||||||
|
|
||||||
fiscal_year = get_fiscal_years(filters)
|
for col in columns:
|
||||||
group_months = False if filters["period"] == "Monthly" else True
|
fieldname = col.get("fieldname")
|
||||||
|
if not fieldname:
|
||||||
|
continue
|
||||||
|
|
||||||
for year in fiscal_year:
|
if fieldname.startswith("budget_"):
|
||||||
for from_date, to_date in get_period_date_ranges(filters["period"], year[0]):
|
budget_fields.append(fieldname)
|
||||||
if filters["period"] == "Yearly":
|
elif fieldname.startswith("actual_"):
|
||||||
labels.append(year[0])
|
actual_fields.append(fieldname)
|
||||||
else:
|
|
||||||
if group_months:
|
|
||||||
label = (
|
|
||||||
formatdate(from_date, format_string="MMM")
|
|
||||||
+ "-"
|
|
||||||
+ formatdate(to_date, format_string="MMM")
|
|
||||||
)
|
|
||||||
labels.append(label)
|
|
||||||
else:
|
|
||||||
label = formatdate(from_date, format_string="MMM")
|
|
||||||
labels.append(label)
|
|
||||||
|
|
||||||
no_of_columns = len(labels)
|
if not budget_fields or not actual_fields:
|
||||||
|
return None
|
||||||
|
|
||||||
budget_values, actual_values = [0] * no_of_columns, [0] * no_of_columns
|
labels = [
|
||||||
for d in data:
|
col["label"].replace("Budget", "").strip()
|
||||||
values = d[2:]
|
for col in columns
|
||||||
index = 0
|
if col.get("fieldname", "").startswith("budget_")
|
||||||
|
]
|
||||||
|
|
||||||
for i in range(no_of_columns):
|
budget_values = [0] * len(budget_fields)
|
||||||
budget_values[i] += values[index]
|
actual_values = [0] * len(actual_fields)
|
||||||
actual_values[i] += values[index + 1]
|
|
||||||
index += 3
|
for row in data:
|
||||||
|
for i, field in enumerate(budget_fields):
|
||||||
|
budget_values[i] += flt(row.get(field))
|
||||||
|
|
||||||
|
for i, field in enumerate(actual_fields):
|
||||||
|
actual_values[i] += flt(row.get(field))
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"data": {
|
"data": {
|
||||||
"labels": labels,
|
"labels": labels,
|
||||||
"datasets": [
|
"datasets": [
|
||||||
{"name": _("Budget"), "chartType": "bar", "values": budget_values},
|
{
|
||||||
{"name": _("Actual Expense"), "chartType": "bar", "values": actual_values},
|
"name": _("Budget"),
|
||||||
|
"chartType": "bar",
|
||||||
|
"values": budget_values,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": _("Actual Expense"),
|
||||||
|
"chartType": "bar",
|
||||||
|
"values": actual_values,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"type": "bar",
|
"type": "bar",
|
||||||
|
|||||||
Reference in New Issue
Block a user