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
developernewbiedevelopernewbie 

Apex Code help on Triggers

Hi everyone,

 

I am new to the whole force.com platform and with the help of these discussion boards was able to write quite a bit of the code below.

 

here is what i am trying to achieve: I am trying to capture the unit price associated with each product on an opportunity level and display it in a new object (called- RevenueObject)

These product names are named like this:

eg. test/email/testproduct/ with Unit price say 400

 

I have to read these product names to search for key words like "/email" and then capture the revenue associated with it and update a counter field.

 

RevenueObject Fields-

 

FieldA - Counter number field ; FieldB - Currency; FieldC - Counter number field ; FieldD - Currency

 

The code below is able to read these product names and capture revenue and update counter. But here is the issue, it updates the revenue and counter fields (FieldA, FieldB, FieldC, FieldD) every time I save the opportunity- in turn double counting. I just want to capture one instance of it.

 

Currently RevenueObject UI shows (I saved the opportunity twice) -

FieldA - 2 (should be 1)

FieldB - $800 (should be $400)

 

Here is my code-

 

 

 

trigger RevenueProject2 on Opportunity (after insert, after update) {

String myString1 = '/email';
String myString2 = '/rb';

RevenueObject__c rO = [Select FieldA__c, FieldB__c, FieldC__c, FieldD__c from RevenueObject__c where Id='a0tW0000000JwFu'] ;
                              
List <RevenueObject__c> rOList = new List<RevenueObject__c>();

double i=0.0;
decimal j= 0.00;



 // This is to create a list of all product names 
 List<OpportunityLineItem>   Product = 
        [Select CustomProdName__c, UnitPrice
         from OpportunityLineItem 
         where Opportunity.StageName != 'Closed Lost' 
         ]; // excluded FOR update
        
       
// The for loop is to go through each product name and to categorize product types     
    
for(OpportunityLineItem prdct: Product) {
        
        
        if(prdct.CustomProdName__c.contains(myString1)) 
        {   
        if(rO.FieldA__c !=null || rO.FieldB__c !=null)
        	{ 
        	i = rO.FieldA__c; 
        	i++;                     //Updates Counter
        	rO.FieldA__c = i;       // Assigns counter to field  
        	j = prdct.UnitPrice;
        	rO.FieldB__c += j;
        	}
        	else{
        	rO.FieldA__c = 0;	// To avoid null exception
        	rO.FieldB__c = 0;
        	i = rO.FieldA__c; 
        	i++;                   //Updates Counter
        	rO.FieldA__c = i;     // Assigns counter to field  
        	j = prdct.UnitPrice;
        	rO.FieldB__c += j;
        	}
        }
        else
        if(prdct.CustomProdName__c.contains(myString2)) 
        {
            if(rO.FieldC__c !=null || rO.FieldD__c !=null)
            {
                i = rO.FieldC__c;  
        	i++;                    //Updates Counter
        	rO.FieldC__c = i;      // Assigns counter to field  
        	j = prdct.UnitPrice;
        	rO.FieldD__c += j;
            }
            else{
            rO.FieldC__c = 0;
            rO.FieldD__c = 0;
           i = rO.FieldC__c;  
        	i++;                    //Updates Counter
        	rO.FieldC__c = i;      // Assigns counter to field  
        	j = prdct.UnitPrice;
        	rO.FieldD__c += j;
            }
        }
    
    }// for loop
    
    // for updating field
     
     	update rO;
     
     
} // for trigger

 

In my revenue object I do have a Lookip relationship field to the opportunity. I understand that i should check for for opportunity id in the code..but I am having a hard time tryiing to figure this out.

 

I apoloize for the long post. but any help will be a bonus for me.

 

thanks in advance for al the help.

 

crop1645crop1645

developernewbie

 

You are missing a fundamental aspect of Triggers -- the Trigger.new list - please look at the APEX Developers Guide under Triggers - there are great examples there

 

All Triggers need to have  a main loop that is something like this:

 

for (Opportunity o: Trigger.new) {

  // do your work here on each triggered opportunity. No SOQL inside the loop


}

All triggers need to be bulkified - you can't assume they apply to only one Opportunity 

 

Since you want to get OpportunityLineItems for each triggered Opportunity, you'll need two loops like this:

 

Set<ID> oIdSet = new Set<ID> ();
for (Opportunity o: Trigger.new)
  oIdSet.add(o.id);

for (OpportunityLineItem prdct: [select id, customProdname__c, unitPrice, opportunityId from OpportunityLineItem where opportunityId IN: oIdSet]) {
 // do work for each OLI. The SOQL will retrieve all OLI for all triggered Opportunities 
}

// update RO

 I'm leaving out many details about your specific rules and business logic here, more to focus on the Trigger.new

 

Lastly - you do not want to use hard coded IDs for the RevenueObject__c -- IDs don't port between sandboxes and PROD. Use a SOQL select to find the relevant RevenueObject__c

developernewbiedevelopernewbie

Thanks so much for your reply and insight into this. As you can tell I am new to the platform and missed an integral part of the triggers. I am defnitely going back to the books and reading more about them.

 

I looked at your sugestions and accordingly modified my code to that. But now I get an error System.QueryException: List has more than 1 row for assignment to SObject: Trigger.RevenueProject2  when I try to save the opportunity.

 

The error is on line: RevenueObject__c rO = [Select FieldA__c, FieldB__c, FieldC__c, FieldD__c from RevenueObject__c] ;

 

I believe from my understanding that it has go to do with Id mapping. On the opportunity object, I created a new revenue object to check for FieldA and Field B.

 

trigger RevenueProject2 on Opportunity (after insert, after update) {

String myString1 = '/email';
String myString2 = '/rb';

Set<ID> oIdSet = new Set<ID> ();
         for(Opportunity opp: Trigger.new){
         oIdSet.add(opp.id);
         } 

RevenueObject__c rO = [Select FieldA__c, FieldB__c, FieldC__c, FieldD__c from RevenueObject__c] ;
                              
List <RevenueObject__c> rOList = new List<RevenueObject__c>();

double i=0.0;
decimal j= 0.00;



 // This is to create a list of all product names 
/*  List<OpportunityLineItem>   Product = 
        [Select CustomProdName__c, UnitPrice
         from OpportunityLineItem 
         where Opportunity.StageName != 'Closed Lost' 
         ]; */
        
       
// The for loop is to go through each product name and to categorize product types     
    
for(OpportunityLineItem prdct: [Select id,CustomProdName__c, UnitPrice, opportunityID from
    OpportunityLineItem where opportunityID IN: oIdset])

 {
        
        
        if(prdct.CustomProdName__c.contains(myString1)) 
        {   
        if(rO.FieldA__c !=null || rO.FieldB__c !=null)
        	{ 
        	i = rO.FieldA__c; 
        	i++;                     //Updates Counter
        	rO.FieldA__c = i;       // Assigns counter to field  
        	j = prdct.UnitPrice;
        	rO.FieldB__c += j;
        	}
        	else{
        	rO.FieldA__c = 0;	// To avoid null exception
        	rO.FieldB__c = 0;
        	i = rO.FieldA__c; 
        	i++;                   //Updates Counter
        	rO.FieldA__c = i;     // Assigns counter to field  
        	j = prdct.UnitPrice;
        	rO.FieldB__c += j;
        	}
        }
        else
        if(prdct.CustomProdName__c.contains(myString2)) 
        {
            if(rO.FieldC__c !=null || rO.FieldD__c !=null)
            {
                i = rO.FieldC__c;  
        	i++;                    //Updates Counter
        	rO.FieldC__c = i;      // Assigns counter to field  
        	j = prdct.UnitPrice;
        	rO.FieldD__c += j;
            }
            else{
            rO.FieldC__c = 0;
            rO.FieldD__c = 0;
           i = rO.FieldC__c;  
        	i++;                    //Updates Counter
        	rO.FieldC__c = i;      // Assigns counter to field  
        	j = prdct.UnitPrice;
        	rO.FieldD__c += j;
            }
        }
    
    }// for loop
    
    // for updating field
     
     	update rO;
     
     
} // for trigger

 

developernewbiedevelopernewbie

Hi Eric,

 

So i was trying to debug the error and i believe i was able to solve the error and i am able to now save the opportunity. But now the revenue object field still double counts, every time i save the opportunity. I need to make sure it is not double counting.

 

This is the updated code:

trigger RevenueProject2 on Opportunity (after insert, after update) {

String myString1 = '/email';
String myString2 = '/rb';

Set<ID> oIdSet = new Set<ID> ();
for(Opportunity opp: Trigger.new){
         oIdSet.add(opp.id);
} 

RevenueObject__c rO = [Select FieldA__c, FieldB__c, FieldC__c, FieldD__c from RevenueObject__c where Opportunity__c IN :oIdset] ;
                              
List <RevenueObject__c> rOList = new List<RevenueObject__c>();

double i=0.0;
decimal j= 0.00;



 // This is to create a list of all product names 
/*  List<OpportunityLineItem>   Product = 
        [Select CustomProdName__c, UnitPrice
         from OpportunityLineItem 
         where Opportunity.StageName != 'Closed Lost' 
         ]; */
        
       
// The for loop is to go through each product name and to categorize product types     
    
for(OpportunityLineItem prdct: [Select id,CustomProdName__c, UnitPrice, opportunityID from
    OpportunityLineItem where opportunityID IN: oIdset])

 {
        
        
        if(prdct.CustomProdName__c.contains(myString1)) 
        {   
        if(rO.FieldA__c !=null || rO.FieldB__c !=null)
        	{ 
        	i = rO.FieldA__c; 
        	i++;                     //Updates Counter
        	rO.FieldA__c = i;       // Assigns counter to field  
        	j = prdct.UnitPrice;
        	rO.FieldB__c += j;
        	}
        	else{
        	rO.FieldA__c = 0;	// To avoid null exception
        	rO.FieldB__c = 0;
        	i = rO.FieldA__c; 
        	i++;                   //Updates Counter
        	rO.FieldA__c = i;     // Assigns counter to field  
        	j = prdct.UnitPrice;
        	rO.FieldB__c += j;
        	}
        }
        else
        if(prdct.CustomProdName__c.contains(myString2)) 
        {
            if(rO.FieldC__c !=null || rO.FieldD__c !=null)
            {
                i = rO.FieldC__c;  
        	i++;                    //Updates Counter
        	rO.FieldC__c = i;      // Assigns counter to field  
        	j = prdct.UnitPrice;
        	rO.FieldD__c += j;
            }
            else{
            rO.FieldC__c = 0;
            rO.FieldD__c = 0;
           i = rO.FieldC__c;  
        	i++;                    //Updates Counter
        	rO.FieldC__c = i;      // Assigns counter to field  
        	j = prdct.UnitPrice;
        	rO.FieldD__c += j;
            }
        }
    
    }// for loop
    
    // for updating field
     
     	update rO;
     
     
} // for trigger

 Thanks again for all the help

crop1645crop1645

developernewbie

 

A few questions/points:

 

1. How many RevenueObjects are in your application design?

 

RevenueObject__c rO = [Select FieldA__c, FieldB__c, FieldC__c, FieldD__c from RevenueObject__c where Opportunity__c IN :oIdset] ;

 The above line presumes that oIdSet contains one and only one OpportunityId. This will never be true in a batch operation. Your schema implies a one:many relationship between Opportunity and RevenueObject__c.

 

Since several RevenueObject__c could have the same parent Opportunity, how do you know which RevenueObject to update?  Is there an implicit 1:1 relationship between RevenueObject__c and Opportunity?  For the moment, I'll assume you have a 1:1 relationship

 

Then to handle bulkification, you will need in lieu of the above line:

 

Map<ID,RevenueObject__c> oIdToRevenueObjectMap = new Map<ID,RevenueObject__c> ();

for (RevenueObject__c ro : [Select FieldA__c, FieldB__c, FieldC__c, FieldD__c from RevenueObject__c where Opportunity__c IN :oIdset] )
 oIdToRevenueObjectMap.put(ro.opportunity__c, ro); 

 

Now, onto the main loop

 

for(OpportunityLineItem prdct: [Select id,CustomProdName__c, UnitPrice, opportunityID from
    OpportunityLineItem where opportunityID IN: oIdset]) {..}

 In a bulkified trigger, this loop will retrieve OLI in unpredictable order, to wit: Oppo 1, OLI 1; Oppo 2 OLI 1; Oppo1, OLI 2, ... 

 

You need something like this:

 

for (Opportunity o : [select id, (select Select id,CustomProdName__c, UnitPrice, opportunityID from     OpportunityLineItem) from Opportunity where id IN : oIdSet)
  for (OpportunityLineItem prdct: o.opportunityLineItems) {
  // do work here

)
)

 This will go through each triggered Opportunity and within that outer loop, each of the triggered opportunity's OLI

 

In your work section, you need to locate the RevenueObject__c that applies to the outer loop Opportunity

 

RevenueObject__c ro = oIdToRevenueObjectMap.get(o.id);

 and this object, identified by inner loop variable ro, is the one to update. . At the end of each outer loop, you do:

 

roList.add(ro);

 and at the end of all the outer loop, you do 

 

try {update roList;}
catch (DMLException e) {//some error handling .. a topic in and of itself if you want to allow for partial successes in the batch or reject all Opportunities in the batch if any one fails}

 Double counting may be occurring if you have > 1 OLI per Opportunity -- 

 

Lastly, I urge you to add System.debug statements and examine the debug log after you execute - you'll quickly learn what is going on