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
Jill HedbergJill Hedberg 

Help change a trigger that converts products to assets to just update quantity the second time

Hi!

 

I have a trigger that converts products in an opportunity to assets in the account when the opportunity moves to the stage Closed Won. This part works good.

However, when a product is converted to an asset for that account it gets marked as Converted (Checkbox). If another opportunity (under the same account) with for example additional users for that same product is closed it does not add anything to the assets for the account today.

 

What I want it to do is: IF Product X is already converted to Asset X on the account - then update the quantity of existing Asset X from the last opportunity Closed Won + quantity from Product X for the newly Closed Won opportunity.

 

Old Asset X (Quantity10) + New Product X (Quantity 5) = Old Asset X (Quantity 15).

 

Help please! :)

 

My Trigger:

trigger CreateAssetonClosedWon on Opportunity (after insert, after update) {
     for(Opportunity o: trigger.new){ 
      if(o.isWon == true && o.HasOpportunityLineItem == true && o.RecordTypeId == '012200000004fxZ'){
         String opptyId = o.Id;
         OpportunityLineItem[] OLI = [Select Total_Price__c, Cost__c, Quantity, Revenue_Type__c, PricebookEntry.Product2Id, PricebookEntry.Product2.Name, Description, Converted_to_Asset__c  
                                      From OpportunityLineItem 
                                      where OpportunityId = :opptyId and Converted_to_Asset__c = false];
         Asset[] ast = new Asset[]{};
         Asset a = new Asset();
         for(OpportunityLineItem ol: OLI){
            a = new Asset();
            a.AccountId = o.AccountId;
            a.Product2Id = ol.PricebookEntry.Product2Id;
            a.Quantity = ol.Quantity;
            a.Price =  ol.Total_Price__c;
            a.PurchaseDate = o.CloseDate;
            a.Status = 'A6 - Closed Won';
            a.Description = ol.Description;
            a.Name = ol.PricebookEntry.Product2.Name;
            ast.add(a);
            ol.Converted_to_Asset__c = true;
       }
      update OLI; 
      insert ast;
     }
    }
}

 

Best Answer chosen by Admin (Salesforce Developers) 
sfdcfoxsfdcfox

Your code is also not bulkified, so it's a good thing you came here to get your code fixed.

 

Here's how you might go about that:

 

trigger x on opportunity (after update) {
  map<id,map<id,asset>> assets = new map<id,map<id,asset>>();
  set<id> prodids = new set<id>();
  opportunitylineitem[] lines = [select id, total_price__c, cost__c, quantity, revenue_type__c, pricebookentry.product2id, pricebookentry.product2.name, description, opportunityid from opportunitylineitem where opportunityid in :trigger.new and converted_to_asset__c = false];
  for(opportunitylineitem line:lines)
    prodids.add(line.pricebookentry.product2id);
  for(opportunity record:trigger.new)
    assets.put(record.accountid,new map<id,asset>());
  for(asset record:[select id, accountid, product2id, quantity, price, purchasedate, status, description, name from asset where accountid in :assets.keyset() and product2id in :prodids) {
    assets.get(record.accountid).put(record.product2id,record);
    if(record.quantity==null)
      record.quantity=0;
    if(record.price==null);
      record.price=0;
  }
  for(opportunitylineitem line:lines) {
    opportunity lineopp = trigger.newmap.get(line.opportunityid);
    asset ast = assets.get(lineopp.accountid).get(line.pricebookenry.product2id);
    if(ast==null)
      ast = new asset(quantity=0,price=0);
    ast.accountid = lineopp.accountid;
    ast.product2id = line.pricebookentry.product2id;
    ast.quantity += line.quantity;
    ast.price += line.total_price__c;
    ast.purchasedate = lineopp.closedate;
    ast.status = 'A6 - Closed Won';
    ast.description = line.description;
    ast.name = line.pricebookentry.product2.name;
    assets.get(lineopp.accountid).put(line.pricebookentry.product2id,ast);
  }
  asset[] updates = new asset[0];
  for(id accountid:assets.keyset())
    updates.addall(assets.get(accountid).values());
  upsert updates;
  for(opportunitylineitem line:lines)
    line.converted_to_asset__c = true;
  update lines;
}

I don't think it can get any less complex than that, so you're probably going to have to read the code a couple of times. I hope this helps.

All Answers

sfdcfoxsfdcfox

Your code is also not bulkified, so it's a good thing you came here to get your code fixed.

 

Here's how you might go about that:

 

trigger x on opportunity (after update) {
  map<id,map<id,asset>> assets = new map<id,map<id,asset>>();
  set<id> prodids = new set<id>();
  opportunitylineitem[] lines = [select id, total_price__c, cost__c, quantity, revenue_type__c, pricebookentry.product2id, pricebookentry.product2.name, description, opportunityid from opportunitylineitem where opportunityid in :trigger.new and converted_to_asset__c = false];
  for(opportunitylineitem line:lines)
    prodids.add(line.pricebookentry.product2id);
  for(opportunity record:trigger.new)
    assets.put(record.accountid,new map<id,asset>());
  for(asset record:[select id, accountid, product2id, quantity, price, purchasedate, status, description, name from asset where accountid in :assets.keyset() and product2id in :prodids) {
    assets.get(record.accountid).put(record.product2id,record);
    if(record.quantity==null)
      record.quantity=0;
    if(record.price==null);
      record.price=0;
  }
  for(opportunitylineitem line:lines) {
    opportunity lineopp = trigger.newmap.get(line.opportunityid);
    asset ast = assets.get(lineopp.accountid).get(line.pricebookenry.product2id);
    if(ast==null)
      ast = new asset(quantity=0,price=0);
    ast.accountid = lineopp.accountid;
    ast.product2id = line.pricebookentry.product2id;
    ast.quantity += line.quantity;
    ast.price += line.total_price__c;
    ast.purchasedate = lineopp.closedate;
    ast.status = 'A6 - Closed Won';
    ast.description = line.description;
    ast.name = line.pricebookentry.product2.name;
    assets.get(lineopp.accountid).put(line.pricebookentry.product2id,ast);
  }
  asset[] updates = new asset[0];
  for(id accountid:assets.keyset())
    updates.addall(assets.get(accountid).values());
  upsert updates;
  for(opportunitylineitem line:lines)
    line.converted_to_asset__c = true;
  update lines;
}

I don't think it can get any less complex than that, so you're probably going to have to read the code a couple of times. I hope this helps.

This was selected as the best answer
Jill HedbergJill Hedberg
Wow thanks! Looks a bit more complicated than what I had :)

I get an error with this though on the end (lines.converted_to_asset__c = true;)
Error: Compile Error: Initial term of field expression must be a concrete SObject: LIST<OpportunityLineItem> at line 36 column 5

I removed the s so that it was line.converted_to_asset__c=true;
Then I could save the trigger, but that might change the meaning of the code?

When trying to save products in an opportunity it gives me an error now:
Apex trigger CreateAssetonClosedWon caused an unexpected exception, contact your administrator: CreateAssetonClosedWon: execution of AfterUpdate caused by: System.SObjectException: SObject row was retrieved via SOQL without querying the requested field: OpportunityLineItem.OpportunityId: Trigger.CreateAssetonClosedWon: line 17, column 1

This is line 17: opportunity lineopp = trigger.newmap.get(line.opportunityid);

Any idea of why and how to fix it?
cl0s3rcl0s3r

You hae a typo in your code:

Change --  lines.converted_to_asset__c = true;

to---             line.converted_to_asset__c = true;

trigger x on opportunity (after update) {
  map<id,map<id,asset>> assets = new map<id,map<id,asset>>();
  set<id> prodids = new set<id>();
  opportunitylineitem[] lines = [select id, total_price__c, cost__c, quantity, revenue_type__c, pricebookentry.product2id, pricebookentry.product2.name, description from opportunitylineitem where opportunityid in :trigger.new and converted_to_asset__c = false];
  for(opportunitylineitem line:lines)
    prodids.add(line.pricebookentry.product2id);
  for(opportunity record:trigger.new)
    assets.put(record.accountid,new map<id,asset>());
  for(asset record:[select id, accountid, product2id, quantity, price, purchasedate, status, description, name from asset where accountid in :assets.keyset() and product2id in :prodids) {
    assets.get(record.accountid).put(record.product2id,record);
    if(record.quantity==null)
      record.quantity=0;
    if(record.price==null);
      record.price=0;
  }
  for(opportunitylineitem line:lines) {
    opportunity lineopp = trigger.newmap.get(line.opportunityid);
    asset ast = assets.get(lineopp.accountid).get(line.pricebookenry.product2id);
    if(ast==null)
      ast = new asset(quantity=0,price=0);
    ast.accountid = lineopp.accountid;
    ast.product2id = line.pricebookentry.product2id;
    ast.quantity += line.quantity;
    ast.price += line.total_price__c;
    ast.purchasedate = lineopp.closedate;
    ast.status = 'A6 - Closed Won';
    ast.description = line.description;
    ast.name = line.pricebookentry.product2.name;
    assets.get(lineopp.accountid).put(line.pricebookentry.product2id,ast);
  }
  asset[] updates = new asset[0];
  for(id accountid:assets.keyset())
    updates.addall(assets.get(accountid).values());
  upsert updates;
  for(opportunitylineitem line:lines)
 Typo>>>   line.converted_to_asset__c = true;
  update lines;
}

 

sfdcfoxsfdcfox
cl0s3r is right, I meant to write line instead of lines on the original code. I'll fix that in the original post. I also forgot the OpportunityId field on the query. See my previous post for updates.
Jill HedbergJill Hedberg

Woohoo, success! Thank you so much! I need this to only apply for one opportunity record type though to make it perfect - RecordTypeId = '012200000004fxZ'. Where would I add that in the code?

 

Also, could you help me with the test class aswell? My old one does not work now and I don't know how to fix it. Please, pleeeease, pleeeeeease! :)

sfdcfoxsfdcfox
trigger x on opportunity (after update) {
  map<id,map<id,asset>> assets = new map<id,map<id,asset>>();
  set<id> prodids = new set<id>();
  opportunitylineitem[] lines = [select id, total_price__c, cost__c, quantity, revenue_type__c, pricebookentry.product2id, pricebookentry.product2.name, description, opportunityid from opportunitylineitem where opportunityid in :trigger.new and opportunity.recordtypeid = '012200000004fxZ' and converted_to_asset__c = false];
  for(opportunitylineitem line:lines)
    prodids.add(line.pricebookentry.product2id);
  for(opportunity record:trigger.new)
    assets.put(record.accountid,new map<id,asset>());
  for(asset record:[select id, accountid, product2id, quantity, price, purchasedate, status, description, name from asset where accountid in :assets.keyset() and product2id in :prodids) {
    assets.get(record.accountid).put(record.product2id,record);
    if(record.quantity==null)
      record.quantity=0;
    if(record.price==null);
      record.price=0;
  }
  for(opportunitylineitem line:lines) {
    opportunity lineopp = trigger.newmap.get(line.opportunityid);
    asset ast = assets.get(lineopp.accountid).get(line.pricebookenry.product2id);
    if(ast==null)
      ast = new asset(quantity=0,price=0);
    ast.accountid = lineopp.accountid;
    ast.product2id = line.pricebookentry.product2id;
    ast.quantity += line.quantity;
    ast.price += line.total_price__c;
    ast.purchasedate = lineopp.closedate;
    ast.status = 'A6 - Closed Won';
    ast.description = line.description;
    ast.name = line.pricebookentry.product2.name;
    assets.get(lineopp.accountid).put(line.pricebookentry.product2id,ast);
  }
  asset[] updates = new asset[0];
  for(id accountid:assets.keyset())
    updates.addall(assets.get(accountid).values());
  upsert updates;
  for(opportunitylineitem line:lines)
    line.converted_to_asset__c = true;
  update lines;
}

You can just add the filter into the query (see how I did it, above).

 

@istest(seealldata=true)
static void test() {
  pricebook2 pb = [select id,isactive from pricebook2 where isstandard=true limit 1];
  if(!pb.isactive) {
    pb.isactive = true;
    update pb;
  }
  product2 pr = new product2(name='test',isactive=true);
  insert pr;
  pricebookentry pe = new pricebookentry(product2id=pr.id,pricebook2id=pb.id,usestandardprice=false,listprice=9.99,isactive=true);
  insert pe;
  account a = new account(name='test');
  insert a;
  opportunity o = new opportunity(name='test',closedate=date.today(),stagename='open',accountid=a.id,pricebook2id=pb.id,recordtypeid='012200000004fxZ');
  insert o;
  opportunitylineitem ol = new opportunitylineitem(opportunityid=o.id,quantity=1,pricebookentryid=pe.id,unitprice=9.99);
  insert ol;
  opportunity o2 = o.clone(false,false,false);
  insert o2;
  opportunitylineitem ol2 = new opportunitylineite(opportunityid=o2.id,quantity=2,pricebookentryid=pe.id,unitprice=9.99);
  insert ol2;
  asset aa = [select id,quantity,price,purchasedate,status,descriptoin,name from asset where accountid=:a.id];
  system.assertequals(3,aa.quantity);
  // other asserts here
}

This test method is only for positive assertion (e.g. it covers only conditions that produce code coverage). You should write similar test method for negative assertions (e.g. make sure it doesn't update assets when it's a different record type).

Jill HedbergJill Hedberg

Thank you for all your help! I love the way you explain things.

Just have to do some final testing but all seems to be working as I want it to, and my new pretty code will soon be ready to move to production :D

Jill HedbergJill Hedberg
Actually... just realized that it converts the products to assets as soon as I add them, and not until the stage of the opportunity is set to Closed won. So want to add isWon somehow. I thought I would be able to do that just after the record type id that I added, but only got errors.
Jill HedbergJill Hedberg
Never mind, got it in there myself ;)
sfdcfoxsfdcfox
That's good. I've had a busy day or two, but I'm sure it was a pretty easy fix, huh?