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
JakesterJakester 

Stuck on simple (?) trigger update on Opportunity/OpportunityLineItem

Hi All,

What I'm trying to do is set a boolean field on the Opportunity called Has_Kiosk_Hardware__c to True if a product (which is of type "Kiosk Hardware" according to my custom Product_Group__c picklist on the Product2 object) is added to that opportunity.

Here's what I came up with:

Code:
trigger updateHasKioskHardware on OpportunityLineItem (after insert) {
For (OpportunityLineItem nOLI:Trigger.new)
//
{If (nOLI.pricebookentry.product2.product_group__c == 'Kiosk Hardware')
{
nOLI.Opportunity.Has_Kiosk_Hardware__c = True;
}
}
}

It compiles and I get no errors when I add products, but it does not update the Has_Kiosk_Hardware__c when I add a kiosk hardware product. To troubleshoot, I changed the If statement to always be true like so:

Code:
trigger updateHasKioskHardware on OpportunityLineItem (after insert) {
For (OpportunityLineItem nOLI:Trigger.new)
//
{If (True)
{
nOLI.Opportunity.Has_Kiosk_Hardware__c = True;
}
}
}

And I got this error sent via email:
updateHasKioskHardware: execution of AfterInsert

caused by: System.NullPointerException: Attempt to de-reference a null object

Trigger.updateHasKioskHardware: line 6, column 8

What am I doing wrong?

ps Sorry for the double-post, but I after I initially replied in that thread I got concerned that it would be buried there and not get noticed, so I created a new post with the question.
TehNrdTehNrd
Since your trigger is on the OLI object you must update your Opportunity(s) with a separate DML statement:.

Code:
trigger updateHasKioskHardware on OpportunityLineItem (after insert) {

Set<Id> oppIds = new Set<Id>();
List<Opportunity> oppsToUpdate = new List<Opportunity>()l


for(OpportunityLineItem nOLI:Trigger.new){
//first, can you debug this line, I'm not even sure if this is possible
system.debug('group ' + nOLI.pricebookentry.product2.product_group__c);

if(nOLI.pricebookentry.product2.product_group__c == 'Kiosk Hardware'){
oppIds.add(nOLUI.OpportunityId);
}
}

for(Opportunity opp : [select Id, Has_Kiosk_Hardware__c from Opportunity where ID IN :oppIds]){
opp.Has_Kiosk_Hardware__c = true;
oppsToUpdate.add(opp);
}

if(oppsToUpdate.size() > 0){
update oppsToUpdate;
}

}




Message Edited by TehNrd on 07-17-2008 04:58 PM
JakesterJakester

Wow - I just learned a TON from that sample code - thank you!!

You were right, though, the debug statement you put in there returned a null:

Beginning updateHasKioskHardware on OpportunityLineItem trigger event AfterInsert for 00kR0000001Vcqp 20080718033418.989:Trigger.updateHasKioskHardware: line 5, column 14: SelectLoop:LIST:SOBJECT:OpportunityLineItem 20080718033418.989:Trigger.updateHasKioskHardware: line 7, column 14: group null 20080718033418.989:Trigger.updateHasKioskHardware: line 14, column 14: SelectLoop:LIST:SOBJECT:Opportunity 20080718033418.989:Trigger.updateHasKioskHardware: line 14, column 36: SOQL query with 0 rows finished in 4 ms

Any suggestions for how I can get around this?

TehNrdTehNrd
This should do the trick:

Code:
trigger updateHasKioskHardware on OpportunityLineItem (after insert) {
     
 Set<Id> oppIds = new Set<Id>();
 Set<ID> pbeIds = new Set<Id>()
 List<Opportunity> oppsToUpdate = new List<Opportunity>()l
    
 for(OpportunityLineItem nOLI:Trigger.new){
     pbeIds.add(oli.pricebookentryid);
 }
 
 Map<Id, PricebookEntry> entries = new Map<Id, PricebookEntry>([select product2.product_group__c from pricebookentry where id in :pbeIds]);

 for(OpportunityLineItem nOLI:Trigger.new){
  //lets get the product family from the map
  String family = entries.get(oli.pricebookEntryId).product2.Product_Group__c;
    
  if(family == 'Kiosk Hardware'){
     oppIds.add(nOLI.OpportunityId);
  }
 }
  
 for(Opportunity opp : [select Id, Has_Kiosk_Hardware__c from Opportunity where ID IN :oppIds]){
  opp.Has_Kiosk_Hardware__c = true;
  oppsToUpdate.add(opp);
 }
  
 if(oppsToUpdate.size() > 0){
  update oppsToUpdate;
 }
  
}

 

JakesterJakester

ZOMG - no wonder they call you the Super Contributor! You are a genius! Thank you so much - I'm doing a little happy dance at my desk right now!

You totally rock, and I really, really appreciate your help!

In case anyone else wants to follow along, I had to make a few super-tiny edits to your code (adding semicolons and changing a few "oli"s to "nOLI"s):

Code:
trigger updateHasKioskHardware on OpportunityLineItem (after insert) {
     
 Set<Id> oppIds = new Set<Id>();
 Set<ID> pbeIds = new Set<Id>();
 List<Opportunity> oppsToUpdate = new List<Opportunity>();
    
 for(OpportunityLineItem nOLI:Trigger.new){
     pbeIds.add(nOLI.pricebookentryid);
 }
 
 Map<Id, PricebookEntry> entries = new Map<Id, PricebookEntry>([select product2.product_group__c from pricebookentry where id in :pbeIds]);

 for(OpportunityLineItem nOLI:Trigger.new){
  //lets get the product family from the map
  String family = entries.get(nOLI.pricebookEntryId).product2.Product_Group__c;
    
  if(family == 'Kiosk Hardware'){
     oppIds.add(nOLI.OpportunityId);
  }
 }
  
 for(Opportunity opp : [select Id, Has_Kiosk_Hardware__c from Opportunity where ID IN :oppIds]){
  opp.Has_Kiosk_Hardware__c = true;
  oppsToUpdate.add(opp);
 }
  
 if(oppsToUpdate.size() > 0){
  update oppsToUpdate;
 }
  
}


 

TehNrdTehNrd
Haha, glad I could help.

My bad on the syntax as I just typed it up in notepad.
JakesterJakester

As proof that no good deed goes unpunished, I have another question...

The trigger is working perfectly, but I actually have 3 checkboxes on the opportunity: Has_Kiosk_Hardware__c, Has_Kiosk_Software__c, and Has_Web_Software__c, and I really need the appropriate box checked when a product is added. I got as far as updating the IF statement to check for all three product groups, but I'm not sure where to go next. If you're sick of me I totally understand, but I'm sheepishly asking anyway!

Here's what I currently have:

Code:
trigger updateHasKioskHardware on OpportunityLineItem (after insert) {
     
 Set<Id> oppIds = new Set<Id>();
 Set<Id> pbeIds = new Set<Id>();
 List<Opportunity> oppsToUpdate = new List<Opportunity>();
    
 for(OpportunityLineItem nOLI:Trigger.new){
     pbeIds.add(nOLI.pricebookentryid);
 }
 
 Map<Id, PricebookEntry> entries = new Map<Id, PricebookEntry>([select product2.product_group__c from pricebookentry where id in :pbeIds]);

 for(OpportunityLineItem nOLI:Trigger.new){
  //lets get the product family from the map
  String productGroup = entries.get(nOLI.pricebookEntryId).product2.Product_Group__c;
    
  if((productGroup == 'Kiosk Software') || (productGroup == 'Kiosk Hardware') || (productGroup == 'Web Software')) {
     oppIds.add(nOLI.OpportunityId);
  }
 }
  
 for(Opportunity opp : [select Id, Has_Kiosk_Hardware__c, Has_Kiosk_Software__c, Has_Web_Software__c from Opportunity where ID IN :oppIds]){
  //I'm hoping there's a way right here to check for all 3 cases and update the appropriate checkbox
  opp.Has_Kiosk_Software__c = true;
  oppsToUpdate.add(opp);
 }
  
 if(oppsToUpdate.size() > 0){
  update oppsToUpdate;
 }
  
}


 

TehNrdTehNrd
I think this should work. There may be technically more efficient way of doing this but it should work:

Code:
trigger updateHasKioskHardware on OpportunityLineItem (after insert) {
     
 Set<Id> oppIds = new Set<Id>();
 Set<Id> pbeIds = new Set<Id>();
 List<Opportunity> oppsToUpdate = new List<Opportunity>();
 
 //Create three sets so we can track what products are on what opps.
 Set<Id> kioskSoftwareOpps = new Set<Id>();
 Set<Id> kioskHardwareOpps = new Set<Id>();
 Set<Id> webSoftwareOpps = new Set<Id>();
    
 for(OpportunityLineItem nOLI:Trigger.new){
     pbeIds.add(nOLI.pricebookentryid);
 }
 
 Map<Id, PricebookEntry> entries = new Map<Id, PricebookEntry>([select product2.product_group__c from pricebookentry where id in :pbeIds]);

 for(OpportunityLineItem nOLI:Trigger.new){
  //lets get the product family from the map
  String productGroup = entries.get(nOLI.pricebookEntryId).product2.Product_Group__c;
  
  //You'll want to break these up so you can keep track of what products are on what opp. 
  if(productGroup == 'Kiosk Software') {
     oppIds.add(nOLI.OpportunityId);
     kioskSoftwareOpps.add(nOLI.OpportunityId);
  }
  
  if(productGroup == 'Kiosk Hardware'){
     oppIds.add(nOLI.OpportunityId);
     kioskHardwareOpps.add(nOLI.OpportunityId);
  }
  
  if(productGroup == 'Web Software') {
     oppIds.add(nOLI.OpportunityId);
     webSoftwareOpps.add(nOLI.OpportunityId);
  }
 }
  
 for(Opportunity opp : [select Id, Has_Kiosk_Hardware__c, Has_Kiosk_Software__c, Has_Web_Software__c from Opportunity where ID IN :oppIds]){
  //Here we check the sets above to see if the opps contains one of the product families
  if(kioskSoftwareOpps.contains(opp.Id){
     Has_Kiosk_Software__c = true;
  }
  
  if(kioskHardwareOpps.contains(opp.Id){
     Has_Kiosk_Hardware__c = true;
  }
  
  if(webSoftwareOpps.contains(opp.Id){
     Has_Web_Software__c = true;
  }
     
  oppsToUpdate.add(opp);
 }
  
 if(oppsToUpdate.size() > 0){
  update oppsToUpdate;
 }
  
}

Perhaps another happy dance is in order?
http://www.youtube.com/watch?v=GD_5Rtc1gk8


Message Edited by TehNrd on 07-18-2008 10:20 AM
JakesterJakester
Dude. You are absolutely, completely, AMAZING! It works perfectly. I loved the youtube link, too - I'll be sending that off to everyone around the office when I take all the credit for your work, er, um, announce that we have this trigger in place. :smileyvery-happy:
 
Are you going to be at Dreamforce? I'd love to shake your hand! I'm so jealous of people such as yourself that can crank this stuff out like it's nothing. Thank you, thank you, thank you!
 
Now I'm off to stumble through writing a unit test for this bad-boy...
TehNrdTehNrd
Haha, you're too kind.

I haven't registered yet but I do plan on attending Dreamforce this year. PM me closer to the event and maybe we can find time to meet up.

Tests can be tricky so report back if you hit any issues.

-Jason


Message Edited by TehNrd on 07-18-2008 11:00 AM
JakesterJakester

Right on Jason-

Thanks again, and I'll definitely PM as the event gets closer!

JakesterJakester

I got the unit test written! Wahoo! It's deployed and works beautifully.

Ok, I really hate to ask this, and I know I'm really looking a gift horse in the mouth here, BUT...

What are your thoughts on making it so that, if a product gets deleted, the checkbox gets unchecked? Please don't feel like you have to write complete code for me - you've already done soooo much - but I'm just wondering if you think I could somehow enhance this existing trigger or if I'd need to create another 'after delete' trigger.

Have I mentioned that you rock?

TehNrdTehNrd
Ya.... I thought you might ask about unchecking the boxes on delete. Only because we have something very similar and had to do the same thing. It is doable and you don't need a separate trigger. In your trigger you can use trigger.isbefore, isAfter......


Code:
trigger name on object (after insert, after delete) {

if(trigger.isInsert)
  //do insert logic
}

if(trigger.isDelete){
  //do delete logic
}

}

In terms of the logic I can reply later im more detail as it's getting near the end of the day and I'm getting cramped for time. Basically, after the delete you need to re-evaluate all of the lines (SOQL query on all OLI where oppID is of the olis delelted) and then re update the check box on the opp if necessary.

-Jason

JakesterJakester

Awesome, thanks again!

Crap on a stick - I started writing this and realized that it's not as simple as I was thinking, because if 2 products in the same group are on the opportunity then if one is deleted I don't want to uncheck the box. I think I'm going to need to do some sort of grouping/counting query or something, aren't I?

TehNrdTehNrd
Ok....happy dance #3?

http://www.youtube.com/watch?v=8pwfO8G5uIo

I think this should work. I haven't tested it but it should be good or at least pretty close. May need to clean up the variable names/syntax as I did it in notepad.

Code:
trigger aftertest on OpportunityLineItem (after Delete) {

 Set<Id> oppIds = new Set<Id>();
 Set<Id> pbeIds = new Set<Id>();
 
 //Create three sets so we can track what products are on what opps.
 Set<Id> kioskSoftwareOpps = new Set<Id>();
 Set<Id> kioskHardwareOpps = new Set<Id>();
 Set<Id> webSoftwareOpps = new Set<Id>();

 //------------variables above can be reused so only the logice below should go in the after delete part.
    
 //since this is after delete we need to use trigger.old and there are trigger.new is null
 for(OpportunityLineItem  oli : trigger.old){
     oppIds.add(oli.id);
 }
    
 //We need to go get all the olis for the opps from which one was deleted
 List<OpportunityLineItem> olis = [select Id, PricebookEntryId from OpportunityLineItem where OpportunityId IN :oppIds];
 
 for(OpportunityLineItem oli : olis){
  pbeIds.add(oli.pricebookEntryId);
 }
 
 Map<Id, PricebookEntry> entries = new Map<Id, PricebookEntry>([select product2.product_group__c from pricebookentry where id in :pbeIds]);
 
 for(OpportunityLineItem nOLI : olis){
  //lets get the product family from the map
  String productGroup = entries.get(nOLI.pricebookEntryId).product2.Product_Group__c;
  
  //You'll want to break these up so you can keep track of what products are on what opp. 
  if(productGroup == 'Kiosk Software') {
   oppIds.add(nOLI.OpportunityId);
   kioskSoftwareOpps.add(nOLI.OpportunityId);
  }
  
  if(productGroup == 'Kiosk Hardware'){
   oppIds.add(nOLI.OpportunityId);
   kioskHardwareOpps.add(nOLI.OpportunityId);
  }
  
  if(productGroup == 'Web Software') {
   oppIds.add(nOLI.OpportunityId);
   webSoftwareOpps.add(nOLI.OpportunityId);
  }
 }
 
 //now we re-evaluate
  for(Opportunity opp : [select Id, Has_Kiosk_Hardware__c, Has_Kiosk_Software__c, Has_Web_Software__c from Opportunity where ID IN :oppIds]){
   
   //before we re-evaluate set all the checkboxes to false
   opp.Has_Kiosk_Software__c = false;
   opp.Has_Kiost_Hardware__c = false;
   opp.Has_Web_Software__c = false;
      
   //Here we check the sets above to see if the opps contains one of the product families
   if(kioskSoftwareOpps.contains(opp.Id){
      Has_Kiosk_Software__c = true;
   }
   
   if(kioskHardwareOpps.contains(opp.Id){
      Has_Kiosk_Hardware__c = true;
   }
   
   if(webSoftwareOpps.contains(opp.Id){
      Has_Web_Software__c = true;
   }
   
   oppsToUpdate.add(opp);
  
  }
  
  if(oppsToUpdate.size() > 0){
   update oppsToUpdate;
  }

}

 
- Jason


Message Edited by TehNrd on 07-20-2008 11:13 PM