A Poor Man’s Product Configurator

I was recently contacted by one of my blog readers to help him with a project. It sounded like an interesting challenge, so I agreed to help him with a little guidance and also post an overview of what we did so others could learn as well.

The business problem: let’s say your company makes custom widgets. A part of the sales process is creating a quote request. You don’t want your sales people to have a generic quote request form to fill out. Instead, you want to only show them the options based on the type of  product for which they are creating the quote request.

There are a few ways to accomplish this. I originally proposed a fairly complicated, but extensible model using related lists so you could configure an unlimited number of options. It turned out that the product model was a bit simpler and we could do it with just the product object and some Visualforce and a controller extension. My example here also uses field sets to make it easily configurable once it is in production.

To set up for this, I created a custom product object and a quote request object. On the custom product, I created a picklist field with options such as Group1, Group2, etc. On the quote request object, I created all the options as fields and then created field sets to group them together for display on the quote request entry page. The benefit of using the field set is that if I decide I need to add a new option to a product group, I can do it without having to add code. I just add the field to the relevant field set.

Here’s the Visualforce page. You can see the repeaters with the field sets.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<apex:page standardController="QuoteRequest__c" extensions="QuoteRequestExtension">
  <apex:form >
    <apex:pageBlock >
      <apex:pageMessages />
      <apex:pageBlockButtons >
        <apex:commandButton value="Save" action="{!save}"/>
        <apex:commandButton value="Cancel" action="{!Cancel}"/>
      </apex:pageBlockButtons>
      <apex:pageBlockSection title="Custom Product" collapsible="false">
        <apex:outputField value="{!QuoteRequest__c.CustomProduct__c}"/>
      </apex:pageBlockSection>
      <apex:pageBlockSection columns="1" title="Options" collapsible="false">
        <apex:repeat value="{!$ObjectType.QuoteRequest__c.FieldSets.Group1}" var="f" >
          <apex:inputfield value="{!QuoteRequest__c[f]}" rendered="{!CustomProduct.ConfigGroup__c == 'Group1'}"/>
        </apex:repeat>
        <apex:repeat value="{!$ObjectType.QuoteRequest__c.FieldSets.Group2}" var="f" >
          <apex:inputfield value="{!QuoteRequest__c[f]}" rendered="{!CustomProduct.ConfigGroup__c == 'Group2'}"/>
        </apex:repeat>
      </apex:pageBlockSection>
    </apex:pageBlock>
  </apex:form>
</apex:page>

And here’s the Apex controller extension. It takes the product ID passed as a parameter to the page and queries the configuration group on the custom product to display on the Visualforce page.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public with sharing class QuoteRequestExtension {
 
  public CustomProduct__c CustomProduct {get; set;}
  private final QuoteRequest__c qr;
 
  public QuoteRequestExtension(ApexPages.StandardController controller) {
    this.qr = (QuoteRequest__c)controller.getRecord();
    try {
      CustomProduct = [select id, ConfigGroup__c from CustomProduct__c where id=
                       :ApexPages.currentPage().getParameters().get('product')];
    } catch(Exception e) {
      ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.FATAL, 'Error Getting Product Info'));
    }
    if (CustomProduct != null)
      this.qr.CustomProduct__c = CustomProduct.Id;
  }
}

The last ingredient to this is to add a lookup field on the Opportunity object. We can then add a custom button on to the Opportunity that takes the user to the quote request page and passes in the custom product ID from the opportunity.

My Next Adventure

I’m taking a break from my regular technical topics to share a bit of philosophy. I recently gave my notice at my current job and will be starting a new job at the end of May as a Salesforce.com Business Analyst at Zions Bancorporation. I’m really excited about this opportunity because it will give me the chance to focus on two things I love: solving business problems and implementing those problems in Salesforce. It is also going to give me the experience of working at a really large company.

I’ve worked at Petzl for 12.5 years (10 more years than I thought I would when I started). Although it is an amazing place to work, I felt that I had hit the ceiling with my professional growth there. I’ve always sought out challenges and have never been pleased with the status quo. In addition to my job description duties, I felt a commitment to try and make Petzl a better place to work: I organized blood drives, planned camping and climbing trips, and advocated for employee friendly policies. I could have just come in, done my job and gone home. But I feel that since we spend so many of our waking hours at work, we should really enjoy working there.

Another thing I’ve always believed is that work should be challenging and fulfilling. Here’s an excerpt from the email I sent to my coworkers:

There are all sorts of things each of you can do to grow professionally and build your career.  Don’t get too comfortable with your daily tasks. Never pass up anything interesting to you because it is different or unknown. Seek out those new challenges and let your manager know what floats your boat. You might be surprised what you can do. I want to thank Petzl for the opportunity they have given me to grow and develop as an IT professional. I’m excited to step out of my comfort zone and jump into this new adventure.

I’m looking forward to sharing the new things I learn in the coming months. Stay tuned!