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
Gabe Rothman 8Gabe Rothman 8 

Need help mapping productId to pricebookentryid

Hey all, 
I'm working a bit of code to automatically re-add products to an opportunity if they get deleted as a result of the user changing the pricebook.  I experimented with trying to clone the OLIs in a beforeDelete trigger, but couldn't get that work, I assume becuase updating the pricebook deleted the newly created clones in addition to the old products.

Instead I've built a custom object called OLI_Clone__c for which records are inserted every time a new OLI is added to an oppty and then deleted later when the oppty goes to closed won or closed lost.  Basically, when the pricebook updates on the oppty, the code queries back to the OLI_Clone__c records associated with the oppty and then uses the data stored there to recreate the OLIs.  The biggest challenge is that I can't create a lookup to ProcebookEntry on a custom object so I had to create a lookup to Product and then use a combination of the Opportunity's new Pricebook Id and the OLI_Clone__c's product Id to query for the correct PricebookEntry.  

I've been able to get a list of PricebookEntryIds, but I can't figure out how get the correct PricebookEntryId for each OLI_Clone__c without querying for it inside of my for loop, which is obviously a big no no.  I do know that I should be using a map, but I am relatively novice coder, and I just can't seem to get the syntax right.

Thanks in advance for your help!

I've pasted my service class below with a few comments:
public with sharing class autoAddProductsAfterPBChange {

//create a filter to listen for a change in the pricebook below
      public static list<opportunity> filterOpp(map<id, opportunity> oldMap, list<opportunity> newList) {
        list<opportunity> temp=new list<opportunity>();
        for (opportunity newOpp : newList){
            if (oldMap.get(newOpp.id).PriceBook2Id != newOpp.PriceBook2Id){
                temp.add(newOpp);
            }
        }
        return temp;
    }        

//find the pricebook entries and recreate the OLIs   
    public static void autoAdd(list<Opportunity> opps){
        Opportunity opp = new Opportunity(); 
        Pricebook2 pb = new Pricebook2();    
        for(Opportunity opp2 : opps){
            opp.id = opp2.Id;
            pb.id = opp2.Pricebook2Id;
        }
        list<OLI_Clone__c> olics = [SELECT Quantity__c, Additional_Discount_off_List__c, Manual_Discount__c, Opportunity__c, Product__c, Sales_Price__c, Service_Term_in_Months__c
                                                     FROM OLI_Clone__c
                                                     WHERE Opportunity__c =: opp.id];
        list<Id> prodids = new list<Id>();
        for(OLI_Clone__c olicprod : olics){
            prodids.add(olicprod.Product__c);
        }
        List<PricebookEntry>pbeids = [SELECT Id, Product2Id
                           			         FROM PricebookEntry
                                                         WHERE Product2Id in: prodids AND Pricebook2Id =: pb.id];
 		Map<Id, PricebookEntry> pbeMap = new Map<Id, PricebookEntry>(pbeids);  // Is this map correct? 
 		list<OpportunityLineItem>toInsert = new list<OpportunityLineItem>();
        for(OLI_Clone__c olic : olics){
            if(pbeMap.containsKey(olic.Product__c)){
                OpportunityLineItem oli = new OpportunityLineItem();
                PricebookEntry pbe = pbeMap.get(); // I know I need to be "getting" something here but I'm not sure how to structure the syntax, and I'm not sure that my map above is configured correctly either.  Basically I need to get the correct PricebookEntryId based on the Product__c field on the OLI_Clone__c in this loop and the parent Opportunity's pricebook.
                oli.Quantity = olic.Quantity__c;
                oli.UnitPrice = olic.Sales_Price__c;
                toInsert.add(oli);
            }
        }
        insert toInsert;
	}

}




 
Best Answer chosen by Gabe Rothman 8
Terence_ChiuTerence_Chiu
Gabe, you are right to use Maps to faciliate the mapping for products to pricebooks entries. I made some slight modifications to your code and added some bulkification. In case you have the same product with different pricebooks (and you have multiple opps chaning pricebooks) I am using a map where Product2Id + Pricebook2Id is the key and the pricebook entry Id is the value. Not sure if this completely accomplishes your goals, but perhaps it will set you off in the right direction. 
 
public with sharing class autoAddProductsAfterPBChange {

//create a filter to listen for a change in the pricebook below
      public static list<opportunity> filterOpp(map<id, opportunity> oldMap, list<opportunity> newList) {
        list<opportunity> temp=new list<opportunity>();
        for (opportunity newOpp : newList){
            if (oldMap.get(newOpp.id).PriceBook2Id != newOpp.PriceBook2Id){
                temp.add(newOpp);
            }
        }
        return temp;
    }        

//find the pricebook entries and recreate the OLIs   
    public static void autoAdd(list<Opportunity> opps){
        Opportunity opp = new Opportunity(); 
        Pricebook2 pb = new Pricebook2();
        Set<Id> pbIds = new Set<Id>();
        Map<String, Id> productPBToPBEIdMap =  new Map<String, Id>();    
        for(Opportunity opp2 : opps){
            //opp.id = opp2.Id;
            //pb.id = opp2.Pricebook2Id;
            pbIds.add(opp2.pricebook2Id);
            
        }
        
        //pulling the Opp pricebook2id with the below query.
        list<OLI_Clone__c> olics = [SELECT Quantity__c, Additional_Discount_off_List__c, Manual_Discount__c, Opportunity__c, Opportunity__r.Pricebook2Id Product__c, Sales_Price__c, Service_Term_in_Months__c
                                                     FROM OLI_Clone__c
                                                     WHERE Opportunity__c =: opps];
        list<Id> prodids = new list<Id>();
        for(OLI_Clone__c olicprod : olics){
            prodids.add(olicprod.Product__c);
        }
        
        for(PricebookEntry pbe : [SELECT Id, Product2Id
                           			         FROM PricebookEntry
                                                         WHERE Product2Id in: prodids AND Pricebook2Id IN: pbIds and isactive = true]){
         	
         	productPBToPBEIdMap.put(pbe.Product2Id + pbe.Pricebook2Id, pbe.Id);
        }
 		Map<Id, PricebookEntry> pbeMap = new Map<Id, PricebookEntry>(pbeids);  // Is this map correct? 
 		list<OpportunityLineItem>toInsert = new list<OpportunityLineItem>();
        for(OLI_Clone__c olic : olics){
            if(productPBToPBEIdMap.containsKey(olic.product__c + olic.opportunity__r.pricebook2id)){
                OpportunityLineItem oli = new OpportunityLineItem();
                Id pbeId = productPBToPBEIdMap.get(olic.product__c + olic.opportunity__r.pricebook2id); // I know I need to be "getting" something here but I'm not sure how to structure the syntax, and I'm not sure that my map above is configured correctly either.  Basically I need to get the correct PricebookEntryId based on the Product__c field on the OLI_Clone__c in this loop and the parent Opportunity's pricebook.
                oli.Quantity = olic.Quantity__c;
                oli.UnitPrice = olic.Sales_Price__c;
                oli.PricebookEntryId = pbeId;
                toInsert.add(oli);
            }
        }
        insert toInsert;
	}

}

 

All Answers

Terence_ChiuTerence_Chiu
Gabe, you are right to use Maps to faciliate the mapping for products to pricebooks entries. I made some slight modifications to your code and added some bulkification. In case you have the same product with different pricebooks (and you have multiple opps chaning pricebooks) I am using a map where Product2Id + Pricebook2Id is the key and the pricebook entry Id is the value. Not sure if this completely accomplishes your goals, but perhaps it will set you off in the right direction. 
 
public with sharing class autoAddProductsAfterPBChange {

//create a filter to listen for a change in the pricebook below
      public static list<opportunity> filterOpp(map<id, opportunity> oldMap, list<opportunity> newList) {
        list<opportunity> temp=new list<opportunity>();
        for (opportunity newOpp : newList){
            if (oldMap.get(newOpp.id).PriceBook2Id != newOpp.PriceBook2Id){
                temp.add(newOpp);
            }
        }
        return temp;
    }        

//find the pricebook entries and recreate the OLIs   
    public static void autoAdd(list<Opportunity> opps){
        Opportunity opp = new Opportunity(); 
        Pricebook2 pb = new Pricebook2();
        Set<Id> pbIds = new Set<Id>();
        Map<String, Id> productPBToPBEIdMap =  new Map<String, Id>();    
        for(Opportunity opp2 : opps){
            //opp.id = opp2.Id;
            //pb.id = opp2.Pricebook2Id;
            pbIds.add(opp2.pricebook2Id);
            
        }
        
        //pulling the Opp pricebook2id with the below query.
        list<OLI_Clone__c> olics = [SELECT Quantity__c, Additional_Discount_off_List__c, Manual_Discount__c, Opportunity__c, Opportunity__r.Pricebook2Id Product__c, Sales_Price__c, Service_Term_in_Months__c
                                                     FROM OLI_Clone__c
                                                     WHERE Opportunity__c =: opps];
        list<Id> prodids = new list<Id>();
        for(OLI_Clone__c olicprod : olics){
            prodids.add(olicprod.Product__c);
        }
        
        for(PricebookEntry pbe : [SELECT Id, Product2Id
                           			         FROM PricebookEntry
                                                         WHERE Product2Id in: prodids AND Pricebook2Id IN: pbIds and isactive = true]){
         	
         	productPBToPBEIdMap.put(pbe.Product2Id + pbe.Pricebook2Id, pbe.Id);
        }
 		Map<Id, PricebookEntry> pbeMap = new Map<Id, PricebookEntry>(pbeids);  // Is this map correct? 
 		list<OpportunityLineItem>toInsert = new list<OpportunityLineItem>();
        for(OLI_Clone__c olic : olics){
            if(productPBToPBEIdMap.containsKey(olic.product__c + olic.opportunity__r.pricebook2id)){
                OpportunityLineItem oli = new OpportunityLineItem();
                Id pbeId = productPBToPBEIdMap.get(olic.product__c + olic.opportunity__r.pricebook2id); // I know I need to be "getting" something here but I'm not sure how to structure the syntax, and I'm not sure that my map above is configured correctly either.  Basically I need to get the correct PricebookEntryId based on the Product__c field on the OLI_Clone__c in this loop and the parent Opportunity's pricebook.
                oli.Quantity = olic.Quantity__c;
                oli.UnitPrice = olic.Sales_Price__c;
                oli.PricebookEntryId = pbeId;
                toInsert.add(oli);
            }
        }
        insert toInsert;
	}

}

 
This was selected as the best answer
Gabe Rothman 8Gabe Rothman 8
This worked perfectly!  Thanks Terence!