function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
DahveedDahveed 

Customized Quote pdf quote and attach to quotedocument

Because of particular integration I have to leave certain line items out of the quote based on a checkbox. Because of this I can't use the default quote templates unless anyone else knows how to exclude quotelineitems from a quote. What I did was model it after Jeff Douglas attaching a pdf to an object. I have a custom button called 'Create PDF'.  The button goes to the VF page below. On it is a commandlink which references the viewPDF method in the QuoteConsultingExtension to view the PDF before saving. This works great and I can view the PDF no problem with all the quote fields.

However when I click the savePDF button. I get "SObject row was retrieved via SOQL without querying the requested field:" on all the quote standard fields. I even tried to write my own SOQL query which references all the default quote fields and it then throws an error SObject row was retrieved via SOQL without querying the requested field: User.LastName when I'm not even referencing User.LastName on my VF page.  This is crazy?? Can anyone help me with this or provide a better solution?

 

Thanks heaps

 

Dahveed

 

<apex:page standardController="Quote" showHeader="true"  extensions="QuoteConsultingExtension" >
  <apex:sectionHeader title="Create and attach a quote for signing" subtitle="Attach a PDF" 
    description="You must attach a PDF quote for use with docusign"/>
  <apex:form >
    <apex:pageBlock title="PDF Input">
 
      <apex:pageBlockButtons >
      <apex:commandLink  action="{!viewPDF}" value="View PDF" target="_blank" />
      <apex:commandButton action="{!savePDF}" value="Save"/>
      <apex:commandButton action="{!Cancel}" value="Cancel"/>
      </apex:pageBlockButtons>
      <apex:pageMessages />
 
      <apex:pageBlockSection >
 
        <apex:pageBlockSectionItem >
            <apex:outputLabel value="File Name" for="pdfName"/>
   <!--       <apex:inputText value="{!pdfName}" id="pdfName"/> -->
        </apex:pageBlockSectionItem>
 
        <apex:pageBlockSectionItem >
            <apex:outputLabel value="Account ID" for="id"/>
  <!--        <apex:inputText value="{!parentId}" id="id"/> -->
        </apex:pageBlockSectionItem>
 
      </apex:pageBlockSection>
 
    </apex:pageBlock>
  </apex:form>

 

public class QuoteConsultingExtension {
    
    private List<QuoteLineItem> conLineItems;
    private decimal conTotal;
    public final Quote myQuote;
   
    
    public QuoteConsultingExtension(ApexPages.StandardController stdController) {
        this.myQuote = (Quote)stdController.getRecord();

    }

    
    public List<QuoteLineItem> consultingLineItems{
        get{
            if(conLineItems == null)
                conLineItems = new List<QuoteLineItem>();
            
            conLineItems = [SELECT Id,Product_CodeF__c, Product_NameF__c,Quantity,
                            ListPrice,Discount,UnitPrice,TotalPrice, 
                            isConsultingF__c, isGlobalProductF__c 
                            FROM QuoteLineItem 
                            WHERE isConsultingF__c = true AND isGlobalProductF__c = false];                                                        
            
            return conLineItems;
        
        }
        set;
        
     }
     
    public decimal consultingTotal{
         get{    
             conTotal=0;
             for(QuoteLineItem qli: conLineItems){
                 conTotal += qli.Quantity * qli.UnitPrice;   
             }
         return conTotal;
         }  
         set;
    } 
    public PageReference viewPDF() {
        string quoteId = ApexPages.currentPage().getParameters().get('id');
        PageReference thePDF = Page.Consulting_Services_Quote;
        thePDF.getParameters().put('id',quoteId);
        thePDF.setRedirect(true);
        return thePDF;
    
    }     
    public PageReference savePDF() { 
        string qId = ApexPages.currentPage().getParameters().get('id');
        System.debug(qId);
        PageReference thePDF = Page.Consulting_Services_Quote;
        thePDF.getParameters().put('id',qId);
        thePDF.setRedirect(true);
                
        quotedocument qd = new quotedocument();
        qd.document = thePDF.getContentAsPDF();
        qd.quoteId = qId;
        insert qd;
    
        PageReference quotePage = new PageReference('/'+ qId);
        quotePage.setRedirect(true);
        return quotePage;
    }
     
}

 

Ashley@WCELAshley@WCEL
There's some code missing I think. I had expected to see a second VF page with renderAs="PDF".

I assume that is the page being referenced by Page.Consulting_Services_Quote.
DahveedDahveed

Ignore the below in the PDF as I was hoping initially to be able to put a button on the pdf as in the standard quoting system.


 

<apex:actionFunction name="savePDF" action="{!savePDF}" />
Ashley@WCELAshley@WCEL

In that second VF page that renders as a PDF, what is qQuote? Makes me wonder if that's a search and replace gone awry and should really be Quote.ExpirationDate, etc.

 

 

<apex:outputText value="Date: {!TODAY()} "/><br></br>
<apex:outputText value="Offer Expires: {!qQuote.ExpirationDate}"/><br></br>
<apex:outputText value="From: {!qQuote.Opportunity.Owner.FirstName} {!qQuote.Opportunity.Owner.LastName} "/><br></br>
<apex:outputText value="Reference: {!qQuote.Opportunity}"/>
</apex:panelGroup>
</apex:panelGrid>

DahveedDahveed

I posted that before I changed things back. The qQuote was my attempt to write a custom SOQL query on the Quote object to place data in the fields it was saying that I was calling on the Quote sObject without a SOQL query. 

I am seeing more an issue like this I not i'm not emailing.

http://salesforce.stackexchange.com/questions/5432/object-not-found-issue-in-pdf-attachment-visualforce-page-for-an-email

and

http://boards.developerforce.com/t5/Apex-Code-Development/Error-on-getContentAsPDF-PDF-preview-page/td-p/228835

 

This is where I am at now. I put in a try catch and the attachment now works however I cannot view it. It's as if the Quote standard controller isn't running but my quote line item is. This is my current code. Sorry about the qQoute. I'm asking for help and not giving the exact problem. I promise this is accurate. Thank you for looking at it.

I think maybe I should try making separate extensions instead of using one?

public class QuoteConsultingExtension {
    
    private List<QuoteLineItem> conLineItems;
    private decimal conTotal;
    public final Quote myQuote;
   
    
    public QuoteConsultingExtension(ApexPages.StandardController stdController) {
        this.myQuote = (Quote)stdController.getRecord();

    }


    
    public List<QuoteLineItem> consultingLineItems{
        get{
            if(conLineItems == null)
                conLineItems = new List<QuoteLineItem>();
            
            conLineItems = [SELECT Id,Product_CodeF__c, Product_NameF__c,Quantity,
                            ListPrice,Discount,UnitPrice,TotalPrice, 
                            isConsultingF__c, isGlobalProductF__c 
                            FROM QuoteLineItem 
                            WHERE isConsultingF__c = true AND isGlobalProductF__c = false];                                                        
            
            return conLineItems;
        
        }
        set;
        
     }
     
    public decimal consultingTotal{
         get{    
             conTotal=0;
             for(QuoteLineItem qli: conLineItems){
                 conTotal += qli.Quantity * qli.UnitPrice;   
             }
         return conTotal;
         }  
         set;
    } 
    //This works ok
    public PageReference viewPDF() {
        string quoteId = ApexPages.currentPage().getParameters().get('id');
        PageReference thePDF = Page.Consulting_Services_Quote;
        thePDF.getParameters().put('id',quoteId);
        thePDF.setRedirect(true);
        return thePDF;
    
    }
    
 
         
    public PageReference savePDF() { 
        string qId = ApexPages.currentPage().getParameters().get('id');
  //The debug show it is retrieving the quote Id in the logs      
System.debug(qId); PageReference thePDF = viewPDF(); //The debug shows that it's grabbing the correct pagereference in the logs System.debug('PageRefernce' + thePDF); //these are commented out but using the try catch was the only way to get it to attach a pdf
// however the pdf is unviewable // Blob pdfBlob; // try{ // pdfBlob = thePDF.getContent(); // pdfBlob = thePDF.getContentAsPDF(); //} //catch(VisualforceException e){ // pdfBlob = Blob.valueOf('Blob when occur error'); // } //It error comes when I use the getContentAsPDF() function and it basically is acting as if it's
// Not running the Quote standard constructor which gives you all the fields on the quote object
QuoteDocument qd = new QuoteDocument(quoteId = qId, Document = thePDF.getContentAsPDF()); System.debug('qd : ' + qd); insert qd; PageReference quotePage = new PageReference('/'+ qId); quotePage.setRedirect(true); return quotePage; } }

 

<apex:page standardController="Quote" showHeader="false" renderAs="pdf" applyhtmltag="false" extensions="QuoteConsultingExtension" >
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
<style type="text/css">
body {
    font-family: sans-serif;
    font-size:12px;
}
table.imagetable {
    width:100%;
    font-family: sans-serif;
    font-size:12px;
    color:#333333;
    border-width: 1px;
    border-color: #999999;
    border-collapse: collapse;
}

table.imagetable th {
    background:#C0D5D8;
    border-width: 1px;
    padding: 8px;
    border-style: solid;
    border-color: #999999;
}

table.imagetable td {
    background:#EFEFEF;
    border-width: 1px;
    padding: 8px;
    border-style: solid;
    border-color: #999999;
}

table.sectionHeadings {
    font-family: sans-serif;
    font-size:16px;
    color:#1E4481;
    border-width: .5px;
    border-color: #999999;
    border-collapse: collapse;
}

table.sectionHeadings th{
    padding: 15px;
    border-top-style:solid;
    border-top-color:#1E4481;
    border-bottom-style:solid;
    border-bottom-color:#1E4481;
    width: 690px;
}

table.headingTopLine th{
    font-family: sans-serif;
    font-size:16px;
    color:#1E4481;
    padding: 15px;
    border-top-style:solid;
    border-top-color:#1E4481;
    width: 690px;
}
table.headingBottomLine td{
    font-family: sans-serif;
    font-size:16px;
    color:#1E4481;
    padding: 15px;
    border-bottom-style:solid;
    border-bottom-color:#1E4481;
    width: 40px;
}

</style>
</head>    
<body> 
     <apex:form >
     <apex:actionFunction name="savePDF" action="{!savePDF}" />
     </apex:form>  
     <apex:panelGrid columns="3" width="100%" style="font-size: 12px; font-family: sans-serif">
         <apex:image url="https://c.cs14.content.force.com/servlet/servlet.ImageServer?oid=00Dc0000001LPXH&id=015c000000075xm" width="208" height="79" />    
         <apex:panelGroup layout="block" >
             <apex:outputText value="{!$Organization.Name}" /><br></br>
             <apex:outputText value="{!$Organization.Street}"/><br></br>
             <apex:outputText value="{!$Organization.City}, {!$Organization.State} {!$Organization.PostalCode}"/><br></br>
             <apex:outputText value="{!$Organization.Phone}"/>
         </apex:panelGroup>
         <apex:panelGroup layout="block" >             
             <apex:outputText value="Date: {!TODAY()} "/><br></br>             
             <apex:outputText value="Offer Expires: {!Quote.ExpirationDate}"/><br></br>
             <apex:outputText value="From: {!Quote.Opportunity.Owner.FirstName} {!Quote.Opportunity.Owner.LastName} "/><br></br>
             <apex:outputText value="Reference: {!Quote.Opportunity}"/>
         </apex:panelGroup>
     </apex:panelGrid>
     <br> </br>
     
     <!-- Order Schedule Section Heading  -->
     
     <table Class="sectionHeadings" >
         <tr>
         <th>Order Schedule<br> </br></th>
         </tr>         
     </table>
     <br> </br>
     
     <!-- Prepared for Bill To Section -->
     
     <table  width="100%" >
         <tr >
         <th><apex:outputText value="Prepared for" /></th>         
         <th><apex:outputText value="Bill to" /></th>        
         </tr>       
         <tr>             
         <td><apex:outputText value="Name    : {!Quote.Contact.FirstName} {!Quote.Contact.LastName}" /></td>             
         <td><apex:outputText value="Name    : {!Quote.BillingName}" /></td>
         </tr>
         <tr>             
         <td><apex:outputText value="Company    : {!Quote.Opportunity.Account.Name}" /></td>             
         <td><apex:outputText value="Company    : {!Quote.Opportunity.Account.Name}" /></td>
         </tr>
         <tr>             
         <td><apex:outputText value="Address : {!Quote.Contact.MailingStreet}" /></td>             
         <td><apex:outputText value="Address : {!Quote.BillingStreet}" /></td>
         </tr>
         <tr>             
         <td><apex:outputText value="  {!Quote.Contact.MailingCity}, {!Quote.Contact.MailingState} {!Quote.Contact.MailingPostalCode}" style="margin-left:55px" /></td>             
         <td><apex:outputText value="  {!Quote.BillingCity}, {!Quote.BillingState} {!Quote.BillingPostalCode}" style="margin-left:55px" /></td>
         </tr>
         <tr>             
         <td><apex:outputText value="Phone : {!Quote.Contact.Phone}" /></td>             
         <td><apex:outputText value="Phone : {!Quote.Opportunity.Account.Phone}" /></td>
         </tr>
         <tr>             
         <td><apex:outputText value="Email : {!Quote.Contact.Email}" /></td>             
         <td><apex:outputText value="Email : {!Quote.Contact.Email}" /></td>
         </tr>                    
     </table>
     <br> </br>
     
     <!-- Terms Section  -->
     
         <table class="headingTopLine">
         <tr>
         <th > Terms </th>
         </tr>
         <tr>
                      
         <td><apex:outputText value="{!Quote.Billing_Terms__c}" style="font-weight:bold" /></td>                     
         </tr> 
         <tr>         
         <td><apex:outputText value="Payment terms    " style="font-weight:bold" rendered="{!IF((Quote.Billing_Terms__c="Fixed Fee Upfront"),true,false)}" escape="false" /> 
         <apex:outputText value=": Professional fees are due 100% upon the Effective Date.  All invoices are due upon receipt." rendered="{!IF((Quote.Billing_Terms__c="Fixed Fee Upfront"),true,false)}" escape="false" style="margin-left:10px" />          
         </td>
         </tr>
         <tr>
         <td><apex:outputText value="Payment method  " style="font-weight:bold" rendered="{!IF((Quote.Billing_Terms__c="Fixed Fee Upfront"),true,false)}" escape="false" /> 
         <apex:outputText value="   : Standard Invoicing - This option is available only for annual billing. You will be invoiced per the agreed upon payment terms as outlined on this Order Schedule." rendered="{!IF((Quote.Billing_Terms__c="Fixed Fee Upfront"),true,false)}" escape="false"  />          
         </td>         
         </tr>
         <tr>
         <td><apex:outputText value="Payment terms  " style="font-weight:bold" rendered="{!IF((Quote.Billing_Terms__c="Fixed Fee Milestone Project Based"),true,false)}" escape="false"/> 
         <apex:outputText value="   : Professional fees are due …  .All invoices are due upon receipt." rendered="{!IF((Quote.Billing_Terms__c="Fixed Fee Milestone Project Based"),true,false)}" escape="false" style="margin-left:10px"/>          
         </td>
         </tr>
         <tr>
         <td><apex:outputText value="Payment method  " style="font-weight:bold" rendered="{!IF((Quote.Billing_Terms__c="Fixed Fee Milestone Project Based"),true,false)}" escape="false" /> 
         <apex:outputText value="   : To be completed" rendered="{!IF((Quote.Billing_Terms__c="Fixed Fee Milestone Project Based"),true,false)}" escape="false" />          
         </td>         
         </tr>
         <tr>
         <td><apex:outputText value="Payment terms  " style="font-weight:bold" rendered="{!IF((Quote.Billing_Terms__c="Time And Materials"),true,false)}" escape="false" /> 
         <apex:outputText value="   : To be completed" rendered="{!IF((Quote.Billing_Terms__c="Time And Materials"),true,false)}" escape="false"  style="margin-left:10px"/>          
         </td>         
         </tr>
         <tr>
         <td><apex:outputText value="Payment method  " style="font-weight:bold" rendered="{!IF((Quote.Billing_Terms__c="Time And Materials"),true,false)}" escape="false" /> 
         <apex:outputText value="   : To be completed" rendered="{!IF((Quote.Billing_Terms__c="Time And Materials"),true,false)}" escape="false" />          
         </td>         
         </tr>
         </table>
         <br> </br>  
         
               
     <!-- Start of Quote Line Items *****
     ****                           *****
     ****                           **-->
     <div style="page-break-after:always;">  
     <table class="headingTopLine">
         <tr>
         <th > Product - Consulting </th>
         </tr>
     </table>
          <table class="imagetable">
         <tr>
         <th><apex:outputText value="{!$ObjectType.Product2.Fields.ProductCode.Label}" /></th>
         <th><apex:outputText value="Product" /></th>
         <th><apex:outputText value="Qty" /></th>
         <th><apex:outputText value="List" /></th>
         <th><apex:outputText value="Discount" /></th>
         <th><apex:outputText value="Sale Price" /></th>
         <th><apex:outputText value="Total Price" /></th>
         </tr>
         <apex:repeat value="{!consultingLineItems}" var="line"  >         
         <tr>
             
             <td><apex:outputField value="{!line.Product_CodeF__c}" /></td>
             <td><apex:outputField value="{!line.Product_NameF__c}" /></td>
             <td><apex:outputField value="{!line.Quantity}" /></td>
             <td><apex:outputField value="{!line.ListPrice}" /></td>
             <td><apex:outputField value="{!line.Discount}" /></td>
             <td><apex:outputField value="{!line.UnitPrice}" /></td>
             <td><apex:outputField value="{!line.TotalPrice}" /></td>
              
         </tr>
         </apex:repeat>             
     </table>
      <apex:outputPanel layout="block" />
          <apex:panelGrid columns="2" columnClasses="right" width="100%">
              <apex:panelGrid columns="2" cellpadding="10" columnClasses="right totalLabel,right total" width="100%">
                  <apex:outputText value="Total"/>
                  <apex:outputText value="{!consultingTotal}"/>
              </apex:panelGrid>
          </apex:panelGrid>
          
      <table class="headingTopLine">
      <tr>
      <th >  </th>
      </tr>
      </table>
      </div>
      <apex:outputPanel layout="block" styleClass="line"/>
      
          <table class="headingTopLine">      
         <tr>
         <th >  </th>
         </tr>
         <tr>
         <td>This Order Schedule is submitted pursuant to the Software as a Service Provider Agreement (the “Agreement”) an something else? 
         </td>
         </tr>
         <br></br>
         <tr>
         <td>Prices shown above do not include any taxes that may apply. 
         </td>
         <br></br>
         </tr>
         <tr>
         <td style="font-weight:bold" >IN WITNESS WHEREOF, the parties hereto have caused this Order Schedule and related Agreement to be executed by their duly authorized officers or representatives as of the Effective Date, by signature below.         </td>
         </tr>
         <br> </br>
         <br> </br>
    </table>
    <br> </br>
    <!-- Signatures -->
         <table style="padding:10px" width="100%"  >
         <tr >
         <th><apex:outputText value="{!Quote.Opportunity.Account.Name} " /></th> 
          
               
         <th><apex:outputText value="Kyriba Corporation " /></th> 
         <th></th>                
         </tr>
         <br> </br>
     
         <tr>
         <td >Authorized signature</td>
         <td style="border-bottom-style:solid; border-bottom-color:black;width:150px"></td>
         <td style="border-bottom-style:clear;width:40px"> </td>
         <td >Authorized signature</td>
         <td style="border-bottom-style:solid; border-bottom-color:black;width: 170px"></td>                   
         </tr>
         <br></br>
         <br></br>
         <tr>
         <td >Print Name and Title</td>
         <td style="border-bottom-style:solid; border-bottom-color:black;width:150px"></td>
         <td style="border-bottom-style:clear;width:40px"> </td>
         <td>Print Name and Title</td>
         <td style="border-bottom-style:solid; border-bottom-color:black;width: 170px"></td>                   
         </tr>
         <br></br>
         <br></br>
         <tr>
         <td >Signature date</td>
         <td style="border-bottom-style:clear;width:25px"></td>
         <td style="border-bottom-style:solid; border-bottom-color:black;width:150px"></td>
         <td style="border-bottom-style:clear;width:40px"> </td>
         <td>Signature date</td>         
         <td style="border-bottom-style:clear;width:28px"> </td>
         <td style="border-bottom-style:solid; border-bottom-color:black;width: 170px"></td>                   
         </tr>
         <br></br>
         <br></br>
         </table>
</body>
</html>      
  </apex:page>

 

<apex:page standardController="Quote" showHeader="true"  extensions="QuoteConsultingExtension" >
  <apex:sectionHeader title="Create and attach a quote for signing" subtitle="Attach a PDF" 
    description="You must attach a PDF quote for use with docusign"/>
  <apex:form >
  <apex:outputField value="{!Quote.Billing_Terms__c}" />
    <apex:pageBlock title="PDF Input">
 
      <apex:pageBlockButtons >
      <apex:commandLink action="{!viewPDF}" value="View PDF" target="_blank" />
      <apex:commandButton action="{!savePDF}" value="Save"/>
      <apex:commandButton action="{!Cancel}" value="Cancel"/>
      </apex:pageBlockButtons>
      <apex:pageMessages />
 
      <apex:pageBlockSection >
 
        <apex:pageBlockSectionItem >
            <apex:outputLabel value="File Name" for="pdfName"/>
   <!--       <apex:inputText value="{!pdfName}" id="pdfName"/> -->
        </apex:pageBlockSectionItem>
 
        <apex:pageBlockSectionItem >
            <apex:outputLabel value="Account ID" for="id"/>
  <!--        <apex:inputText value="{!parentId}" id="id"/> -->
        </apex:pageBlockSectionItem>
 
      </apex:pageBlockSection>
 
    </apex:pageBlock>
  </apex:form>
      
  </apex:page>

 

 

 

DahveedDahveed

Yes!! I think it was a transaction issue with the way salesforce executes Apex. Once I separated the two controllers it worked fine.  Thanks for your help.

Ashley@WCELAshley@WCEL

Glad you got it working.

 

One question: have you written unit tests for this extension? Creating those in tandem with your classes (as opposed to at the very end) can make debugging a whole lot easier and helps to ensure that all subsequent changes don't break previously working functionality.

DahveedDahveed

That's a great suggestion. I usually write them after the fact because I get so consumed with finding if my logic is even correct.  I haven't been coding for very long(if you can't tell) so I think as my confidence builds so will the time when I write my unit tests. I used to create unit tests pretty quickly once I ran into problems but this slowed as soon as I saw I could run my user in debug mode and find which values are getting passed to variables using the System.Debug().

 

I also wasn't finding any documentation on the QuoteDocument object and started trying to attach it via Attachments. Is there a good place or way to find more information on those lesser documented things in salesforce like QuoteDocument?

 

Thanks for taking the time :-)

 

Dahveed


Ashley@WCEL wrote:

Glad you got it working.

 

One question: have you written unit tests for this extension? Creating those in tandem with your classes (as opposed to at the very end) can make debugging a whole lot easier and helps to ensure that all subsequent changes don't break previously working functionality.


 

Ashley@WCELAshley@WCEL

Writing the tests first helps verify your logic and also tends to make for better design since it forces you to think first about your interfaces. It's called test-driven development. I'm relatively new to Apex and OOP in general but this way of coding has changed my world.

 

I highly recommend this book which introduced me to this way of programming: http://www.amazon.ca/Software-Development-Principles-Patterns-Practices/dp/0135974445

 

Afraid I don't have any good resources for lesser documented parts of Salesforce. I use the Nonprofit Start Pack and have never touched the Quote object.

 

Best of luck!