[SalesForce] Trigger to get related list of Opportunity Line Items and create a new Opportunity

I am having some trouble with writing a trigger to get a related list of Opportunity Line Items IF the stage on Opportunity = Closed Won. Once I get this related list, I need to create a new opportunity with the account field on opportunity being equal to the dealership__c field on opportunity line item.
So for example, for dealership 'Test Dealership 1' (Which is an account type lookup) will have some opportunity line items associated with it in the related list on Opportunity like:

Order Item: Wifi
Quantity: 2
Ratecard: 30
Sales Price: 360
Total Price: 720
Dealership: Test Dealership 1

Order Item: Mobile
Quantity: 4
Ratecard: 69
Sales Price: 828
Total Price: 3312
Dealership: Test Dealership 1

And there can also be another Dealership 'Test Dealership 2'

Order Item: Laptop
Quantity: 8
Ratecard: 630
Sales Price: 630
Total Price: 5040
Dealership: Test Dealership 2

What needs to happen: One new opportunity created with account name = Dealership 1, this opportunity should have a related list of opportunity line items with products Wifi and Mobile.

Then another new opportunity should be created with account name = Dealership 2 with a related list of products containing Laptop opportunity line item.

This is my trigger code at the moment:

trigger ParentOppToChild on Opportunity (before insert)
{

    public void OnAfterUpdate(Opportunity[] updatedOpportunities)
    {
        Map<ID,OpportunityLineItem> lineItemDealership = [SELECT id, (SELECT ID, Dealership__c From OpportunityLineItems) FROM Opportunity];
        List<Opportunity> opptys = [SELECT Id, Name, (SELECT Id, PricebookEntry.Product2.Name FROM OpportunityLineItems) FROM Opportunity WHERE Id =: updatedOpportunities];
        Map<String, List<OpportunityLineItem>> opportunityLineItemsMap = new Map<String, List<OpportunityLineItem>>();

        for (Opportunity oppty : [SELECT Id, Name, (SELECT Id, Name, Dealership__c FROM OpportunityLineItems) FROM Opportunity WHERE Id =: updatedOpportunities])
        {
            if(oppty.StageName == 'Closed Won')
            {
                opportunityLineItemsMap.put(oppty.Id, oppty.OpportunityLineItems);
                system.debug(opportunityLineItemsMap);
            }
        }
    }      
}

Is this close to what I need to do?

Best Answer

Let me begin by saying that what you're trying to do is not a trivial task.

Next, this would seem to me to need to be an AfterInsert, AfterUpdate trigger. Even if your org creates Opportunities that are already Closed-Won, then you'd still want to create this as an After Insert so you'd have the necessary Id's needed to relate the child Opps to their parents. You just couldn't do that with a Before trigger.

I also see that you've declared a Method within a trigger. You can call a class method from within a trigger to run the logic for you, but you can't declare one. You need to write that code separately. You'd use something like the Tidy Bulkified Trigger Pattern where you'd pass Trigger.new and Trigger.Old and possibly other things to the Class.

Below, I've rewritten your trigger code to show you the gist of the logic you'll want to have for the basic trigger. You can always move that logic to an external class using the above reference as a guide.

trigger ParentOppToChild on Opportunity (AfterInsert, AfterUpdate)
{
   set<Id>oids = new set<Id>();

   For(Opportunity o : trigger.new)
   }
      if(o.StageName = 'Closed-Won' && trigger.oldmap.get(o.Id).StageName != 'Closed-Won`)
      {
         oids.add(o.Id);  
         //get opp.ids where stageName has just changed to Closed Won
       }
   }


   map<Id,Opportunity>OppOLI = new map<Id,Opportunity>([SELECT Id, Name, Account.Id, Account.Name, StageName, Amount,(SELECT Id,Quantity, PricebookEntry.Product2.Name, TotalPrice, Dealership__c FROM OpportunityLineItems FROM Opportunity WHERE Id IN : oids];
   //Have added more fields from OLI since you'll be duplicating them later. You'll probably want more.

   //Extract Line Items
   list<Account>Dealerships = new list<Account>();

   map<Id,Dealership__c>Opp2Deal = new map<Id, Dealership__c>();

   map<Id,Dealership__c>OLI2Deal = new map<Id, Dealership__c>();

   map<Id,OpportunityLineItem>OliMap = new map<Id,OpportunityLineItem>();

   For(Id OppId : OppOLI.keyset())
   {      
       Opportunity opp = OppOLI.get(OppId);

       List<OpportunityLineItem>OLI = opp.OpportunityLineItems;


      for (OpportunityLineItem OppLI : OLI) 
      {

         Account acc = new Account(Parent.Account = Opp.Account.Id, Id = OLI.Dealership__c);
         // I've assumed the Dealers are child Accounts & Dealership__c is a look-up to Acct ID.

         Dealerships.add(acc);

         Opp2Deal.put(opp.Id, Dealership__c);

         OLI2Deal.put(OppLI.Id, Dealership__c,);

         OliMap.put(OppLI.Id, OppLI);

      } 

   }


   //Create new Opps for each Child Opp/Dealer

   list<opportunity>toinsert = new list<opportunity>();
   list<opportunityLineItem>oliInsert = new list<OpportunityLineItem>();    
   Decimal Amt = 0.00 

   For(opportunity o : oids)
   {

      Opportunity op = new Opportunity(Account = Opp2Deal.get(o.Id), Name = o.Name + ' Child', StageName = 'Pending' );
      //Will leave details of Names and Stages to you

      for(OpportunityLineItem oLnI : OLI2Deal.keyset())
      {                     
          if(OLI2Deal.contains(op.Opp2Deal.get(o.Id)){

             for(Id olid : OLI2Deal.keyset())
             {
                if(OLI2Deal.get(olid).Dealership__c = Opp2Deal.get(o.Id)
                {
                   OpportunityLineItem oppLnItm = new OpportunityLineItem(Quantity = OliMap.get(olid).Quantity, PricebookEntry.Product2.Name = OliMap.get(olid).PricebookEntry.Product2.Name, TotalPrice = OliMap.get(olid).TotalPrice);
                   // creates new line item for each matching Dealership__c OLI record
                }
             }

              Amt += oppLnItm.TotalPrice;
              //Calculates total of line items to get Opp Amount

              oliInsrt.add(oppLnItm);
          }  
      }     

      op.Amount = Amt;

      Opp2OLI.put(op,oliInsrt);
      // add each OLI list to map separately to retrieve later to add Opp.ID

      toinsert.add(op);

      Amt = 0.00;
      //reset Amt to 0

    }

    //insert the new Opps
    Insert toinsert;

    integer i = 0;
    list<OpportunityLineItem>oliInlst = new list<OpportunityLineItem>();


    //Add Opp Ids to OLI's before insertion
    for(opportunity opty : toinsert)
    {

        list<OpportunityLineItem>oliInlst = Opp2OLI.get(toinsert[i]);
        //To be safe, using counter since lists in orig map didn't have Ids before insertion

        for(OpportunityLineItem opli : oliInlst)
        {

            opli.OpportunityId = opty.Id;

            OLIinsert.add(opli);
         }

        i++;
     }        

     //Insert related OLI records
     insert OLIinsert;

}// end of trigger

Note: The above code has not been tested or debugged. It is intended as a guide. Check your org for required fields to create these objects. Also be sure to add plenty of debug statements as you proceed.

Edited to add One limitation of this code is that multiple Opps containing the same Dealership__c could be problematic. They might wind up being combined into a single Opportunity even though they may have come from different parents.

This code doesn't create a Parent-Child relationship of the Opps as there isn't a field I know of that does that (didn't see one in the Object Reference). You could choose to create a Parent Opp field to relate them or use the Name of the parent Opp as I did above as the first part of the child Opp's Name to help create the reference. Having a clearly defined Parent-child relationship could be used to solve any issues related to mulitple Opps hitting the trigger containing the same Dealership__c in it's record not being combined in the same new Opportunity.

The only other way I know of around the issue be to use hash code which is an advanced coding method. I strongly suspect introducing hash methods would make it very difficult for you to debug or modify your code in the future.

Related Topic