mirror of
https://github.com/frappe/erpnext.git
synced 2026-08-14 23:18:40 +00:00
Merge pull request #57210 from frappe/mergify/bp/version-15-hotfix/pr-57179
feat(stock): automatically link portal users to their associated contact profiles for customers and suppliers (backport #57179)
This commit is contained in:
@@ -16,7 +16,10 @@ from erpnext.accounts.party import (
|
||||
validate_party_accounts,
|
||||
validate_party_currency_before_merging,
|
||||
)
|
||||
from erpnext.controllers.website_list_for_contact import add_role_for_portal_user
|
||||
from erpnext.controllers.website_list_for_contact import (
|
||||
add_role_for_portal_user,
|
||||
link_portal_users_to_contacts,
|
||||
)
|
||||
from erpnext.utilities.transaction_base import TransactionBase
|
||||
|
||||
|
||||
@@ -103,6 +106,7 @@ class Supplier(TransactionBase):
|
||||
def on_update(self):
|
||||
self.create_primary_contact()
|
||||
self.create_primary_address()
|
||||
link_portal_users_to_contacts(self)
|
||||
|
||||
def add_role_for_user(self):
|
||||
for portal_user in self.portal_users:
|
||||
|
||||
@@ -204,7 +204,28 @@ class TestSupplierPortal(FrappeTestCase):
|
||||
supplier.append("portal_users", {"user": user})
|
||||
supplier.save()
|
||||
|
||||
frappe.set_user(user)
|
||||
_, suppliers = get_customers_suppliers("Purchase Order", user)
|
||||
with self.set_user(user):
|
||||
_, suppliers = get_customers_suppliers("Purchase Order", user)
|
||||
|
||||
self.assertIn(supplier.name, suppliers)
|
||||
|
||||
def test_portal_user_contact_link(self):
|
||||
user_email = frappe.generate_hash() + "@example.com"
|
||||
user = frappe.new_doc("User")
|
||||
user.email = user_email
|
||||
user.first_name = "Test Portal Contact User"
|
||||
user.send_welcome_email = False
|
||||
user.insert(ignore_permissions=True)
|
||||
|
||||
contact = frappe.new_doc("Contact")
|
||||
contact.first_name = "Test Portal Contact User"
|
||||
contact.add_email(user_email, is_primary=1)
|
||||
contact.links = []
|
||||
contact.insert(ignore_permissions=True)
|
||||
|
||||
supplier = create_supplier()
|
||||
supplier.append("portal_users", {"user": user.name})
|
||||
supplier.save()
|
||||
|
||||
contact.reload()
|
||||
self.assertTrue(contact.has_link("Supplier", supplier.name))
|
||||
|
||||
@@ -7,6 +7,8 @@ import json
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.modules.utils import get_module_app
|
||||
from frappe.query_builder import Criterion
|
||||
from frappe.query_builder.functions import Lower
|
||||
from frappe.utils import cint, flt, has_common
|
||||
from frappe.utils.user import is_website_user
|
||||
|
||||
@@ -306,3 +308,63 @@ def add_role_for_portal_user(portal_user, role):
|
||||
|
||||
user_doc.add_roles(role)
|
||||
frappe.msgprint(_("Added {1} Role to User {0}.").format(frappe.bold(user_doc.name), role), alert=True)
|
||||
|
||||
|
||||
def link_portal_users_to_contacts(doc):
|
||||
"""When portal users are added to Supplier/Customer, link them to the Contact profile."""
|
||||
# a User's name is its (lowercased) email, so portal_users are already the emails
|
||||
portal_users = {p.user for p in doc.get("portal_users") or [] if p.user}
|
||||
if not portal_users:
|
||||
return
|
||||
|
||||
before = doc.get_doc_before_save()
|
||||
if before:
|
||||
previous_users = {p.user for p in before.get("portal_users") or [] if p.user}
|
||||
if portal_users == previous_users:
|
||||
return
|
||||
|
||||
portal_users = list(portal_users)
|
||||
|
||||
contact = frappe.qb.DocType("Contact")
|
||||
contact_email = frappe.qb.DocType("Contact Email")
|
||||
|
||||
query = (
|
||||
frappe.qb.from_(contact)
|
||||
.left_join(contact_email)
|
||||
.on(contact_email.parent == contact.name)
|
||||
.select(contact.name)
|
||||
.distinct()
|
||||
)
|
||||
|
||||
conditions = [
|
||||
contact.user.isin(portal_users),
|
||||
Lower(contact.email_id).isin(portal_users),
|
||||
Lower(contact_email.email_id).isin(portal_users),
|
||||
]
|
||||
|
||||
query = query.where(Criterion.any(conditions))
|
||||
contacts = query.run(pluck=True)
|
||||
|
||||
if not contacts:
|
||||
return
|
||||
|
||||
dynamic_link = frappe.qb.DocType("Dynamic Link")
|
||||
existing_links = (
|
||||
frappe.qb.from_(dynamic_link)
|
||||
.select(dynamic_link.parent)
|
||||
.where(
|
||||
(dynamic_link.parenttype == "Contact")
|
||||
& (dynamic_link.parent.isin(contacts))
|
||||
& (dynamic_link.link_doctype == doc.doctype)
|
||||
& (dynamic_link.link_name == doc.name)
|
||||
)
|
||||
.run(pluck=True)
|
||||
)
|
||||
|
||||
contacts_to_link = [name for name in contacts if name not in existing_links]
|
||||
|
||||
for name in contacts_to_link:
|
||||
contact_doc = frappe.get_doc("Contact", name)
|
||||
if not contact_doc.has_link(doc.doctype, doc.name):
|
||||
contact_doc.append("links", {"link_doctype": doc.doctype, "link_name": doc.name})
|
||||
contact_doc.save(ignore_permissions=True)
|
||||
|
||||
@@ -23,7 +23,10 @@ from erpnext.accounts.party import (
|
||||
validate_party_accounts,
|
||||
validate_party_currency_before_merging,
|
||||
)
|
||||
from erpnext.controllers.website_list_for_contact import add_role_for_portal_user
|
||||
from erpnext.controllers.website_list_for_contact import (
|
||||
add_role_for_portal_user,
|
||||
link_portal_users_to_contacts,
|
||||
)
|
||||
from erpnext.utilities.transaction_base import TransactionBase
|
||||
|
||||
|
||||
@@ -243,6 +246,8 @@ class Customer(TransactionBase):
|
||||
|
||||
self.update_customer_groups()
|
||||
|
||||
link_portal_users_to_contacts(self)
|
||||
|
||||
def add_role_for_user(self):
|
||||
for portal_user in self.portal_users:
|
||||
add_role_for_portal_user(portal_user, "Customer")
|
||||
|
||||
@@ -29,7 +29,8 @@ class TestCustomer(FrappeTestCase):
|
||||
make_test_records("Item")
|
||||
|
||||
def tearDown(self):
|
||||
set_credit_limit("_Test Customer", "_Test Company", 0)
|
||||
if frappe.db.exists("Customer", "_Test Customer"):
|
||||
set_credit_limit("_Test Customer", "_Test Company", 0)
|
||||
|
||||
def test_get_customer_group_details(self):
|
||||
doc = frappe.new_doc("Customer Group")
|
||||
@@ -386,6 +387,34 @@ class TestCustomer(FrappeTestCase):
|
||||
self.assertEqual(middle, "Michael")
|
||||
self.assertEqual(last, "Doe")
|
||||
|
||||
def test_portal_user_contact_link(self):
|
||||
user_email = frappe.generate_hash() + "@example.com"
|
||||
user = frappe.new_doc("User")
|
||||
user.email = user_email
|
||||
user.first_name = "Test Portal Customer User"
|
||||
user.send_welcome_email = False
|
||||
user.insert(ignore_permissions=True)
|
||||
|
||||
contact = frappe.new_doc("Contact")
|
||||
contact.first_name = "Test Portal Customer User"
|
||||
contact.add_email(user_email, is_primary=1)
|
||||
contact.links = []
|
||||
contact.insert(ignore_permissions=True)
|
||||
|
||||
customer = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Customer",
|
||||
"customer_name": "Test Portal Contact Customer",
|
||||
"customer_type": "Individual",
|
||||
"customer_group": "_Test Customer Group",
|
||||
}
|
||||
)
|
||||
customer.append("portal_users", {"user": user.name})
|
||||
customer.insert()
|
||||
|
||||
contact.reload()
|
||||
self.assertTrue(contact.has_link("Customer", customer.name))
|
||||
|
||||
|
||||
def get_customer_dict(customer_name):
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user