Better Management of Opportunity Line Items with SKUID

I’m trying to improve the entry of products (opportunity line items) to opportunities with a custom SKUID page. Instead of the standard Add-product pop-up I just want to add rows to a table and select the product with a drop down. I’ve created a pricebookentry model that selects the possible products and render it a picklst. So when a new line is added to the table the product can be selected in the picklist. Works great and is much simpler for the user when there is a limited amount products to select from. However I’d like the List price for the product to be updated when the selection is made. As it is the list price is only displayed when the opplineitem is saved. I assume that what is needed is a In-Line Component that intercepts changes of the pricebookentry field and looks up the list price. A guide on how to create such a component would be very appreciated.

Also noticed that on first save if I enter a Salesprice of 100 and a Quantity of 10 the List Price/Sales Price/Total Price will display undefined plus the correct values on first save and then the text undefined will disappear on first page refresh

Okay, here’s an example of how this would be done in a single-currency org. In a multi-currency org, it may be necessary to also update the ‘CurrencyIsoCode’ of the current line item based on the PricebookEntry’s currency code. 1. Add a ‘PricebookEntries’ model to your page, retrieving the fields displayed in the image below: (notice that the ‘UnitPrice’ field on the PricebookEntry model was retrieved — this is very important. We will be using this field in a render snippet later on). 2. Add corresponding fields to your OpportunityLineItems model: PricebookEntryId PricebookEntry.Product2Id PricebookEntry.Product2.Name PricebookEntry.UnitPrice 3. Add a custom snippet to your Page’s JavaScript Resources Go to the Resources tab, JavaScript, and add a new Resource of type "Inline (Snippet). Name this Resource “pricebookEntryRenderer”, and use the following content for its body:

var field = arguments[0], value = arguments[1], renderer = skuid.ui.fieldRenderers[field.metadata.displaytype], mode = field.mode; // Get the pricebook entries model we are using for the renderer var pricebookEntriesModel = skuid.model.getModel(field.options.optionmodel); if (mode == 'edit') { // Specify that we want to display our field as a Picklist field.options.type = 'REFPICK'; } // Run standard renderer for the current mode // (applies to read/edit mode) renderer[mode](field,value); // Function that will update the value of our LineItem row's UnitPrice field // based on the selected PricebookEntry's ListPrice / UnitPrice var updateUnitPriceField = function(){ // Find the selected PricebookEntryId var newEntryId = field.model.getFieldValue(field.row,'PricebookEntryId'); // Find the corresponding PricebookEntry record var entry = pricebookEntriesModel.getRowById(newEntryId); var newPrice = pricebookEntriesModel.getFieldValue(entry,'UnitPrice'); // Force updates of the UnitPrice (SalesPrice) field //with the newly-selected PricebookEntry's UnitPrice field field.model.updateRow(field.row,'UnitPrice',newPrice); // Rerender the UnitPrice field in the row, // if that field has been rendered. var item = field.editor.lists[0].renderedItems[field.row.Id]; if (item) { $j.each(item.fields,function(i,f){ if (f.id == 'UnitPrice') { f.render(); return false; } }); } }; if (mode == 'edit') { // If we do not yet have a value for our UnitPrice field, // update it. if (!field.model.getFieldValue(field.row,'UnitPrice')) { updateUnitPriceField(); } // Attach an event handler to our field's <select> picklist element var select = field.element.find('select'); select.on('change',function(){ updateUnitPriceField(); }); } 

4. Add the “PricebookEntryId” field to your Line Items table 5. Customize the “Basic” Properties on the PricebookEntryId field For Basic Properties, do the following: a. Change the field label to “Product” b. Mark the field as “Required” c. Set Field Renderer to “Custom”, and Render Snippet to “pricebookEntryRenderer” 6. Customize the “Advanced” Properties on the PricebookEntryId field For Advanced Properties, do the following: a. Set Option Source to “Model” b. Set Option Model to “PricebookEntries”. c. Set the DisplayTemplate to “{{Product2.Name}} (List: {{UnitPrice}})”. This will display both the PricebookEntry’s related Product’s name, and the PricebookEntry’s Unit Price. Displaying the UnitPrice here is optional, but you can ONLY display fields that you have added to both your OpportunityLineItems model and your PricebookEntries model. 7. Save and you’re done!

Thanks Zack. Tried it without succés on my page. Everything works ok without the autoupdate. Did a new model exactly according to your guide but still no luck. Multicurrency shouldn’t be an issue as I have a filter on the priceBookEntry model that says only pricebookentries with the same currency as the opp. Otherwise I would get multiple copies of the products in the picklist (one per currency). I get the picklist but the price is not updated after making a selection. If I enter a price and save the field Sales price shows “Undefined USD 1” if I enter a price of 1. When i refresh the text undefined disappears. It might be a problem with the org so I’ll try it in a clean org and see if that makes a difference. I’m attaching my XML in case you can spot something wrong: Maybe you can try and paste it in your org and see if it works there. {{Name}} {{Name}} {{Model.Label}} Product var field = arguments[0], value = arguments[1], renderer = skuid.ui.fieldRenderers[field.metadata.displaytype], mode = field.mode; if (mode == ‘edit’) { // Specify that we want to display our field as a Picklist field.options.type = ‘REFPICK’; } // Run standard renderer for the current mode // (applies to read/edit mode) renderermode; // Function that will update the value of our LineItem row’s UnitPrice field // based on the selected PricebookEntry’s ListPrice / UnitPrice var updateUnitPriceField = function(){ var newPrice = field.model.getFieldValue(field.row,‘PricebookEntry.UnitPrice’); // Force updates of the UnitPrice (SalesPrice) field //with the newly-selected PricebookEntry’s UnitPrice field field.model.updateRow(field.row,‘UnitPrice’,newPrice); // Rerender the UnitPrice field in the row, // if that field has been rendered. var item = field.editor.lists[0].renderedItems[field.row.Id]; if (item) { $j.each(item.fields,function(i,f){ if (f.id == ‘UnitPrice’) { f.render(); return false; } }); } }; if (mode == ‘edit’) { // If we do not yet have a value for our UnitPrice field, // update it. if (!field.model.getFieldValue(field.row,‘UnitPrice’)) { updateUnitPriceField(); } // Attach an event handler to our field’s

more images . Did not see that image upload worked on the other post

Peter, I think that the issue does actually have to do with the Currency, i’ve seen this in other orgs before. Here’s how to fix, I think: 1. Make sure that the “CurrencyIsoCode” field is added to your PricebookEntries model, and that the PricebookEntry.CurrencyIsoCode field is added to your OpportunityLineItems model. 2. Change the snippet code to this:

var field = arguments[0], value = arguments[1], renderer = skuid.ui.fieldRenderers[field.metadata.displaytype], mode = field.mode; if (mode == 'edit') { // Specify that we want to display our field as a Picklist field.options.type = 'REFPICK'; } // Run standard renderer for the current mode // (applies to read/edit mode) renderer[mode](field,value); // Function that will update the value of our LineItem row's UnitPrice field // based on the selected PricebookEntry's ListPrice / UnitPrice var updateUnitPriceField = function(){ var newPrice = field.model.getFieldValue(field.row,'PricebookEntry.UnitPrice'); var newCurrency = field.model.getFieldValue(field.row,'PricebookEntry.CurrencyIsoCode'); // Force updates of the UnitPrice (SalesPrice) field //with the newly-selected PricebookEntry's UnitPrice field field.model.updateRow(field.row,'UnitPrice',newPrice); if (newCurrency) { field.model.updateRow(field.row,'CurrencyIsoCode',newCurrency); } // Rerender the UnitPrice field in the row, // if that field has been rendered. var item = field.editor.lists[0].renderedItems[field.row.Id]; if (item) { $j.each(item.fields,function(i,f){ if (f.id == 'UnitPrice') { f.render(); return false; } }); } }; if (mode == 'edit') { // If we do not yet have a value for our UnitPrice field, // update it. if (!field.model.getFieldValue(field.row,'UnitPrice')) { updateUnitPriceField(); } // Attach an event handler to our field's <select> picklist element var select = field.element.find('select'); select.on('change',function(){ updateUnitPriceField(); }); } 

that did not seem to solve it unfortunately. Works exactly like before. I’ll do some more testing and see if I can figure out what’s wrong.

Peter, can you add the following console.logs to your code and let me know what shows up? var updateUnitPriceField = function(){ console.log(field.row); var newPrice = field.model.getFieldValue(field.row,‘PricebookEntry.UnitPrice’); console.log(newPrice); var newCurrency = field.model.getFieldValue(field.row,‘PricebookEntry.CurrencyIsoCode’); console.log(newCurrency); …

[22:18:16,177] ({Id:“1”, Id15:“1”, OpportunityId:“006D000000QBWpNIAX”, PricebookEntryId:“01uD000000KILgFIAX”, PricebookEntry:{Id:“01uD000000KILgFIAX”, Name:“DP-201 w. LPDF-B”}, UnitPrice:null, ListPrice:null, CurrencyIsoCode:null}) [22:18:16,184] null [22:18:16,190] null Question is: Why null…

Yeah, I think that’s the source of the problem — Field Level Security issue?

don’t think so. I’m admin with manage all on Opps and full rights on Products. All possible fields are checked as accessible on opportunity lines as well.

Update For those who have tried this but have not included the UnitPrice field in their Display Template, this will cause the original snippet to not work — therefore, we have modified the pricebookEntryRenderer snippet given above, so that it will work regardless of what you put in your Display Template.

Another helpful tool we want to include here to help with easily adding Products / Line Items to an Opportunity: a snippet that will let you clone an OpportunityLineItem inline, without leaving the page. The only difference between this snippet and the cloneRecord snippet given in this community post is that we do not clone the value of the TotalPrice field, since Salesforce only lets you set the Sales Price OR Total Price when creating a new Line Item, but NOT both. You can modify the snippet to just clone TotalPrice instead of Sales Price, depending on your scenario.

var params = arguments[0], item = params.item, row = item.row, model = params.model, $ = skuid.$; // Create a new row in our table var newRow = model.createRow(); // Put in default values from the fields in our current row if (row) { $.each(row,function(fieldId,val) { // Only allow fields that are Objects, // or that are Createable if ((fieldId != 'attributes') && (val != null) && (fieldId != 'Id') && (fieldId != 'TotalPrice')) { var modelField = model.getField(fieldId); if ((typeof val === 'object') || (modelField && modelField.createable)) { model.updateRow(newRow,fieldId,val); } } }); } // Force all registered lists to put our row into edit mode $.each(model.registeredLists,function(){ // See if this item has been rendered in this list yet var newItem = this.renderedItems[newRow.Id]; if (newItem) { newItem.mode = 'edit'; newItem.refreshFields(); } }); 

I changed the display template to: {{Product2.Name}} {{Product2.Description}}(List: {{UnitPrice}}). Would this be what is causing the snippet not to work?

To add an additional field to your Display Template, you’ll have to make sure that: 1. Product2.Description is in your PricebookEntries Model. 2. PricebookEntry.Product2.Description is in your OpportunityLineItems model.