I love Salesforce, but I fail to understand why the basic functionality like generating a presentable quotes PDF is not part of the standard out of the box functionality. Many of you may disagree because, salesforce.com does provide templates to modify the quotes.
But if you look at it closely, the page breaks are not consistent, if my product description is a little too long, the quote extends to multiple pages, without proper page breaks. So, I searched the internet for few solutions and found 1 good solution on the developer force, to dynamically (almost ;-) ) populate pages with the number of quotes. However, again the drawback is that if the description is too long, the page breaks will not be consistent and the quote will look ugly.
The final solution I came up with is an extension to an already existing solution on developer force. Though this is not a neat solution, but I guess we have to live with these kind of solutions until Salesforce comes up with a better out of the box quote generation functionality.
As you would expect, there are 2 components -
1. Visual Force Page to generate the pdf.
2. An extension to return the quotes.
Visual Force Page
Extension
public with sharing SalesQuoteExample{
public List<QuoteLineItem[]> pageBrokenQuoteLines {get; private set; }
// end new code
public Quote quote { get; private set;}
public QuoteLineItem[] quoteLineItems { get; private set; }
public Opportunity opp { get; private set; }
public Boolean hasContact { get { return (opp.opportunityContactRoles.size() == 1);} }
public Contact contact { get; private set; }
private ApexPages.StandardController controller;
// constructor, loads the quote and any opportunity lines
void queryQuoteLines(id id) {
quote = [ Select s.Contact.Name, Contact.Account.Name, s.Contact.MailingStreet, s.Contact.MailingState,
s.Contact.MailingPostalCode, s.Contact.MailingCountry,
s.Contact.MailingCity , s.Contact.Phone, S.Email,
(Select Quantity, PricebookEntry.Product2.Name, ListPrice, Adjusted_Price__c,
Product_Description__c, TotalPrice, LineNumber From QuoteLineItems
order by PricebookEntry.Product2.Name ),
s.Opportunity.HasOpportunityLineItem, s.Opportunity.Name, s.Name, s.QuoteNumber,
s.Opportunity.Id, s.OpportunityId
From Quote s
where s.id = :quoteid limit 1];
quoteLineItems = quote.QuoteLineItems;
prepareQuoteLinesForPrinting();
}
id quoteid;
/*public SalesQuotes() {
quoteid = ApexPages.CurrentPage().getParameters().get('id');
init();
}*/
public SalesQuotes(ApexPages.StandardController c) {
controller = c;
quoteid = c.getRecord().id;
init();
}
// load up quote lines, opportunity lines, opportunity details and contact info
void init() {
queryQuoteLines(quoteid);
}
/* The action method that will generate a PDF document from the QuotePDF page and attach it to
the quote provided by the standard controller. Called by the action binding for the attachQuote
page, this will do the work and take the user back to the quote detail page. */
public PageReference attachQuote() {
/* Get the page definition */
PageReference pdfPage = new PageReference( '/apex/quotePDFAdvanced' );
/* set the quote id on the page definition */
pdfPage.getParameters().put('id',quote.id);
/* generate the pdf blob */
Blob pdfBlob = pdfPage.getContent();
/* create the attachment against the quote */
Attachment a = new Attachment(parentId = quote.id, name=quote.name + '.pdf', body = pdfBlob);
/* insert the attachment */
insert a;
/* send the user back to the quote detail page */
return controller.view();
}
PageReference opportunityPR() { return new pagereference('/'+quote.OpportunityId); }
PageReference quotePR() { return new pagereference('/'+quote.id); }
public PageReference reset() {
queryQuoteLines(quote.id);
return null;
}
//splits the quote lines into an approximate number of rows that can be
//displayed per page
private void prepareQuoteLinesForPrinting()
{
pageBrokenQuoteLines = new List<QuoteLineItem[]>();
Integer linesAvailable = 60;
QuoteLineItem[] pageOfQuotes = new QuoteLineItem[]{};
Integer counter = 0;
boolean firstBreakFound = false;
//Additional variables
String description;
String productName;
Integer descLines=0;
Integer lines;
for(QuoteLineItem q : quoteLineItems)
{
System.debug('Quote Line Item is ****'+q.LineNumber);
description = q.Product_Description__c;
if(description!=null)
{
descLines = description.length()/40;
System.debug('Lines Available::::'+linesAvailable);
System.debug('Description Length==='+description.length());
System.debug('descLines+++'+descLines);
if(linesAvailable > descLines)
{
System.debug('Lines Available');
linesAvailable = linesAvailable - descLines;
pageOfQuotes.add(q);
descLines = 0;
}
else
{
System.debug('NO Lines Available::::'+linesAvailable);
pageBrokenQuoteLines.add(pageOfQuotes);
pageOfQuotes = new QuoteLineItem[]{};
descLines = 0;
}
}
}
//if we have finished looping and have some quotes left lets assign them
if(!pageOfQuotes.isEmpty())
{
pageBrokenQuoteLines.add(pageOfQuotes);
}
}
}
The method prepareQuoteLinesForPrinting is based on the following assumptions
Credits
But if you look at it closely, the page breaks are not consistent, if my product description is a little too long, the quote extends to multiple pages, without proper page breaks. So, I searched the internet for few solutions and found 1 good solution on the developer force, to dynamically (almost ;-) ) populate pages with the number of quotes. However, again the drawback is that if the description is too long, the page breaks will not be consistent and the quote will look ugly.
The final solution I came up with is an extension to an already existing solution on developer force. Though this is not a neat solution, but I guess we have to live with these kind of solutions until Salesforce comes up with a better out of the box quote generation functionality.
As you would expect, there are 2 components -
1. Visual Force Page to generate the pdf.
2. An extension to return the quotes.
Visual Force Page
<div style="page-break-after:always;">
</div>
<apex:repeat value="{!pageBrokenQuoteLines}" var="aPageOfQuotes" id="theList">
<div style="page-break-after:always;">
<apex:dataTable value="{!aPageOfQuotes}" var="c" id="theTable">
<apex:column >
<apex:facet name="header">Qty</apex:facet>
<apex:outputText value="{!c.Quantity}"/>
</apex:column>
<apex:column >
<apex:facet name="header" >Part Number</apex:facet>
<apex:outputText value="{!c.PricebookEntry.Product2.Name}"/>
</apex:column>
<apex:column ></apex:dataTable>
Extension
public with sharing SalesQuoteExample{
public List<QuoteLineItem[]> pageBrokenQuoteLines {get; private set; }
// end new code
public Quote quote { get; private set;}
public QuoteLineItem[] quoteLineItems { get; private set; }
public Opportunity opp { get; private set; }
public Boolean hasContact { get { return (opp.opportunityContactRoles.size() == 1);} }
public Contact contact { get; private set; }
private ApexPages.StandardController controller;
// constructor, loads the quote and any opportunity lines
void queryQuoteLines(id id) {
quote = [ Select s.Contact.Name, Contact.Account.Name, s.Contact.MailingStreet, s.Contact.MailingState,
s.Contact.MailingPostalCode, s.Contact.MailingCountry,
s.Contact.MailingCity , s.Contact.Phone, S.Email,
(Select Quantity, PricebookEntry.Product2.Name, ListPrice, Adjusted_Price__c,
Product_Description__c, TotalPrice, LineNumber From QuoteLineItems
order by PricebookEntry.Product2.Name ),
s.Opportunity.HasOpportunityLineItem, s.Opportunity.Name, s.Name, s.QuoteNumber,
s.Opportunity.Id, s.OpportunityId
From Quote s
where s.id = :quoteid limit 1];
quoteLineItems = quote.QuoteLineItems;
prepareQuoteLinesForPrinting();
}
id quoteid;
/*public SalesQuotes() {
quoteid = ApexPages.CurrentPage().getParameters().get('id');
init();
}*/
public SalesQuotes(ApexPages.StandardController c) {
controller = c;
quoteid = c.getRecord().id;
init();
}
// load up quote lines, opportunity lines, opportunity details and contact info
void init() {
queryQuoteLines(quoteid);
}
/* The action method that will generate a PDF document from the QuotePDF page and attach it to
the quote provided by the standard controller. Called by the action binding for the attachQuote
page, this will do the work and take the user back to the quote detail page. */
public PageReference attachQuote() {
/* Get the page definition */
PageReference pdfPage = new PageReference( '/apex/quotePDFAdvanced' );
/* set the quote id on the page definition */
pdfPage.getParameters().put('id',quote.id);
/* generate the pdf blob */
Blob pdfBlob = pdfPage.getContent();
/* create the attachment against the quote */
Attachment a = new Attachment(parentId = quote.id, name=quote.name + '.pdf', body = pdfBlob);
/* insert the attachment */
insert a;
/* send the user back to the quote detail page */
return controller.view();
}
PageReference opportunityPR() { return new pagereference('/'+quote.OpportunityId); }
PageReference quotePR() { return new pagereference('/'+quote.id); }
public PageReference reset() {
queryQuoteLines(quote.id);
return null;
}
//splits the quote lines into an approximate number of rows that can be
//displayed per page
private void prepareQuoteLinesForPrinting()
{
pageBrokenQuoteLines = new List<QuoteLineItem[]>();
Integer linesAvailable = 60;
QuoteLineItem[] pageOfQuotes = new QuoteLineItem[]{};
Integer counter = 0;
boolean firstBreakFound = false;
//Additional variables
String description;
String productName;
Integer descLines=0;
Integer lines;
for(QuoteLineItem q : quoteLineItems)
{
System.debug('Quote Line Item is ****'+q.LineNumber);
description = q.Product_Description__c;
if(description!=null)
{
descLines = description.length()/40;
System.debug('Lines Available::::'+linesAvailable);
System.debug('Description Length==='+description.length());
System.debug('descLines+++'+descLines);
if(linesAvailable > descLines)
{
System.debug('Lines Available');
linesAvailable = linesAvailable - descLines;
pageOfQuotes.add(q);
descLines = 0;
}
else
{
System.debug('NO Lines Available::::'+linesAvailable);
pageBrokenQuoteLines.add(pageOfQuotes);
pageOfQuotes = new QuoteLineItem[]{};
descLines = 0;
}
}
}
//if we have finished looping and have some quotes left lets assign them
if(!pageOfQuotes.isEmpty())
{
pageBrokenQuoteLines.add(pageOfQuotes);
}
}
}
The method prepareQuoteLinesForPrinting is based on the following assumptions
- The description will have approximately 40 characters per line.
- A page can accommodate 50 lines of quotes. Can be changed by specifying a new value in the variable.
- For each quote line item, calculate the description length.
- Divide this length by 40, to get an approximate number of lines this quote line item will take.
- Subtract the value from above from the available lines. Available lines indicate the space left in the page to display quote line item.
- If the available lines is greater than the description lines(indicating space is available), add this quote on the current page.
- Else, add the quote to new page, reset the available lines to 50 (this is configurable).
Credits