0

I am at a loss as to what is going on in my code. I fill a List with strings, run it through a for loop that adds some of the strings to a different list, and then my list is suddenly truncated to the last ~30 items on it.

It had over 100 in the first for loop, which I can verify by the logs. But the debug call in the second for loop only has the last 30 items of the list? Need some insight please. Code to insert pricebookentry was commented out to avoid log truncation.

List<string> itemList = itemLine.split('@');
for (String s : itemList) {
        // split into list of individual item data elements
        List<String> itemInfo = s.split('\\$');
        productNames.add(itemInfo[5]);
        system.debug(itemInfo[5]);

    }
    List<Product2> p2s = [SELECT Id, Name FROM Product2 WHERE Name IN :productNames];

    /*for (String s : itemList) {
        // split into list of individual item data elements
        List<String> itemInfo = s.split('\\$');

        // find the product2 in the list of them
        Product2 p = new Product2();
        for (Product2 i : p2s) {
            if (string.valueOf(i.Name) == itemInfo[5]) {
                p = i;
            }
        }

        // create custom price book entry
        PriceBookEntry pbe = new PriceBookEntry();
        pbe.Product2Id = p.Id;
        pbe.Pricebook2Id = pb.Id;
        pbe.UnitPrice = Decimal.valueof(itemInfo[2]);
        pbe.IsActive = true;
        //insert pbe;
        pbes.add(pbe);

    }

    insert pbes;*/

    // itemlist somehow loses every item except the last ~30 between this for loop and the previous one????
    for (String il : itemList) {
        // split into list of individual item data elements
        List<String> itemInfo = il.split('\\$');
        system.debug(LoggingLevel.ERROR, itemInfo[5]);
        // find the product2 in the list of them
        Product2 p = new Product2();
        for (Product2 x : p2s) {
            if (string.valueOf(x.Name) == itemInfo[5]) {
                p = x;
            }
        }

        // find the product2 in the list of them
        PriceBookEntry pbe = new PriceBookEntry();
        for (PriceBookEntry i : pbes) {
            //system.debug(p.Id);
            //system.debug(i.Id);
            if (p.Id == i.Id) {
                pbe = i;
            }
        }

        // create opportunity line item
        OpportunityLineItem oli = new OpportunityLineItem();
        oli.OpportunityId = opp.Id;
        oli.PricebookEntryId = pbe.Id;
        oli.UnitPrice = Decimal.valueOf(itemInfo[2]);
        oli.Quantity = Integer.valueOf(itemInfo[1]);
        oli.Free_Goods__c = Integer.valueOf(itemInfo[4]);
        //insert oli;
        olis.add(oli);

    }

    insert olis;

UPDATE: Still not sure why the lists misbehaved, but per cropredy's suggestions I rewrote the code using maps where possible. This got rid of the loop bug where it truncated the List. Also stopped using dynamic PriceBookEntries and PriceBooks since that made them clutter up in the org. Revised code below now handles 200+ products and can probably handle any number up until it hits the heap size limit.

    // lists that will be used for bulk insertions
    List<PriceBookEntry> pbes = new List<PriceBookEntry>();
    List<OpportunityLineItem> olis = new List<OpportunityLineItem>();

    // maps to be used to reference data in bulkifying the PBE and Oppt line item insertions
    // all share a key of the item's P21 number
    Map<String,String> productInfoMap = new Map<String,String>();
    Map<String,Product2> productMap = new Map<String,Product2>();
    Map<String,Id> productIdMap = new Map<String,Id>();
    Map<String,PriceBookEntry> pbeMap = new Map<String,PriceBookEntry>();

    // make a list of the item names needed and then run a bulk SOQL query on them
    for (String s : itemList) {
        // split into list of individual item data elements
        List<String> itemInfo = s.split('\\$');
        // put <ProductName, Id> into the map
        productInfoMap.put(itemInfo[5], itemInfo[0]);

    }

    // put product2 Id and product2 instance into maps
    for (Product2 p : [SELECT Id, Name FROM Product2 WHERE Name IN :productInfoMap.keySet()]) {
        productIdMap.put(p.Name, p.Id);
        productMap.put(p.name, p);
    }

    // find the pricebookentries in the standard price book for our products
    for (PriceBookEntry pbe : [SELECT Id, Product2.Name, PriceBook2.Name FROM PriceBookEntry WHERE Product2.Name IN :productIdMap.keySet() AND Pricebook2.Name='Standard Price Book']) {
        pbeMap.put(String.valueOf(pbe.Product2.Name), pbe);
    }

    // find the data needed from the maps to create the opportunitylineitems then insert them
    for (String s : itemList) {
        List<String> itemInfo = s.split('\\$');
        // find the product2 in the map
        Product2 p = new Product2();
        for (String name : productIdMap.keySet()) {
            if (name == itemInfo[5]) {
                p = productMap.get(name);
            }
        }

        // find the pricebookentry in the map
        PriceBookEntry pbe = new PriceBookEntry();
        for (String name : pbeMap.keySet()) {
            if (name == itemInfo[5]) {
                pbe = pbeMap.get(name);
            }
        }

        // create opportunity line item
        OpportunityLineItem oli = new OpportunityLineItem();
        oli.OpportunityId = opp.Id;
        oli.PricebookEntryId = pbe.Id;
        oli.UnitPrice = Decimal.valueOf(itemInfo[2]);
        oli.Quantity = Integer.valueOf(itemInfo[1]);
        oli.Free_Goods__c = Integer.valueOf(itemInfo[4]);
        olis.add(oli);
    }
    insert olis;
  • Are you sure that all productNames will return a result ? Looks like you are filtering on list of say 100 items and returned a smaller set of results than you expect. Place an assert to confirm your expectation ie soql returns a product for each of the productnames. Result.size should = productnames.size – CloudHugger Jun 8 '16 at 22:02
1

This could be simpler by using maps

List<string> itemList = itemLine.split('@'); //vbl is really itemLines?
map<String,String> pNameToItemMap = new map<String,String>();
for (String item : itemList) {
        // locate productname in 6th token and save in map of pName to item
        pNameToItemMap.put(item.split('\\$')[5],s)        
}
// build map of productname to its PBE
map<String,PriceBookEntry> pNameToPBEMap = new map<String,PriceBookEntry>();
for (PricebookEntry pbe: [select id, product2.name from PricebookEntry 
                               where product2.name in :pNameToItemMap.keySet())
   pNameToPbeMap.put(pbe.product2.name,pbe);

OpportunityLineItem[] olis = new List<OpportunityLineItem>();
for (String item: itemList) { // build an OLI for each item in itemList
   String[] itemProps = item.split('\\$');  
   olis.add(new OpportunityLineItem (
              opportunityId = opp.id,
              pricebookEntryId = pbeMap.get(itemProps[5].id,
              unitPrice = Decimal.valueOf(itemProps[2],
              ...
           ));
}

insert olis;

I've omitted error handling (item product not in database) and by the looks of your code, you are doing this for only one Opportunity so it is not bulkified to handle multiple opportunities.

It also assumes one pricebook and uni-currency as in a multi-pricebook and/or multicurrency org, there can be many PBEs for the same product2 - but the ideas are still valid.

It also assumes that the items' product names match in a case sensitive way the product names in Product2. If not, make sure the map keys are all consistently cased and any lookup in the map transforms the argument to the right case.

  • Thanks for the demo, but I still do not understand why my original code does not work right. I take a list, loop through it without changing it, then loop through it again and the list is altered? I thought that the scope of these variables was the loop itself, but somehow the original list is altered. So you can have multiple PBEs for the same item on the same pricebook? I thought you had to create a new pricebook each time and now my sandbox is cluttered with them. – deflator Jun 9 '16 at 13:56
  • @deflator -- you are dynamically creating PBEs? By convention there should only be one PBE per pricebook-currency-product2 combination . I agree with your list issue mystery but I like to approach the problem by trying to simplify it first and then working from there. – cropredy Jun 9 '16 at 14:23
  • Yes I am dynamically creating PBEs and the Pricebook they go into. This leaves me the need to write cleanup code for all the Pricebooks I am creating. I am doing this because this is a quoting app and the item's price can be changed by the user for each quote. I will rewrite it using maps – deflator Jun 9 '16 at 14:42
  • @deflator - while I can't comment intelligently on your business app, normally, one would define the SKUs in advance with a list price (defined on the PBE) and allow the user to set the selling price (defined on the OLI or QI). This way you can compare list to net. I've also seen folks set list prices to 0.00 for all products and let sales price be user-entered to any value. But dynamic PBE and pricebook creation looks like a mess-o-trouble – cropredy Jun 9 '16 at 16:54
  • I think I understand. I am very new to Apex and I thought the price came from the PBE. I see now that there just has to be a PBE existent for the product, which there is in our standard PB, so I do not have to create a new PB and PBEs for each OLI. I can just lookup the standard PB's PBE for the product and then set the price on the OLI I create from it, correct? – deflator Jun 9 '16 at 17:47

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.