· tutorials · 11 min read
Clone Quotes In Salesforce
Learn how to clone quotes in Salesforce using Flow.
Clone Quotes Using Flows In Salesforce
Cloning quotes is a notable feature that is seemingly missed. As of the time of writing, there are over 7000 votes on the idea of cloning 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 received this as an unmanaged package, 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:
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:
- Go to
Quote
within theObject Manager
- Navigate to
Buttons, Links, and Actions
- 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:
- Going to the desired page layout
- Finding the
Salesforce Mobile and Lightning Experience Actions
section - If this is the first time editing this, there is a wrench at the bottom right of the box that needs to be clicked
- Go to the
Mobile & Lightning Actions
section in the config box. - 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 here to get the latest version of this package.
Need Our Help To Get Your Data Into Salesforce?
Join dozens of other companies by learning how you can get all your company's data in one place.