From 02e0bcb10f2a5d6b3ec2cdff5ac6010e40e212aa Mon Sep 17 00:00:00 2001 From: Yannick Tivisse Date: Fri, 26 Jan 2024 13:29:03 +0100 Subject: [PATCH] [FIX] hr_recruitment: Improve application_count field computation time From 120 seconds to 0.5 seconds for 10000 applicants on odoo.com closes odoo/odoo#151303 Signed-off-by: Yannick Tivisse (yti) --- addons/hr_recruitment/models/hr_applicant.py | 39 ++++++++++++++++--- .../hr_recruitment/tests/test_recruitment.py | 1 - 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/addons/hr_recruitment/models/hr_applicant.py b/addons/hr_recruitment/models/hr_applicant.py index 3752de91321..fdcd1967e0e 100644 --- a/addons/hr_recruitment/models/hr_applicant.py +++ b/addons/hr_recruitment/models/hr_applicant.py @@ -119,6 +119,10 @@ class Applicant(models.Model): ON hr_applicant(job_id, stage_id) WHERE active IS TRUE """) + self.env.cr.execute(""" + CREATE INDEX IF NOT EXISTS hr_applicant_email_partner_phone_mobile + ON hr_applicant(email_normalized, partner_mobile_sanitized, partner_phone_sanitized); + """) @api.onchange('job_id') def _onchange_job_id(self): @@ -150,19 +154,42 @@ class Applicant(models.Model): else: applicant.delay_close = False - @api.depends('email_from', 'partner_phone', 'partner_mobile') + @api.depends('email_from', 'partner_mobile_sanitized', 'partner_phone_sanitized') def _compute_application_count(self): """ The field application_count is only used on the form view. Thus, using ORM rather then querying, should not make much difference in terms of performance, while being more readable and secure. """ + if not any(self._ids): + for applicant in self: + domain = applicant._get_similar_applicants_domain() + if domain: + applicant.application_count = max(0, self.env["hr.applicant"].with_context(active_test=False).search_count(domain) - 1) + else: + applicant.application_count = 0 + return + self.flush_recordset(['email_normalized', 'partner_phone_sanitized', 'partner_mobile_sanitized']) + self.env.cr.execute(""" + SELECT + id, + ( + SELECT COUNT(*) + FROM hr_applicant AS sub + WHERE a.id != sub.id + AND ((a.email_normalized <> '' AND sub.email_normalized = a.email_normalized) + OR (a.partner_mobile_sanitized <> '' AND a.partner_mobile_sanitized = sub.partner_mobile_sanitized) + OR (a.partner_mobile_sanitized <> '' AND a.partner_mobile_sanitized = sub.partner_phone_sanitized) + OR (a.partner_phone_sanitized <> '' AND a.partner_phone_sanitized = sub.partner_mobile_sanitized) + OR (a.partner_phone_sanitized <> '' AND a.partner_phone_sanitized = sub.partner_phone_sanitized)) + ) AS similar_applicants + FROM hr_applicant AS a + WHERE id IN %(ids)s + """, {'ids': tuple(self._origin.ids)}) + query_results = self.env.cr.dictfetchall() + mapped_data = {result['id']: result['similar_applicants'] for result in query_results} for applicant in self: - domain = applicant._get_similar_applicants_domain() - if domain: - applicant.application_count = self.env["hr.applicant"].with_context(active_test=False).search_count(domain) - 1 - else: - applicant.application_count = 0 + applicant.application_count = mapped_data.get(applicant.id, 0) def _get_similar_applicants_domain(self): """ diff --git a/addons/hr_recruitment/tests/test_recruitment.py b/addons/hr_recruitment/tests/test_recruitment.py index 8a059dc6ae3..70e0d518317 100644 --- a/addons/hr_recruitment/tests/test_recruitment.py +++ b/addons/hr_recruitment/tests/test_recruitment.py @@ -76,4 +76,3 @@ class TestRecruitment(TransactionCase): self.assertEqual(D.application_count, 3) # A, B, C self.assertEqual(E.application_count, 0) # Should not match with G self.assertEqual(F.application_count, 1) # B -