Version Your Darn Quotes! 1

Okay, so we all know that we need to keep versions of our quotes once they are a accepted or rejected, right?  How else are executives supposed to know how a sales rep went from a 10k deal to a 1M dollar deal, or vise versa? Obviously, the latter situation is the worse situation to be in. In this post, I will not only share with you how to version a quote and provide a quick link to the most recent version that will have your reps in awe!


Here’s what you’ll need to get started:

  • A developer org (Don’t have one? Get one for free here.)
  • Caffeine
  • A good sense of humor


Here’s what you’ll get:

  • A basic understanding of VisualFlow
  • A basic understanding of Process Builder
  • How to use Process Builder and VisualFlow together
  • A sense of accomplishment (cause you’re #Awesome!)
  • A working process for quote versioning


With that, let’s get started!

Step 1: Activate Quotes in your organization if you have not yet.  Setup | Quotes | Quote Settings.

Step 2: Add a field called Version to the Quotes object with no decimal places.  Be sure to fill out the description field!  If you may have turned over to the dark side of administration.

No one but administrators should require edit permissions on this field.  All others should have read access.  Why you might ask?  Process builder runs in system mode.  Therefore, any user can make updates to the field.  However, should the action to update version be needed by a flow, it would not work in this instance, as standard visualflow, not launched via process builder, respects field-level security.  This is important to remember. Process Builder does not respect field level security.  Flow does.




We don’t need to have decimal places since this is a simple quoting process.  You may find that you may require a more specific kind of versioning for your business process.  Fret not!  This will give you the basics and you can tweek to your business logic.


Step 3: Let’s get down to the flow!  We have to create a flow before the process.  Setup | Create | Workflows & Approvals | Flows  – Create a new flow.

Step 4: Drag a fast lookup element onto the blank space.  Why fast lookup?  We need to get Quote records that are related to the Opportunity and the current record that we enter is counted in the sort.  Unfortunately, in record lookup elements you cannot sort nulls last, thus meaning we need a fast lookup to retrieve all Quote records.

  • Call the element “lookupQuote”
  • Lookup the opportunityId equal to a new variable that we will create, “varOpportunityId”.  Make sure to set the input/output of this variable to input only
  • Sort results descending by the Version__c field just created
  • Assign the Id and Version field to a newly created sobject collection variable called “colQuote.”
  • Make sure to check the box that assigns null if no values are found.  This is important in ensuring that the flow succeeds.





Step 5: Create a loop element to loop through the collection.  Loop through the collection in descending order.  For the loop variable, create a new variable called “sObjQuote.”


Step 6: We need to create a new sObject variable for the quote that we are currently operating on.  Do this from the resource pane.  Call it “sObjInputQuote” and be sure to set it as “Input only.”


Step 7: Create a decision element.  Have the decision value be sObjQuote.Version__c is not null.  Drag the arrow from the loop to the decision element.  We’re only going to ever loop through 2 records.  The first record being the current quote, with a null value for version and the latest version (if there is one).




Step 8: Next, we will need two record update elements.  One if the quote is found, the other if is it not.

  • If the quote is not found, set the sObjInputQuote version to 1.  Drag the arrow from the loop to this element.  The arrow should now say “end of loop.”
  • Otherwise, set the quote number to be the sObjQuote.Version__c field to be + 1  – You will create a formula that stores the value {!sObjQuote.Version__c} + 1 for the record update element.
    • Drag the arrow from the decision element to the record update and assign the “Is Not Blank” outcome.
    • Drag the arrow from the decision element back to the loop.  This is so that we can continue looping until the latest quote is found.



Step 9: Save the flow.  We will then exit and create a field on the Opportunity called the “Latest Quote” – type of lookup.  Add read FLS to this field.

Step 10: Re-enter the flow.  Create a record update element that puts the sObjInputQuote.Id into the Latest Quote Id field that was just created on the Opportunity.  You’ll see why soon enough.



Your final flow should look like this.  Make sure to close out of it and “Activate it.”

Note: If something is going wrong at the end of this process, come back and add a text template, input the variables for the quote loop and the input quote version.  Add it to a “send email” action, put the template in the body and get an email with what might be the problem.  Step back through the instructions to make sure you didn’t miss anything.



Popping the champagne?  Too early for that!  You should feel accomplished though because this is the beginnings of an awesome flow!


Let’s move onto the process builder process!

Step 1: Set the object to be Quote in the first node.  You have two options here: 1. Set it to fire on record creation or 2. Set it to fire on edits or creates.  Set it to fire on creations only.

Step 2: For the diamond in the middle, click on it and set it to “No criteria – just execute the actions!”  We want this process to happen on every record creation.

Step 3: In the immediate actions, choose “Launch a flow”.  Add two values to set in the flow variables:

  • sObjInputQuote as a reference and select the current Quote record
  • varOpportunityId as a reference and select the “Quote.OpportunityId” field.

This should look like this.versionFlow




Your final process should be very straightforward looking.  Just hit the “Activate” button.




Still thinking of popping that champagne?  Don’t do it yet.  We have just a few more steps.  Trust me, it’ll be worth it!


The finishing touches!

Step 1: Add the Latest Quote lookup made earlier in the process to the Opportunity page layout, preferably towards the top.  This allows reps to see the latest version of their proposal without having to go down to the related list section.



Step 2: But if your reps (or their manager) wants to see the evolution of the deal, we should also update the Opportunity page layout to include version on the Quote related list and sort it ascending by the version field.  This keeps the quotes in chronological order and shows the latest information first.




Look at that awesome Salesman go and up the deal from 1K to 10M! *wink*


Oh but wait!  There’s more!

What if you already have Quotes in the system?  No problem.  Subject to your business rules, you can dataloader those out and update them in excel.  I’ll share with you a quick way to update them for all Quotes (provided you don’t have a versioning system in place, or you will want to do other checks).


Go to dataloader (or workbench) and export all quotes with the following query:

SELECT Id, OpportunityId, Version__c , CreatedDate
FROM Quote 
ORDER BY CreatedDate ASC


We will use the above export to find all Quotes that are related to the same Opportunity and will update them in order of created date.


Now open your CSV file.  Go to the developer tools tab and click “View Code.”  If you don’t have this tab available, you can go to File | Options | Customize Ribbon.  Click on “Developer” and “OK.”


Click on “Insert” | “Module”.  Paste the code below into the window.

Function relatedOpps(rng As Range, OpportunityId As Range) As Integer

 Dim counter As Integer
 counter = 0

' Loop through each value in the range provided in the function
 For Each rng In rng
    ' Found a match
    If OpportunityId.Value = rng.Value And OpportunityId.Row = rng.Row Then
        ' Increment counter and then return
        counter = counter + 1
        relatedOpps = counter
        Exit Function

    ' If the oppId is not equal to the opportunityId passed in, then restart counter
    ElseIf rng.Value <> OpportunityId.Value Then
        counter = 0
    Else: counter = counter + 1
    End If
 End Function


Now, in your spreadsheet, do “=relatedOps($B$2:$B$12, B2, B2)”  where $B$2:$B$12 is equal to the entire amount of rows in your spreadsheet (make sure to adjust the last number to match), and B2 the cell that contains the OpportunityId.   Your result will look something like this:



You can see the clear points where this process started and that the numbers match up.

NOTE: If you use this in other areas of Salesforce, the above function assumes that you’ve presorted your data.  Always do a cursory check before uploading data.


Now go pop that champagne! You deserve it you #AwesomeAdmin ! #ClicksNotCode

Leave a comment

Your email address will not be published. Required fields are marked *

One thought on “Version Your Darn Quotes!