[SalesForce] Automatically create contact role on Opportunity Creation

When creating a new Opportunity the contact role is automatically populated when the opportunity is created from a contact record(standard functionality). In the case where users create an opportunity from a different record (e.g Account), the opportunity contact role is not automatically populated.

I have create an after insert trigger to automatically insert an opportunity contact role based on a custom field "Related_Contact__c". This works when the opp is created from any record besides a contact. But in the situation when the record is created from a contact record, the trigger is still firing and therefor the contact has two opportunity contact roles..

Am I overlooking something?

public static void populateOpportunityOcr(List<Opportunity> newOpportunityRecords){

        set<Id> newOppId = new set<Id>();
        set<Id>  newOppRelatedContactId = new set<Id>();

        for(Opportunity o: newOpportunityRecords){
            newOppId.add(o.Id);
            if(o.Related_Contact__c != NULL){
                newOppRelatedContactId.add(o.Related_Contact__c);
            } 
        }

        List<OpportunityContactRole> oppsLst = [SELECT Id, ContactId, OpportunityId FROM OpportunityContactRole WHERE ID IN: newOppId AND ContactId IN :newOppRelatedContactId];

        List<OpportunityContactRole> ocrToInsert = new List<OpportunityContactRole>();
        if(oppsLst.isEmpty()){
            for(Opportunity ops: newOpportunityRecords){
                if(ops.Related_Contact__c != NULL){
                    ocrToInsert.add(new OpportunityContactRole(contactId = ops.Related_Contact__c,
                                                           OpportunityId = ops.Id,
                                                           Role = 'Business User',
                                                           isPrimary = FALSE
                    ));
                } 
            }
        }
        if(!ocrToInsert.isEmpty()){
            Insert ocrToInsert;
        }
    } 

Best Answer

Parent records are always created before their children. The OpportunityContactRole is created after the transaction completes. What you need is some asynchronous code. The easiest fix is to just use a Queueable.

trigger OpportunityPopulateOCR on Opportunity (after insert) {
  System.enqueueJob(new PopulateOCR(Trigger.new));
}

And then...

public class PopulateOCR implements Queueable {
    Opportunity[] records;
    public PopulateOCR(Opportunity[] fromTrigger) {
        records = fromTrigger;
    }
    public void execute(QueueableContext context) {
        Map<Id, Id> oppToContactId = new Map<Id, Id>();
        for(Opportunity record: records) {
            if(record.Related_Contact__c != null) {
                oppToContactId.put(record.OpportunityId, record.Related_Contact__c);
            }
        }
        for(OpportunityContactRole role: [SELECT ContactId, OpportunityId FROM OpportunityContactRole WHERE ID IN: records AND ContactId IN :oppToContactId.values()]) {
            if(oppToContactId.get(role.OpportunityId) == role.ContactId) {
                oppToContactId.remove(role.OpportunityId);
            }
        }
        List<OpportunityContactRole> ocrToInsert = new List<OpportunityContactRole>();
        for(Opportunity ops: newOpportunityRecords){
            if(oppToContactId.containsKey(ops.Id)) {
                ocrToInsert.add(
                    new OpportunityContactRole(
                        contactId = ops.Related_Contact__c,
                        OpportunityId = ops.Id,
                        Role = 'Business User',
                        isPrimary = FALSE
                    )
                ); 
            }
        }
        Insert ocrToInsert;
    }
}

Note: DML on an empty list is okay, there's no need to check for an empty list.

Also, this code demonstrates bulkification in the event that more than one record is created at once for some reason.