Invoice PDF implementation using Visualforce and Apex

  • July 19, 2022

The Invoice Automation is a custom functionality that helps create Invoices from Quotes without third-party applications and share them with the customer using email and attachments. This invoice generator button is actually a Quick Action button, which enables you to create your own invoice using a customized PDF template apart from the template provided by Salesforce.

Assumptions

The following are the assumptions, while implementing the invoice generator functionality,

  • Primary Quote should be generated and synced with Opportunity.
  • Invoice generator is triggered from Order record page (considering Order creation as an immediate process once Quote is accepted by customer).

Setup

Suppose we want to send the invoice email to the customer once the order is being created and the prospect is converted. We can create a Quick Action button, which will be discussed later.

We need to set up the Classic Email Template (Visualforce email) in salesforce org, which contains the letterhead subject and email body according to the requirement. Let’s look into the process of creating the email template:

  1. Go to Classic Email Template and Select “ Visualforce.”
  2. Fill in all the fields based on your needs.
    Visualforce and Apex1
  3. As you can see in the above snap, we’ve filled in the details. Now save and click on Edit Template and create your own body accordingly. For instance, refer to the below snap.
    Visualforce and Apex2
  4. As you can see, the recipientType is set as contact because this email is intended to be sent to the related contact only. You can access any contact related field using {!recipient.FieldAPiName} as shown above.

We can use flow and Apex to integrate our email template and create a Visualforce page to create a PDF template and use this template in our Apex while sending email as an attachment.

Flow (On Order Object)

  1. Go to flows in setup and select screen flow and configure your flow as shown below. We need to send various details required to be sent to Apex to create the email template and send mail to prescribed and authenticated users.
  2. We need to Select Get Records and get the recordID of the record.
    Visualforce and Apex3
    Visualforce and Apex4
  3. Now moving on with the flow, we can put a decision block to check for the valid contact for that associated Quote.
  4. If Contact is available, we can capture that email ID in a readonly textbox and display it for confirmation purposes.
  5. Use assignment block to assign values that need to be passed while calling Apex. Create a Collection Variable. In our case, let’s name it colvar.

    (colvar contains Email ID from associated contact, Related Opportunity ID, Order Id(recordId), Contact ID)

    Visualforce and Apex5
    Visualforce and Apex6
  6. After that it’s time to call our Apex class using Apex action with the parameters as shown.
  7. Before that, lets create a Quick Action Button on Order Object.
    • Go to order Object in Object manager and click on Button, Links and Actions.
    • Click on Quick Action Button on the top right corner.
    • Name your Quick Action like “Invoice Generator” and Select Flow with the flow name which you provided at the time of flow creation.
    • Add this button On Page Layout using the Edit Page option in Order.
  8. Now we’re done with the template as well as Flow. Let’s move on to Apex to know the process and code needed to achieve it.

Flow invoked Apex

public class InvoicePDF {

    @InvocableMethod(label=’email invoice pdf’) // get all the values passed from flow and parse it

    public static void testmail(List<List<String>> args) {

        List<String> nested=args[0];

        String email=nested[0];

        String recordId=nested[1];

        String orderId = nested[2];

        String conId=nested[3];            

        createpdf(email,recordId,orderId,conId);  

    }  

    @future(Callout=true)

    public static void createpdf(String email , String recordId,String orderId,String conId)

    {

        Quote theQuote;

        opportunity ops = [SELECT Id,

                                                    (SELECT Id, 

                                                                    Name,

                                                                    IsSyncing, 

                                                                    CreatedDate

                                                      FROM Quotes                 

                                                      WHERE IsSyncing = true 

                                                      AND Status=’Accepted’  

                                                      ORDER BY CreatedDate DESC) 

                                      FROM Opportunity 

                                      WHERE Id =: recordId];

        if(ops.Quotes.isEmpty()) {      //Checking for Synced quote

            theQuote = null;

        }

        else  {

            theQuote = ops.Quotes;

            PageReference pageRef = new PageReference(‘/apex/InvoiceTemplate?oppId=’+recordId);  // Creating Instance for Invoice template (Visualforce Page)

            Blob pdfbody;

            if(Test.isRunningTest())

                pdfbody=blob.valueOf(‘Unit Test’);

            else

                pdfbody=pageRef.getContent();

            List<Attachment> attList = new List<Attachment>();

            attList.add( new Attachment (                                    //Attaching  it to related list

                Name = ‘Invoice #’ + theQuote.Name + ‘.pdf’,  

                Body = pdfbody,

                ParentId = ops.Id

            ));

            attList.add( new Attachment (

                Name = ‘Invoice #’ + theQuote.Name + ‘.pdf’,

                Body = pdfbody,

                ParentId = orderId

            ));

            if (!attList.isEmpty()) {

                System.debug(‘attList’+attList);

                Database.SaveResult[] srList = Database.insert(attList, false);

                for (Database.SaveResult sr : srList) {

                    if (sr.isSuccess()) {

                        system.debug(‘Successfully inserted’);

                    }

                    else

                    {

                        for(Database.Error err : sr.getErrors()) {

                            System.debug(‘Error attachment’+err.getStatusCode() + ‘: ‘ + err.getMessage());

                            System.debug(‘Account fields that affected this error: ‘ + err.getFields());

                        }

                    }

                }

            }

            EmailTemplate et=[SELECT  Id,

                                                           Subject 

                                          FROM EmailTemplate

                                          WHERE Name = ‘Invoice Classic Template’]; 

            Messaging.EmailFileAttachment attachment=new        Messaging.EmailFileAttachment();

            attachment.setContentType(‘application/pdf’);

            attachment.setFileName(‘e-Invoice.pdf’);

            attachment.body=pdfbody;

            attachment.setInline(false);       

            Messaging.SingleEmailMessage mail=new Messaging.SingleEmailMessage();

            mail.setToAddresses(new String[] {email});

            mail.setTemplateId(et.Id);

            mail.setTargetObjectId(conId);

            mail.setSaveAsActivity(false); 

            mail.setWhatId(conId);

            mail.setBccSender(false); 

            mail.setUseSignature(false); 

            mail.setFileAttachments(new Messaging.EmailFileAttachment[] {attachment}); 

            List<Messaging.SendEmailResult> results = Messaging.sendEmail(new Messaging.SingleEmailMessage[] {mail}); 

            if (!results.get(0).isSuccess()) {

                System.StatusCode statusCode = results.get(0).getErrors()[0].getStatusCode();

                String errorMessage = results.get(0).getErrors()[0].getMessage();

            }

        }

    }

}

VisualForce page template

<apex:page controller=”InvoicePDFController” applyHtmlTag=”false” showHeader=”false” renderAs=”PDF”>

 <html>

        <head></head>        

        <body >

<table style=”font-family:sans-serif; padding-bottom:30px;width:100%”>

<tr>

    <td><apex:image id=”logo2″ value=”{!$Resource.ApimotocorpLogo}” width=”180″ height=”90″/></td>

    <td style=”font:22pt; padding-left:10px; padding-right:140px;”>XYZ Corporation</td>

    <td style=”font:22pt;”>INVOICE</td>

</tr>

</table>

<br></br>

<table style=”font-family:sans-serif;font-size:12px;width:100%”>

<tr style=”color:#fff; background-color: rgb(3, 175, 243)”>

    <th style=”padding-left:5px; padding-right:170px;”>Bill To</th>

</tr>

<tr>

    <td style=”padding-left:5px;width:35%;height:30px”><apex:outputField value=”{!quoteobj.BillingName}”/></td>

    <td style=”width:40%;text-align:right”>Created Date</td>

    <td style=”padding-left:5px;”>{!dt}</td>

</tr>

<tr>

    <td style=”padding-left:5px;height:30px”><apex:outputField value=”{!quoteobj.BillingStreet}”/></td>

    <td style=”width:40%;text-align:right”>Contact Name</td>

    <td style=”padding-left:5px;”>{!quoteobj.contact.Name}</td>

</tr>

<tr>

    <td style=”padding-left:5px;height:30px”><apex:outputField value=”{!quoteobj.BillingState}”/></td>

    <td style=”width:40%;text-align:right”>Email</td>    

    <td style=”padding-left:5px;”>{!quoteobj.contact.Email}</td>

</tr>

<tr>

    <td style=”padding-left:5px;height:30px”><apex:outputField value=”{!quoteobj.BillingCountry}”/></td>

    <td style=”width:40%;text-align:right”>Phone</td>

    <td style=”padding-left:5px;”>{!quoteobj.contact.phone}</td>    

</tr>

<tr>

    <td style=”padding-left:5px;height:30px”><apex:outputField value=”{!quoteobj.BillingPostalCode}”/></td>    

</tr>

</table>

<br></br>

<table style=”font-family:sans-serif;width:100%;font-size:12px;” cellpadding=”0″ cellspacing=”0″>

<tr style=”color:#fff;background-color: rgb(3, 175, 243);”>

    <th style=”text-align:center;”>Description</th>

    <th style=”text-align:right;padding-right:5px;”>Qty</th>

    <th style=”text-align:right;padding-right:5px;”>Unit Price</th>

    <th style=”text-align:right;padding-right:5px;”>Amount</th>

</tr>

<apex:repeat value=”{!quoteobj.quoteLineItems}” var=”lineItem”>

<tr style=”background-color:#ffdecc;”>

     <td style=”height:30px;text-align:center;”>{!lineItem.product2.Name}</td>

     <td style=”text-align:right;padding-right:5px;”>{!lineItem.quantity}</td>

     <td style=”text-align:right;padding-right:5px;”>{!lineItem.unitprice}</td>

     <td style=”text-align:right;padding-right:5px;”>{!lineItem.subtotal}</td>

</tr>

</apex:repeat>

<tr>

    <td style=”height:30px;” ></td>

    <td ></td>

    <td style=”text-align:right;padding-right:5px;”>Subtotal</td>

    <td style=”text-align:right;padding-right:5px;”>{!quoteobj.Subtotal}</td>

</tr>

<tr>

   <td style=”height:30px;text-align:center”> Prepared by</td>

    <td style=”text-align:left”>{!userName}</td>

    <td style=”text-align:right;padding-right:5px;”>Discounts</td>

    <td style=”text-align:right;padding-right:5px;”>{!quoteobj.discount}</td>

</tr>

<tr>

    <td style=”height:30px;text-align:center”>Email</td>

    <td style=”text-align:left”>{!$User.Email}</td>

    <td style=”text-align:right;padding-right:5px;”>Taxes</td>

    <td style=”text-align:right;padding-right:5px;”>{!quoteobj.tax}</td>

</tr>

<tr>

    <td style=”height:30px”></td>

    <td ></td>

    <td style=”text-align:right;padding-right:5px;”>Shipping charge</td>

    <td style=”text-align:right;padding-right:5px;”>{!quoteobj.shippinghandling}</td>

</tr>

<tr>

   <td style=”height:30px;”></td>

    <td ></td>

    <td style=”text-align:right;padding-right:5px;background-color: rgb(3, 175, 243);”>Total</td>

    <td style=”text-align:right;padding-right:5px;background-color: rgb(3, 175, 243);”>{!quoteobj.Grandtotal}</td>

</tr>

</table>

<br></br>

<br></br>

<table style=”width:100%; font-family:sans-serif;font-size:12px”>

<tr>

    <td style=”text-align:center”> If you have any questions about this invoice , Please contact support@xyzcor.com </td>

</tr>

<tr>

    <td style=”text-align:center”>Thank you for your business!</td>

</tr>

</table>

</body>

</html>

</apex:page>

VisualForce Page Custom controller

public class InvoicePDFController {

    public Opportunity opp {get; set;}

    public String oppId {get;set;}

    public Quote quoteobj {get;set;}

    public string userName {get;set;}

    public String dt {get;set;}

    public InvoicePDFController(){

            this.oppId = apexpages.currentpage().getparameters().get(‘oppId’);

         if(this.oppId!=null) {

             this.opp = [SELECT Id 

                                         FROM Opportunity 

                                         WHERE Id =: this.oppID];

             this.quoteobj = [SELECT Id,         

     Name,

     contact.Name,

     contact.phone,

     contact.Email,

     IsSyncing,

                                                                            BillingName,

     BillingStreet, 

     BillingCity,

     BillingState, 

     BillingPostalCode, 

     BillingCountry,

     CreatedDate,

                                                                            Subtotal,

                                                                            Tax,

                                                                             Discount,

      Shippinghandling,

      Totalprice,

      Grandtotal,

                                                                            (SELECT product2.Name,

          Quantity,

          Unitprice,

          Subtotal,

          total price 

        FROM quotelineItems) 

                                                           FROM Quote 

                                                           WHERE IsSyncing = true 

                                                           AND opportunityId = :this.opp.Id 

                                                           ORDER BY CreatedDate 

                                                           DESC ];

             this.userName=userinfo.getName();

             this.dt= date.today().format();        

        }

    }

}

Explanation

  • We’re parsing data passed from flow and passing to createpdf() method.
  • Check for Synced quotes to process further.
  • Creating Instance for VisualForce page template and Passing Opportunity record Id as url parameter (Refer: Invoice Template & InvoicePDF controller).
  • Capturing the rendered Visualforce Page as PDF and using it for mail attachment.
  • Then adding that PDF to Attachments Object related to Order and Opportunity.
  • Then creating Instance for SingleEmailMessage and passing necessary parameters (setTargetObjectId(contactId),fileName() , contentType(). etc).
  • And sending email using sendEmail() method with Invoice PDF attachment.

Sample template

Visualforce and Apex7

— By Ravi Kumar Rai and Mohanraj D