Category Archives: Apex

Using Queueable interface to save page as PDF attachment

Sometimes, it is needed to save current visual force page content as PDF in Salesforce. This could be quite heavy depending upon how long the visual force page is.Ideally, it should be done with combination of @future(Callout = true) & Queueable interface which I’m describing below:

  1. Create a handler class which should have method to save current vf page into PDF attachment.

    @future(callout=true)
    public static void createPDFAttachment(IdcobjId, string PageName){

    Obj__c obj1; // object under which you’ve to save attachment
    //Using object, create name of attachment using your own logic and pass to queueable interface

    string attachmentName = ‘xyz’;
    try {
    myQueuebaleClass pdfGen= new myQueuebaleClass (objId, pageName, attachmentName);
    pdfGenerator.execute(null);
    } catch (Exception e) {
    //log exceptions
    } finally {
    }}

    ////*****************Queueable Interface Class*********************

    public class myQueuebaleClass implements Queueable {

    public myQueuebaleClass(Id objId, String pageName, String attachmentName, ){        //Set member properties

    this.objId= objId;
    this.pageName = pageName;
    this.attachmentName = attachmentName;
    }
    public void execute(QueueableContext context){

    //Building the page URL that will be used to generate the PDF of the form
    PageReference pdfPage = new PageReference(‘/apex/’ + pageName + ‘?ObjectId’ + ‘=’ + objId);

    //Instantiating a new attachment record
    Attachment attachmentObject = new Attachment();

    if (Test.IsRunningTest()) {
    attachmentObject.Body = Blob.valueOf(‘Test’);
    } else {
    //setting the content of the attachment by using Salesforce content and then setting the content type as PDF so that the attachment record is stored as PDF
    attachmentObject.Body = pdfPage.getContent();
    }
    //flag to make sure the attachment record is accessible by the relevant users
    attachmentObject.IsPrivate = false;
    attachmentObject.ParentId = ApplicationId;

    //Query existing attachments of the object

    objects= [SELECT Id,
    (select Id, Description from Attachments)
    from obj__c
    WHERE Id = :objId For Update];

    List<Attachment> attachmentList = new List<Attachment>();
    for (Attachment att : objects[0].Attachments) {
    attachmentList.add(att);
    }

    //If an attachment record exists, overwrite it with the new data.
    if (attachmentList.Size() == 1) {
    attachmentObject.Id = attachmentList[0].Id;
    }

    // clean up multiple PDFs of the same form
    if (attachmentList.size() > 1) {
    // delete them all and start again
    delete attachmentList;
    }
    upsert attachmentObject;

    }

    }

Salesforce : Custom logic for daily running of a Apex job at User Selected days & time

Suppose user wants to send email at ‘multiple days of every week’ at particular ‘time’.
For e.g user wants to send email at Every ‘Monday’, ‘Friday’ and ‘Saturday’ at 10 am.

So apex schedular alone can’be suffice in this case as user is selecting different schedule for different records of some object.
We have to provide new fields on same object(used for sending email) , on which user can ‘set’ schedule days and time like this :

Scheduling Fields

Now an apex schedular can be writtern which run based on information selected by user :

But Schedular needs 2 pieces of information to run :

  1. it needs to find if current record needs to be selected for today’s run .
  2. what should be ‘Next Run date & time, so that when schedular run next time , it can pick up records

For this we can create two new fields on same object in which we’ll populate

  1. Current day and Run time
  2. Next day and Run time

Scheduling Fields2

 

In this post, we’ll be finding how to find Current Run time and Next Run time(both Date & Time fields)through apex trigger on same object.
This trigger would be running on ‘before insert’ and ‘before update’

Scenario could be :
trigger populate these dates, schedular then pick up records , which have to run today  and ‘on or before’ Time selected by user in ‘Time of the day’ field.

After that , Schedular will update another fields like status after run (sending emall) and again trigger could run to populate these fields ‘Current Run time’ and ‘Next Run time’

Logic :
Now logic to find ‘Current day & time’ is simple. We’ve to just find if current record has today’s day(e.g Sunday) as one of the value selected in multiSelect picklist and time is ‘before or equal to’ current time, when trigger is run

But time to find logic for ‘next Run Day and time’ is little tricky.
If today is Monday and job has to next Run on Wednesday , then ‘Next Run Day & time’ will be after ‘TWO’ days.
But if today is ‘Friday’ and job has to run on Wednesday’, then ‘next run Day and time’ will be after 5 days.

In nutshell, we’ve to find relative position of days first based on current day
For e.g if today is Friday, then relative position of Saturday will be ‘1’ , Sunday ‘2’, Monday ‘3’, etc.
For this we’ll write other class , which will be called from trigger every time, it run.
It will be sometime like below, returning map of  all days like Monday tuesday with their relative positions w.r.t to current day.

Public class ClsMapofDaysBasedOnCurrentDay
{

   public Map<String,integer> returnMapofDays() { 
    
    Datetime dt = DateTime.newInstance(Date.today(), Time.newInstance(0, 0, 0, 0));
        String dayOfWeek=dt.format('EEEE');
     System.debug('Day : ' + dayOfWeek);  
   //This will return day in String like 'Monday, 'Tuesday', etc
        
        
        Map<string,integer> mpDaytoNumber = new Map<string,integer>() ; 
        if(dayOfWeek == 'Sunday')
         {
          mpDaytoNumber.put('Monday', 1);
          mpDaytoNumber.put('Tuesday', 2);
          mpDaytoNumber.put('Wednesday', 3);
          mpDaytoNumber.put('Thursday', 4);
          mpDaytoNumber.put('Friday', 5);
          mpDaytoNumber.put('Saturday', 6);
          mpDaytoNumber.put('Sunday', 0);
         }
        else if(dayOfWeek == 'Monday')
         {
          mpDaytoNumber.put('Monday', 0);
          mpDaytoNumber.put('Tuesday', 1);
          mpDaytoNumber.put('Wednesday', 2);
          mpDaytoNumber.put('Thursday', 3);
          mpDaytoNumber.put('Friday', 4);
          mpDaytoNumber.put('Saturday', 5);
          mpDaytoNumber.put('Sunday', 6);
        }
        else if(dayOfWeek == 'Tuesday')
         {
          mpDaytoNumber.put('Monday', 6);
          mpDaytoNumber.put('Tuesday', 0);
          mpDaytoNumber.put('Wednesday', 1);
          mpDaytoNumber.put('Thursday', 2);
          mpDaytoNumber.put('Friday', 3);
          mpDaytoNumber.put('Saturday', 4);
          mpDaytoNumber.put('Sunday', 5);
         
         }
         
          else if(dayOfWeek == 'Wednesday')
         {
          mpDaytoNumber.put('Monday', 5);
          mpDaytoNumber.put('Tuesday', 6);
          mpDaytoNumber.put('Wednesday', 0);
          mpDaytoNumber.put('Thursday', 1);
          mpDaytoNumber.put('Friday', 2);
          mpDaytoNumber.put('Saturday', 3);
          mpDaytoNumber.put('Sunday', 4);
         
         }
          else if(dayOfWeek == 'Thursday')
         {
          mpDaytoNumber.put('Monday',4 );
          mpDaytoNumber.put('Tuesday', 5);
          mpDaytoNumber.put('Wednesday', 6);
          mpDaytoNumber.put('Thursday', 0);
          mpDaytoNumber.put('Friday', 1);
          mpDaytoNumber.put('Saturday', 2);
          mpDaytoNumber.put('Sunday', 3);
         }
          else if(dayOfWeek == 'Friday')
         {
          mpDaytoNumber.put('Monday', 3);
          mpDaytoNumber.put('Tuesday', 4);
          mpDaytoNumber.put('Wednesday', 5);
          mpDaytoNumber.put('Thursday', 6);
          mpDaytoNumber.put('Friday', 0);
          mpDaytoNumber.put('Saturday', 1);
          mpDaytoNumber.put('Sunday', 2);
         }
          else if(dayOfWeek == 'Saturday')
         {
          mpDaytoNumber.put('Monday', 2);
          mpDaytoNumber.put('Tuesday', 3);
          mpDaytoNumber.put('Wednesday', 4);
          mpDaytoNumber.put('Thursday', 5);
          mpDaytoNumber.put('Friday', 6);
          mpDaytoNumber.put('Saturday', 0);
          mpDaytoNumber.put('Sunday', 1);
         }
        return mpDaytoNumber;
 }
}

Now ‘ll write trigger like this :

 


trigger FindNextRunTime on user_data__c (before insert, before update) {

    Datetime dt = DateTime.newInstance(Date.today(), Time.newInstance(0, 0, 0, 0));
   String dayOfWeek=dt.format('EEEE');  
   //Above will return day in String like 'Monday, 'Tuesday', etc
       
   String hour = string.valueOf(system.now().hour());
   system.debug('HOUR:' + hour ); 
 // This will return Current hour in User's time zone 
        
    Map mpDaytoNumber = new map();
    // calling helper class to populate this map based on current day.
    ClsMapofDaysBasedOnCurrentDay clmap = new ClsMapofDaysBasedOnCurrentDay();
   mpDaytoNumber  = clmap.returnMapofDays();
         
   // our first target is to find all such records , 
  //out of those currently getting updated, which have to run today 
   //and before current time
           
       For(user_data__c ud : trigger.new)
        {
          if(ud.DevWorld__Week_Selective_Days__c.contains(dayOfWeek) && 
              Integer.valueof(ud.DevWorld__Time_of_the_day__c) 
<=  integer.valueOf(hour))
          { 
               // first finding current date & time of the Run
  ud.DevWorld__Current_datetime__c= dateTime.newInstance(Date.today(), 
Time.newInstance(Integer.valueof(ud.DevWorld__Time_of_the_day__c), 0 , 0, 0));
           
     // Finding next Run Date & Time
    List CurrentRecSelectedDays =   ud.DevWorld__Week_Selective_Days__c.split(';');
system.debug('CurrentRecSelectedDays =' + CurrentRecSelectedDays );
           
      If(CurrentRecSelectedDays.size() > 0 )
          {
              // out of all days selected in multi picklist, 
                 //we've to just find the next day 
                    //out of all days selected in picklist
                    
    integer IntegerMinValue = mpDaytoNumber.get(CurrentRecSelectedDays[0]);
   // this integer is most important. If today is Friday and 
// value selected are Friday, Monday and Thursday, then it  will 
// Return Number corresponding to Monday, i.e. 3
                    For(string dayw: CurrentRecSelectedDays ) {
                      if ( mpDaytoNumber.get(dayw) < IntegerMinValue )
                          IntegerMinValue  = mpDaytoNumber.get(dayw);
                    }
    ud.DevWorld__Next_Run_Time__c = 
dateTime.newInstance(Date.today() +IntegerMinValue , 
Time.newInstance(Integer.valueof(ud.DevWorld__Time_of_the_day__c)
, 0 , 0, 0));  // Note how IntegerMinValue  is added to today's day
                  }
              }
            else{ 
             //ud.Exception__c= 'Day or Time not present in Range';
             }
        } // end of for loop of trigger.new
}

 

Distinct values in APEX SOQL query

I’ve requirement in Visualforce page to show suggestive search i.e. as soon I start typing,
It should start suggesting names. I’m going to write a separate article specifically for this.

But here I’m going to discuss small issue :
Although ‘suggestive search’ was working properly, but if in controller, if same name exist multiple times ( say, I was searching Doctors by combining their first name and last name) , then auto suggest was giving names multiple times.
So my requirement was- back-end SOQL query should return distinct records.
My query was like this :

List<xyz__c> provlist =  Database.query(‘Select id ,  First_Name__c,  Last_Name__c ,Phone__c, Email_Address__c  from xyz__c’);

I had choice to use aggregate result but was not able to find way to convert aggregate Result into list  as ultimately  in VF, I need to iterate list. compex java script for auto suggest  was also needing list

I was able to solve the issue with following simple code :

declare a new final list of same object  and declare a set of string :

List<xyz__c> provlistFinal = new list<xyz__c> ();                                      List<string> fullnameSet = new set<string>();

Iterate original list and populate new list using some criteria based on ‘set’ as shown below

For(xyz__c p : provlist) 
          { 
            if(!fullnameSet.contains(p.First_Name__c + ' ' + p.Last_Name__c))
               { 
                 fullnameSet.add(p.First_Name__c + ' ' + p.Last_Name__c);
                 provlistFinal.add(P);
               }
          }

So above code if populating new list(provlistFinal) if set doesn’t already contain same name(made by concatenating first name and last name

This new list could be used for iteration in VF page.