5

I'm currently trying to set up a schedule that will create PDF files and save them as attachments in Salesforce.

Methods I have tried:

PageReference.getContentAsPdf() - works fine by itself but is not supported in schedulable / batchable classes, resulting in blank PDF files.

Blob.toPDF() - This method does not support inline images or CSS, resulting in very basic looking PDFs.

REST API Callout to PDF Crowd (https://pdfcrowd.com/html-to-pdf-api/) - Quickly tried this but it returned a corrupted PDF file. It may work but I haven't had a chance to contact their support to see why the returned file was corrupted.

I've seen a few snippets online of people talking about using webservices, or Force.com sites to achieve this functionality.

Surely there's an easier way to do this without resorting to the likes of Conga etc?

Any help appreciated.

3
  • I am running into the same issue. Did you ever find a solution to this? Thanks!
    – Richard N
    Commented Jul 22, 2013 at 0:33
  • We decided to go with a 3rd party app. Commented Jul 22, 2013 at 7:42
  • Thats kinda sad but I'm glad that you found a solution. Unfortunately Im still looking for a solution within Salesforce.
    – Richard N
    Commented Jul 22, 2013 at 14:29

2 Answers 2

11

If you're considering calling out to a 3rd party app, you could also consider calling out to your own instance of Salesforce. In this case, you could create a REST service that calls PageReference.getContentAsPdf() and then insert the Attachment against the object. When the service is being called like this (i.e. using HTTPRequest callout from your Batch Apex), it doesn't know it is from the Batch context thus does not complain. I have done this in the past successfully.

However, to do this, you would need to pass in the Session as initial state to the Batch Apex job (i.e. as part of the constructor so that you have it available to pass to the 'attachment' service. This may or may not present challenges with session timeouts, depending on your use case.

Sample code below, you'll also need to add your salesforce instance to Remote Settings as an external site:

    @RestResource(urlMapping='/AttachPDF/*')
    global class AttachPDFService 
    {
        @HttpGet
        global static void AttachPDFtoRecordREST()
        {
            RestRequest req = RestContext.request;
            id recordId = req.requestURI.substring(req.requestURI.lastIndexOf('/')+1);

            PageReference pdfPage = new PageReference('/apex/yourvftemplate');
            pdfPage.getParameters().put('id',recordId);
            Attachment pdf = new Attachment();
            pdf.Name = 'AttacgPDF.pdf';
            pdf.ParentId = recordId;

            pdf.Body = pdfPage.getContentAsPdf();
            pdf.ContentType  = 'application/pdf';    
            insert pdf;
        }

        // call this method from your Batch Apex
        global static void attachPdfToRecord( Id recordId, String sessionId )
        {
            String addr = 'https://eu2.salesforce.com/services/apexrest/AttachPDF/' + recordId;
            HttpRequest req = new HttpRequest();
            req.setEndpoint( addr );
            req.setMethod('GET');
            req.setHeader('Authorization', 'OAuth ' + sessionId);

            Http http = new Http();
            HttpResponse response = http.send(req);    
        }

    }

And the Scheduled Batch Apex class which reschedules itself to run every 20 mins:

global class BatchAttachPDF implements Database.Batchable<SObject>, Database.Stateful, System.Schedulable, Database.AllowsCallouts
{
    global String SessionId {get;set;}

    global void execute(SchedulableContext SC) 
    {
        BatchAttachPDF batch = new BatchAttachPDF();
        batch.SessionId = SessionId;
        database.executebatch( batch, 1 );

        System.abortJob( sc.getTriggerId() );

    }

    global Database.QueryLocator start(Database.BatchableContext BC) 
    {
        return Database.getQUeryLocator( [Select Id From c2g__codaInvoice__c Where c2g__InvoiceStatus__c = 'Complete'] );
    }

    global void execute( Database.BatchableContext bc, List<SObject> scope )
    {
        AttachPDFService.attachPdfToRecord( scope[0].Id, this.SessionId );
    }

    global void finish( Database.BatchableContext bc )
    {
        // try again in a hour
        Datetime sysTime = System.now().addMinutes( 20 );               
        String chronExpression = '' + sysTime.second() + ' ' + sysTime.minute() + ' ' + sysTime.hour() + ' ' + sysTime.day() + ' ' + sysTime.month() + ' ? ' + sysTime.year();      
        BatchAttachPDF scheduledJob = new BatchAttachPDF();
        scheduledJob.SessionId = this.SessionId;

        System.schedule( 'Invoice PDF Attacher ' + sysTime, chronExpression, (System.Schedulable)scheduledJob );

    }

    private static Integer getCurrentJobCount()
    {
        return (Integer)[Select count() From AsyncApexJob Where JobType = 'BatchApex' and ( Status = 'Queued' or Status = 'Processing' or Status = 'Preparing' )];
    }

}

The the code to call this:

    Datetime sysTime = System.now().addMinutes( 5 ); 

    String chronExpression = '' + sysTime.second() + ' ' + sysTime.minute() + ' ' + sysTime.hour() + ' ' + sysTime.day() + ' ' + sysTime.month() + ' ? ' + sysTime.year();

    BatchAttachPDF scheduledJob = new BatchAttachPDF();
    scheduledJob.SessionId = UserInfo.getSessionId();

    System.schedule( 'PDF Attacher ' + sysTime, chronExpression, (System.Schedulable)scheduledJob );
16
  • Sounds good, any chance you have some code samples / examples? It's not a method I'm familiar with, thanks! Commented Jul 15, 2013 at 23:21
  • 1
    Basic sample added above, you'd be advised to add some error checking etc. Commented Jul 16, 2013 at 6:50
  • Cheers mate I'll give it a try. Commented Jul 16, 2013 at 6:53
  • I tried it, but I'm getting this error: 'Callout from scheduled Apex not supported.' My code structure is I have a schedulable class, which calls a batch class which calls 'attachPdfToRecord' as you recommended. Any ideas on a resolution? Commented Jul 16, 2013 at 11:05
  • 2
    Yes, you have to have implements Database.Batchable, Database.AllowCallouts, Database.Stateful on your Batch Apex class declaration, have a butchers at salesforce.com/us/developer/docs/apexcode/Content/… Commented Jul 16, 2013 at 11:28
3

I Had a same kind of requirement the above code helped me and its working perfectly (Thanks @Phil Hawthorn): Requirement:- is Generate the PDF and and send email notification and insert new related record to keep track of the pdf and attachment to the record.

Below is my complete process, modified few things:

set up remote site Setup->Security Controle->Remote Site setting. click on New Remote Site enter the detailed as in the image

Schedule class:-

global class ScheduleFinacialNotification implements Schedulable {
    global string sendSessionId;

    global ScheduleFinacialNotification(String sessionId){
        this.sendSessionId = sessionId;
    }
    global void execute(SchedulableContext SC) {
     BatchSendFinacialNotification batchApex=new BatchSendFinacialNotification();
     batchApex.sessionId = this.sendSessionId;
     Database.executeBatch(batchApex,1);  }}

Batch class:-

global class BatchSendFinacialNotification implements Database.Batchable<sObject>,Database.Stateful,Database.AllowsCallouts {
    public String query;
    global string sessionId;
    global BatchSendFinacialNotification(){ 
        this.sessionId  = sessionId;       
        query           = 'Select Id,Name,Project__r.Go_Live_Date__c from Service_Project_Junction__c ';
    }
    global Database.QueryLocator start(Database.BatchableContext BC){
        System.debug('=========='+Database.getQueryLocator(query));
        return Database.getQueryLocator(query);
    }

    global void execute(Database.BatchableContext BC, List<Service_Project_Junction__c> scope) {
        //List<Service_Project_Junction__c> pojJunRecList = [Select Id,Name,Project__c,Opportunity_Deal__c,Project__r.Id,Project__r.Go_Live_Date__c,Product_Opportunity_Junction__r.Service__r.Service_Type__c,Product_Opportunity_Junction__r.Service__r.Product_Price__c,Product_Opportunity_Junction__r.No_Of_Months__c,Product_Opportunity_Junction__r.Total_Price__c,Product_Opportunity_Junction__r.No_Of_Listings__c from Service_Project_Junction__c where Opportunity_Deal__c != null AND Project__r.Go_Live_Date__c != null];

        for(Service_Project_Junction__c proRec: scope){

            Integer duration = integer.valueOf(proRec.Product_Opportunity_Junction__r.No_Of_Months__c);
            Integer totMonthTillToday = proRec.Project__r.Go_Live_Date__c.monthsBetween(System.today());
            System.debug('Test in for loop==>'+proRec);
            if(totMonthTillToday <= duration && proRec.Project__r.Go_Live_Date__c.Day() == Date.today().day()){
                System.debug(sessionId+'Test in if loop==>'+this.sessionId);
                ServiceInvoiceRedirectController.attachPdfToRecord( proRec.Project__r.Id, this.sessionId);
                System.debug('Test in if loop after condiyion==>');
            }
        }
    }

    global void finish(Database.BatchableContext BC){       
    }

}

Call of REST Api class:-

@RestResource(urlMapping='/AttachPDF/*')
global class ServiceInvoiceRedirectController
 {
    @HttpGet
        global static void AttachPDFtoRecordREST() 
{

            RestRequest req = RestContext.request;
            id recordId = req.requestURI.substring(req.requestURI.lastIndexOf('/')+1);

            PageReference pdfPage = new PageReference('/apex/ServiceInvoicePDF?Id='+recordId);
            pdfPage.getParameters().put('id',recordId);
            Blob pdf = pdfPage.getContentAsPdf(); //!Test.isRunningTest() ? pdfPage.getContentAsPdf() : Blob.ValueOf('dummy text');

            Project__c prjRec = [Select Id,Name,Builder_Name__c from Project__c where Id =: recordId];
            //Insert Invoice record for related project
            Invoice__c invoRec = new Invoice__c();
            invoRec.RecordTypeId = Constants_PicklistVariables.serInvoice; 
            invoRec.Project__c = recordId;
            invoRec.Account__c = prjRec.Builder_Name__c;
            try{
            insert invoRec;
            }catch(Exception e){
                System.debug('Invoice=Exception-->'+e);
            }
            System.debug('Invoice=invoRec.id-->'+invoRec);

            //Insert Attacment to the invoice        
            Attachment a = New Attachment();
            a.body = pdf;
            a.parentID = invoRec.id;
            a.ContentType  = 'application/pdf';
            a.Name = 'SER-INV-'+invoRec.id+'.pdf';
            try{
            insert a;
            }catch(Exception e){
                System.debug('Attachment=Exception-->'+e);
            }

            //Create the email attachment
            Messaging.EmailFileAttachment efa = new Messaging.EmailFileAttachment();
            efa.setFileName('pdfName.pdf');//Pleace ad pdf name from contact name project and oppty concatinate 
            efa.setContentType('application/pdf');
            efa.setBody(pdf);

            // Create the Singal Email Message
            Messaging.SingleEmailMessage email = new Messaging.SingleEmailMessage();
            email.setSubject('Financial Notification');
            email.setToAddresses(new String[] { '[email protected]' });//add account email id
            email.setPlainTextBody( 'Please Find Attachment your financial report Attached with this email.');
            email.setFileAttachments(new Messaging.EmailFileAttachment[] {efa});

            // Sends the email
            Messaging.SendEmailResult [] r = Messaging.sendEmail(new Messaging.SingleEmailMessage[] {email});

        }

        // call this method from your Batch Apex
        global static void attachPdfToRecord( Id recordId, String sessionId )
        {
            System.debug('recordId==>'+recordId);
            System.debug('sessionId==>'+sessionId);
            System.debug('Iam in REST API Call method');
           // String addr = 'https://cs5.salesforce.com/services/apexrest/AttachPDF/' + recordId;
            HttpRequest req = new HttpRequest();
            req.setEndpoint('https://cs5.salesforce.com/services/apexrest/AttachPDF/a04O000000EHJN6IAP');
            req.setMethod('GET');
            req.setHeader('Authorization', 'OAuth ' + sessionId);

            Http http = new Http();
            HttpResponse response = http.send(req);    
        }

}

Schedule class from Developer Console in Anonymous Window:-

Datetime sysTime = System.now();
sysTime = sysTime.addSeconds(5);
String chron_exp =' '+sysTime.second() + ' ' + sysTime.minute() + ' ' + sysTime.hour() + ' ' + sysTime.day() + ' ' + sysTime.month() + ' ? ' + sysTime.year();
System.schedule('Job1',chron_exp , new ScheduleFinacialNotification(UserInfo.getSessionId()));

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .