Merge pull request #19973 from deepeshgarg007/budget_variance_dimension

feat: Dynamic filters for dimensions in budget variance report
diff --git a/erpnext/accounts/report/budget_variance_report/budget_variance_report.js b/erpnext/accounts/report/budget_variance_report/budget_variance_report.js
index 2451187..3ec4d30 100644
--- a/erpnext/accounts/report/budget_variance_report/budget_variance_report.js
+++ b/erpnext/accounts/report/budget_variance_report/budget_variance_report.js
@@ -46,13 +46,24 @@
 			fieldtype: "Select",
 			options: ["Cost Center", "Project"],
 			default: "Cost Center",
-			reqd: 1
+			reqd: 1,
+			on_change: function() {
+				frappe.query_report.set_filter_value("budget_against_filter", []);
+				frappe.query_report.refresh();
+			}
 		},
 		{
-			fieldname: "cost_center",
-			label: __("Cost Center"),
-			fieldtype: "Link",
-			options: "Cost Center"
+			fieldname:"budget_against_filter",
+			label: __('Dimension Filter'),
+			fieldtype: "MultiSelectList",
+			get_data: function(txt) {
+				if (!frappe.query_report.filters) return;
+
+				let budget_against = frappe.query_report.get_filter_value('budget_against');
+				if (!budget_against) return;
+
+				return frappe.db.get_link_options(budget_against, txt);
+			}
 		},
 		{
 			fieldname:"show_cumulative",
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 8d65ac8..39e218b 100644
--- a/erpnext/accounts/report/budget_variance_report/budget_variance_report.py
+++ b/erpnext/accounts/report/budget_variance_report/budget_variance_report.py
@@ -12,22 +12,22 @@
 from pprint import pprint
 def execute(filters=None):
 	if not filters: filters = {}
-	validate_filters(filters)
+
 	columns = get_columns(filters)
-	if filters.get("cost_center"):
-		cost_centers = [filters.get("cost_center")]
+	if filters.get("budget_against_filter"):
+		dimensions = filters.get("budget_against_filter")
 	else:
-		cost_centers = get_cost_centers(filters)
+		dimensions = get_cost_centers(filters)
 
 	period_month_ranges = get_period_month_ranges(filters["period"], filters["from_fiscal_year"])
-	cam_map = get_cost_center_account_month_map(filters)
+	cam_map = get_dimension_account_month_map(filters)
 
 	data = []
-	for cost_center in cost_centers:
-		cost_center_items = cam_map.get(cost_center)
-		if cost_center_items:
-			for account, monthwise_data in iteritems(cost_center_items):
-				row = [cost_center, account]
+	for dimension in dimensions:
+		dimension_items = cam_map.get(dimension)
+		if dimension_items:
+			for account, monthwise_data in iteritems(dimension_items):
+				row = [dimension, account]
 				totals = [0, 0, 0]
 				for year in get_fiscal_years(filters):
 					last_total = 0
@@ -55,10 +55,6 @@
 
 	return columns, data
 
-def validate_filters(filters):
-	if filters.get("budget_against") != "Cost Center" and filters.get("cost_center"):
-		frappe.throw(_("Filter based on Cost Center is only applicable if Budget Against is selected as Cost Center"))
-
 def get_columns(filters):
 	columns = [_(filters.get("budget_against")) + ":Link/%s:150"%(filters.get("budget_against")), _("Account") + ":Link/Account:150"]
 
@@ -98,11 +94,12 @@
 	else:
 		return frappe.db.sql_list("""select name from `tab{tab}`""".format(tab=filters.get("budget_against"))) #nosec
 
-#Get cost center & target details
-def get_cost_center_target_details(filters):
+#Get dimension & target details
+def get_dimension_target_details(filters):
 	cond = ""
-	if filters.get("cost_center"):
-		cond += " and b.cost_center=%s" % frappe.db.escape(filters.get("cost_center"))
+	if filters.get("budget_against_filter"):
+		cond += " and b.{budget_against} in (%s)".format(budget_against = \
+			frappe.scrub(filters.get('budget_against'))) % ', '.join(['%s']* len(filters.get('budget_against_filter')))
 
 	return frappe.db.sql("""
 			select b.{budget_against} as budget_against, b.monthly_distribution, ba.account, ba.budget_amount,b.fiscal_year
@@ -110,8 +107,8 @@
 			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
 		""".format(budget_against=filters.get("budget_against").replace(" ", "_").lower(), cond=cond),
-		(filters.from_fiscal_year,filters.to_fiscal_year,filters.budget_against, filters.company), as_dict=True)
-
+		tuple([filters.from_fiscal_year,filters.to_fiscal_year,filters.budget_against, filters.company] + filters.get('budget_against_filter')), 
+		as_dict=True)
 
 
 #Get target distribution details of accounts of cost center
@@ -153,14 +150,14 @@
 
 	return cc_actual_details
 
-def get_cost_center_account_month_map(filters):
+def get_dimension_account_month_map(filters):
 	import datetime
-	cost_center_target_details = get_cost_center_target_details(filters)
+	dimension_target_details = get_dimension_target_details(filters)
 	tdd = get_target_distribution_details(filters)
 
 	cam_map = {}
 
-	for ccd in cost_center_target_details:
+	for ccd in dimension_target_details:
 		actual_details = get_actual_details(ccd.budget_against, filters)
 
 		for month_id in range(1, 13):