0

I need to give the permission to all users to recall an approval process (not only the admin or assigned approver).

I wrote an apex class and created a button that call the method that updates the approval process. However, as you may already know, even with "without sharing", users still have the "Insufficient privileges" error. Here's my code:

global without sharing class ApprovalProcessRecall implements Messaging.InboundEmailHandler  {

@InvocableMethod
global static void recallApproval(List<Id> saList) { 
    
    ProcessInstance processInstance = new ProcessInstance();  
    
    if(processInstance.CreatedById == UserInfo.getUserId()){
        List<ProcessInstanceWorkItem> workItemList = [SELECT Id, CreatedById
                                                      FROM ProcessInstanceWorkitem 
                                                      WHERE ProcessInstance.Status = 'Pending' 
                                                      AND ProcessInstance.TargetObjectId IN :saList LIMIT 1];
        
        Approval.ProcessWorkitemRequest pwr = new Approval.ProcessWorkitemRequest(); 
        pwr.setAction('Removed');
        pwr.setComments('Approbation rappelée');
        
        if (Test.isRunningTest() == false) {
            pwr.setWorkItemId(workItemList[0].id);
            Approval.ProcessResult pr = Approval.process(pwr);
        }     
        
    }else{
        for(Id sa: saList){
            if(Approval.IsLocked(sa)){
                Approval.unLock(sa);
            }
            
        }
        
        List<Messaging.SingleEmailMessage> messages = new List<Messaging.SingleEmailMessage>();
        try{   
            String idServiceAppointment = string.join(saList, ',');
            Messaging.SingleEmailMessage message = new Messaging.SingleEmailMessage();
            EmailServicesAddress emailServiceAddress = [SELECT e.SystemModstamp, e.RunAsUserId, e.LocalPart, e.LastModifiedDate, 
                                                        e.LastModifiedById, e.IsActive, e.Id, e.FunctionId, e.EmailDomainName,
                                                        e.CreatedDate, e.CreatedById, e.AuthorizedSenders 
                                                        FROM EmailServicesAddress e 
                                                        WHERE e.localpart = 'recallprocess' LIMIT 1]; 
            message.toaddresses = new String[]{
                emailServiceAddress.LocalPart + '@' + emailServiceAddress.EmailDomainName
                    };
                        message.setHtmlBody(idServiceAppointment);
            message.setPlainTextBody(idServiceAppointment);
            message.isUserMail();
            messages.add(message);
            Messaging.SendEmailResult[] results = Messaging.sendEmail(messages);
            system.debug('message: ' + messages);
            system.debug('results Message: ' + results);
        }
        catch(Exception e){
            System.debug('Erreur : ' + e);
        }
    }
}

global Messaging.InboundEmailResult handleInboundEmail(Messaging.inboundEmail inboundEmail, Messaging.InboundEnvelope inboundEnvelope){ 
    system.debug('inboundEmail: ' + inboundEmail.plainTextBody);

    Messaging.InboundEmailResult inboundEmailResult = new Messaging.InboundEmailResult();
    
    try {
        
        List<ProcessInstanceWorkItem> workItemList = [Select ProcessInstance.TargetObjectId, ActorId From ProcessInstanceWorkitem 
                                                      WHERE ProcessInstance.TargetObjectId =:inboundEmail.plainTextBody];
        
        Approval.ProcessWorkitemRequest pwr = new Approval.ProcessWorkitemRequest(); 
        pwr.setAction('Removed');
        pwr.setComments('Approbation rappelée');
        
        if (Test.isRunningTest() == false) {
            pwr.setWorkItemId(workItemList[0].id);
            Approval.ProcessResult pr = Approval.process(pwr);
        }     
        
    }
    catch (QueryException e) {
    }
    
    inboundEmailResult.success = true;
    return inboundEmailResult;
}
}

I've read some post that talk about setting up an email service, but I don't understand how it works. I've setup an email service with an admin as the Context User, but still have the same "Insufficient privileges" issue.

ETA : code updated

1

yes, that's correct. currently, salesforce does not support the recall option for all users - only to admins and to the original submitter. this can be selected in the approval process setup. Two ways to achieve this is by

  1. Custom Email Service
  2. API Callout to Salesforce to Recall.

In both cases, you need to run in admin context user.

In your code,

Method: handleInboundEmail

  • Issue1:

    List workItemList = [Select ProcessInstance.TargetObjectId, ActorId From ProcessInstanceWorkitem where ProcessInstance.TargetObjectId = :processInstance.ID];

This is wrong. ProcessInstance.TargetObjectId is mapped to processInstance.ID

ProcessInstance.TargetObjectId should be the id of the record that is being recalled from the approval process.

  • Issue2:

setAction should be 'Removed' for Recall. Not 'Reject'.

Sample Apex Code for Recall:

List<ProcessInstanceWorkitem> workItems = new List<ProcessInstanceWorkitem>();
Approval.ProcessWorkitemRequest req;
workItems = [
  SELECT p.Id, p.ActorId
  FROM ProcessInstanceWorkitem p
  WHERE p.ProcessInstance.TargetObjectId = :recordId
  ORDER BY ID DESC
  LIMIT 1
];
if (workItems.size() > 0) {
  req = new Approval.ProcessWorkitemRequest();
  req.setComments('Test Comments');
  req.setAction('Removed');
  req.setWorkitemId(workItems[0].Id);
  Approval.ProcessResult result = Approval.process(req);
}
1
  • Thank you, I've updated my code. However, I still have the same "Insufficient privileges" error. I've setup the email service and use myself as the context user. I think I'm missing something but don't know what...
    – chloe
    Dec 1 '20 at 10:43
1

Will add another method that i used in the past. It is little bit risky, but worked for my case.

1.create permission set that provide Modify All access to the object that the approval process work on.

2.in your code assign the permission set to the user that need to recall the approval.

3.write queueable process that will do the recall, and at the end will remove the permission set from the user.

3
  • thanks for your help. However, the user should not be able to approve or reject the record. Only recall it, that's one of the reason "Modify All" access is not good for my use case
    – chloe
    Dec 12 '20 at 11:01
  • It does not matter. Because you can provide custom button/page for that and in your customization the user will only be able to recall. The risk is that in the few seconds between the code that assigning the permission to the queueable that remove it, the user does have too much access that might be abuse.
    – Liron C
    Dec 12 '20 at 13:46
  • Yes, that's the few seconds I'm afraid of with the users I have... But I've found another solution, thanks again !
    – chloe
    Dec 12 '20 at 14:07

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.