[SalesForce] Approval Process Instance – Status question

I am trying to build a trigger that runs directly after a Salesforce approval process is submitted. Basically, I am trying to copy the approval information into a separate custom object that will create records that have both the approval information (Approver Name, Approval Status, etc.) and my custom object record information. This will allow me to run more relevant reports off of the approval instances (than available through out of the box reports). I also need the ability to generate a document with the approval information on it (who approved and when) and I can't find a solution for that without creating records in another object with the approval information copied into it.

In an attempt to do the above, I have created an after update trigger that is set to run each time an approval process is started and on each step of that process (using field updates). During the trigger, I am running a query to find the ProcessInstance (and the more specific objects that give the individual approval info). The problem I'm having is that for some reason when I run the query in the trigger, the ProcessInstance Status is set as "Started," but if I run the same query outside of trigger then it is listed as "Pending." The ProcessInstance record appears to not be set to "Pending" until after the trigger is finished. This means I can't query to find the actual approvals because the system doesn't think they exist yet.

Has anyone done anything similar like the above or know of a way to do what I'm interested in?

I've put my trigger below. It's not even close to complete, but I can never get any ProcessInstances found when the approval is first submitted so I want to make sure that is possible first before finishing it up.

Thanks!

trigger Approval_History_AR_Trigger on ECM_Appropriations_Request__c (after update) {
    ProcessInstance PI = new ProcessInstance();
    List<ProcessInstance> PIlist = new List<ProcessInstance>();
    List<ProcessInstanceStep> PISlist = new List<ProcessInstanceStep>();
    List<ProcessInstanceWorkItem> WIlist = new List<ProcessInstanceWorkItem>();

    for (ECM_Appropriations_Request__c AR :trigger.new)
        {
            system.debug('Approval Status: ' + AR.Approval_Status__c + '     Current Step Update: ' + AR.Step_Update__c + '    Previous Step Update: ' + Trigger.oldMap.get(AR.Id).Step_Update__c + '    If statement (Pending Approval)? ' + (AR.Approval_Status__c == 'Pending Approval') + '    If statement (Step Update)? ' +  (AR.Step_Update__c != Trigger.oldMap.get(AR.Id).Step_Update__c));
            if(AR.Approval_Status__c == 'Pending Approval' && (AR.Step_Update__c != Trigger.oldMap.get(AR.Id).Step_Update__c)){
                try{
                    for(ProcessInstance p: [SELECT CompletedDate,CreatedById,CreatedDate,ElapsedTimeInDays,ElapsedTimeInHours,ElapsedTimeInMinutes,Id,IsDeleted,LastActorId,LastModifiedById,LastModifiedDate,ProcessDefinitionId,Status,SubmittedById,SystemModstamp,TargetObjectId FROM ProcessInstance WHERE TargetObjectId = :AR.id  AND Status = 'Pending' LIMIT 1]){
                        PIlist.add(p);
                        system.debug('PIlist.size() = ' + PIlist.size());
                    }
                    PIlist = [SELECT CompletedDate,CreatedById,CreatedDate,ElapsedTimeInDays,ElapsedTimeInHours,ElapsedTimeInMinutes,Id,IsDeleted,LastActorId,LastModifiedById,LastModifiedDate,ProcessDefinitionId,Status,SubmittedById,SystemModstamp,TargetObjectId FROM ProcessInstance WHERE TargetObjectId = :AR.id  AND Status = 'Started' LIMIT 1];
                    system.debug('There is a Process Instance.');
                    PI=[SELECT CompletedDate,CreatedById,CreatedDate,ElapsedTimeInDays,ElapsedTimeInHours,ElapsedTimeInMinutes,Id,IsDeleted,LastActorId,LastModifiedById,LastModifiedDate,ProcessDefinitionId,Status,SubmittedById,SystemModstamp,TargetObjectId FROM ProcessInstance WHERE TargetObjectId = :AR.id  AND Status = 'Started' LIMIT 1];
                }
                catch(Exception e){
                    system.debug('No ProcesssInstance.  AR.Id=' + AR.id + '    Heres the query:' + [SELECT CompletedDate,CreatedById,CreatedDate,ElapsedTimeInDays,ElapsedTimeInHours,ElapsedTimeInMinutes,Id,IsDeleted,LastActorId,LastModifiedById,LastModifiedDate,ProcessDefinitionId,Status,SubmittedById,SystemModstamp,TargetObjectId FROM ProcessInstance WHERE TargetObjectId = :AR.id]);
                }   
                system.debug('PIlist.size() > 0 = ' + (PIlist.size() > 0));
                if(PIlist.size() > 0){
                    try{
                        system.debug('PI.Id=  ' + PIlist[0].id + '    Heres the query: ' + [SELECT ActorId,CreatedById,CreatedDate,ElapsedTimeInDays,ElapsedTimeInHours,ElapsedTimeInMinutes,Id,IsDeleted,OriginalActorId,ProcessInstanceId,SystemModstamp FROM ProcessInstanceWorkitem WHERE ProcessInstanceId = :PIlist[0].id ]);
                        for (ProcessInstanceWorkItem c: [SELECT ActorId,CreatedById,CreatedDate,ElapsedTimeInDays,ElapsedTimeInHours,ElapsedTimeInMinutes,Id,IsDeleted,OriginalActorId,ProcessInstanceId,SystemModstamp FROM ProcessInstanceWorkitem WHERE ProcessInstanceId = :PIlist[0].id ]) {
                            WIlist.add(c);
                        }
                        system.debug('WIlist size: ' + Wilist.size());
                        if(WIlist.size()>0){
                            for (Integer i = 0; i < WIlist.size(); i++) {
                                Approval_History__c AH;
                                try{                                
                                    AH = [SELECT ID, ProcessInstanceId__c, WorkItemId__c    FROM Approval_History__c WHERE WorkItemId__c = :WIlist[i].Id LIMIT 1];
                                    system.debug('Does AH==null?' + AH.workItemId__c == null);
                                }
                                catch (exception e3){
                                    system.debug('No AH record exists.  Does AH==null?' + AH.workItemId__c == null);
                                }
                            //if([SELECT ID, ProcessInstanceId__c, WorkItemId__c    FROM Approval_History__c WHERE WorkItemId__c = :WIlist[i].Id LIMIT 1]==null){
                                //Approval_History__c AH = new Approval_History__c(WorkItemId__c = WIlist[i].Id, Approver_Name__c = WIlist[i].ActorId, Date_Assigned__c=WIlist[i].CreatedDate);
                                if(AH.workItemId__c == null){
                                    AH.WorkItemId__c = WIlist[i].Id;
                                    AH.Approver_Name__c = WIlist[i].ActorId;
                                    AH.Date_Assigned__c=WIlist[i].CreatedDate;
                                    insert AH;
                                }
                                else if(AH.Approver_name__c != WIlist[i].ActorId){
                                    AH.Approver_name__c = WIlist[i].ActorId;
                                    update AH;
                                }
                        }   
                    }                     
                }
                catch(exception e2){
                    system.debug('No ProcesssInstanceWorkItems.');
                    system.debug('PI.Id=  ' + PIlist[0].id + '    Heres the query: ' + [SELECT ActorId,CreatedById,CreatedDate,ElapsedTimeInDays,ElapsedTimeInHours,ElapsedTimeInMinutes,Id,IsDeleted,OriginalActorId,ProcessInstanceId,SystemModstamp FROM ProcessInstanceWorkitem WHERE ProcessInstanceId = :PIlist[0].id ]);

                }
                try{
                    for (ProcessInstanceStep c: [select ActorId, Comments, CreatedById, CreatedDate, ElapsedTimeInDays, ElapsedTimeInHours, ElapsedTimeInMinutes, OriginalActorId, Id, ProcessInstanceId, SystemModstamp, StepStatus, StepNodeId from ProcessInstanceStep where ProcessInstanceId=:PIlist[0].id AND StepStatus = 'Approved']) {
                        PISlist.add(c);
                    }
                    if(PISlist.size()>0){
                        for (Integer i = 0; i < WIlist.size(); i++) {
                            Approval_History__c AH = [SELECT ID, ProcessInstanceId__c, WorkItemId__c    FROM Approval_History__c WHERE ProcessInstanceId__c = :PISlist[i].ProcessInstanceId AND Approver_Name__c= :PISlist[i].OriginalActorId LIMIT 1];
                            //if([SELECT ID, ProcessInstanceId__c, WorkItemId__c    FROM Approval_History__c WHERE WorkItemId__c = :WIlist[i].Id LIMIT 1]==null){
                                //Approval_History__c AH = new Approval_History__c(WorkItemId__c = WIlist[i].Id, Approver_Name__c = WIlist[i].ActorId, Date_Assigned__c=WIlist[i].CreatedDate);
                            if(AH == null){
                                AH.WorkItemId__c = WIlist[i].Id;
                                AH.Approver_Name__c = WIlist[i].ActorId;
                                AH.Date_Assigned__c=WIlist[i].CreatedDate;
                                insert AH;
                            }
                            else if(AH.Approver_name__c != WIlist[i].ActorId){
                                AH.Approver_name__c = WIlist[i].ActorId;
                                update AH;
                            }
                        }   
                    }                     
                }
                catch(exception e2){
                    system.debug('No ProcesssInstanceWorkItems');
                }
            }
            }



        }
}

Best Answer

What's happening here is that because your code is running in the same execution context as the update to the record that initiated the approval process, the DML for it hasn't been "committed" to the database. See Order of Execution in the Apex Developer Guide to help you follow along with what's happening.

After any Before Insert/Update Triggers, Validation and Duplicate Record Rules run on the record being submitting for approval. The record is then "conditionally saved" to the database. It's a tentative save, not a "commit" to the database. It's at this point I'd expect to see the code for the Approval Process to be initiated (see Approval Processing in the Apex Developer Guide).

This is also the point in the execution context when the After Update trigger will fire. Note that both of these processes happen in the same execution context. In the execution context of the record being submitted for approval, it's not going to be committed to the database, until long after the AfterUpdate trigger has finished running. Workflow, Processes, and numerous other things will be run against the record before the update is "committed" to the database 12 steps in the order of execution later!

At this point, should something happen, the entire Save can still be rolled back for both the record being submitted and insertion of the new Approval Process Instance. As such, it's not possible to query an Id for the latter during the same execution context.

Initiating an Approval Process is what sets the status to 'Started'. It won't be until a successful ProcessResult operation completes that a query will be returned with results that show it as 'Pending'.

What you could do to work around this, is use an @future call to move the query to an asynchronous method that later updates the new custom object record at a later time. Have the method run the query to retrieve the Id of the approval process instance and update the new record on the custom object with it.

You may also want to consider moving most all of this to an asynchronous process since it doesn't sound as though it's something you need immediate updates on.

Related Topic