0

I am creating records and using these records to create PDF Attachments (both are happening in the same call). As shown below:

        List<ResellerRevenue__c> resellerRevenues = new List<ResellerRevenue__c>();
        Map<Id, Double> partnerTotalMRRGeneratedMap = getPartnerTotalMRRsGenerated(orders);
        Savepoint sp = Database.setSavepoint();
        try {
            resellerRevenues = createResellerRevenueEntries(partnerTotalMRRGeneratedMap);
            updateOrdersWithItsResellerRevenueEntries(resellerRevenues);
            List<Attachment> pdfInvoicesForResellerToInsert = new List<Attachment>();
            for (ResellerRevenue__c resellerRevenue :resellerRevenues) {
                PageReference pdf = getPdfFromVisualForcePage('ResellerInvoice', resellerRevenue.Id);
                pdfInvoicesForResellerToInsert.add(createAttachment(pdf.getContent(), resellerRevenue.Id));
            }

            insert pdfInvoicesForResellerToInsert;

        } catch (Exception e) {
            System.debug(e);
            Database.rollback(sp);
        }

The issue I am having is that the visualforce page says that the records does not exist.

So my question is:

Is it possible to first insert records then create attachments using this records all over the same call?

Please, if it is possible let me know what I am doing wrong. Otherwise, which approach do you suggest?

7
  • Are you talking about objects or records? Can you please clarify? Also, what line is this erroring in? Commented Jul 5, 2016 at 20:07
  • Sorry, I meant to say records. I will amend this. Commented Jul 5, 2016 at 20:09
  • I can't quite test this right now, but there is something simple you can do to narrow it down. If, when you remove the savePoint, the PDF works, then you've found your answer. If it doesn't then you have a different problem. Commented Jul 5, 2016 at 20:11
  • I took out the savePoint and I got the same issue: System.VisualforceException: Failed to fetch object Commented Jul 5, 2016 at 20:23
  • Then your problem is not related to the savepoint at all. Let's see some more code. Commented Jul 5, 2016 at 20:24

1 Answer 1

3

PageReference.getContent (and getContentAsPDF) runs in a separate transaction, as they're treated as callouts back to the API. Since the data is not committed until the end of a transaction (i.e. your action method returns successfully), this means that the page you invoke with getContent can't see the newly created records; this rule would also affect you if you tried to update a record and call a Visualforce page in the same transaction, as the Visualforce page would only gain access to the pre-saved version of the data.

The only solution is to perform a chained callback. That might look like this:

<apex:actionFunction name="getPdfContent" action="{!getPdfContent}" reRender="theForm" />
<apex:commandButton value="Save" action="{!saveRecords}" onComplete="getPdfContent()" reRender="theForm" />

What happens here is that the user clicks the button, which must have a reRender target to make it perform as a JavaScript AJAX function, and then the onComplete handler will be called once saveRecords returns (thus ensuring that the records are committed). You'll probably want to store the ID values you need in a variable within the controller so you can pass the ID from the first function to the second.

4
  • Thank for the answer. I am creating the attachments and adding them in the records, so that the user does not need to create the pdf by clicking on any button. I understand that as they are not in the same transaction these new records cannot be reached by the visualforce page. I am thinking of using the @feature feature, but not sure if that is the best approach. source: developer.salesforce.com/docs/atlas.en-us.apexcode.meta/… Commented Jul 5, 2016 at 21:32
  • @Cubancoffee You can use whatever form of chaining you want. In Visualforce, I usually prefer a commandButton/actionFunction chain, because that allows you to provide a visual display of progress and guarantee your work will be done in a given amount of time. Using future/batchable/queuable are all alternatives as well, but do not guarantee a maximum amount of time a user may have to wait (e.g. it could be hours if the system's already heavily loaded).
    – sfdcfox
    Commented Jul 5, 2016 at 21:36
  • I see, it makes sense. I am new in salesforce, I hope this question does not shock you. Can I implement a code to run commandButton/actionFunction chain or that is something the end user will need to do (click on the button)? Commented Jul 5, 2016 at 21:45
  • @Cubancoffee You can chain actionFunction methods together, too. You could call the function from anything, even DOMContentLoaded if you wanted to. There's no reason why it has to be a button.
    – sfdcfox
    Commented Jul 5, 2016 at 21:55

You must log in to answer this question.

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