Salesforce Bundles for Lightning

Exactly four years ago, I wrote a post that tackled a business issue I had regarding Product Bundles.  A lot has changed in those 4 years, so I want to bring that post up to date for the Lightning world.

Back then, I was new to Flow and made a fundamental error in the design of my solution.  The solution also relied on some code (a very simple vf page).  Now I can show you how this problem can be tackled in Lightning – with absolutely no code.

Along the way, I hope to introduce you to the delights of Flow and open your mind to an ever more powerful tool that can change the way you approach tackling business problems in Salesforce.

Note that this solution is limited in its application.  If your requirements are more complex than those I outline, it may be time to look at Salesforce CPQ.

What is a Bundle?

bundle image

It’s a common need for businesses to sell a collection of products as a single offering.  Examples are:

  • Collection of products sold as a special offer
  • Kit of products that are sold as one, but their elements need to be considered separately for back-office functions
  • Products sold with services, where different processes happen for each element

The principle is that the set of products (and the price at which they’re to be sold at) are predetermined.  The salesperson does not need to concern themselves with what is in each bundle.  To them, it is the same as selling a single product.

Bundles also go under the name of product bombs – the idea being that you sell one thing and it explodes into many elements.  “Bundles” is less dramatic.

Solution Principle

The solution works by using two opportunity record types.  One record type – we’ll call it “Bundle” –  for the people setting up bundles (I’ll assume it is a marketing team doing this) and the other – “Sale” –  for the actual sales opportunities.  Remember that restricting record types to profiles only prevents a user from creating a record using that record type.  It does not prevent the user from having visibility of the record.

In this solution, the marketing team create “Bundle” opportunities that have all the products on them for the proposed bundle, at the price they want them sold at.  It will also be useful if we have a way of flagging bundles as active or not.  I’m going to do this by adding a new Sales Process with 3 stages: Draft; Available; Archived.

We then present the list of Bundle opportunities to the sales people on the Opportunity record page (you may ask “how?” – keep reading!) where the person creating the sale can select the bundle and we copy across the product details from the bundle opportunity to the sale opportunity.

If you need help with Record Types, TrailHead has some fantastic resources.

Entity Diagram

You might think you know how Products are added to an Opportunity – it’s by creating Opportunity Product records, yes?  Well, you’re right, but the relationship between Opportunities and Products is a little more complex than may first be realized.

Here’s how it looks in the Schema Builder:

Opp Prod Ent 1

I hate to say it, but the Schema Builder lies!

There’s a relationship between Price Book Entry and Opportunity Product.  There’s a lookup field on Opportunity Product to Price Book Entry, but you’ll not see it in the field list or in the schema builder.  Why?  No idea!  If anyone knows, please feel free to drop a comment onto this post!

So here’s the same entity diagram with a bit of red pen on it:

Opp Prod Ent 2.png

Why does this matter?

In order to create an Opportunity Product record via any means other than the native UI, we need to provide all the following data as a minimum:

  • Opportunity ID (which opportunity will this Opportunity Product relate to?)
  • Quantity (how many are being sold.  Cannot be zero)
  • Unit Price (what price is each ‘unit’ being sold at)
  • Price Book Entry ID (what product and what currency is being used – the Price Book must match the Price Book on the Opportunity)

So we don’t provide the Product ID at all.  That gets populated automatically by Salesforce.  What is important is that the Opportunity has a Price Book (and Currency, if your org uses multi-currency) associated to it.

Solution Strategy

So far we’ve talked a bit about Opportunity record types and the data model that Salesforce uses.

Our solution is going to have a Flow running as an element of a Lightning Page for an Opportunity record.  This flow will first give the user a list of bundles to choose from, then take the Opportunity Product data from the Bundle Opportunity and use them to build new Opportunity Product records on the Sales Opportunity.

We also need to manage a few exceptions to the ‘happy path’.  These are:

  • The Sales Opportunity doesn’t have a price book associated to it (we’re going to make the user select a price book if that’s the case)
  • There are no bundles available

Flow Time!

When building a flow, it helps to think of it as a tool that is somehow external to Salesforce.  We will bring Salesforce data into the flow, gather new data from user input  in the flow, manipulate it to our needs, then use the new data we have to change the data in Salesforce by creating, updating or deleting Salesforce records.  It’s also important to think of the data as a whole – not just individual records, but groups of records that we can create or amend and then pass back into Salesforce in one lump (purists prefer the term “batch” to “lump”).

Spoiler Alert

Here’s what the flow is going to look like

All Flow 1

It’s probably about as complex as I’d ever want to make a single Flow.  There are a dozen elements to it and each should take no more than 5 minutes to create.

Here’s the step-by-step guide to build our flow.

Creating a New Flow

From the Setup page, go to the Home>Flow and click on the “New Flow” button.

This is going to be a flow that needs user input, so it’s a Screen Flow.


Hit Create!

This takes you to the canvas where we will build the magic.

Before we start adding to the canvas

The first step is going to be to grab extra information about the Opportunity record that the user is looking at.  Specifically, we want to know if it has been assigned to a Price Book (and which currency it is using).  At the beginning of this section, I mentioned that we can think of flow as being somehow external to Salesforce.  To this end, we may need to give names to things in our flow that already seem to have perfectly good names in Salesforce.  Since the Winter 20 release of Salesforce, flow has become a lot better at using the names it already has for objects and fields without us having to give them new names, but there are some situations where we need to give names to elements ourselves.

The first thing that we’re going to name is our Sales Opportunity – specifically, the Id field of that record.  To do this, we go to the Manager tab:

Manager Tab

Then click New Resource.

The Resource Type will be “Variable”, Let’s set the API name to “SaleOpportunityId” and the Data Type to “Text”. Default value should be left blank and “Allow multiple values” should not be ticked.  However, please tick the “Available for input” and “Available for output” options.

Since you’re a responsible administrator, you will also want to add a description.  Something along the lines of “ID of the Sales Opportunity that will be set by the Lightning component on the Lightning record page of a sales opportunity.” would fit well. It costs little time to fill in these description fields, but is so helpful for the administrator who follows once you’ve moved on to do something else.

Hitting the Canvas

All Flow 1a

Before we take each of these 12 elements carefully in turn, this is an overview of what’s going on:

  1. Get more details about the sales opportunity from where the flow is being launched.  As mentioned earlier, we pass the opportunity id into the flow – this element gets some other field values: Pricebook Id and Currency ISO Code.
  2. We mentioned that we need to know which pricebook to use.  This decision box checks if the opportunity already has a Pricebook Id value set.  If so, it’ll jump on to step 5, else it’ll go to step 3.
  3. This screen shows all price books available to the user and insists that one is selected before we can go any further.
  4. Here we update the sales opportunity with the pricebook selected from step 3.
  5. In step 6, we want to get a list of opportunities that have the “Bundle” opportunity record type.  In order to define that, we need to have the specific Salesforce Id of that record type (if you didn’t know, record types are actual salesforce records in their own right).  This step finds that Id and stores it in the flow.
  6. This is the step where the user finally gets to pick a bundle!  A screen will be displayed showing all available bundles (based on matching pricebook, currency and user’s record access).  The user must pick one to continue.
  7. Now we know which bundle is to be used, we need to get all the Opportunity Product records that are related to the bundle opportunity and store them in our flow in a collection variable (don’t panic with that term!)
  8. The collection of records that we’ve just got from step 7 can now be iterated through (or looped through) one at a time.  This step sets up that loop.
  9. We now take a record from the collection of Bundle opportunity product records and use the data from it to build a different Opportunity Product record – one associated to the Sale Opportunity.
  10. Now we have a Sale opportunity product record, we need to put it to one side in the flow by adding it to a new collection of opportunity product records. We now go back to repeat steps 8, 9 and 10 until each of the bundle opportunity product records has been cycled through and used to create new sales opportunity product records.
  11. Now we create all the new Sale Opportunity Product records in one go into Salesforce.  It’ll be an “all or none” thing – if there’s a problem with any of the new records, then none will be created.
  12. Finally, it’s a nice touch to have a confirmation page to let the user know that everything has worked okay.  Without this page, the user would be taken back to the start of the flow and might think that nothing had happened.

For each element, drag the relevant icon over from the “Elements” tab on the left menu into the canvas.  As soon as you do this, a window will appear asking for more information.

Element 1: Get Sale Opportunity

Element 1

Note:  If your org does not use multi-currency, CurrencyIsoCode will not be visible.  Just ignore it here (and in the other elements where it appears).

Element 2: Pricebook Known?

Element 2

Few things to note here:

  • To ask if a field is blank, we select the field and use the “Is Null” operator, then set the value to True or False depending on if you are asking if the field is blank (Is Null = True) or is not blank (Is Null = False).
  • In the Resource and Value fields on this form, click in the box and navigate to a value by scrolling and clicking. So to get to {!Get_Sale_Opportunity.Pricebook2Id}, click in the resource box, then click on “Opportunity from Get_Sale_Opportunity  >”, then click on Pricebook2Id.  The “>” indicates that there is a finer level of detail available.
  • The Pricebook Id field is called Pricebook2Id.  Why the 2?  There’s an interesting story regarding the early days of Salesforce in there somewhere…  For now, just go with the “it is because it is” philosophy.

Element 3: Choose a Pricebook

This uses the Screen element:

Element 3

I’ve opted for using a radio button element, but if your org has a lot of pricebooks available to the users, then a picklist may be more appropriate.

Once the radio button element is dragged into the screen builder (middle panel), set the component’s attributes like this:

Element 3b

In the “Choice” box, select “New Resource”

Resource Type: Record Choice Set:

Element 3c.png

This is telling the flow to create an option in the radio button list for every pricebook record found that matches the currency of the sale opportunity and is flagged as active in Salesforce.  It uses the pricebook name as the label value in the radio button list, and stores the selected record’s ID value in the flow once the user has made a choice.

You’ll find element 6 is virtually identical!

Element 4: Update Sales Opportunity

Element 4

This one’s a lot simpler!  We’re just writing back to the Salesforce database updating the value of Pricebook2Id.

Element 5: Get Bundle Record Type Id

Element 5

Here, we’re querying the Record Type object to find the Id for the Bundle record type of opportunity.  Note that we’re checking against the DeveloperName field (not the Name field) as DeveloperName is unique.  “SobjectType” is just the name of the object that the record type relates to (which – obviously – is opportunity in this instance)

Element 6: Select Bundle

This is the second Screen element in the flow.  It is almost identical to element 3.

Element 6

The Record Choice Set should be create so:

Element 6b

The criteria here are that we’re looking up opportunities with the record type “Bundle” (we got the record type Id in the previous step) and the stage “Available”.  Adding this Stage criterion allows the marketing team (or whoever is controlling the bundles) to control which bundles are available or not.

Note that it is the name of the bundle opportunity that the user will be presented with on this page – important to make the bundle opportunity name unambiguous!

Element 7: Get Bundle Products

Element 7.png

Now we getting all the Opportunity Product records that are associated to the bundle opportunity selected in step 6.  The fields that we want to use in the flow need to be specified – here we have specified PricebookEntryId, Quantity, UnitPrice and Description (ID is always selected).

Element 8: Loop Opportunity Products

Element 8

All the loop element is there to do is to iterate through a set of records.  Our set of records is the collection of Bundle Opportunity Records we pulled in the previous step.

Element 9: Assign Sale Opportunity Product Values

Here we’re using a new element – the Assignment element.  It allows us to assign values to variables we have within the flow.  The loop is feeding this Assignment one Bundle Opportunity Product record at a time and it is going to assign values to a new resource that we need to create that I’ve called SaleOpportunityProduct.  So it’s back to the Manager Tab (left menu) to create a new resource:

Element 9b

Note that this variable is of Data Type “Record”.

Now go back to the Elements Tab (left menu) and drag the Assignment element onto the canvas.

Element 9

We’re setting the values in our new record to match the values we’re getting from the Bundle Opportunity Record with the noticeable exception of the parent Opportunity Id.  For that, we’re using the Id of the Sales Opportunity.

Element 10: Add Sale Opportunity Product to Collection

Element 9 gives us the Opportunity record we want.  It is tempting to go straight for a “Create Record” element and write this new record into the Salesforce database.  Job done!

Do this and you will soon make friends with the Salesforce Governor Limits.  These are the things that allow us all to use the same platform without being resource hogs and impacting on each-other’s system performance.  It’s much more efficient if we only make one call to the Salesforce database with all the records we want to create in one go (within reason).

Element 10 takes the record we built in element 9 and adds it to a new collection of records that we will keep within the flow until we’re ready to commit them to the Salesforce database.

We have yet another new Resource here that I’ve called SalesOpportunityProducts (note the plural!)

Element 10b

This is another Record Variable, but this time the “Allow multiple values” is ticked.

Adding records to a collection is another thing the Assignment element does:

Element 10

Note that the Operator is “Add” and the Value is the single record variable from step 9 without any field specified – i.e. the whole record.

Element 11: Create Sales Opportunity Products

Now we can push for the finish line with a very simple flow elements: Create Records

Element 11.png

This takes the entire collection and writes it to Salesforce.  All the relevant Opportunity Product records will be created and be visible in the sales opportunity.

Element 12: Done Screen

Our final element is another screen which uses the Display Text screen element to show a friendly message to the user.

Element 12

Note that it is a good idea to remove the navigation options of Previous or Pause, leaving just the Finish button for the user to click.

Final Flow Flourish

Don’t forget to save your flow.

Before it can be used it must also be activated.

The Activate button is towards the top-right, near the save button.

Lightning Page

OK – now we need a Lightning Page.

  1. Go back to the Setup homepage
  2. Go to the Object Manager tab and select Opportunity
  3. Go to Lightning Record Pages and select the appropriate page or create a new one

Drag the Flow element onto your Lightning Page (where it goes is up to you):

Lightning Page.png

Ticking that “Pass record ID into this variable” box will set the variable we created at the very start.  Setting the component visibility so that it only appears on Sales opportunities is a nice touch too.

Once saved, give it a spin!  Make sure you’ve created some Bundle Opportunities first!

General Notes

This solution is given as an example of how Flow can be used to solve a business issue.  The Product Bundle problem is given as an example to hopefully get your creative mind going.

If you do build out this solution specifically with regards to product bundles, there are a few things to consider:

  1. Using the Opportunity object to hold the Bundles data carries risks, not least with reporting.  It might happen that your users do not filter out the Bundle record type when creating reports and accidentally include them in the pipeline.  Custom Objects could be used instead of the bundle opportunity record type, but this is a bit more complex (note that you cannot create a lookup field to the Pricebook Entry object).
  2. If the sales user has delete or edit permissions for Opportunity Product on their profile/permission sets, they might assign a bundle to an opportunity, then remove or amend elements of that bundle.  This can be countered, but not without making the solution more complicate and also creating a trigger to prevent deletions (and I don’t code!).  I might write an addendum to this article later to cover this off.
  3. For orgs that use multi-currency, the solution still works, but a Bundle Opportunity is required for each currency.
  4. Where multiple price books are available in an org, a Bundle Opportunity is required for each price book.
  5. Where an org has multiple price books and multi-currency, the maintenance of bundle opportunities might become quite tiresome!

Since you’re a responsible Salesforce Administrator, you will have created all this in a sandbox.  If you migrate a flow from a sandbox to another environment, the flow will always deploy as “Inactive”, so will need to be activated in the target org.

Please let me know how you get on with Flow.  If you need any help, I will try to support where I can!


About Nick Spencer

Salesforce Bundles for Lightning

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s