+ Start a Discussion
kyle.tkyle.t 

Apex Share on duplicate record

I am leveraging the cookbook solution to prevent duplicate leads in order to stop duplicate contacts from being created. That is working just fine at this point, but here's the twist.  Contacts are private, so even if they search, they may not find the contact in question.  When they try to create a contact that is a duplicate, we want to automatically share the original contact with them using sharing rules and present a link to the newly shared contact.

I am leveraging the cookbook solution to prevent duplicate leads in order to stop duplicate contacts from being created. That is working just fine at this point, but here's the twist.  Contacts are private, so even if they search, they may not find the contact in question.  When they try to create a contact that is a duplicate, we want to automatically share the original contact with them using sharing rules and present a link to the newly shared contact.

 

 

Independantly the two actions work.  I can insert a sharing rule, I can stop the duplicte from being created, but when I try to do both together, it appears that the addError on the contact rolls back the insert of the sharing record.

 

 

trigger ContactDuplicateTrigger on Contact (before insert, before update) {
	Map<String, Contact> contactMap = new Map<String, Contact>();
    
    for (Contact c : trigger.new) {
         
        // Make sure we don't treat an email address that     
        // isn't changing during an update as a duplicate. 
     
        if ((c.Email != null) && (trigger.isInsert || (c.Email != trigger.oldMap.get(c.Id).Email))) {
         
            // Make sure another new lead isn't also a duplicate      
            if (contactMap.containsKey(c.Email)) {
                c.Email.addError('Another new contact has the same email address.');
            } else {	
                contactMap.put(c.Email, c);
            }
       }
    }
     
    // Using a single database query, find all the leads in 
    // the database that have the same email address as any 
    // of the leads being inserted or updated. 
     
    for (Contact c : [SELECT Email, Owner.Name FROM contact WHERE Email IN :contactMap.KeySet()]) {
        Contact newContact = contactMap.get(c.Email);       	
        ContactShare cShare = new ContactShare();
        cShare.ContactId=c.id;
        cShare.UserOrGroupId = UserInfo.getUserId(); 
        cShare.ContactAccessLevel = 'Edit';		
		
		newContact.addError('A contact with this email address already exists and is owned by ' + c.Owner.Name + 
                            '.  You have been granted access to this record and ' + c.Owner.Name + ' has been notfied.' +
                            'The Link for the existing contact is: <a href="/' + c.id + '"> Link </a>' ); 
		Database.insert(cShare,false);      		          	      	        
    }
}
Best Answer chosen by Admin (Salesforce Developers) 
kyle.tkyle.t

I had actually just started down that path, which seems promising.  Thought is to direct the user to the VF page with the contact id as a parameter.  In the page call the contrller which will share the record and then redirec the user to that record.

 

Will keep the thread posted on how it turns out.  Thanks.

All Answers

dmchengdmcheng

I think you need to do this in two for loops.  In the first one, you create the shares and then when the loop is done, you insert the shares.  Be sure to do insert outside of the for loop.  Then in the second for loop, you do the addError.

kyle.tkyle.t

Thanks for the input dmcheng, unfortunately that didn't help.  I found this in the documentation: "If the trigger was spawned by a DML statement in Apex, any one error results in the entire operation rolling back."

 

My thought is now to present the user with a way to invoke a class which will create the share after the error has been produced.  I was thinking of invoking a method from a link in the error message.  However, I cannot figure out if it is possible to invoke a method from an html link.

 

The idea is present user with message that says, "Contact already exists...click here to gain access to the record and notify the owner."  Clicking the link would invoke a method which would share the record and then create and email to the record owner.

 

Anybody have any thoughts?

dmchengdmcheng

Hmm.  I think there's no way to do both actions from the trigger.  I thought about using a future method to do the sharing action but someone else tried a future method from a trigger and said it didn't work with addError either.

 

You may have to allow the contact record to be saved, and perhaps flag it as a duplicate in some way by using record type or field value, and develop a separate process for regular deletion.

kyle.tkyle.t

Clicking the link would invoke a method.  The context of that call would the ther user action to call that method and it would not be in the context of the trigger, so the addError shouldn't affect the share as far as I can see.  the problem is, can i output something that will give me a clickable link to invoke the method.

 

Somthing like:  Click this <a href="{call to method}">Link</a>

 

Thanks.

dmchengdmcheng

You' might be able to do it with a VisualForce page with a default action that calls an Apex controller method.

kyle.tkyle.t

I had actually just started down that path, which seems promising.  Thought is to direct the user to the VF page with the contact id as a parameter.  In the page call the contrller which will share the record and then redirec the user to that record.

 

Will keep the thread posted on how it turns out.  Thanks.

This was selected as the best answer
kyle.tkyle.t

I was able to get this working.  Since this is a proof of concept, I haven't put too much effort into it, but here is the outcome.  First the trigger, which simply querys for any contact with the same email address.  If one is found and error is presented with a link to gain access.  The link directs to a visualforce page.

 

trigger ContactDuplicateTrigger on Contact (before insert, before update) {
	Map<String, Contact> contactMap = new Map<String, Contact>();
    
    for (Contact c : trigger.new) {         
        // Make sure we don't treat an email address that     
        // isn't changing during an update as a duplicate.      
        if ((c.Email != null) && (trigger.isInsert || (c.Email != trigger.oldMap.get(c.Id).Email))) {         
            // Make sure another new lead isn't also a duplicate      
            if (contactMap.containsKey(c.Email)) {
                c.Email.addError('Another new contact has the same email address.');
            } else {	
                contactMap.put(c.Email, c);
            }	
        }
    }
     
    // Using a single database query, find all the contacts in 
    // the database that have the same email address as any 
    // of the contacts being inserted or updated. 
    List<ContactShare> shareList = new List<ContactShare>();
    Map<Contact, Contact> contactErrorMap = new Map<Contact,Contact>();
     
    for (Contact c : [SELECT Id, Email, Owner.Name FROM contact WHERE Email IN :contactMap.KeySet()]) {
        Contact newContact = contactMap.get(c.Email);       	
        	
		//shareList.add(cShare);
		contactErrorMap.put(c,newContact);		      		          	      	        
    }
    //Database.insert(shareList,true);
    
    for (Contact sc : contactErrorMap.keyset()){
    	contactErrorMap.get(sc).addError(
    		'The Link for the existing contact is: ' +
			'<a href="/apex/pages/ShareContact?id='+sc.id+'"> Link </a>');
    }
}

 

Here is the error message:

Error: Invalid Data.
Review all error messages below to correct your data.
The Link for the existing contact is:
Link

 

The visualforce page is pretty simple with a command button and javascript which is called onload to click the command button:

<apex:page controller="ShareContact">
	<apex:form >
		<apex:OutputText value="Sharing Contact and Notifying Owner"/>
		<apex:commandButton value="" id="shareBtn" action="{!shareThisContact}" />
		<script> var shareBtn = document.getElementById("{!$Component.shareBtn}"); </script>		
	</apex:form>
	<script>
		window.onload = new function() { shareBtn.click();};
	</script>	
</apex:page>

 

 And finally the controller which pulls the parameter from the apext page, grants the current user access to the contact and then redirects the user to the contact page.

 

I originally thought I could create the sharing record in the constructor of the visualforce page controller, but you aren't allowed to perform DML statments in a construtor. 

 

Here is the code for the controller:

 

public without sharing class ShareContact {
	
	public shareContact(){
		Id i = ApexPages.currentPage().getParameters().get('id');
		system.debug('Constructor - ID is: '+ i);	
	}
		
	public PageReference shareThisContact(){					
		System.debug('I am here');
		ContactShare cShare = new ContactShare(ContactId=ApexPages.currentPage().getParameters().get('id'), UserOrGroupId=UserInfo.getUserId(),ContactAccessLevel='Edit');		
		insert cshare;
		PageReference p = new PageReference('/'+ApexPages.currentPage().getParameters().get('id'));
		p.setRedirect(true);
		return p;
	}
}

 

Jack InsJack Ins

kyle.t,

Was wondering if you could lend a hand?  Part of the code that you have here works perfectly for stopping a user from creating a duplicate contact based on email address.  What I was wondering is, can it be taken a step further to include a record type id? 

 

I need to check based on email address for the record type id that the user or bulk integration is creating to make sure that there are no duplicates.  Record type id is key for us as we can have a contact with a email address in one rec type and yet have another contact record with the same email and a different rec type id.

 

Any help would be great.

THanks Dwayne