Better Management of Opportunity Line Items with SKUID

  • 1
  • Question
  • Updated 5 years ago
  • Answered
  • (Edited)
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.
Photo of Peter Baeza

Peter Baeza

  • 2,868 Points 2k badge 2x thumb

Posted 6 years ago

  • 1
Photo of Peter Baeza

Peter Baeza

  • 2,868 Points 2k badge 2x thumb
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
Photo of Zach McElrath

Zach McElrath, Employee

  • 49,004 Points 20k badge 2x thumb
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!

Photo of Peter Baeza

Peter Baeza

  • 2,868 Points 2k badge 2x thumb
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)
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');
// 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();
});
}

Photo of Peter Baeza

Peter Baeza

  • 2,868 Points 2k badge 2x thumb
more images . Did not see that image upload worked on the other post





Photo of Zach McElrath

Zach McElrath, Employee

  • 49,004 Points 20k badge 2x thumb
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();
});
}
Photo of Peter Baeza

Peter Baeza

  • 2,868 Points 2k badge 2x thumb
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.
Photo of Zach McElrath

Zach McElrath, Employee

  • 49,004 Points 20k badge 2x thumb
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);

....
Photo of Peter Baeza

Peter Baeza

  • 2,868 Points 2k badge 2x thumb
[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....
Photo of Zach McElrath

Zach McElrath, Employee

  • 49,004 Points 20k badge 2x thumb
Yeah, I think that's the source of the problem --- Field Level Security issue?
Photo of Peter Baeza

Peter Baeza

  • 2,868 Points 2k badge 2x thumb
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.
Photo of Zach McElrath

Zach McElrath, Employee

  • 49,004 Points 20k badge 2x thumb
**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.
Photo of Zach McElrath

Zach McElrath, Employee

  • 49,004 Points 20k badge 2x thumb
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();
}
});

Photo of bimbyc

bimbyc

  • 408 Points 250 badge 2x thumb
I changed the display template to:

{{Product2.Name}} {{Product2.Description}}(List: {{UnitPrice}}).

Would this be what is causing the snippet not to work?
Photo of Zach McElrath

Zach McElrath, Employee

  • 49,004 Points 20k badge 2x thumb
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.