1

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;
        }
    } 

2 Answers 2

2

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.

0

You miss the point that during opportunity creation (before or even after trigger), the standard SF OCR creation is triggered after commitment opps to database. Therefore, for new opportunities oppsLst.isEmpty() is always true.

To avoid this, schedule OCR creation in 30 seconds in trigger, that would help.

public without sharing class Scheduler implements Schedulable {
    private List<Opportunity> newOpportunityRecords;

    public Scheduler(List<Opportunity> newOpportunityRecords) {
        this.newOpportunityRecords = newOpportunityRecords;
    }

    public void execute(System.SchedulableContext sc) {
        populateOpportunityOcr(this.newOpportunityRecords);
    }

    public static void populateOpportunityOcr(List<Opportunity> newOpportunityRecords){
        /*your code here*/
    } 
}

How to schedule job:

Datetime nextFireTime = Datetime.now().addSeconds(30);
String strJobName = 'Job-' + nextFireTime.second() + ' ' + nextFireTime.minute();
System.schedule(strJobName, strSchedule, new Scheduler(newOpportunityRecords));
2
  • Thanks for your answer. Could you please provide an example of how to do this delay? Preferably something that adheres to best practice.
    – Jordan
    Feb 28, 2020 at 13:12
  • @Jemat Have updated the answer. Feb 28, 2020 at 13:20

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .