[SalesForce] Best way to create PDFs on a schedule

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.

Best Answer

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 );
Related Topic