0

I am sending a pdf in an email when the opportunity is created on updated which works fine. Now when the opportunity is created through batch so its throws the error future method can not call future method because I use @future (callout= true) in the handler class. when I remove @future from the handler method so it also throws the error Getting content from within triggers is currently not supported. I also tried to use if(System.IsBatch() == false && System.isFuture() == false) then trigger can not executed.What is the best way to solve the problem like its works when I create a single opportunity without a batch? How to solve this while using batch?

 if(trigger.isAfter && trigger.isInsert){ 
            list<opportunity> oppList = [select id, name ,Email_Sending_Option__c,Language__c,Template__c,Email__c,
                                         Email_Date__c,Email_Status__c from opportunity where id  =: trigger.new ];
            for(Opportunity opp:oppList) {
                
                if(opp.Template__c != null && opp.Language__c != null){
                    if(opp.Email_Sending_Option__c == 'Send Now'){ 
                        opp.Email_Date__c = date.today();
                        opp.Email_Status__c = 'Email Sent';
                        updateOpp.add(opp);
                        if(System.IsBatch() == false && System.isFuture() == false){ 
                            PDFGenerationService.generatePDF1(opp.Template__c+'.pdf',opp.Email__c,opp.Id,opp.Template__c,opp.Language__c);
                        }

Apex classs


public class PDFGenerationService {
    @future (callout=true)
    public static void generatePDF(string filename, string email,ID OpportunityId,string pageName,string lang )
    {
        if(lang == 'English'){
            EmailTemplate coverTemplate = EmailTemplateSelector.getEmailTemplate('Development And Peace');
            Map<String, String> ncParams=new Map<String, String> {'{name}' => 'name'};
                Messaging.EmailFileAttachment attach = EmailHelper.attachementBuilder(filename,OpportunityId,pageName,lang);
            Messaging.SingleEmailMessage mail=EmailHelper.emailBuilder(email,coverTemplate.Subject,coverTemplate.HtmlValue,ncParams);
            mail.setFileAttachments(new Messaging.EmailFileAttachment[] { attach}); 
            Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });
        }
5
  • Hi and welcome to SFSE. Is there a specific question here? Please edit to state the specific issue you face with the code you have posted. I assume the issue is that with the condition where you test for batch or future is that this then prevents the sending, which you do actually want to happen...?
    – Phil W
    Jun 21 at 6:54
  • 1) how to call batch or future to the future method
    – relcey
    Jun 21 at 7:01
  • how to solve the problem.
    – relcey
    Jun 21 at 7:02
  • You need to separate out the sending from the trigger itself. There are several options. One is to publish a platform event from the trigger that indicates the record needs to have sending done and use an apex trigger based event subscriber to process the event and send the PDF. The problem here is that platform events are not guaranteed to be delivered. So instead, you could mark the record as needing to be processed and have a scheduled job that periodically looks for records that need to be processed, process them and clear the mark. The problem here is a delay. Other options exist.
    – Phil W
    Jun 21 at 7:03
  • BTW, don't add comments in reply, edit your question to add the detail.
    – Phil W
    Jun 21 at 7:05

1 Answer 1

1

Explanation:

https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_queueing_jobs.htm

When chaining jobs with System.enqueueJob, you can add only one job from an executing job. Only one child job can exist for each parent queueable job. Starting multiple child jobs from the same queueable job isn’t supported.

So you can run queue from trigger AND batch trigger context, but from batch only 1 queue, so you'll need to bulkify it.

You can replace future job with queue, so your class will look something like this (also I suggest to bulkify queue, this is required):

public class PDFGenerationService implements Queueable {
    private List<String> filename;
    private List<String> email;
    private List<Id> opportunityId;
    private List<String> pageName;
    private List<String> lang;

    public PDFGenerationService(List<String> filename, List<String> email, List<Id> opportunityId, List<String> pageName, List<String> lang) {
        this.filename = filename;
        this.email = email;
        this.opportunityId = opportunityId;
        this.pageName = pageName;
        this.lang = lang;
    }

    public void execute(QueueableContext context) {
        for (Integer i = 0; i < this.filename.size(); i++) {
            generatePDF(this.filename[i], this.email[i], this.OpportunityId[i], this.pageName[i], this.lang[i]);
        }
    }

    public static void generatePDF(String filename, String email, Id opportunityId, String pageName, String lang) {
        //...
    }
}

And you'll be calling it from trigger like:

List<String> filename = new List<String>();
List<String> email = new List<String>();
List<Id> opportunityId = new List<Id>();
List<String> pageName = new List<String>();
List<String> lang = new List<String>();
//...
for(Opportunity opp:oppList) {

    if(opp.Template__c != null && opp.Language__c != null){
        if(opp.Email_Sending_Option__c == 'Send Now'){
            opp.Email_Date__c = date.today();
            opp.Email_Status__c = 'Email Sent';
            updateOpp.add(opp);

            filename.add(opp.Template__c+'.pdf');
            email.add(opp.Email__c);
            opportunityId.add(opp.Id);
            pageName.add(opp.Template__c);
            lang.add(opp.Language__c);
//...
PDFGenerationService newQueue = new PDFGenerationService(fileName, email, opportunityId, pageName, lang);
System.enqueueJob(newQueue);

UPDATE:

As @Phil W mentioned in comments section, there is possibility that batch may fail if you're updating opportunity records not in execute method or with batch size more than 200 records, so his approach with just setting checkbox for future update and scheduling update job is also reasonable.

7
  • This won't work if a bulkified update of more than 200 records is performed in an asynchronous context because in an async context you can enqueue just one queueable and the trigger will be invoked once for each 200 records in the bulk update (and thus would try to enqueue more than one queueable in the over-all async context).
    – Phil W
    Jun 21 at 7:52
  • You are wrong, that will work if you set your batch size to <= 200, therefore, execute method will be in async, but there will be 200 records per transaction. The only possible failure is when there is mass update in finish or start methods. Jun 21 at 8:11
  • Async isn't necessarily a batch; if you have a queueable, for example, that performs a bulk update of the opportunities you will hit issues if more than 200 records are updated. It would also be good to only enqueue if there is at least one opp that requires sending.
    – Phil W
    Jun 21 at 8:34
  • Yes, sure, you are right, but in this exact question user has encountered problem in Batch specifically. Jun 21 at 8:35
  • I think you misunderstand me; if the opportunities are updated in any async scenario, outside of your solution, because you enqueue in your trigger this can fail with your solution. This is why I recommend separating this from the trigger via platform events or scheduled jobs. You can get an outline of what I call "adaptive batch" in this earlier Q&A.
    – Phil W
    Jun 21 at 8:48

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.