Skip to content

Conversation

@luka-03256
Copy link

Description

This pull request adds the "Accumulated Values in Group Company" feature to the Budget Variance Report, enabling consolidated reporting across parent and child companies similar to the Consolidated Financial Statement reports.

Changes Made

  • Added a new checkbox filter accumulated_values_in_group_company in budget_variance_report.js.
  • Implemented server-side consolidation logic in budget_variance_report.py to aggregate actual, budget, and variance values across child companies.
  • Supports both single-company and multi-company modes based on the checkbox selection.
  • Follows the implementation pattern used in the Consolidated Financial Statement report for consistency.

Problem Solved

Previously, the Budget Variance Report only displayed data for a single company.
With this feature:

  • Users can view consolidated budget variance across group companies.
  • Eliminates the need for manual aggregation for multi-company reporting.

Testing & Validation

  • Single company: Report behaves as before.
  • Parent with child companies: Consolidated totals are correctly calculated.
  • Checkbox toggling correctly switches between consolidated and individual views.
  • Verified data consistency against manual aggregation.

Screenshots / GIFs

(Include before/after screenshots or GIFs showing the checkbox and consolidated output)

Issue Reference

Closes #50608

@github-actions github-actions bot added the needs-tests This PR needs automated unit-tests. label Nov 18, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 18, 2025

📝 Walkthrough

Walkthrough

Adds an "accumulated_in_group_company" filter and frontend logic to show/hide it based on whether the selected company is a group company. Frontend registers onload and on_change handlers to update the filter and refresh the report. Backend adds get_companies_for_report(filters) and, when the accumulated flag is true, iterates companies to merge per-company month-by-account dimension maps into a consolidated cam_map by summing target/actual values and recomputing variances; otherwise existing per-company behavior remains.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Review backend consolidation: merging logic for cam_map, accumulation of target/actual per month, and variance recomputation.
  • Verify get_companies_for_report correctness and interaction with filters.
  • Inspect frontend: onload handler, update_group_company_checkbox implementation, and registration of on_change for company and budget_against filters.
  • Confirm formatter guards and any changes to filter default/hidden state.

Suggested reviewers

  • ruthra-kumar

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding an 'Accumulated Values in Group Company' checkbox to Budget Variance Report, which aligns with the primary objective of the changeset.
Description check ✅ Passed The description is directly related to the changeset, explaining the feature implementation, problem solved, and testing validation performed.
Linked Issues check ✅ Passed The pull request implements the exact requirement from issue #50608: adding an 'Accumulated Values in Group Company' checkbox to enable consolidated budget variance reporting across group companies.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing the 'Accumulated Values in Group Company' feature requested in issue #50608, with no unrelated modifications detected.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (2)
erpnext/accounts/report/budget_variance_report/budget_variance_report.js (2)

21-45: Add on_change handler for company filter.

The onload handler checks the company's is_group status only once when the report loads. If the user changes the company filter after the report has loaded, the visibility of the accumulated_in_group_company checkbox won't update dynamically.

Consider adding an on_change handler to the company filter definition:

{
	fieldname: "company",
	label: __("Company"),
	fieldtype: "Link",
	options: "Company",
	default: frappe.defaults.get_user_default("Company"),
	reqd: 1,
	on_change: function () {
		let company = frappe.query_report.get_filter_value("company");
		if (!company) return;

		frappe.call({
			method: "frappe.client.get_value",
			args: {
				doctype: "Company",
				filters: { name: company },
				fieldname: "is_group",
			},
			callback: function(r) {
				const filter = frappe.query_report.get_filter("accumulated_in_group_company");
				if (!filter) return;

				if (r.message && r.message.is_group) {
					filter.df.hidden = 0;
				} else {
					filter.df.hidden = 1;
					frappe.query_report.set_filter_value("accumulated_in_group_company", 0);
				}
				filter.refresh();
			},
		});
	},
},

137-143: Fix indentation inconsistency.

Lines 137-143 use spaces for indentation, while the rest of the file uses tabs. This inconsistency should be corrected for code uniformity.

Apply this diff to use tabs consistently:

-      {
-        fieldname: "accumulated_in_group_company",
-        label: __("Accumulated Values in Group Company"),
-        fieldtype: "Check",
-        default: 0,
-        hidden: 1,
-      },
+		{
+			fieldname: "accumulated_in_group_company",
+			label: __("Accumulated Values in Group Company"),
+			fieldtype: "Check",
+			default: 0,
+			hidden: 1,
+		},
📜 Review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cf73de9 and f954dba.

📒 Files selected for processing (2)
  • erpnext/accounts/report/budget_variance_report/budget_variance_report.js (2 hunks)
  • erpnext/accounts/report/budget_variance_report/budget_variance_report.py (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
erpnext/accounts/report/budget_variance_report/budget_variance_report.py (1)
erpnext/accounts/report/budget_variance_report/budget_variance_report.js (2)
  • filters (67-144)
  • company (22-22)
🪛 Ruff (0.14.5)
erpnext/accounts/report/budget_variance_report/budget_variance_report.py

48-48: Unexpected indentation

(invalid-syntax)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Summary
🔇 Additional comments (1)
erpnext/accounts/report/budget_variance_report/budget_variance_report.py (1)

13-31: LGTM! Clean implementation for company retrieval.

The function correctly handles both single-company and consolidated modes, retrieving child companies and ensuring the parent is always included.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
erpnext/accounts/report/budget_variance_report/budget_variance_report.py (2)

13-28: Scope of get_companies_for_report may miss deeper company trees

The helper returns the selected company plus its direct children (parent_company = company). If group structures can be more than one level deep (group → sub‑group → operating company), those deeper descendants won’t be included in consolidation.

If you expect multi‑level hierarchies, consider reusing an existing helper that returns all descendant companies (or iteratively traversing parent_company) so the consolidated view truly covers the full group structure. Otherwise, documenting that only direct children are included would avoid surprises.


47-83: Minor: avoid redundant variance computation in the consolidated map

Inside the consolidation loop, you recompute and store ["variance"] for each month (Lines 75-78), but get_final_data later derives variance as budget - actual and overwrites totals[2] accordingly. The stored variance values in cam_map are effectively unused for the final numbers.

You can simplify and slightly reduce work by only aggregating "target" and "actual", skipping the "variance" assignment in this block. That keeps the data structure cleaner and avoids confusion about which variance is “authoritative”.

erpnext/accounts/report/budget_variance_report/budget_variance_report.js (1)

103-112: Ensure company on_change still refreshes the report

The new on_change handler for the company filter now only calls update_group_company_checkbox(company). Depending on Frappe’s default behavior, adding this handler may override any implicit auto-refresh that previously occurred when the company changed.

Please confirm whether query reports still refresh automatically when a filter has a custom on_change. If not, consider explicitly refreshing:

 		{
 			fieldname: "company",
@@
-			on_change: function () {
-				let company = frappe.query_report.get_filter_value("company");
-				if (company) update_group_company_checkbox(company);
-			},
+			on_change: function () {
+				const company = frappe.query_report.get_filter_value("company");
+				if (company) {
+					update_group_company_checkbox(company);
+				}
+				frappe.query_report.refresh();
+			},

(Optionally also hide/reset the checkbox when company becomes empty, for extra safety.)

📜 Review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f954dba and ada7858.

📒 Files selected for processing (2)
  • erpnext/accounts/report/budget_variance_report/budget_variance_report.js (3 hunks)
  • erpnext/accounts/report/budget_variance_report/budget_variance_report.py (1 hunks)
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: vorasmit
Repo: frappe/erpnext PR: 49098
File: erpnext/accounts/doctype/financial_report_row/financial_report_row.json:118-124
Timestamp: 2025-11-14T04:52:35.353Z
Learning: In ERPNext Financial Report Row (erpnext/accounts/doctype/financial_report_row), when data_source is "Account Data" and advanced_filtering is false, the filters_editor field provides a UI that automatically populates the calculation_formula field behind the scenes. The calculation_formula field can be hidden but mandatory in this case because it gets set automatically before validation occurs.
📚 Learning: 2025-11-14T04:52:35.353Z
Learnt from: vorasmit
Repo: frappe/erpnext PR: 49098
File: erpnext/accounts/doctype/financial_report_row/financial_report_row.json:118-124
Timestamp: 2025-11-14T04:52:35.353Z
Learning: In ERPNext Financial Report Row (erpnext/accounts/doctype/financial_report_row), when data_source is "Account Data" and advanced_filtering is false, the filters_editor field provides a UI that automatically populates the calculation_formula field behind the scenes. The calculation_formula field can be hidden but mandatory in this case because it gets set automatically before validation occurs.

Applied to files:

  • erpnext/accounts/report/budget_variance_report/budget_variance_report.js
📚 Learning: 2025-10-14T04:57:50.118Z
Learnt from: vorasmit
Repo: frappe/erpnext PR: 49098
File: erpnext/accounts/financial_report_template/horizontal_profit_and_loss_(columnar)/horizontal_profit_and_loss_(columnar).json:212-225
Timestamp: 2025-10-14T04:57:50.118Z
Learning: In ERPNext Financial Report Template calculation formulas for filtering service-related accounts, use `account_name` field with a "like" operator (e.g., `account_name like "Service"`) rather than `account_type`, as the backend automatically adds SQL wildcards during evaluation.

Applied to files:

  • erpnext/accounts/report/budget_variance_report/budget_variance_report.js
🧬 Code graph analysis (1)
erpnext/accounts/report/budget_variance_report/budget_variance_report.py (2)
erpnext/accounts/report/budget_variance_report/budget_variance_report.js (3)
  • filters (72-151)
  • company (23-23)
  • company (110-110)
erpnext/controllers/trends.py (1)
  • get_period_month_ranges (330-342)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Summary
🔇 Additional comments (3)
erpnext/accounts/report/budget_variance_report/budget_variance_report.js (3)

7-15: Formatter null-guards look good

Adding checks for column.fieldname and data before testing variance columns hardens the formatter against missing metadata/rows without changing behavior for valid data. Negative and positive variances will still be color‑coded as before.


22-50: Group-company checkbox toggling is robust

The onload + update_group_company_checkbox flow correctly:

  • Reads the current company filter.
  • Fetches is_group via frappe.client.get_value.
  • Shows the accumulated_in_group_company filter only for group companies and hides + resets it to 0 otherwise.

This keeps the server-side flag in sync with the selected company and avoids stale consolidated-state leakage when switching between group and non-group companies.


144-150: New accumulated_in_group_company filter wiring matches backend

The added accumulated_in_group_company Check filter (hidden by default) aligns with the Python flag of the same name and is correctly controlled by update_group_company_checkbox. Naming and behavior are consistent with the server-side consolidation logic.

Comment on lines 36 to +45
columns = get_columns(filters)

if filters.get("budget_against_filter"):
dimensions = filters.get("budget_against_filter")
else:
dimensions = get_cost_centers(filters)

period_month_ranges = get_period_month_ranges(filters["period"], filters["from_fiscal_year"])
cam_map = get_dimension_account_month_map(filters)
period_month_ranges = get_period_month_ranges(
filters["period"], filters["from_fiscal_year"]
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Consolidated cam_map can drop child-company dimensions when no explicit filter is set

In execute:

  • dimensions is initialized once (Lines 38-41) using either budget_against_filter or get_cost_centers(filters), i.e., cost centers/projects for the initial company.
  • In consolidated mode, cam_map is then built by iterating companies (Lines 47-83) and merging get_dimension_account_month_map(filters) for each company.
  • Later, you iterate for dimension in dimensions: and access cam_map.get(dimension) (Lines 85-92).

If accumulated_in_group_company is enabled and no budget_against_filter is provided, dimensions only contains entries from the original company, but cam_map can also contain dimensions from child companies. Any dimension that exists only in a child company will never be visited and its data will be silently omitted from the consolidated report.

Concrete effect: consolidated totals will under‑report amounts for cost centers/projects that exist solely in child companies unless the user explicitly includes them in budget_against_filter.

A minimal fix, keeping existing semantics when a filter is set, would be:

 	# consolidate dimension_account_month map across companies
 	companies = get_companies_for_report(filters)
 	if filters.get("accumulated_in_group_company"):
@@
-		filters["company"] = original_company
-		cam_map = consolidated_cam_map
+		filters["company"] = original_company
+		cam_map = consolidated_cam_map
+
+		# in consolidated mode without an explicit dimension filter,
+		# derive the dimension list from the consolidated map so
+		# child-company dimensions are not dropped
+		if not filters.get("budget_against_filter"):
+			dimensions = list(cam_map.keys())
 	else:
 		cam_map = get_dimension_account_month_map(filters)

You can further sort dimensions if ordering matters. This keeps filtered behavior unchanged while ensuring consolidated mode includes all dimensions that have data.

Also applies to: 47-83, 85-97

🤖 Prompt for AI Agents
In erpnext/accounts/report/budget_variance_report/budget_variance_report.py
around lines 36-45 (and related logic in 47-83, 85-97), dimensions are built
once from the initial company which omits dimensions that exist only in child
companies when consolidated; change the logic so that if
filters.get("budget_against_filter") is present you keep current behavior, but
when it is not present you collect dimensions from all companies by taking the
union of keys from each company's get_dimension_account_month_map (the cam_map
entries) after building/merging cam_map — then set dimensions to that union
(optionally sorted) so every dimension with data in any company is iterated and
included in the consolidated totals.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-tests This PR needs automated unit-tests.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Consolidated Budget Variance Report

1 participant