WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content

Conversation

@gk-maker0105
Copy link

@gk-maker0105 gk-maker0105 commented Nov 25, 2025

Description

Adds "Include Company Descendants" checkbox filter to Employee Analytics report, allowing users to view employees from parent company and all child companies in a single report.

Context

Changes Made

  1. JavaScript (employee_analytics.js):

    • Added "Include Company Descendants" checkbox filter (default: checked)
  2. Python (employee_analytics.py):

    • Added import: from frappe.utils.nestedset import get_descendants_of
    • Updated get_conditions() to build company list including descendants
    • Modified SQL queries to use IN clause for multiple companies
    • Updated get_chart_data() to aggregate data from all companies in hierarchy

Testing

Tested with company hierarchy:

Tech Group (parent)
├── Tech North (child)
└── Tech South (child)

Results:

  • With checkbox enabled: Shows employees from all 3 companies
  • Backend logic verified via console test
  • SQL query correctly uses: company in ('Tech Group', 'Tech South', 'Tech North')

Console Test Results

Verified that the filter correctly includes child companies:

Test 1 - With descendants enabled:

filters = {"company": "Tech Group", "parameter": "Department", "include_company_descendants": 1}
conditions = get_conditions(filters)
# Result: " and department IS NOT NULL  and company in ('Tech Group', 'Tech South', 'Tech North')"

Test 2 - Without descendants:

filters = {"company": "Tech Group", "parameter": "Department", "include_company_descendants": 0}
conditions = get_conditions(filters)
# Result: " and department IS NOT NULL  and company = 'Tech Group'"

Test 3 - Chart data generation:

chart = get_chart_data(parameters, employees, filters1)
# Result: True (chart data generated successfully)

Additional Notes

Frontend Note:
JavaScript changes follow the exact pattern from PR #2177 which is already merged and working. Could not be fully tested in local Docker due to version mismatch (ERPNext v15 with HRMS v16 develop branch), but backend logic is thoroughly verified and working correctly.

Screenshot

output2

Summary by CodeRabbit

  • New Features
    • Added an "Include Company Descendants" filter to the Employee Analytics report, letting users include or exclude descendant companies in analyses (enabled by default).
  • Impact
    • Report charts and totals now respect the descendant-inclusive option, providing broader or narrower company scopes for more accurate aggregated metrics.

✏️ Tip: You can customize this high-level summary in your review settings.

- Add checkbox filter to include/exclude employees from child companies
- Update query logic to use company descendants when filter is enabled
- Update chart data to include employees from all companies in hierarchy
- Follows same pattern as Monthly Attendance Sheet (PR frappe#2177)
- Addresses frappe/erpnext#43381
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 25, 2025

Walkthrough

A new checkbox filter include_company_descendants (default checked) was added to the Employee Analytics report UI. Backend logic now uses get_descendants_of to expand the company scope when the filter is enabled, collecting multiple companies and switching query conditions from single-company equality to IN clauses. Chart generation and total employee counting were updated to apply the expanded company list and to restrict counts to employees with status Active. Function signatures were unchanged; changes are limited to query construction and filtering logic.

Pre-merge checks

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely describes the main change: adding an 'Include Company Descendants' filter to the Employee Analytics report, which aligns with the primary modifications across both the JavaScript and Python files.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 293485d and e2ebc7a.

📒 Files selected for processing (1)
  • hrms/hr/report/employee_analytics/employee_analytics.py (4 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-08-14T10:46:28.404Z
Learnt from: asmitahase
Repo: frappe/hrms PR: 3335
File: hrms/hr/report/employee_leave_balance/employee_leave_balance.py:198-213
Timestamp: 2025-08-14T10:46:28.404Z
Learning: The original employee_leave_balance report used sophisticated record-by-record processing logic to handle partial date overlaps correctly. When an allocation spans beyond the reporting period, it calculated expired leaves by taking the full allocation minus leaves already used within that specific period, and only counted allocations that START within the filter period as new allocations. The current refactored implementation using database aggregations loses this nuanced business logic and can produce incorrect results for partially overlapping allocations.

Applied to files:

  • hrms/hr/report/employee_analytics/employee_analytics.py
📚 Learning: 2025-11-13T12:51:15.011Z
Learnt from: asmitahase
Repo: frappe/hrms PR: 3509
File: hrms/hr/utils.py:540-571
Timestamp: 2025-11-13T12:51:15.011Z
Learning: In hrms/hr/utils.py query patterns, when performing a LEFT JOIN with a child table (like Earned Leave Schedule) and using COUNT aggregation, GROUP BY should use the parent table's primary key (e.g., leave_allocation.name) rather than the child table's parent reference field (e.g., earned_leave_schedule.parent). Grouping by the child's parent field can cause multiple parent rows without children (all having NULL parent values) to collapse together or produce undefined behavior for non-aggregated columns.

Applied to files:

  • hrms/hr/report/employee_analytics/employee_analytics.py
⏰ 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 (2)
hrms/hr/report/employee_analytics/employee_analytics.py (2)

111-116: status = 'Active' alignment for total count looks correct.

The total employee count now filters by status = 'Active' and the same company in %s scope as the per‑parameter counts, so the “Not Set” bucket (others) will be consistent and non‑negative for the same population the chart is built on. No further issues here.

Also applies to: 125-125


99-103: Disregard the review comment suggestion — it does not align with Frappe's standard pattern used in this codebase.

The web search and codebase evidence show the recommended Frappe pattern uses dynamic placeholders with IN ({", ".join(["%s"] * len(list))}) and tuple(list), as seen in professional_tax_deductions.py (line 61–62) and salary_structure.py (line 319).

The review's suggestion to wrap companies in tuple() is incomplete and non-standard. Since companies is already a list, wrapping it in tuple() does not fix the underlying issue—the code should instead use dynamic placeholders to match the established pattern in this codebase. The current use of single company in %s with a list/tuple passed directly is not how Frappe's db.sql is standardly used here.

The proper refactor would follow the dynamic placeholder pattern already established across the codebase, not the tuple-wrapping suggestion provided.

Likely an incorrect or invalid review comment.

Tip

📝 Customizable high-level summaries are now available in beta!

You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.

  • Provide your own instructions using the high_level_summary_instructions setting.
  • Format the summary however you like (bullet lists, tables, multi-section layouts, contributor stats, etc.).
  • Use high_level_summary_in_walkthrough to move the summary from the description to the walkthrough section.

Example instruction:

"Divide the high-level summary into five sections:

  1. 📝 Description — Summarize the main change in 50–60 words, explaining what was done.
  2. 📓 References — List relevant issues, discussions, documentation, or related PRs.
  3. 📦 Dependencies & Requirements — Mention any new/updated dependencies, environment variable changes, or configuration updates.
  4. 📊 Contributor Summary — Include a Markdown table showing contributions:
    | Contributor | Lines Added | Lines Removed | Files Changed |
  5. ✔️ Additional Notes — Add any extra reviewer context.
    Keep each section concise (under 200 words) and use bullet or numbered lists for clarity."

Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later.


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: 3

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 09eb381 and 293485d.

📒 Files selected for processing (2)
  • hrms/hr/report/employee_analytics/employee_analytics.js (1 hunks)
  • hrms/hr/report/employee_analytics/employee_analytics.py (4 hunks)
⏰ 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 (2)
hrms/hr/report/employee_analytics/employee_analytics.js (1)

15-20: LGTM!

The new filter follows the established pattern and is well-positioned after the company filter. Default of 1 (enabled) is appropriate since users typically want to see aggregated data across the company hierarchy.

hrms/hr/report/employee_analytics/employee_analytics.py (1)

111-116: Good use of parameterized query.

The company in %s with tuple parameter is the correct Frappe pattern for safe IN clause handling.

Comment on lines +46 to +56
companies = [filters["company"]]
if filters.get("include_company_descendants"):
descendants = get_descendants_of("Company", filters["company"])
if descendants:
companies.extend(descendants)

if len(companies) == 1:
conditions += " and company = '%s'" % companies[0].replace("'", "\\'")
else:
company_list = "', '".join([c.replace("'", "\\'") for c in companies])
conditions += f" and company in ('{company_list}')"
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

SQL injection risk with manual string escaping.

Using replace("'", "\\'") is insufficient protection against SQL injection. While the company filter comes from a Link field (limiting direct injection), this pattern is fragile and could break with edge cases or future modifications.

Consider using Frappe's query builder or restructuring to use parameterized queries:

 def get_conditions(filters):
-	conditions = " and " + filters.get("parameter").lower().replace(" ", "_") + " IS NOT NULL "
+	conditions = []
+	conditions.append(filters.get("parameter").lower().replace(" ", "_") + " IS NOT NULL")
+	return conditions, get_company_list(filters)

-	if filters.get("company"):
-		companies = [filters["company"]]
-		if filters.get("include_company_descendants"):
-			descendants = get_descendants_of("Company", filters["company"])
-			if descendants:
-				companies.extend(descendants)
-		
-		if len(companies) == 1:
-			conditions += " and company = '%s'" % companies[0].replace("'", "\\'")
-		else:
-			company_list = "', '".join([c.replace("'", "\\'") for c in companies])
-			conditions += f" and company in ('{company_list}')"
-	
-	return conditions
+def get_company_list(filters):
+	"""Get list of companies including descendants if enabled."""
+	companies = [filters["company"]]
+	if filters.get("include_company_descendants"):
+		descendants = get_descendants_of("Company", filters["company"])
+		if descendants:
+			companies.extend(descendants)
+	return companies

Then modify get_employees to use parameterized query:

def get_employees(filters):
    parameter_field = filters.get("parameter").lower().replace(" ", "_")
    companies = get_company_list(filters)
    return frappe.db.sql(
        f"""SELECT name, employee_name, date_of_birth,
        branch, department, designation, gender, company 
        FROM `tabEmployee` 
        WHERE status = 'Active' 
        AND {parameter_field} IS NOT NULL
        AND company IN %s""",
        (companies,),
        as_list=1,
    )
🤖 Prompt for AI Agents
In hrms/hr/report/employee_analytics/employee_analytics.py around lines 46 to
56, the code builds SQL conditions by manually escaping single quotes
(replace("'", "\\'")), which is fragile and risks SQL injection; change this to
build a safe parameterized query or use Frappe's query builder: create a helper
that returns a list/tuple of company names (including descendants), and then
replace the manual string interpolation with an IN parameter passed to
frappe.db.sql (or use frappe.get_all with filters) so the company values are
bound as parameters (handle the single-company case by still passing a
one-element tuple/list for the IN clause); also update get_employees to accept
the company list and pass it as a parameter to the SQL call rather than
concatenating company names into the query string.

Comment on lines +87 to +93

# Get list of companies including descendants
companies = [filters["company"]]
if filters.get("include_company_descendants"):
descendants = get_descendants_of("Company", filters["company"])
if descendants:
companies.extend(descendants)
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Duplicate company list logic.

This duplicates the company list construction from get_conditions(). Extract to a shared helper function (see suggestion above for get_company_list()).

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants