Skip to content

fix: N+1 pattern in project threshold reminder task - #1950

Draft
wreckage0907 wants to merge 5 commits into
version-16-hotfixfrom
fix/project-threshold-reminder-n-plus-one
Draft

fix: N+1 pattern in project threshold reminder task#1950
wreckage0907 wants to merge 5 commits into
version-16-hotfixfrom
fix/project-threshold-reminder-n-plus-one

Conversation

@wreckage0907

@wreckage0907 wreckage0907 commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Description

Relevant Technical Choices

Testing Instructions

Additional Information:

Screenshot/Screencast

Checklist

  • I have carefully reviewed the code before submitting it for review.
  • This code is adequately covered by unit tests to validate its functionality.
  • I have conducted thorough testing to ensure it functions as intended.
  • A member of the QA team has reviewed and tested this PR (To be checked by QA or code reviewer)

Fixes #767

Copilot AI review requested due to automatic review settings July 30, 2026 13:51
Copilot AI and others added 5 commits July 30, 2026 19:24
…er task

Co-authored-by: mrrobot47 <25586785+mrrobot47@users.noreply.github.com>
…Material TODO

Co-authored-by: mrrobot47 <25586785+mrrobot47@users.noreply.github.com>
…eshold calculation

Co-authored-by: mrrobot47 <25586785+mrrobot47@users.noreply.github.com>
@wreckage0907
wreckage0907 force-pushed the fix/project-threshold-reminder-n-plus-one branch from 3b198e3 to d491391 Compare July 30, 2026 13:56

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Note

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

This PR fixes an N+1 query pattern in the project threshold reminder task by batching data fetches (Projects, budgets, shares/roles, templates) and removing the older per-project querying implementation.

Changes:

  • Replaced the old reminder task implementation with a batched-query version to reduce database roundtrips.
  • Updated scheduled hook to reference the corrected module name/path.
  • Added helper functions to calculate thresholds and send reminders using pre-fetched data.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
next_pms/project_currency/tasks/reminder_project_threshold.py New batched implementation for threshold reminders (projects/budgets/shares/roles/templates) to eliminate N+1 queries.
next_pms/project_currency/tasks/reminde_project_threshold.py Removed legacy implementation that queried per-project and contributed to N+1 behavior.
next_pms/hooks.py Updated weekly scheduler path to point to the new reminder task module.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +139 to +165
all_pms = [user for user in user_list if user in pm_set]

if not all_pms:
return

# Get email template from pre-fetched data
reminder_template = email_templates.get(project.custom_email_template)
if not reminder_template:
return

email_message = ""
if reminder_template.use_html:
email_message = reminder_template.response_html
else:
email_message = reminder_template.response

email_subject = reminder_template.subject
recipients = all_pms

args = {
"project": project,
}

message = frappe.render_template(email_message, args) # nosemgrep - trusted Email Template from DB
subject = frappe.render_template(email_subject, args) # nosemgrep - trusted Email Template from DB

frappe.sendmail(recipients=recipients, subject=subject, message=message)
Comment on lines +194 to +199
elif project.custom_billing_type == "Time and Material":
# total_sales_amount is the project's total budget
if not project.total_sales_amount or project.total_sales_amount <= 0 or project.total_billable_amount is None:
return None

return (project.total_billable_amount * 100) / project.total_sales_amount
Comment on lines +41 to +45
budget_map = {}
for bh in budget_hours:
if bh.parent not in budget_map:
budget_map[bh.parent] = []
budget_map[bh.parent].append(bh)
@wreckage0907
wreckage0907 marked this pull request as draft August 6, 2026 08:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants