diff --git a/partner_email_duplicate_warn/__manifest__.py b/partner_email_duplicate_warn/__manifest__.py index 15fb7098f16..89e5948581b 100644 --- a/partner_email_duplicate_warn/__manifest__.py +++ b/partner_email_duplicate_warn/__manifest__.py @@ -4,7 +4,7 @@ { "name": "Partner Email Duplicate Warn", - "version": "19.0.1.0.0", + "version": "19.0.2.0.0", "category": "Partner Management", "license": "AGPL-3", "summary": "Warning banner on partner form if other partners have the same email", diff --git a/partner_email_duplicate_warn/models/res_partner.py b/partner_email_duplicate_warn/models/res_partner.py index 2920f5275dd..4548b8eb5a0 100644 --- a/partner_email_duplicate_warn/models/res_partner.py +++ b/partner_email_duplicate_warn/models/res_partner.py @@ -14,11 +14,17 @@ class ResPartner(models.Model): string="Partner with same e-mail", compute_sudo=True, ) + same_email_inaccessible_count = fields.Integer( + compute="_compute_same_email_partner_ids", + string="Partners with same e-mail you cannot access", + compute_sudo=True, + ) @api.depends("email", "company_id") def _compute_same_email_partner_ids(self): for partner in self: same_email_partner_ids = [] + inaccessible_count = 0 if partner.email and partner.email.strip(): partner_email = partner.email.strip().lower() domain = [("email", "=ilike", "%" + partner_email + "%")] @@ -38,13 +44,21 @@ def _compute_same_email_partner_ids(self): ("id", "parent_of", partner_id), ] search_partners = self.with_context(active_test=False).search(domain) - for search_partner in search_partners: - if ( - search_partner.email - and search_partner.email.strip().lower() == partner_email - ): - same_email_partner_ids.append(search_partner.id) + matches = search_partners.filtered( + lambda p, email=partner_email: p.email + and p.email.strip().lower() == email + ) + # This field is computed as superuser (compute_sudo), so the + # match set may include partners the acting user cannot read. + # Only expose the readable ones as links; rendering the others + # would raise an AccessError when the web client fetches their + # display_name. The rest are merely counted so the banner can + # warn about them without disclosing their identity. + readable = matches.with_env(self.env(su=False))._filtered_access("read") + same_email_partner_ids = readable.ids + inaccessible_count = len(matches) - len(readable) partner.same_email_partner_ids = same_email_partner_ids or False + partner.same_email_inaccessible_count = inaccessible_count def action_open_business_doc(self): """Method called when you click on the link in the duplicate warning banner""" diff --git a/partner_email_duplicate_warn/tests/test_email_duplicate.py b/partner_email_duplicate_warn/tests/test_email_duplicate.py index 6be54e8d8b9..f5e8c9e7275 100644 --- a/partner_email_duplicate_warn/tests/test_email_duplicate.py +++ b/partner_email_duplicate_warn/tests/test_email_duplicate.py @@ -117,6 +117,46 @@ def test_partner_duplicate_multi_company(self): partner_company2.write({"company_id": False}) self.assertEqual(partner_company2.same_email_partner_ids, partner_company1) + def test_partner_duplicate_inaccessible(self): + """A duplicate the user cannot read is counted, not linked, and reading + the computed fields raises no AccessError.""" + hidden_partner = self.env["res.partner"].create( + {"name": "Hidden", "email": "hidden@example.com"} + ) + # Internal user allowed to create partners (so creation does not trip + # on unrelated access checks, e.g. base_partner_sequence reading + # ir.sequence) but blocked from reading the conflicting record. + groups = self.env.ref("base.group_user") | self.env.ref( + "base.group_partner_manager" + ) + restricted_user = self.env["res.users"].create( + { + "name": "Restricted", + "login": "dup_warn_restricted_user", + "group_ids": [(6, 0, groups.ids)], + } + ) + # Global rule (no groups) so it applies to every non-superuser + # regardless of the user's other groups; match by id to stay + # independent of fields other modules may populate. + self.env["ir.rule"].create( + { + "name": "Hide conflicting partner from everyone", + "model_id": self.env.ref("base.model_res_partner").id, + "groups": [(6, 0, [])], + "domain_force": f"[('id', '!=', {hidden_partner.id})]", + } + ) + partner = ( + self.env["res.partner"] + .with_user(restricted_user) + .create({"name": "New", "email": "hidden@example.com"}) + ) + # No link to the inaccessible partner, but it is counted, and no + # AccessError is raised while reading the computed fields. + self.assertFalse(partner.same_email_partner_ids) + self.assertEqual(partner.same_email_inaccessible_count, 1) + def test_partner_duplicate_parent_child(self): parent_partner = self.env["res.partner"].create( { diff --git a/partner_email_duplicate_warn/views/res_partner.xml b/partner_email_duplicate_warn/views/res_partner.xml index 61383d79cb1..a22c32fb40d 100644 --- a/partner_email_duplicate_warn/views/res_partner.xml +++ b/partner_email_duplicate_warn/views/res_partner.xml @@ -8,12 +8,20 @@