7

When attempting to create a quote and then generate a PDF using the quote in the same context, I get one of the dwarf errors:

Line: -1, Column: -1 ORA-20001: ORA-06512: at "HAPPY.CACCESS", line 1492ORA-06512: at "HAPPY.CACCESS", line 2995ORA-06512: at "HAPPY.CACCESS", line 2768ORA-06512: at line 1SQLException while executing plsql statement: {call cAccess.check_entity_access_proc_ncu(?,?,?,?,?,?)}(EXCLUDED, EXCLUDED, 0Q00v000000CaYh, EXCLUDED, true, false)

(So far I've seen Happy and Dopey but they both reference CACCESS).

Code to reproduce (Execute Anonymous):

Opportunity openOpp = [
    SELECT Id
    FROM Opportunity
    WHERE IsClosed = false
    LIMIT 1
][0];

Quote newQuote = new Quote(
    OpportunityId = openOpp.Id,
    Name = 'doesn\'t matter'
);

insert newQuote;

// You can't query this object so you have to hard code a quote template ID
// We use a custom setting
Id templateId = '';

String url = '/quote/quoteTemplateDataViewer.apexp?id=' + 
    newQuote.Id + '&summlid=' + templateId;

PageReference contentSource = new PageReference(url);
Blob pdf = contentSource.getContentAsPDF();

If you run this with an existing quote instead of a new one, there are no errors.

I believe this is because PageReference.getContentAsPDF(); is treated as a callout and you cannot make a callout while you have uncommitted work.

The only solution I can think of is to split this into two separate transactions. This poses an issue for me as I'm already doing my work in a batch class (which means I can't use @future methods). I believe this means my only option is to use a queueable.

Can anyone think of any solution other than using a queueable? My concern is that there are limits to queueables and we're writing this as a batch as we expect a high volume of records to process.

Use Case: We are generating renewal opps/quotes automatically and sending out renewal emails with quote pdfs attached (we're also attaching the PDFs to the quotes for record keeping).

Update

Took me a bit of digging but I found a similar question. I don't believe their solution will work for me though.

3

Even if the callout-after-DML wasn't allowed, you still wouldn't be allowed anyways because the callout is a separate context. What this means is that the Quote has not been fully committed and released from transaction isolation, so the called context wouldn't be able to use that ID anyways.

As an alternative, consider committing a bunch of records in your batch, then doing the callout during the next batch. Pseudocode follows:

public void GenerateAndEmailPDF implements Database.Stateful, Database.Batchable<SObject>, Database.AllowCallouts {
    Id[] callouts = new Id[0];
    public Database.QueryLocator start(Database.BatchableContext context) {
        ...
    }
    public void execute(Database.BatchableContext context, Opportunity[] records) {
        doCallouts(callouts);
        callouts.clear();
        generateQuotes(records);
    }
    public void finish(Database.BatchableContext) {
        doCallouts(callouts);
    }
    public void doCallouts(Id[] quoteIds) {
        ...
    }
    public void generateQuotes(Opportunity[] records) {
        ...
        callouts.addAll(new Map<Id, Quote>(newQuotes).keySet());
   }
}

doCallouts generates the PDF files from the prior iteration, and then the new quotes are saved. One final push is needed in the finish method to finish generating the rest of the PDF files.

| improve this answer | |
  • Duh, I can't believe I forgot about the finish method :/ – gNerb May 22 '18 at 16:18
  • I assume the finish method get's it's own context after all batches are finished and that the context is subject to async limits as well? I'm wondering how viable this is as there is still ALOT of work to do after the quotes have been created. – gNerb May 22 '18 at 16:21
  • @gNerb Note that what I'm doing is putting the callouts before the DML, as the framework requires. The PDF generation is actually occurring one transaction after the quotes are created. You can do any additional work (sending emails, etc) after doing the callouts. You don't need to do this exclusively in the finish method. – sfdcfox May 22 '18 at 16:23
  • 1
    @gNerb You're overthinking it. It's just my preferred style of code. Within the asynchronous code, this code executes in order. It's just more efficient to call addAll once than it is to call add one hundred times. – sfdcfox May 22 '18 at 16:42
  • 1
    @gNerb I've mixed set/list data types; you could change the variable to a set if you prefer, then the direct assignment would be practical. Again, this is more pseudo-code, not meant to be taken literally. – sfdcfox May 22 '18 at 16:44
0

(This is a sample of using a queueable, I'm not accepting as I'd like other's to weigh in)

As stated in the question, the only solution I can think to try is to move the code around generating PDFs into a queueable. I'm concerned about limits related to queueables.

In an attempt to reduce the impact on limits, I'm bulkifying my queueable by using a list of quotes in order to reduce the number of jobs I have to enqueue. This should mean I only need 1 queueable per transaction:

Queueable:

public class QueueableAutoRenewal implements Queueable {
    public List<Quote> newQuotes;

    public void execute(QueueableContext context) {
        if (this.newQuotes != null) {
            // Do the stuff
        }
    }
}

Enqueueing the Job

QueueableAutoRenewal queueableJob = new QueueableAutoRenewal();
queueableJob.newQuotes = quotesToInsert;
System.enqueueJob(queueableJob);

From what I can tell by researching limits, this is the most restrictive limit so if I can workaround it by bulkifying my queueble, I think It'll work. Feel free to point out any other limits I need to be aware of.

Note

For my needs, I don't think I'll need to perform DML on the quotes so I'm not re-querying them and I'm not super concerned with access issues.

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.