Kindly let me know incase someone can spot the error in the code.
Problem:
Prevent contact creation with duplicate email or phone.
When the code is tested in the org only duplicate rule is active and which is being bypassed by double save.
Handler:
public class preventionConDupliTriggerHandler {
public static void emailPhoneDupliCheck(List<contact> conOldList , List<contact> conNewList){
set<string> emailSet = new set<string>();
set<string> phoneSet = new set<string>();
if(conOldList != null){
for(contact con : conOldList){
if(con.phone != null){
phoneSet.add(con.phone);
}
if(con.email != null){
emailSet.add(con.email.toLowerCase());
}
}
}
for(contact con1 : conNewList){
if(con1.Email != null){
if(emailSet.contains(con1.email.toLowerCase())){
con1.AddError('Kindly use another email,the entered one is already in use.');
System.debug('Duplicate email found: ' + con1.Email);
}
else{
emailSet.add(con1.email.toLowerCase());
}
}
if(con1.phone != null){
if(phoneSet.contains(con1.phone)){
con1.AddError('Kindly use another phone number,the entered one is already in use.');
System.debug('Duplicate phone found: ' + con1.phone);
}
else{
phoneSet.add(con1.phone);
}
}
}
}
}
Trigger :
trigger preventionConDupliTrigger on Contact (before insert , before update) {
if(Trigger.isBefore){
if(Trigger.isInsert){
preventionConDupliTriggerHandler.emailPhoneDupliCheck(null,Trigger.new);
}
if(Trigger.isUpdate){
preventionConDupliTriggerHandler.emailPhoneDupliCheck(Trigger.old,Trigger.new);
}
}
}
Hi @Ashwin Punyani - The first issue I see is that you're not checking against existing records in the database. The Trigger.old context variable contains a list of the old versions of the sobjects in the trigger, hence why it's unavailable in insert triggers. It's also not really relevant here, since it sounds like we only care about new values with duplicates. I've removed that input parameter in my examples, so the updated trigger looks like this:
trigger preventionConDupliTrigger on Contact (before insert, before update) {
if(Trigger.isBefore){
if(Trigger.isInsert || Trigger.isUpdate){
preventionConDupliTriggerHandler.emailPhoneDupliCheck(Trigger.new);
}
}
}
To check if an email or phone are already present in the database, you can use SOQL to query for these contacts. In this example, we look for matching contacts in the database, store them in a map so we can access them via their phone/email value, then loop through our original contact list to add errors where a contact exists with that value:
public static void emailPhoneDupliCheck(List<Contact> conNewList) {
// Collect emails and phone numbers from the contacts being processed
Set<String> emailSet = new Set<String>();
Set<String> phoneSet = new Set<String>();
for (Contact contact : conNewList) {
if (!String.isBlank(contact.Email)) {
emailSet.add(contact.Email);
}
if (!String.isBlank(contact.Phone)) {
phoneSet.add(contact.Phone);
}
}
// Query existing contacts with matching emails or phone numbers
Map<String, Contact> existingEmailMap = new Map<String, Contact>();
Map<String, Contact> existingPhoneMap = new Map<String, Contact>();
for (Contact existingContact : [
SELECT Id, Email, Phone, Name
FROM Contact
WHERE (Email IN :emailSet OR Phone IN :phoneSet)
]) {
if (emailSet.contains(existingContact.Email)) {
existingEmailMap.put(existingContact.Email, existingContact);
}
if (phoneSet.contains(existingContact.Phone)) {
existingPhoneMap.put(existingContact.Phone, existingContact);
}
}
// Add errors to the sobjects in the trigger context for duplicates found
for (Contact contact : conNewList) {
if (contact.Email != null && existingEmailMap.containsKey(contact.Email)) {
contact.addError('Kindly use another email, ' + contact.Email + ' is already in use for ' + existingEmailMap.get(contact.Email).Name);
}
if (contact.Phone != null && existingPhoneMap.containsKey(contact.Phone)) {
contact.addError('Kindly use another email, ' + contact.Phone + ' is already in use for ' + existingPhoneMap.get(contact.Phone).Name);
}
}
}
If you'd like to check against all the email or phone fields on a contact record (e.g. MobilePhone), you might consider using a SOSL query, instead.
Another approach to consider here would be to use the FindDuplicates class. You mentioned there are active duplicate rules in the org. You can use the FindDuplicates class to look for matches based on those same duplicate rules and their matching criteria. As you stated, duplicate rules with alerts can be bypassed in the UI when the user clicks Save again. Here's a version that looks through FindDuplicateResults and adds the error to offending records:
public static void emailPhoneDupliCheck(List<Contact> conNewList) {
Set<Contact> contactsWithDuplicates = new Set<Contact>();
// Filter to contacts that have an email and/or phone value
List<Contact> contactsWithEmailOrPhone = new List<Contact>();
for (Contact con : conNewList) {
if (con.Email != null || con.Phone != null) {
contactsWithEmailOrPhone.add(con);
}
}
// Return if no contacts have email or phone
if (contactsWithEmailOrPhone.isEmpty()) {
return;
}
// FindDuplicates.findDuplicates() returns one result for each sobject in the input (even when no matches are found)
List<Datacloud.FindDuplicatesResult> findResults = Datacloud.FindDuplicates.findDuplicates(contactsWithEmailOrPhone);
for (Integer i = 0; i < findResults.size(); i++) {
// Results are returned in the same order as the input,
// so we can retrieve the contact that these results belong to from our original list
Contact currentContact = contactsWithEmailOrPhone[i];
Datacloud.FindDuplicatesResult findResult = findResults[i];
for (Datacloud.DuplicateResult dupeResult : findResult.duplicateResults) {
for (Datacloud.MatchResult matchResult : dupeResult.getMatchResults()) {
// The MatchResult has a getSize() method that returns the number of matching records found
if (matchResult.getSize() > 0) {
contactsWithDuplicates.add(currentContact);
}
}
}
}
// For each result with duplicates, add an error
for (Contact con : contactsWithDuplicates) {
con.addError('The contact\'s email or phone already exists in the system.');
}
}
An important note in the documentation:
"The input array is limited to 50 elements. If you exceed this limit, an exception is thrown with the following message: Configuration error: The number of records to check is greater than the permitted batch size."
So if going that route, you'd want to add some pagination to your calls to Datacloud.FindDuplicates.findDuplicates(), ensuring you call it for 50 or less sobjects at a time.