function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
Alaric WimerAlaric Wimer 

Trigger Help - Add Opp Product names to custom field on Opportunity?

I'm attempting to write a trigger that adds Opportunity Products' names to custom fields on the Opportunity. I'm running in to a couple of issues: 

1. How can I reference :opp.Id in a before trigger? Ideally, I'd like to avoid using an after trigger.

2. How can I move that SOQL query outside of the trigger loop?

3. I keep getting an error that says I'm referencing "read only" fields on the oli. 

Here's my trigger so far:

trigger AddProductNames on Opportunity (before insert, before update) {
    for (Opportunity opp : Trigger.new) {
        // Get opportunity products
        List<OpportunityLineItem> olis = [
          SELECT Product2.Family, Product2.Name
            FROM OpportunityLineItem
           WHERE OpportunityId = :opp.Id
        ];
        if (!olis.isEmpty()) {
            // iterate through olis and assign their names to opp fields
            for (OpportunityLineItem oli : olis) {
                if (oli.Product2.Family == 'Inverters') {
                    opp.Inverter_Name__c = oli.Product2.Name;
                } else if (oli.Product2.Family == 'Panels') {
                    opp.Panel_Name__c = oli.Product2.Name;
                }
            }
        }
    }
}
Best Answer chosen by Alaric Wimer
Ajay K DubediAjay K Dubedi
Hi Alaric,
If you write a trigger on insert of an opportunity either it before or after you never get any oli's in opportunity you can write trigger on update to get oli's.
Try below code:
//trigger code
trigger AddProductNames on Opportunity (before update) {
    if(trigger.isBefore && trigger.isUpdate){
        OpportunityClass.OpportunityMethod(trigger.new);
    }
}




//helper code
public class OpportunityClass{
    public static void OpportunityMethod(List<Opportunity> oppList){
        Set<Id> OpportunityIds = new Set<Id>(); 
        List<OpportunityLineItem> olis = new List<OpportunityLineItem>();
        for(Opportunity Opp : oppList){
            OpportunityIds.add(Opp.Id);
        }
        if(OpportunityIds.size() > 0){
            olis  = [SELECT Product2.Family, Product2.Name,OpportunityId FROM OpportunityLineItem WHERE OpportunityId IN: OpportunityIds LIMIT 10000];
            
            if (!olis.isEmpty()) {
                for(Opportunity Opp : oppList){            
                    for (OpportunityLineItem oli : olis) {
                        if(Opp.Id == oli.OpportunityId){
                            if (oli.Product2.Family == 'Inverters') {
                                opp.Inverter_Name__c = oli.Product2.Name;
                            } else if (oli.Product2.Family == 'Panels') {
                                opp.Panel_Name__c = oli.Product2.Name;
                            }
                        }
                            
                    }
                }
            }
        }
    }
}


   

I hope you find the above solution helpful. If it does, please mark as Best Answer to help others too.
Thanks,
Ajay Dubedi

All Answers

Andrew GAndrew G
Hi Alaric

If we think through the process, i would recommend putting the trigger on the OLI object.
We create an Opportunity.  We then add Oppty Products to that Oppty.  So the product name is available when the Oppty Product is added, not when we Insert the Oppty.  And if we are adding Oppty Products, there is no real guarantee that the Oppty will be updated to force the Update trigger on Oppty to fire.
 
trigger AddProductNames on OpportunityLineItem (after insert, after update, after delete ) {
    list<id> opptyIds;
    for (OpportunityLineItem oli : trigger.new ) {
        opptyIds.add (oli.OpptyId);
    }

    Map(Id,Oppty) = [SELECT Id, productfield FROM oppty WHERE Id IN:opptyIds];

    List<OLI> olis = [SELECT Id, productname FROM OLI WHERE oli.opptyId in: opptyIds;

   Loop LIST olis to make Map <OpptyId,List<OLI>> where the key is the OPPTY ID
   end make map loop

   Then loop Map of olis, 
       extract key 
       extract list of OLI
       loop list to make product name string
       use key to get Oppty from MAP<Id,Oppty>
       update the Oppty;
       add Oppty to UpdateOpptyList;
    end loop
update UpdateOpptyList;
the above is a psuedo code on the process that I would follow.
Sorry, i don't quite have time to do a full code solution

Regards
Andrew
 
Ajay K DubediAjay K Dubedi
Hi Alaric,
If you write a trigger on insert of an opportunity either it before or after you never get any oli's in opportunity you can write trigger on update to get oli's.
Try below code:
//trigger code
trigger AddProductNames on Opportunity (before update) {
    if(trigger.isBefore && trigger.isUpdate){
        OpportunityClass.OpportunityMethod(trigger.new);
    }
}




//helper code
public class OpportunityClass{
    public static void OpportunityMethod(List<Opportunity> oppList){
        Set<Id> OpportunityIds = new Set<Id>(); 
        List<OpportunityLineItem> olis = new List<OpportunityLineItem>();
        for(Opportunity Opp : oppList){
            OpportunityIds.add(Opp.Id);
        }
        if(OpportunityIds.size() > 0){
            olis  = [SELECT Product2.Family, Product2.Name,OpportunityId FROM OpportunityLineItem WHERE OpportunityId IN: OpportunityIds LIMIT 10000];
            
            if (!olis.isEmpty()) {
                for(Opportunity Opp : oppList){            
                    for (OpportunityLineItem oli : olis) {
                        if(Opp.Id == oli.OpportunityId){
                            if (oli.Product2.Family == 'Inverters') {
                                opp.Inverter_Name__c = oli.Product2.Name;
                            } else if (oli.Product2.Family == 'Panels') {
                                opp.Panel_Name__c = oli.Product2.Name;
                            }
                        }
                            
                    }
                }
            }
        }
    }
}


   

I hope you find the above solution helpful. If it does, please mark as Best Answer to help others too.
Thanks,
Ajay Dubedi
This was selected as the best answer