Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion partner_email_duplicate_warn/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
26 changes: 20 additions & 6 deletions partner_email_duplicate_warn/models/res_partner.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 + "%")]
Expand All @@ -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"""
Expand Down
40 changes: 40 additions & 0 deletions partner_email_duplicate_warn/tests/test_email_duplicate.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
{
Expand Down
16 changes: 12 additions & 4 deletions partner_email_duplicate_warn/views/res_partner.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,20 @@
<div
class="alert alert-warning"
role="alert"
invisible="not same_email_partner_ids"
invisible="not same_email_partner_ids and not same_email_inaccessible_count"
>
<span invisible="not same_email_partner_ids">
Duplicate warning: partner(s) <field
name="same_email_partner_ids"
widget="x2many_links"
/> has/have the same <b>Email</b>.
name="same_email_partner_ids"
widget="x2many_links"
/> has/have the same <b>Email</b>.
</span>
<span invisible="not same_email_inaccessible_count">
<field
name="same_email_inaccessible_count"
/> partner(s) you do not have permission to view also use this <b
>Email</b>. Please contact your administrator.
</span>
</div>
</xpath>
</field>
Expand Down
Loading