Was this page helpful?

Creating a Proposal Generation Solution With Dekiscript

    Time required for initial development: 1 week
    Skill level:Intermediate
    Benefits: Learn how to utilize the Salesforce connector to develop a time-saving proposal generation solution

    This tutorial shows how to create a proposal generator using the Salesforce connector and Dekiscript. It requires a MindTouch Deki Enterprise- or Standard-licensed installation. An example of a actual styled PDF proposal can be viewed here: MindTouch Proposal

    The templates and other page code discussed here have been attached at the bottom of this tutorial.

     

    Enterprises use any number of various means (including MindTouch Deki!) to create proposals for customers and clients. Oftentimes, however, these methods are not implemented well, are not automated, or are not uniform across the sales department. Our own internal system of generating proposals was like many other companies in this regard; while there was general direction, there certainly was no uniformity amongst proposals generated by different salespeople. Obviously, this is undesirable.

     

    To solve this problem, I was tasked with creating a proposal generator that would allow the sales team to quickly create standardized proposals that reflected the information as entered in Salesforce. Here's how I did it.


    First, I had to create a custom field in Salesforce (which I called Generate Proposal) with the following content:

    "https://server_address/Sales/Proposals/ProposalGenerator" & "?Id=" & AccountId & "&oppId=" & Opportunity_ID__c & "&OwnerId=" & OwnerId

     

    This places a link in Salesforce. The link, when clicked, opens up a new tab with the URL //server_address/Sales/Proposals/ProposalGenerator, and passes the Account ID, the Opportunity ID, and the Owner ID parameters (for more detailed information on Salesforce objects, visit http://www.salesforce.com/us/developer/docs/api/index.htm) . From those parameters, we can start using Dekiscript to generate our proposals.

     

    First step: the Proposal Generator options page

    The ProposalGenerator page looks like this:

     

     proposalgenpic.png

    This page performs several functions. First, it needs to retrieve a bit more data than is passed from Salesforce in the URL. This is the code in the topmost <div> (thus allowing the variables to be used throughout the entire document):

    <div init="var q1 = 'Select a.Name from Account a where a.id = \'' .. __request.args.Id .. '\'';
    var q2= 'Select u.FirstName, u.LastName from User u where Id = \'' .. __request.args.OwnerId .. '\'';
    var compNameQuery = salesforce.Record{query: q1};
    var salesNameQuery = salesforce.Record{query: q2};
    var compName = compNameQuery.Name;
    var salesName = salesNameQuery.FirstName .. ' ' .. salesNameQuery.LastName;">

    Salesforce queries are defined and made using the salesforce.Record Dekiscript function. It retrieves the account name and the first and last name of the sales team member responsible for the sale (using the __request.args.Id and __request.args.OwnerId, respectively, retrieved from the URL). I then defined the company name in compName and combined the first and last name into a single salesName variable. Note that at least in our case, the opportunity name differs from the company/account name, so I had to get the name of the company based on the opportunity ID. 

     

    Next, I put the title at the top of the page along with the name of the opportunity, and created the form as laid out above. I also created some hidden form variables to be passed on to the second portion of the generator. I broke up a bit of the formatting for easier reading.

    <p style="text-align: center;"><strong>Proposal generator for {{compName}}</strong></p>
    <br />
    <form name="proposalGenForm" action="{{'/Sales/Proposals/' .. salesName .. '/' .. compName .. '_-_' .. date.format(date.now, 'MM-dd-yyyy')}}" method="get">
    <input type="hidden" name="salesName" value="{{salesName}}" /><input type="hidden" value="false" name="requestTraining" />
    <input type="hidden" value="{{__request.args.oppId}}" name="oppId" /><input type="hidden" value="{{compName}}" name="companyName" />
    <input type="hidden" value="{{__request.args.OwnerId}}" name="OwnerId" />

    The form, when submitted, will create a second page in a specified location under a folder labeled with the salesperson's name. The company name followed by the date of the proposal comprise the title of the page. The generator form will also pass the name of the template that will lay out the proposal itself. We have three different proposal types (Short, Enterprise, or Community), the selection of which is based on the entity receiving the proposal as well as the items being purchased. The user decides upon the type of proposal, and selects the option to define the template used.

    The code for that option is just a set of basic Javascript radio buttons laid out like this (again, breaking up the formatting for easier reading here):

    <p style="text-align: center;">Proposal Type</p>
        <table cellspacing="1" cellpadding="1" border="1" align="center" width="300">
            <tbody>
                <tr>
                    <td>
                    <p style="text-align: center;"><input type="radio" name="template" value="shortProposal" checked="true" /> Short Form 
                    <input type="radio" name="template" value="enterpriseProposal" /> Enterprise 
                    <input type="radio" name="template" value="communityProposal" />Community</p>
                    </td>
                </tr>
            </tbody>
        </table>
        <br />

     

    We have certain sets of standard verbage that we wanted to use for the different products that MindTouch offers. These options aren't always defined in Salesforce, as they are custom items (skinning, for example, is done on a client-by-client basis). They're selected here by the salesperson. For example:

    <tr>
                <td style="text-align: center;">Training</td>
                <td><input type="radio" value="0" checked="true" name="noTraining" onclick="endUserTraining.checked=false;adminTraining.checked=false;developerTraining.checked=false;requestTraining.value=false" />None<br />
                <input type="checkbox" name="endUserTraining" value="true" onclick="noTraining.checked=false;requestTraining.value=true" />End-User<br />
                <input type="checkbox" name="adminTraining" value="true" onclick="noTraining.checked=false;requestTraining.value=true" />Administrative<br />
                <input type="checkbox" name="developerTraining" value="true" onclick="noTraining.checked=false;requestTraining.value=true" />Developer</td>
            </tr>

     

    Some options are radio buttons, and some are checkboxes. The type is dependent upon which service; obviously we don't want to enable a sales team member to accidentally select "No skinning" and "Intermediate Skinning", but we do want them to be able to select "Administrative Training" and "Developer Training" if desired. In addition, you can see above that I disabled the checkboxes for the service via Javascript if the user selects "None" to prevent any accidental selections. Here's a block showing the radio buttons for Support:

    <tr>
                <td style="text-align: center;">&nbsp;Support</td>
                <td><input type="radio" name="supportType" value="none" checked="true" />None<br />
                <input type="radio" name="supportType" value="Basic" />Basic<br />
                <input type="radio" name="supportType" value="Silver" />Silver<br />
                <input type="radio" name="supportType" value="Gold" />Gold<br />
                <input type="radio" name="supportType" value="Platinum" />Platinum</td>
            </tr>

     

    In the last part of this form, I put in a Javascript drop-down box for selecting payment terms (also not defined in Salesforce), defined a submit button, and closed out the form and div.

    <tr>
                    <td style="text-align: center;">Payment Terms</td>
                    <td><select id="menu" name="paymentTerms">
                    <option value="Upon Receipt">Upon Receipt</option>
                    <option value="Net 15">Net 15</option>
                    <option value="Net 30">Net 30</option>
                    </select></td>
                </tr>
            </tbody>
        </table>
        <input type="submit" value="Generate Proposal" />
    </form>
    </div>

     

    When the salesperson clicks "Generate Proposal", the form is submitted and a new page is created with the proposal. Let's start with the Short Form.

     

    Short Form Proposal

    The short form looks like this when generated (note that the CSS styling hasn't been applied here):

    shortform.png

    The picture compression is taking out the bottom line in the items list (trust me, it's there!). 

     

    Time to dig in to the template code being used.

    <h1>Template:ShortProposal</h1>
    <p><img align="left" class="internal lwrap" alt="horizontal_thumb.png" style="width: 258px; height: 86px;" src="/@api/deki/files/1127/=horizontal_thumb.png" /></p>
    <p>&nbsp;</p>
    <p>&nbsp;</p>
    <p>&nbsp;</p>
    <p>&nbsp;</p>

    First, I just name the template and place the MindTouch logo in the upper left hand corner. I then put some spaces in via the WYSIWYG editor, which explains the <p>'s.

    Next, I used the parameters passed from the ProposalGenerator page to get some info about the company, opportunity contact, and sales team member. That information is laid out in a table that floats on the left of the page under the logo. It's important to note that this page opens in "edit" mode; the salesperson may need to add or change any information here (i.e., add custom skinning information or any special notes not stored in Salesforce). As such, any Dekiscript in this page would need to run at edit time. This is achieved by placing edit: before the Dekiscript that needs to be run. Once again, the formatting has been broken up for ease of reading.

    <table cellspacing="1" cellpadding="1" border="1" width="300" style="float: left;">
        <tbody>
            <tr>
                <td>Prepared by:</td>
                <td>{{edit:__request.args.salesName}}</td>
            </tr>
            <tr>
                <td>Phone Number:</td>
                <td>{{edit: var q1='Select u.Extension, u.Phone from User u where u.id=\'' .. __request.args.OwnerId .. '\''; 
                    var ownerPhone = salesforce.Record{query: q1};web.html(ownerPhone.phone .. ' x' .. ownerPhone.extension)}}</td>
            </tr>
            <tr>
                <td>Email Address:</td>
                <td>{{edit: var q2='Select u.email from User u where u.id=\'' ..__request.args.OwnerId .. '\'';
                    var ownerEmail = salesforce.Record{query: q2};web.html(ownerEmail.email)}}</td>
            </tr>
            <tr>
                <td>Prepared For:</td>
                <td>{{edit: var q3='Select o.ContactId from OpportunityContactRole o where o.opportunityid= \'' ..__request.args.oppId .. '\''; 
                    var contactID = salesforce.Record{query: q3}; var q4= 'Select c.FirstName, c.LastName from Contact c where c.id= \'' .. contactID.contactid .. '\''; 
                    var contactName=salesforce.Record{query: q4}; web.html(contactName.firstname .. ' ' .. contactName.lastname)}}</td>
            </tr>
        </tbody>
    </table>

     

    The next thing is to get the current and valid-until date using the Dekiscript date function, and lay that out in a table on the right-hand side of the page.

    <table cellspacing="1" cellpadding="1" border="1" align="right" width="200" style="float: right;">
        <tbody>
            <tr>
                <td>Proposal&nbsp;Date:</td>
                <td style="text-align: left;">{{edit:date.format(date.now,&nbsp; "d")}}</td>
            </tr>
            <tr>
                <td>Valid Until:</td>
                <td style="text-align: left;">{{edit:date.format(date.addDays(date.now, 15),&nbsp; "d")}}</td>
            </tr>
        </tbody>
    </table>

     

    Next I put in a blurb about MindTouch, and set up the items table:

    <p><span style="font-size: medium;">MindTouch Deki is recognized as the most sophisticated, popular, award-winning, enterprise-scale, open source collaboration solution in the market today. MindTouch Deki is built with a Web Oriented Architecture (WOA), enables users to connect teams, enterprise systems, publishing systems, Web services and Web 2.0 applications to create unique content oriented experiences while maintaining IT governance.</span></p>
    <p>&nbsp;</p>
    <p>&nbsp;</p>
    <p>&nbsp;</p>
    <p>&nbsp;</p>
    <table cellspacing="1" border="1" width="100%" rules="rows">
        <tbody>
            <tr>
                <td style="background-color: rgb(192, 192, 192);"><strong>&nbsp;Product Name</strong></td>
                <td style="background-color: rgb(192, 192, 192); text-align: center;"><strong>Product Description</strong></td>
                <td style="background-color: rgb(192, 192, 192); text-align: center;"><strong>Unit Price</strong></td>
                <td style="background-color: rgb(192, 192, 192); text-align: center;"><strong>Qty</strong></td>
                <td style="background-color: rgb(192, 192, 192); text-align: right;">&nbsp;<strong>Totals (USD)</strong></td>
            </tr>

     

    After that, I create the items list:

    {{edit: var q3 = 'Select o.totalopportunityquantity from Opportunity O where o.id=\'' .. __request.args.oppId .. '\''; 
    var quantity=salesforce.Record{query: q3}; 
    var itemInfo; 
    if(quantity==1) { var q4 ='Select o.PriceBookEntryId, o.Description, o.TotalPrice, o.UnitPrice, o.Quantity from OpportunityLineItem o where o.opportunityid=\'' .. __request.args.oppId .. '\''; 
    let itemInfo = salesforce.Record{query: q4} } 
    else { var q5 ='Select o.PriceBookEntryId, o.Description, o.TotalPrice, o.UnitPrice, o.Quantity from OpportunityLineItem o where o.opportunityid=\'' .. __request.args.oppId .. '\''; 
    let itemInfo = salesforce.RecordList{query: q5} }; 
    foreach (var item in itemInfo) { var q6 ='Select p.Name, p.Product2ID from PricebookEntry p where p.id= \''..item.pricebookentryid..'\''; 
    let desc = salesforce.Record{query: q6}; var q7 = 'Select p.Description from Product2 p where p.id= \''.. desc.product2id .. '\'';
    var productDescription = salesforce.Record{query: q7};
    var lineItem='&lt;tr&gt;&lt;td style="vertical-align: top;" width="20%;"&gt;&lt;strong&gt;'..desc.name..'&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;'..productDescription.description..'&lt;/td&gt;&lt;td style=<br />
            text-align="right;"&gt;&nbsp; '..num.format(item.unitprice, "$#,###")..'&lt;/td&gt;&lt;td style="text-align:center"&gt;'..item.quantity..'&lt;/td&gt;&lt;td style="text-align: right;"&gt;'..num.format(item.totalprice, "$#,###")..'&lt;/td&gt;'; web.html(lineItem)}}}
            <tr>
                <td><strong>Total</strong></td>
                <td>&nbsp;</td>
                <td>&nbsp;</td>
                <td>&nbsp;</td>
                <td style="text-align: right;">{{edit:var q8 = 'Select o.Amount from Opportunity o&nbsp; where o.id= \'' ..__request.args.oppId .. '\''; var totalSum=salesforce.Record{query: q8}; num.format(totalSum.amount, "$#,###")}}</td>
            </tr>
        </tbody>
    </table>

    There is a LOT going on here, and yes, there is more than one way to skin a cat. All of the code in this tutorial would probably benefit from some refactoring, as it went through a few stages and had functionality added and removed. For example, the web.html function is a is a bit clumsy since it's hard to modify after initial creation (and just looks ugly!). This could also likely be done with fewer Salesforce calls as well, but for now it works.

    Without getting into too much detail, the code above retrieves information about the line items for the particular opportunity in Salesforce, and iterates through them grabbing their name, brief description, price information and quantity. It lays out the table fomatting the fields as necessary, and plugs in the total at the bottom.

     

    The last part of this template lays out the payment terms, and places the approval lines with the company name that was passed from the ProposalGenerator page earlier.

    <p style="text-align: right;"><span style="font-size: smaller;">Payment terms: {{edit:__request.args.paymentTerms}}</span></p>
    <p>&nbsp;</p>
    <p>&nbsp;</p>
    <p>Special instructions:</p>
    <table height="200" cellspacing="1" cellpadding="1" border="1" width="100%">
        <tbody>
            <tr>
                <td style="text-align: left; vertical-align: top;">{{edit: var q8 = 'Select o.Special_Instructions__c from Opportunity o where o.id= \'' .. __request.args.oppId .. '\''; var specialInst=salesforce.Record{query: q8}; web.html(specialInst.special_instructions__c)}}</td>
            </tr>
        </tbody>
    </table>
    <p>&nbsp;</p>
    <p>&nbsp;</p>
    <p>&nbsp;</p>
    <p>&nbsp;</p>
    <table cellspacing="1" cellpadding="1" border="0" align="center" width="90%">
        <tbody>
            <tr>
                <td style="text-align: right;">&nbsp;_________________________________</td>
                <td style="text-align: right;">&nbsp; _________________________________</td>
            </tr>
            <tr>
                <td style="text-align: right;">&nbsp;MindTouch, Inc</td>
                <td style="text-align: right;">&nbsp;{{edit:__request.args.companyName}}</td>
            </tr>
        </tbody>
    </table>

     

    That's it for the Short Form proposal. Next, let's take a look at the Enterprise proposal. It's similar to the short form, but with a different layout and a few more moving parts.

    Enterprise Proposal

    The Enterprise Proposal is a bit more complex; it's geared more towards multiple services or large-scale project deployments. You can see an example of what it looks like here: Enterprise Proposal. Please note that there is no CSS styling here, and that some of the detail is "lorem ipsum". Obviously CSS styles would be applied and actual detail would be filled in before saving this. Unless you're presenting a proposal to someone who is interested in Cicero but hates visually appealing documents, in which case you'd probably leave it as-is.

    The layout is a bit different, but the first part is essentially the same, getting the salesperson's name, the company for whom the proposal is being prepared, as well as the date for the proposal. That's pretty straightforward, so I'll skip the code for it. The same goes for the company information, no Dekiscript voodoo there.

    The next part is where the Enterprise Proposal really begins to differ; as this proposal has a number of items and it is likely to be passed to individuals that aren't entirely familiar with the wonderful goodness that Deki has to offer, some in-depth description of the line items (which is not stored in Salesforce) needs to be presented. This is where the real usefulness comes in. Previously, a sales team member would have to copy and paste a number of paragraphs into a document and format it all, spending a lot of time and effort on something other than the making sales. In addition, errors could be made, formatting could differ wildly between proposals, and the resulting document might not be up-to-par. With the following bit of code, all of that goes away:

    <p style="text-align: left;">
    {{edit: if(__request.args.projMgmt){wiki.template.ProposalProjMgmt()}}}
    {{edit: if(__request.args.projMgmt){web.html('&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;')}}}
    {{edit: if(__request.args.archConsult){wiki.template.ProposalArchConsult()}}}
    {{edit: if(__request.args.archConsult){web.html('&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;')}}}
    {{edit: if(__request.args.skinningType !=0){wiki.template.ProposalSkinning()}}}
    {{edit: if(__request.args.skinningType !=0){web.html('&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;')}}}
    {{edit: if(__request.args.hostingType !=0){wiki.template.ProposalHostingHeader()}}}
    {{edit: if(__request.args.hostingType !=0){web.html('&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;')}}}
    {{edit: if(__request.args.requestTraining == "true"){wiki.template.ProposalTrainingHeader()} }}
    {{edit: if(__request.args.endUserTraining){wiki.template.ProposalTrainingEndUser()} }}
    {{edit: if(__request.args.adminTraining){wiki.template.ProposalTrainingAdmin()}}}
    {{edit: if(__request.args.developerTraining){wiki.template.ProposalTrainingDeveloper()}}}
    {{edit: if(__request.args.requestTraining == "true"){web.html('&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;')}}}
    {{edit: if(__request.args.supportType == "Basic"){wiki.template.ProposalSupportBasic()} }}
    {{edit: if(__request.args.supportType == "Basic"){web.html('&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;')}}}
    {{edit: if(__request.args.supportType == "Silver"){wiki.template.ProposalSupportSilver()} }}
    {{edit: if(__request.args.supportType == "Silver"){web.html('&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;')}}}
    {{edit: if(__request.args.supportType == "Gold"){wiki.template.ProposalSupportGold()}}}
    {{edit: if(__request.args.supportType == "Gold"){web.html('&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;')}}}
    {{edit: if(__request.args.supportType == "Platinum"){wiki.template.ProposalSupportPlatinum()} }}
    the the{{edit: if(__request.args.supportType == "Platinum"){web.html('&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;')}}}</p>

    Essentially, the statements look at the URL to see if the parameter passed from the ProposalGenerator page applies, and calls the requisite template (or applies the spacing in between the item detail, more on that in a bit). Services that have could have multiple options selected have to have a header called first, and the descriptions are laid out in order after that.

    Initially, I had an enormous amount of data arranged all on this one page, depending upon the inelegant web.html function to lay out the data as it was called. This had a huge drawback, however; if the descriptions of the line items were changed by the sales team over time, someone would have to go in to this template, encode it all, and place it in the right spot. That's not ideal as far as maintainability goes, and you'd either have to take your chances with a salesperson doing HTML encoding or someone familiar with Dekiscript would have to be called upon to do it. To recitfy that situation, I opted to place the item detail in templates that could be called. This would allow anyone, Dekiscripter or not, to come in and update the detail as needed and not have to worry about encoding or formatting. In regards to the spacing I mentioned above, the proper formatting here in the proposal itself would be dependent upon the person updating the detail template to remember to place an exact number of line breaks or <p> tags at the end. It's highly unlikely that the person would always remember to do that, and so I chose to put it in here.

    Again, this could be refactored into more sleek code, but it works so for now my intial thought process is on display.
     

    After the in-depth detail is created, the same line item table, special instruction box, and signature lines that we saw in the short form are applied. The Community Proposal is the same as the Enterprise, but with wording geared towards a different audience.

     

    I know, that was a long one! There are a lot of moving parts involved, and a lot of different options that had to be addressed. The good news is, broken down into its individual parts, it's clear that there's no black magic involved. The power of Dekiscript and the extensibility of MindTouch Deki itself makes developing a solution like this not just possible but relatively easy. This particular solution was developed start-to-finish in under a week! I hope you enjoyed reading about the code and thought process involved as I had developing it.

    While this particular solution utilizes the Salesforce connector, it could very easily be reworked to use the SugarCRM adapter as well (which also ships with Deki Enterprise).

    I've attached the three main files, the main ProposalGenerator page code, and the ShortForm and EnterpriseProposal templates, so that you can see them in their entirety. I didn't include any of the line item detail templates, as they're just paragraphs of text pasted into them. Please feel free to check them out!

    Was this page helpful?
    Tag page

    Files 7

    FileVersionSizeModified 
    Viewing 3 of 3 comments: view all
    @robertm, this tutorial needs a video and a pretty proposal sample to be added to the page.
    Posted 20:56, 6 Mar 2009
    It seems that your solution is not dynamically "linked" with salesforce. If some OpportunityLineItem are added to your Opportunity, your table is not updated.

    Correct ?

    Would it be possible to create that dynamic link using Page Properties (Store AccountId,Opportunity_ID__c and OwnerId) ?

    Thanks for answering
    Posted 05:46, 2 Apr 2009
    @vdaron These are proposals that are generated, saved, and then sent out to customers. As these are documents we'd want to maintain some type of audit trail for, they are saved in "static" mode; if they were not, every time someone visited the page it would update the information based on what's in Salesforce. This isn't ideal here, since the potential client would have one version of the document but ours might be different. If you wanted to have the items update on page load (implying that this would be a different type of document or functionality) that could be done as well by simply removing the "edit:" from the Dekiscript for those items.
    Posted 10:38, 16 Apr 2009
    Viewing 3 of 3 comments: view all
    You must login to post a comment.

    Copyright © 2011 MindTouch, Inc. Powered by