2

We are creating OpportunityLineItem through Apex, now in isAfterInsert we are creating a Test object record whose Unique_Key is same as OpportunityLineItem unique_key.

Now attaching the 'Test' to OpportunityLineItem is written in beforeUpdate of OpportunityLineItem. So we are expecting that in isAfterInsert the Test record should be created and when we come to beforeUpdate, we can query the Test record with the unique_key from OpportunityLineItem and attach those to the OpportunityLineItem.

Now the issue is when the code runs the Test record is created, but when it comes to beforeUpdate it cannot find any test record with the Unique key. I have put the debug statements and can see blank List printed after query is executed.

How can I delay this process so that the beforeUpdate runs after the test record is created?

In is after update of OpportunityLineItemTriggerHandler

public static void createTestRecordWithMatchingOLIKey(List<OpportunityLineItem> newList, Map<Id, Opportunity> mapOfOpp){
    //Initialise Variables
    Map<Id, String> mapOliWithTestKey = new Map<Id, String>();
    List<Test> listUpdateYearlyTests = new List<Test>();

    for(Integer i=0; i<newList.size();i++){
            mapOliWithTestKey.put(newList[i].Id,newList[i].TestMactchingKey__c);

    }
    if(!mapOliWithTestKey.isEmpty()){
        Test yearlySpa = createSpa('Yearly',mapOliWithTestKey.get(newList[i].Id),
        newList[i], mapOfOpp.get(newList[i].OpportunityId),null, mapOfOpp.get(newList[i].OpportunityId).test_type__c);
        listUpdateYearlyTests.add(yearlySpa);

        if(!listUpdateYearlyTests.isEmpty()){
            Set<Test> uniqueSPAs = new Set<Test>();
            uniqueSPAs.addAll(listUpdateYearlyTests);
            listUpdateYearlyTests.clear();
            listUpdateYearlyTests.addAll(uniqueSPAs);
            List<Database.UpsertResult> upsertResults = Database.upsert(listUpdateYearlyTests,false);
        }
    }
}

Now in is before Update

matchingKeySet.add(lNew[i].TestMactchingKey__c); 
List<Test> testList = new List<Test>([Select Id from Test where Unique_Key__c IN :matchingKeySet]);

Trigger OLI code

if(trigger.isBefore){
    // INSERT
    if(trigger.isInsert)
    {
        OpportunityLineItemTriggerHandler.IsBeforeInsert(lNew,mNew);

    }
    // UPDATE
    if(trigger.isUpdate)
    {
        OpportunityLineItemTriggerHandler.IsBeforeUpdate(lOld,mOld,lNew,mNew);

    }
    // DELETE
    if(trigger.isDelete)
        OpportunityLineItemTriggerHandler.IsBeforeDelete(lOld,mOld);
}

/*************************************************
********************* AFTER **********************
*************************************************/
if(trigger.isAfter){
    // INSERT
    if(trigger.isInsert)
        OpportunityLineItemTriggerHandler.IsAfterInsert(lNew,mNew);
    // UPDATE
    if(trigger.isUpdate)
        OpportunityLineItemTriggerHandler.IsAfterUpdate(lOld,mOld,lNew,mNew);
    // DELETE
    if(trigger.isDelete)
        OpportunityLineItemTriggerHandler.IsAfterDelete(lOld,mOld);
}

The matching key from OLI's TestMactchingKey__c matches Test's UnikeyKey__c. The createSPA method creates the SPA with data from OLI (OpportunityLineItem). The funny part is after creation it does not find anything in testList. But if I just do a dummy update then it finds the testList.

6
  • HI Rahul. While you're question is beautifully written, I think it would benefit us if we could see a bit of code, just so there's nothing lost in translation. Could you please edit in some code for us to look at? Thanks!
    – sfdcfox
    Commented Jun 9, 2018 at 4:47
  • @sfdcfox I have written some code snippets, but I suspect the insertion of Test is delayed and before the Test is inserted the code passes the isBeforeUpdate on OLI and that is why it is not able to find the Test record. This works after we do a dummy update in OLI,. Commented Jun 9, 2018 at 5:41
  • Could you share the trigger code as well? Is your before update and after update both in OppLineItem? If so think about changing trigger points. I.e you can create the test on after insert of the oppLineItem then on after insert of Test you can link it to the oppLineItem .
    – Ranga
    Commented Jun 9, 2018 at 7:45
  • @Ranga Yes I think thats the design I need to change. But I was surprised to see why would before update code not work. As Salesforce is single threaded. So when it fires an Upsert on Test, it should create Test and run all its triggers then only it should fire Update logic in OLI. Right? Commented Jun 9, 2018 at 10:36
  • Rahul, yes you are right. I agree with the answer provided by @crmprogdev but if you are calling the createTestRecordWithMatchingOLIKey() method in after context, I'm not sure why it's not working. May be check whether your update trigger is firing multiple times. Also if possible, add check only execute your code when certain criteria is met by comparing old value and new value.
    – Ranga
    Commented Jun 9, 2018 at 15:38

1 Answer 1

2

I suspect your code is not working because you're trying to use use Opportunity Id to relate the records to the unique_key that should be matched with OLI's unique_key at a time when the Opportunity Id hasn't yet been populated. During After Insert and during Before Update contexts you'll have the Opp's Id. During Before Insert, you won't.

It's important to remember that trigger.old does not change during the Insert context. During Before Insert, both trigger.new and trigger.old do not contain a record.Id. However during the After Insert context, trigger.new should contain the record.Id. At Before Update, both trigger contexts will again contain the record.Id and these Ids will allow you to query the OLI's you're looking for.

Edit

In response to comments:

"for this check I am passing a String Operation in my code internally to run the code only when the Operation is Update. What I an surprised to see is this : I was surprised to see why would before update code not work... ...when it fires an Upsert on Test, it should create Test and run all its triggers then only it should fire Update logic in OLI. Right?"

An Upsert operation works like this: If it can insert, it will do the insert. If it can update, it will do an update. To do the update, an Id is required. Since the Id isn't present, it will only do an insert and will NEVER update. Your method needs to be specific to do one or the other depending on whether the Id is present. Remember that, even when testing, your Insert trigger code runs before you'll reach your Update code.

"Can you please help me understand what will happen in below scenario... ...when OLI is created, it triggers Test record creation. OLI's before insert gets called, then after insert gets called. In after insert (on Oppoertunity) we create OLI test record. Now before and after triggers for (OLI) test record gets called. Now OLI's workflow gets called and triggers an update operation on OLI. Which should trigger my before udpate (on Opportunity) code and find the related Test record. Right? Is this flow of execution correct?"

I would expect the following order of execution for the transaction:

New Opportunity --> Before Insert : Opportunity --> DML : Opportunity --> Workflow : Opportunity --> After Insert : Opportunity --> Before Insert : OLI --> DML : OLI --> Workflow : OLI --> After Insert : OLI

--> OLI causes update to Opportunity --> Before Update : Opportunity --> DML : yearlySpa --> triggers & workflow on yearlySpa execute --> DML : Opportunity --> After Update : Opportunity

That's the general order of execution that I would expect to occur. If you're performing Before Insert logic on OLI, you MUST have a valid OpportunityId since it's a required field for OLI. Establishing a relationship with the correct Opportunity is important. From the Object Reference for OLI, here's why:

• Creating an OpportunityLineItem increments the Opportunity Amount value by the TotalPrice of the OpportunityLineItem. Additionally, inserting an OpportunityLineItem increments the ExpectedRevenue on the opportunity by the TotalPrice times the opportunity Probability.

• The Opportunity Amount becomes a read-only field when the opportunity has line items. The API ignores any attempt to update this field on an opportunity with line items. Update requests are not rejected, but the updated value is ignored.

• You can’t update the PricebookId field or the CurrencyIsoCode field on the opportunity if line items exist. The API rejects any attempt to update these fields on an opportunity with line items.

• When you create or update an OpportunityLineItem, the API verifies that the line item corresponds to a PricebookEntry in the Pricebook2 that is associated with the opportunity. If the opportunity does not have an associated Pricebook2, the API automatically sets the pricebook on the opportunity if the line item corresponds to a PricebookEntry in an active Pricebook2, and if the PricebookEntry has a CurrencyIsoCode field that matches the CurrencyIsoCode field of the opportunity. If the Pricebook2 is not active or the CurrencyIsoCode fields do not match, an error is returned.

• The Opportunity HasOpportunityLineItem field is set to true when an OpportunityLineItem is inserted for that Opportunity.

5
  • Hi @crmprogdev Thank you for the answer. Yes I do know that the Ids won't be present at the time of before insert context. So for this check I am passing a String Operation in my code internally to run the code only when the Operation is Update. What I an surprised to see is this : I was surprised to see why would before update code not work. As Salesforce is single threaded. So when it fires an Upsert on Test, it should create Test and run all its triggers then only it should fire Update logic in OLI. Right? Commented Jun 9, 2018 at 15:34
  • An Upsert operation works like this: If it can insert, it will do the insert. If it can update, it will do an update. To do the update, an Id is required. Since the Id isn't present, it will only do an insert and will NEVER update. Your method needs to be specific to do one or the other depending on whether the Id is present. I don't know the details of your string implementation, but remember that, even when testing, your Insert trigger code runs before you'll reach your Update code. That could be the source of the issue.
    – crmprogdev
    Commented Jun 9, 2018 at 20:42
  • Hi @crmprogdev Yes, you are correct. Can you please help me understand what will happen in below scenario. OLI has some triggers and workflows. So when OLI is created, it triggers Test record creation. Now OLI's before insert gets called and then after insert gets called. In after insert we create test record. Now before and after triggers for test record gets called. Now the OLI's workflow gets called and triggers an update operation on OLI. Which should trigger my before udpate code and find the related Test record. Right? Is this flow of execution correct? Commented Jun 10, 2018 at 5:22
  • Hi @crmprogdev Its not OLI test record, the Test is another object getting created from AfterInsert of OLI. New OLI --> Before Insert : OLI --> DML : After Insert:OLI (Here we create Test Object)--> Before Insert : Test --> After Insert : Test --> WorkFlow: OLI --> : BeforeUpdate: OLI (Here we map Test Object to OLI)-> AfterUpdate: OLI. Is this flow correct? Commented Jun 11, 2018 at 1:01
  • Hi @crmprogdev Thank you for the detailed answer. I was finally able to figure out after reading your answer the root cause. The root cause was the same code for creating Test, was called from isAfterInsert and isAfterUpdate, there was an if condition which was preventing it from creating Test in isAfterInsert, thus OLITriggerHandler was not able to search for it in BeforeUpdate. Commented Jun 11, 2018 at 5:49

You must log in to answer this question.

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