Clone Quotes In Salesforce Using Flows — Tutorial

Justin Wills
10 min readMar 28, 2023

--

Cloning quotes is a simple feature that still doesn’t exist natively in Salesforce. As of the time of writing, there are over 7000 points for a native feature to clone quotes. That being said, it is easy to clone quotes using flow. Below is a tutorial on flow, as well as how to build out this flow.

If you are just interested in installing this in your Salesforce org, check out this link here.

Desired Outcome

For the desired outcome of the flow, there are a few things that should occur:

  • The flow should be triggered by a button press on the quote record.
  • A new quote should be created with necessary fields populated.
  • The quote line items should be created with necessary fields populated
  • After the flow finishes, the user should be redirected to the new quote.

Additionally, some customization and edge cases should be considered. These are:

  • It should be easy to add custom fields to the mapping
  • If a price book entry on a quote line item is marked as inactive, this quote line item cannot be cloned.
  • If no quote line items exist, the quote should not be cloned.

With these considerations in mind, let’s discuss how to build out this flow.

Creating The Quote Version Field

We need a field that we can use to store the next quote version. This will allow incrementing of the quote version, as well as a unique quote name.

To create the field, go to Quote in the Object Manager, then create a field with the following properties:

  • Type: Number
  • Field Label: Next Quote Version
  • Field Name: Next_Quote_Version
  • Default Value: 2
  • Length: 3
  • Decimal Places: 0

This means that moving forward, all quotes will start off with a next quote version of 2, or the incremented quote version if in a chain of cloned quotes. For existing records with a blank Next Quote Version, these will be handled in the flow.

Initial Flow Creation

Navigate to Flows and create a new Screen Flow.

The first step is to grab the records that will be cloned. This includes the Quote and the QuoteLineItems related to the record that the flow runs from.

First, the record id from the quote needs to be passed into the flow. This can be accomplished using a flow input variable.

Open the toolbox on the left side of the screen and select New Resource. This resource should have the following properties:

  • Resource Type: Variable
  • API Name: recordId
  • Data Type: Text
  • Availability for input: True

Once the flow is created and have changes, it can be saved. Save the flow with the following properties:

  • Flow Label: Quote Clone Flow
  • Flow API Name: Quote_Clone_Flow

Next, the quote and related quote line items need to be queried within the flow. Using the Get Records element, these can be queried. To query the quote, add the element Get Records with the following properties:

  • Label: Get Quote
  • API Name: Get_Quote
  • Object: Quote
  • Filter: Id equals recordId

To get the Quote Line Items, create a Get Records element with the following properties:

  • Label: Get Quote Line Items
  • API Name: Get_Quote_Line_Items
  • Object: Quote Line Item
  • Filter: QuoteId equals Quote from Get_Quote > Quote Id
  • How Many Records to Store: All records

It’s critical that the How Many Records to Store property is set to All records, which stores the quote line items as a collection.

Next, the new quote and quote line items should be created. Before the quotes are created, the version name needs to be created. The format of the name should be the following if a quote is cloned:

  • Test Quote
  • Test Quote_v2
  • Test Quote_v3

We can use formulas to handle this. To handle the null value discussed earlier, we need a formula called NextQuoteVersion. Create a new resource with the following properties:

  • Resource Type: Formula
  • API Name: NextQuoteVersion
  • Data Type: Number
  • Decimal Places: 0
  • Formula: IF(ISNULL({!Get_Quote.Next_Quote_Version__c}), 2 ,{!Get_Quote.Next_Quote_Version__c})

Next, we need an incremented version number to create the chain of versions. To accomplish this, a formula can store this incremented value. Create a new resource with the following properties:

  • Resource Type: Formula
  • API Name: QuoteVersionIncremented
  • Data Type: Number
  • Decimal Places: 0
  • Formula: {!NextQuoteVersion} + 1

Finally, the name of the quote needs to be generated. If you guessed that this could be done with formulas, you would be correct. Create a new resource with the following properties:

  • Resource Type: Formula
  • API Name: QuoteName
  • Data Type: Text
  • Formula: IF(CONTAINS({!Get_Quote.Name}, "_v"), LEFT({!Get_Quote.Name} , FIND("_v", {!Get_Quote.Name}) - 1) & "_v" & TEXT({!NextQuoteVersion}), LEFT({!Get_Quote.Name}, 250 ) & "_v" & TEXT({!NextQuoteVersion}))

This formula accomplishes a few things:

  • If the name of the quote takes up 255 characters, the string is truncated to fit the version code
  • If the version exists in the version name, this is found and removed.

With this, all the resources are created to finish the basic quote cloning.

To create the new quote, add the element Create Records with the following properties:

  • Label: Create Quote
  • API Name: Create_Quote
  • How to Set the Record Fields: Use separate resources, and literal values
  • Object: Quote

For the field mappings, the following is required:

  • Name: !{QuoteName}
  • Opportunity: {!Get_Quote.OpportunityId}
  • Pricebook2Id: {!Get_Quote.PriceBook2Id}
  • Description: {!Get_Quote.Description}
  • Next_Quote_Version__c: {!QuoteVersionIncremented}

Additionally, any other custom fields that need to be mapped that are specific to your org can be done here.

To create quote line items, the collection needs to be iterated through to create mappings individually. To do this, add the element Loop to the flow. The flow should have the following properties:

  • Label: Loop Through Quote Line Items
  • API Name: Loop_Through_Quote_Line_Items
  • Collection Variable: {!Get_Quote_Line_Items}

During implementation, if you are unable to see the Quote Line Items collection, ensure that the How Many Records to Store property is set to All records on the Get Quote Line Items element.

Next, inside the loop, create a new Create Records element with the following properties:

  • Label: Create Quote Line Items
  • API Name: Create_Quote_Line_Items
  • How to Set the Record Fields: Use separate resources, and literal values
  • Object: Quote Line Item

For the field mappings, the following fields are required:

  • QuoteId: {!Create_Quote}
  • Quantity: {!Loop_Through_Quote_Line_Items.Quantity}
  • UnitPrice: {!Loop_Through_Quote_Line_Items.UnitPrice}
  • PricebookEntryId: {!Loop_Through_Quote_Line_Items.PricebookEntryId}
  • Description: {!Loop_Through_Quote_Line_Items.Description}

From here, any additional fields can be added to the quote line item mapping.

Redirecting the Page

With this current configuration, after running the flow, the records will be created, but the view will not redirect to the newly created quote. Thus, a redirect component is needed. While there is a no native way of doing this within flow, you can use lightning web components to redirect users to the newly created record. I will be referencing the article written by Sebastiano Schwarz.

To create LWCs, there is no native way of doing this through the UI. The easiest way to create LWCs is by downloading SFDX, and installing VS Code. I made a good guide here to configure a development environment for Salesforce:

Install VS Code and SFDX on your computer

Once your environment is setup, with VS Code and it’s easy to add the Lightning Web Component to your Sandbox.

Select the command SFDX: Create Lightning Web Component, and input the name openRecordPageFlowAction

From there, add the following to the HTML, JS, and XML files respectively:

Now that the lightning web component is added to your Salesforce org, integrating the LWC into your flow is easy.

Add a Screen element before the end of the flow with these properties:

  • Label: End Screen
  • API Name: End_Screen

Then on the components section, there is a component called Open Record Page Flow Action that can be added to the screen layout. Additionally, the component will need to have the following configuration:

  • API Name: RecordRedirect
  • Record Id: {!Create_Quote} (Quote Id From Create Quote)
  • Target: _self

With this configuration, everything needed to create quotes is complete within the flow. Your flow should look like this:

It’s time to activate the flow so this can be added to page layouts.

Launching the Flow

To launch the flow, an action can be used to add a button on the page layout. To create this, perform the following:

  1. Go to Quote within the Object Manager
  2. Navigate to Buttons, Links, and Actions
  3. Press New Action

Then, add the following configuration

  • Action Type: Flow
  • Flow: Quote Clone Flow
  • Label: Clone Quote
  • API Name: Clone_Quote

With this new button, it’s time to add it to the page layout. Head back to the quote settings within the object manager, and add this button to your desired page layouts. This can be performed by:

  1. Going to the desired page layout
  2. Finding the Salesforce Mobile and Lightning Experience Actions section
  3. If this is the first time editing this, there is a wrench at the bottom right of the box that needs to be clicked
  4. Go to the Mobile & Lightning Actions section in the config box.
  5. Drag the Clone Quote action to the desired location.

Now everything is complete to launch and quote clones. The core functionality is completed as described above. But there are a few more features that can be added to make this flow bulletproof.

Not Allowing Quotes To Be Cloned Without QLIs

Now it’s time to tackle a few of the niceties that were discussed in the beginning. The first one is that Quotes should not be cloned if there are no QLIs associated to the Quote. This can be accomplished easily through assignments.

To start, create a new resource with the following properties:

  • Resource Type: Variable
  • API Name: qli_size
  • Data Type: Number
  • Decimal Places: 0

Next, after the Get Quote Line Items element, add a Assignment element with these properties:

  • Label: Assign QLI Size
  • API Name: Assign_QLI_Size
  • Variable: qli_size equals count {!Get_Quote_Line_Items}

With this variable, we can use this for the decision of whether or not there are qlis attached to the quote. After the Assignment element just created, create a Decision element with the following properties:

  • Label: QLI Size Decision
  • API Name: QLI_Size_Decision

Under the outcome details, create an outcome with the following properties:

  • Label: No QLIs
  • Outcome API Name: No_QLIs
  • Resource: qli_size equals 0

Now, under the No QLIs path we can create a screen that displays the error message to the end user, then end the flow. Create a Screen element with the following properties:

  • Label: QLIs DNE Screen
  • API Name: QLIs_DNE_Screen

Additionally, add a Display Text component with the following properties:

  • API Name: QLI_DNE_Text
  • Inside the text box, add the message: Please add quote line items to the quote before cloning.

After this screen, add an End element.

With this, the QLI check is done. The final step is to check for inactive price book entries.

Inactive Price Book Entry Check

If a QLI is inserted through the API or flow with an inactive Price book entry, the following error occurs:

The price book entry is inactive. Ask your Salesforce admin for help.: Price Book Entry ID

Because of this error, it is advisable to check for inactive price book entries. This can be accomplished with collections, and more decisions.

First, create a new resource with the following properties:

  • Resource Type: Variable
  • API Name: Product2Ids
  • Data Type: Text
  • Allow multiple values: True

Next, after the Default Outcome from the QLI Size Decision, and before the Create Quote element, create a loop element with these properties:

  • Label: QLI Loop
  • API Name: QLI_Loop
  • Collection Variable: {!Get_Quote_Line_Items}

Inside the loop, add an assignment element with the following properties:

  • Label: Product2 Assignment
  • API Name: Product2_Assignment
  • Variable: {!Product2Ids} Add {!QLI_Loop.Product2Id}

Now that there are all product2 ids associated with the Quote, query all price book entries using a Get Records element with the following properties:

  • Label: Get Inactive Price Book Entries
  • API Name: Get_Inactive_Price_Book_Entries
  • Object: Price Book Entry
  • How Many Records To Store: All Records

Additionally, add these conditions:

  • Pricebook2Id Equals {!Get_Quote.Pricebook2Id}
  • Product2Id In {!Product2Ids}
  • IsActive Equals {!$GlobalConstant.False}

Next, create a new resource with the following properties:

  • Resource Type: Variable
  • API Name: pbe_size
  • Data Type: Number
  • Decimal Places: 0

Similar to the quote line item section, create an assignment element after the Get Inactive Price Book Entries element:

  • Label: Assign PBE Size
  • API Name: Assign_PBE_Size
  • Variable: pbe_size equals count {!Get_Inactive_Price_Book_Entries}

Now, create a branch to check the pbe size using the following:

Decision Element:

  • Label: Check PBE Size
  • API Name: Check_PBE_Size

Outcome Details:

  • Label: Inactive PBEs Exist
  • Outcome API Name: Inactive_PBEs_Exist
  • Resource: pbe_size Greater Than 0

Next, in the Inactive PBEs Exist branch, create a screen element with the following properties:

  • Label: PBE Exist Screen
  • API Name: PBE_Exist_Screen

Additionally, add a display text as follows:

  • API Name: PBE_Error_Text
  • Message: This quote cannot be cloned because there are inactive price book entries associated with this quote. Please activate these price book entries before cloning, or remove them from the quote.

Now, the price book entries that are included within this quote can be displayed to the end user using the Data Table component.

  • API Name: Inactive_PBE_Datatable
  • Label: Inactive PBEs
  • Use Label as the table title: True

Under the Configure Data Source Section, add the source collection as {!Get_Inactive_Price_Book_Entries}

In the Configure Columns section, we can control what fields from the price book entries are displayed to the end user. At a minimum, the following field should be added to the data table:

Product Name

Conclusion

With all this configuration done, congratulations! You can now clone a quote. If you are just looking to set this up yourself without the hassle, head over to here to get the latest version of this package.

Additionally, if you are looking for Salesforce consulting, get in touch

This article was originally posted here.

--

--

Justin Wills

Salesforce Consultant Specializing in Connecting Shopify and Quickbooks for Businesses https://www.1sync.co/contact