7

I am attempting to recall an approval process with the following Apex code:

Approval.ProcessWorkitemRequest pwr = new
Approval.ProcessWorkitemRequest();  pwr.setAction('Removed');
pwr.setWorkItemId(workItems[0].id);
Approval.ProcessResult result = Approval.process(pwr);

It works fine when I run it as an administrator or when the user running it is the submitter of the approval process.

But it doesn't work for any other users; I get this error:

Process failed. First exception on row 0; first error: INSUFFICIENT_ACCESS_ON_CROSS_REFERENCE_ENTITY, insufficient access rights on cross-reference id

How can I make this work for any user?

To complete my question, I created a visualforce page to add a custom button on the object page layout calling the apex method below ManageButton.documentCustomGetApprovalProcess

public without sharing class ManageButton 
{
    // Constructor
    public ManageButton(ApexPages.StandardController stdController) 
    {
    }

    /**
     * method used by the Get Approval Process button
     */   
    public PageReference documentCustomGetApprovalProcess()
    { 
        string docId = ApexPages.currentPage().getParameters().get('id');

        ProcessInstanceWorkitem[] workItems = [
             SELECT Id
               FROM ProcessInstanceWorkitem 
              WHERE ProcessInstance.TargetObjectId = :docId
                AND ProcessInstance.Status = 'Pending'];

        Approval.ProcessWorkitemRequest pwr = new Approval.ProcessWorkitemRequest();
        pwr.setAction('Removed');
        pwr.setWorkItemId(workItems[0].id);
        Approval.ProcessResult result = Approval.process(pwr);

        // Redirect the user back to the original page
        PageReference pageRef = new PageReference('/' + docId);
        pageRef.setRedirect(true);
        return pageRef;
    }
}

Even specifing "without sharing" on the class I have the error I mentioned.

  • Have the code that is getting called in a class that executes 'without sharing'. – dphil Nov 2 '15 at 16:10
  • Hello, I tried this but it has no impact. I fould the link link stipulating the without sharing has no impact on this error. – Jean-Guy Buon Nov 2 '15 at 16:40
  • The link you posted there, actually says it solved this error. In my experience, it has solved it as well. – dphil Nov 2 '15 at 16:43
  • You are right the link says it should work adding without sharing. I tried it and I still have the issue. – Jean-Guy Buon Nov 2 '15 at 16:51
  • You should edit your question with that code instead of placing it in comments. – dphil Nov 2 '15 at 17:05
7

By design, Approval Processes only allow either a System Administrator and/or the Initial Submitter to recall the submission.

Per Salesforce documentation:

enter image description here

However, to answer your question How can I make this work for any user? you will need your Apex code to change the context user to be a System Administrator.

One way to achieve this is by using Apex Email Service and sending an email to its service address from your Apex code. Email Services let you specify a context user that the code will run under and so in that apex code you can try performing your recall action.

Side note, I've used this Apex Email Service technique to run code as a specific user in other projects like Salesforce Chatter Bot for Feeds.

enter image description here

1

Based on @Doug suggestion below is one way to achieve recall of an approval process without compromising security or being the submitter.

Code Excerpt:

global virtual with sharing class ID_Item extends cms.ContentTemplateController implements Messaging.InboundEmailHandler  {

...       

@RemoteAction
global static Map<String, Object> mApprovalRecallAction(String sContentID, String sName) {
    System.debug('>>> ID_Item.mApprovalRecallAction(...)');

    ...

    // submitter is the person recalling, so we have permission to do so (otherwise we need to be an admin: https://success.salesforce.com/ideaView?id=08730000000iY1aAAE)
    if (oApproval.CreatedById == UserInfo.getUserId()) {        
        Approval.ProcessWorkitemRequest oPWR = new Approval.ProcessWorkitemRequest(); 
        oPWR.setComments('Recalled by `' + oApproval.CreatedBy.Name + '`');
        oPWR.setAction('Removed');
        if (!Test.isRunningTest()) { // can't seem to create ProcessInstanceWorkItem records in tests
            oPWR.setWorkItemId(oWorkItemList[0].id);
            Approval.ProcessResult oPR = Approval.process(oPWR);
        }

        // Next the approval process needs to have a recalled action that sets `cms__Approval__c.Status = 'Recalled'` which is levraged by OrchestraCMS - but just incase these were deleted we are changing the status in apex as well
        oApproval.cms__Status__c = 'Recalled';
        oApproval.Content_Status__c = 'Recalled';
        update oApproval;
        System.debug('=== ID_Item.mApprovalRecallAction(...): `JSON.serialize(oPWR)=' + JSON.serialize(oPWR) + '`');
        return new Map<String, Object>{'bSuccess' => true, 'sPWR' => JSON.serialize(oPWR)};
    }
    // we assume `this.bApprovalRecalVisible` did our security checks  
    else {
        // we leverage the functionality that an email service allows us to run under a adming user
        Messaging.SingleEmailMessage oMessage = new Messaging.SingleEmailMessage();
        try {
            // find our email service to use, as convention we prefixed it with class name 
            EmailServicesAddress oEmailServiceAddress = [
                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.IsActive = true AND e.LocalPart LIKE 'ID_Item%' // `LIKE` is case-insensetive, so we keep capitialization same as class name as it's easier for search and replace 
                ].get(0); 
            oMessage.toAddresses = new String[] {
                oEmailServiceAddress.LocalPart + '@' + oEmailServiceAddress.EmailDomainName
            };
        }
        catch (Exception e) {
            System.debug('=== ID_Item.mApprovalRecallAction(...): ERROR: no email service setup, `e' + String.valueOf(e) + '`');
            return new Map<String, Object>{'bSuccess' => true, 'e' => JSON.serialize('ERROR: no email service setup, `e' + String.valueOf(e) + '`')};
        }
        Map<String, Object> oParameterMap = new Map<String, Object>{'sAction' => 'mApprovalRecallActionWithAdminPermissions', 'sApprovalID' => oApproval.Id};
        oMessage.subject = '=== ID_Item.mApprovalRecallAction(...): `JSON.serialize(oParameterMap)=' + JSON.serialize(oParameterMap) + '`'; // using subject is not safe because SF can modify it sometimes (ex. prefixing `Sandbox`  
        oMessage.plainTextBody = JSON.serialize(oParameterMap);
        Messaging.SendEmailResult[] oResults = Messaging.sendEmail(new List<Messaging.SingleEmailMessage> {oMessage});

        System.debug('=== ID_Item.mApprovalRecallAction(...): `oResults' + String.valueOf(oResults) + '`');

        System.debug('<<< ID_Item.mApprovalRecallAction(...)');
        if (oResults[0].success) {
            return new Map<String, Object>{'bSuccess' => true, 'oResults' => JSON.serialize(String.valueOf(oResults))};
        } else {
            return new Map<String, Object>{'bSuccess' => false, 'oResults' => JSON.serialize(String.valueOf(oResults))};
        }
    }       
}


global Messaging.InboundEmailResult handleInboundEmail(Messaging.inboundEmail oInboundEmail, Messaging.InboundEnvelope oInboundEnvelope){
    System.debug('>>> ID_Item.handleInboundEmail(...)');
    Messaging.InboundEmailResult oInboundEmailResult = new Messaging.InboundEmailResult();

    Map<String,Object> oParameterMap = new Map<String, Object>{'sAction' => 'UNKNOWN'};
    try {
        oParameterMap = (Map<String, Object>)JSON.deserializeUntyped(oInboundEmail.plainTextBody); 
        // expected string input format is "{'sAction': 'fooBar', 'sApprovalID': 'foo', ...}"
    } 
    catch (QueryException e) {
        System.debug('=== ID_Item.handleInboundEmail(...): ERROR: `e=' + e + '`');
        System.debug('<<< ID_Item.handleInboundEmail(...)');
        return oInboundEmailResult;
    }
    try {
        if (oParameterMap.get('sAction') == 'mApprovalRecallActionWithAdminPermissions') {
            String sApprovalID = (String)oParameterMap.get('sApprovalID');
            cms__Approval__c oApproval = [Select ID, c.cms__Content__c FROM cms__Approval__c c WHERE c.Id = :sApprovalID].get(0);
            List<ProcessInstanceWorkItem> oWorkItemList = [Select ProcessInstance.TargetObjectId, ActorId From ProcessInstanceWorkitem where ProcessInstance.TargetObjectId = :oApproval.ID];

            //System.debug(oWorkItemList[0]);
            Approval.ProcessWorkitemRequest oPWR = new Approval.ProcessWorkitemRequest(); 
            oPWR.setComments('Recalled by `' + oInboundEnvelope.fromAddress + '`');
            oPWR.setAction('Reject');

            if (Test.isRunningTest() == false) {
                oPWR.setWorkItemId(oWorkItemList[0].id);
                Approval.ProcessResult oPR = Approval.process(oPWR);
            }

        }

    }
    catch (QueryException e) {
         System.debug('=== ID_Item.handleInboundEmail(...): ERROR: `e=' + e + '`');
    }

    oInboundEmailResult.success = true;
    System.debug('<<< ID_Item.handleInboundEmail(...)');
    return oInboundEmailResult;
}

...

}

  • usually answers should have the system.debug lines removed – cropredy Nov 27 '17 at 20:04

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.