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
iaestemaniaesteman 

Problem with Update StandardPrice on product with trigger

I am new with triggers and I am trying to achieve the following. Every time a BundlePrice field is inserted or updated it should automatically update the standard price with that info according the exchange rate.
This is the trigger that I made.

 

trigger UpdateStandardPrices on Product2 (after insert, after update) {

//Update the Standard price in all currencies from the Cost price on the Product. Cost Price is always in SEK
//Create a PriceBookEntry if it's a new Product

System.Debug('USP TRIGGER');

//This trigger will cause recursion if it's not stopped. 
//The TriggerMonitor class is static, and remains for the lifetime of the insert/update
//This prevents recursion.
//string TriggerName = 'UpdateStandardPrices';
if (TriggerMonitor.runOnce())
{       
    //TriggerMonitor.ExecutedTriggers.add(TriggerName);


    // GET ALL DATA //
    Id PriceBookId = [SELECT Id From Pricebook2 WHERE IsStandard=TRUE].Id;                                                          //Get the ID of the Standard Price Book
    list<CurrencyType> ActiveCurrencies = [SELECT ISOCode, ConversionRate FROM CurrencyType WHERE IsActive=TRUE];                   //Get a list of all active currencies
    list<Product2> Products = new List<Product2>([SELECT Id, Bundle_Price__c, CurrencyIsoCode FROM Product2 WHERE Id in :Trigger.new]);  //Get a list of all new entries
    list<PricebookEntry> resultList = new list<PricebookEntry>();                                                                   //List to populate and update

    for( Product2 product : Products)
    {

        list<PricebookEntry> loopList = new list<PricebookEntry>([Select Id, CurrencyISOCode FROM PricebookEntry WHERE PricebookEntry.Product2Id =: product.Id]); //Get a list of all entries in the Standard Price Book for the product id

        for(CurrencyType c : ActiveCurrencies)
        {

            integer currencyExists = 0;                 //Reset the currency exists flag
            PricebookEntry price = new PricebookEntry();

            for(PricebookEntry l : loopList)        
            {                                           //Is there already an entry in the currency
                if(l.CurrencyIsoCode == c.IsoCode)
                    currencyExists = 1;
            }


            if(currencyExists == 0)
            {                                           //Does not exist in this currency: Insert new StandardPrice

                price.UnitPrice = product.Bundle_Price__c * c.ConversionRate; 
                price.Product2Id = product.Id;
                price.CurrencyISOCode = c.IsoCode;
                price.Pricebook2Id = PriceBookId;
                price.IsActive = TRUE;
                insert price;
            }
            else
            {                                           //Exist in this currency: Update with new StandardPrice
                for(PricebookEntry l : loopList)        
                {                                       //Loop through to find the unique Id for the PricebookEntry

                    if(l.CurrencyIsoCode == c.IsoCode)
                        price.Id = l.Id;
                }

                price.UnitPrice = product.Bundle_Price__c* c.ConversionRate;
                price.IsActive = TRUE;
                resultList.add(price);
            }
        }

    }
    update resultList;
}


What the trigger does is checks currencies check rate and add them to the standard price. But when i test it, it change the price to all currencies the same and ignores the rate. I need to edit the product so the rates will get changed. What I am doing wrong?
Additionally how can I update the trigger to check the PricebookId(sweden) that I have and add the corresponding Standardprice(SEK) to the Swedish price-book.
Any help to reach this mission will be highly appreciated.

 

Thanks, Darko

monsterloomismonsterloomis
Full disclosure: I'm not 100% sure I understand the logic here, but I can offer some suggestions. One BIG caveat - I don't have a multi-currency enabled org handy, so I'm flying blind and I don't know that this would even compile, let alone work. 

BUT, at a high level, my recommendations are:
  • Avoid DML and SOQL inside your record loop. Try preparing collections like you see below, then using those inside the for loop to avoid problems with bulk processing
  • Put in more debug statements to test the assumptions in your logic. Something happening here is not happening the way you think it is. Without an active org to test in, it's hard to say exactly, but debug statements will help
  • You shouldn't need to query the records in your product2 trigger set - you have them already, by virtue of being in the trigger context.
trigger UpdateStandardPrices on Product2 (after insert, after update) {

	if (TriggerMonitor.runOnce()) {       

	    Id PriceBookId = [SELECT Id From Pricebook2 WHERE IsStandard=TRUE].Id;                                                         
    	List<CurrencyType> ActiveCurrencies = [SELECT ISOCode, ConversionRate FROM CurrencyType WHERE IsActive=TRUE];                   
    	List<PricebookEntry> updatedPrices = new list<PricebookEntry>();                     
        List<PricebookEntry> newPrices = new List<PricebookEntry>();
        
    	// SOQL and DML inside for loops isn't bulk-ready, so you'll want to set up your collections OUTSIDE the for loop
		Map<String,List<PriceBookEntry>> prodToPriceBookEntries = new Map<String,List<PriceBookEntry>>();
    	Set<Id> prodIds = Trigger.newMap.keyset();
    	for (Product2 p2:[Select Id, (SELECT Id, CurrencyISOCode FROM PriceBookEntries) FROM Product2 WHERE Id IN :prodIds]) {
			prodToPriceBookEntries.put(p2.Id,p2.PriceBookEntries);        
    	}        
        
        Integer currencyExists = 0;                 
        
	    for( Product2 product : Trigger.new)
    	{
			
            List<PricebookEntry> loopList = prodToPriceBookEntries.get(product.Id); // needs null checking
            
            // loop through all your currencies
            for(CurrencyType c : ActiveCurrencies)
        	{
                PricebookEntry price = new PricebookEntry();
				currencyExists = 0;
                
                for(PricebookEntry l : loopList)        
            	{                                           //Is there already an entry in the currency
                    System.debug('l.CurrencyIsoCode: ' + l.CurrencyIsoCode); // debug or use checkpoints so you know what you're actually getting
                    System.debug('c.IsoCode: ' + c.IsoCode);
                    if(l.CurrencyIsoCode == c.IsoCode) {
                    	currencyExists = 1;
                        break; // stop this looping once you know it exists, for efficiency                  
                    }
            	}

	            if(currencyExists == 0)
    	        {                                           //Does not exist in this currency: Insert new StandardPrice
                    System.debug('For product id ' + product.Id + ', the currency does not exist.');
	                price.UnitPrice = product.Bundle_Price__c * c.ConversionRate; 
    	            price.Product2Id = product.Id;
        	        price.CurrencyISOCode = c.IsoCode; // 
            	    price.Pricebook2Id = PriceBookId;
                	price.IsActive = TRUE;
                	newPrices.add(price);
            	} 
                else
            	{                                           //Exist in this currency: Update with new StandardPrice
                    System.debug('For product id ' + product.Id + ', the currency DOES exist.');
                    for(PricebookEntry l : loopList)        
                	{                                       //Loop through to find the unique Id for the PricebookEntry
	                    if(l.CurrencyIsoCode == c.IsoCode)
    	                    price.Id = l.Id;
        	        }
                    System.debug('product.Bundle_Price__c * c.ConversionRate: ' + product.Bundle_Price__c * c.ConversionRate);
            	    price.UnitPrice = product.Bundle_Price__c * c.ConversionRate;
                	price.IsActive = TRUE;
                	updatedPrices.add(price);
            	}
        	}
    	}
        
        System.debug('new prices:' + newPrices);
        System.debug('updated prices:' + updatedPrices);        
        insert newPrices;
    	update updatedPrices;
	}
}
Good luck! Hope that helps.
iaestemaniaesteman

Thanks monsterloomis for the reply. I didn't replied earlier but i managed to fix the issue. I also tested your code it works fine except when you try to create a product with a bundle_price__c to be empty. then I get this error:
 

Error: Invalid Data. 
Review all error messages below to correct your data.
Apex trigger UpdateStandardPrices caused an unexpected exception, contact your administrator: UpdateStandardPrices: execution of AfterInsert caused by: System.NullPointerException: Attempt to de-reference a null object: Trigger.UpdateStandardPrices: line 43, column 1
 

Do you know by any chance how can i dodge that? So if the Bundle_Price__c is empty just create the product. I kinda need this so i wont need to create a default value of 0 for this field and have it inside the code to check if its empty create the product if it has a value of 0 predefine the price.

Thanks,

Darko

 

monsterloomismonsterloomis
Hey Darko,

No problem, and glad you got it working. Just so I'm sure I understand: when you say to create the product, isn't all this happening in a trigger context on the product itself? If you just want to see if the bundle_price__c is empty and then create a product, you can certainly do it:
if (product.Bundle_Price__c == NULL) { 
// create a product that meets your needs here
} else if (product.Bundle_Price__c == 0){ 
// your existing logic 
}
...but why would you create the product if the bundle price is null, but update the price if the value is 0? The bundle price may be empty/null, but the product itself is the scope of the trigger itself, so it seems odd to create a new product. Maybe I'm not fully understanding the requirement, though. If you can help me figure out what you're after and why, I'd be happy to offer some suggestions. 
iaestemaniaesteman

Well my products in salesforce are organised like this: I have a software, hardware accessories and bundle. All of them are products. I have made a custom CPQ for salesforce that combines them in a different matter and fields that are used accordingly for bundle hardware software accessories and so on. Now i have a custom object that sorts them and its called bundle line item and in that bundle line item i have lines that can be combined.

When i am creating a bundle i am making api calls and i need that bundle price to be 0. So when I add bundle line items that price needs to be updated and from that field bundle price i want to standard price to be updated for all currencies and with possibility on adding predefined pricebook. 

Anyway thank you so much for your code. Let me test it good and i will give you a feedback :)
Cheers,
D.

iaestemaniaesteman

I kinda feel I need to explain my logic better, because the more I test it the more problems i am facing :(

My Product has the following product Family values:

  • Bundle
  • Software
  • hardware
  • Accessories

Then I have a custom object called Bundle_Line_Item__c where I am adding only when I am picking Bundle from the product family. This way I need to update the price of the bundle all the time and for that I am having a trigger to do that which works fine. 

I have couple of scenarios that I need to take care of:

  1. when i am creating a software or hardware i want the bundle price to be default 0. I am managing that inside the field. (fixed)
  2. when i am picking up product family to be software/hardware/accessories and that default bundle price to be 0 i don't want the standard price to be updated with the bundle price 0. Instead i want to be updated with software price/hardware price/accessories price accordingly. (kinda fixed)
  3. Only when I pick Bundle and I have a Bundle Price the price to be updated. (fixed)
  4. If I have accessories/hardware/software i want their fields to be taken care of software price/hardware price/accessories price to update the standard price. (same as second)
  5. When I pick software as family and software price i want the standard price to be updated for all currencies, same goes with accessories and hardware, only when i pick bundle as family and bundle price as value i just want the price in the field to update the pricebookentry. (NOT FIXED)
  6. Optional: Standard price to update the all pricebooks with the values accordingly. But this is another problem that I am facing and i am having it later on my bucketlist.

Here is what I did with the code so far with monsterloomis update.

trigger UpdateStandardPrices on Product2 (after insert, after update) {

    if (TriggerMonitor.runOnce()) {       

        Id PriceBookId = [SELECT Id From Pricebook2 WHERE IsStandard=TRUE].Id;                                                         
        List<CurrencyType> ActiveCurrencies = [SELECT ISOCode, ConversionRate FROM CurrencyType WHERE IsActive=TRUE];                   
        List<PricebookEntry> updatedPrices = new list<PricebookEntry>();                     
        List<PricebookEntry> newPrices = new List<PricebookEntry>();
        
        // SOQL and DML inside for loops isn't bulk-ready, so you'll want to set up your collections OUTSIDE the for loop
        Map<String,List<PriceBookEntry>> prodToPriceBookEntries = new Map<String,List<PriceBookEntry>>();
        Set<Id> prodIds = Trigger.newMap.keyset();
        for (Product2 p2:[Select Id, (SELECT Id, CurrencyISOCode FROM PriceBookEntries) FROM Product2 WHERE Id IN :prodIds]) {
            prodToPriceBookEntries.put(p2.Id,p2.PriceBookEntries);        
        }        
        
        Integer currencyExists = 0;                 
        
        for( Product2 product : Trigger.new)
        {
            
            List<PricebookEntry> loopList = prodToPriceBookEntries.get(product.Id); // needs null checking
            
            // loop through all your currencies
            for(CurrencyType c : ActiveCurrencies)
            {
                PricebookEntry price = new PricebookEntry();
                currencyExists = 0;
                
                for(PricebookEntry l : loopList)        
                {                                           //Is there already an entry in the currency
                    System.debug('l.CurrencyIsoCode: ' + l.CurrencyIsoCode); // debug or use checkpoints so you know what you're actually getting
                    System.debug('c.IsoCode: ' + c.IsoCode);
                    if(l.CurrencyIsoCode == c.IsoCode) {
                        currencyExists = 1;
                        break; // stop this looping once you know it exists, for efficiency                  
                    }
                }

                if(currencyExists == 0)
                {   
                    //Does not exist in this currency: Insert new StandardPrice
                    if(product.Family == 'Bundle'){
                        System.debug('For product id ' + product.Id + ', the currency does not exist.');
                        price.UnitPrice = product.Bundle_Price__c * c.ConversionRate; 
                        price.Product2Id = product.Id;
                        price.CurrencyISOCode = c.IsoCode; // 
                        price.Pricebook2Id = PriceBookId;
                        price.IsActive = TRUE;
                        newPrices.add(price);
                    }else if(product.Family == 'Software'){
                        System.debug('For product id ' + product.Id + ', the currency does not exist.');
                        price.UnitPrice = product.Software_Price__c * c.ConversionRate; 
                        price.Product2Id = product.Id;
                        price.CurrencyISOCode = c.IsoCode; // 
                        price.Pricebook2Id = PriceBookId;
                        price.IsActive = TRUE;
                        newPrices.add(price);
                    }
                    
                } 
                else
                {                                           //Exist in this currency: Update with new StandardPrice
                    System.debug('For product id ' + product.Id + ', the currency DOES exist.');
                    for(PricebookEntry l : loopList)        
                    {                                       //Loop through to find the unique Id for the PricebookEntry
                        if(l.CurrencyIsoCode == c.IsoCode)
                            price.Id = l.Id;
                    }
                    if(product.Family == 'Bundle'){
                        System.debug('product.Bundle_Price__c * c.ConversionRate: ' + product.Bundle_Price__c * c.ConversionRate);
                        price.UnitPrice = product.Bundle_Price__c * c.ConversionRate;
                        price.IsActive = TRUE;
                        updatedPrices.add(price);
                    }else if(product.Family == 'Software'){
                        System.debug('product.Software_Price__c * c.ConversionRate: ' + product.Software_Price__c * c.ConversionRate);
                        price.UnitPrice = product.Software_Price__c * c.ConversionRate;
                        price.IsActive = TRUE;
                        updatedPrices.add(price);
                    }
                }
            }
        }
        
        System.debug('new prices:' + newPrices);
        System.debug('updated prices:' + updatedPrices);        
        insert newPrices;
        update updatedPrices;
    }
}
 


 

iaestemaniaesteman

Silly me, I've disabled the currencies, now that they are enabled I have tested everything and most of the things work fine.Except i am facing additional 2 problems:

1 When I add bundle line item to the product the bundle price updates but it doesn't change the value of the standard price in the pricebookentries. 

2 is error when I clone the product.

Error: Invalid Data. 
Review all error messages below to correct your data.
common.exception.SfdcSqlException: ORA-20119: ORA-06512: at "DOC.CPRICEBOOKENTRY", line 745 ORA-00001: unique constraint (CORE.AKPRICEBOOK_ENTRY) violated ORA-06512: at line 1 {call cPricebookEntry.insert_pricebookentries(?,?,?,?,?,?,?,?,?,?)} {call cPricebookEntry.insert_pricebookentries(?,?,?,?,?,?,?,?,?,?)}


I've  googled the solution and i found this https://help.salesforce.com/articleView?id=000199313&language=en_US&type=1

But doesn't help me much... :(

 

iaestemaniaesteman

After debuging when I create a new bundle_line_item__c record the value of the bundle_price__c is updated but that value doesn't affect the prices in the pricebook entry. Somehow it returns the old value even so the value is updated. This only occurs when I create a new bundle line item record.

Check error:

User-added image

iaestemaniaesteman

Ok so I found the solution partially the reason for the trigger not to work right and get me the old value was this code:

if (TriggerMonitor.runOnce()) { 
 //rest of code
}

The trigger runs once and because of that it doesn't update as it should. Now I need to find a way to handle the loops differently and still to figure out the code with the cloning of the product.
error of the cloning:

Error: Invalid Data. 
Review all error messages below to correct your data.
common.exception.SfdcSqlException: ORA-20119: ORA-06512: at "DOC.CPRICEBOOKENTRY", line 745 ORA-00001: unique constraint (CORE.AKPRICEBOOK_ENTRY) violated ORA-06512: at line 1 {call cPricebookEntry.insert_pricebookentries(?,?,?,?,?,?,?,?,?,?)} {call cPricebookEntry.insert_pricebookentries(?,?,?,?,?,?,?,?,?,?)}
monsterloomismonsterloomis
So - for unique constraint issue: the link you posted above details the issue pretty well around it: https://help.salesforce.com/articleView?id=000199313&language=en_US&type=1

Your trigger is trying to insert a pricebook entry that has already been inserted. Instead of creating a new PriceBookEntry in this trigger, can you try querying for the newly inserted pricebook entry and updating that instead? So for line 27, you'd want to refer to a collection you query outside the loop and then update that PBE instead of attempting to insert a new one. I'll admit that I haven't spent a lot of time with products and PBEs, so there may be factors and black box behaviors I'm not considering, but: it seems to me that if you're violating a unique constraint on insert, then you're attempting to create a record that already exists, and the best course of action would be to attempt to retrieve it first using the product2.id and pricebook id, and then update it rather than attempting to insert a new one.