Switching BPF Stage in a Dynamics 365 Plugin

Adam Murchison, 22 April 2021

When working with Business Process Flows (BPF) you may want to keep the BPF stage aligned with field value(s) when being updated by web request or any other server-side operation. One scenario could be based on a records status reason, you may want to have the BPF stage reflecting that automatically. In this blog I’ll walk through that scenario: switching BPF stage in a Dynamics 365 plugin.

Example:

For example, say we have 3 statuses on the ‘Party’ entity: Proposing, Planning And Live. On the Business Process Flow we also have these three stages (Proposing, Planning And Live).image

In logical order, we grab the active process, get the active stage name and compare to the status code. If the active stage name isn’t equal to the equivalent status code, then we should set the active stage that lines up with the equivalent status code.

The full plugin code is here:

private static Dictionary<int, string> STATUS_CODES = new Dictionary<int, string>() { { 1, "Proposing" }, { 809730000, "Planning" }, { 809730001, "Live" } };
        public void Execute(IServiceProvider serviceProvider)
        {
            ITracingService tracer = (ITracingService)serviceProvider.GetService(typeof(ITracingService)));
            IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
            IOrganizationServiceFactory factory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
            IOrganizationService sdk = factory.CreateOrganizationService(null);

            Entity target = (Entity)context.InputParameters["Target"];

            if (target != null)
            {
                tracer.Trace($"target.id={target.Id}");

                int statusCode = target.GetAttributeValue<OptionSetValue>("statuscode").Value; 

                if (STATUS_CODES.ContainsKey(statusCode))
                {
                    Process(sdk, tracer, target, statusCode);
                }
            }
        }

        private void Process(IOrganizationService sdk, ITracingService tracer, Entity target, int statusCode)
        {
            RetrieveProcessInstancesRequest req = new RetrieveProcessInstancesRequest() { EntityId = target.Id, EntityLogicalName = target.LogicalName };
            RetrieveProcessInstancesResponse response = (RetrieveProcessInstancesResponse)sdk.Execute(req);

            if (response?.Processes != null && response.Processes?.Entities != null && response.Processes.Entities.Count > 0)
            {
                //Retrieve the active stage id
                Guid stageId = response.Processes.Entities[0].GetAttributeValue<Guid>("processstageid");
                string activeStageName = GetActiveStageName(sdk, stageId);
                //find the stage name based off the status code value
                string newStageName = STATUS_CODES[statusCode];
                tracer.Trace($"stageId={stageId}, activeStageName={activeStageName}, newStageName={newStageName}");

                if (!newStageName.Equals(activeStageName, StringComparison.InvariantCultureIgnoreCase))
                {
                    Guid processId = response.Processes.Entities[0].Id;
                    tracer.Trace($"processId={processId}");

                    //get all stages for the BPF
                    RetrieveActivePathResponse pathResp = GetPathResponse(sdk, processId);

                    if (pathResp?.ProcessStages != null && pathResp.ProcessStages?.Entities != null && pathResp.ProcessStages.Entities.Count > 0)
                    {
                        // iterate the stages to find the new EntityReference of the stage you want to set as the active one
                        Entity newStage = pathResp.ProcessStages.Entities.ToList().Where(stage => newStageName.Equals(stage.Get<string>("stagename"), StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault();
                        tracer.Trace($"newStageId={newStage.Id}");

                        if (newStage != null)
                        {
                            Entity update = new Entity("mag_partyprocess", processId); //the business process flow entity
                            update["activestageid"] = newStage.ToEntityReference();
                            sdk.Update(update);
                            tracer.Trace($"Updated bpf with id={processId} with activestageid={newStage.Id}");
                        }
                    }
                }
            }
        }

        private static RetrieveActivePathResponse GetPathResponse(IOrganizationService sdk, Guid processId)
        {
            RetrieveActivePathRequest pathReq = new RetrieveActivePathRequest
            {
                ProcessInstanceId = processId
            };
            RetrieveActivePathResponse pathResp = (RetrieveActivePathResponse)sdk.Execute(pathReq);
            return pathResp;
        }

        private string GetActiveStageName(IOrganizationService sdk, Guid stageId)
        {
            Entity stageEnt = sdk.Retrieve("processstage", stageId, new ColumnSet("stagename"));
            string activeStageName = stageEnt.GetAttributeValue<string>("stagename");
            return activeStageName;
        }

You use the RetrieveProcessInstanceRequest to retrieve the active Business Process Flow, it’s always the first one in the entity collection (I.e response.Processes.Entities[0]). From there you can see the attributes within the active process, the key part here is retrieving the stage name. You can see in the below screenshot you can get the processstageid which allows you to retrieve the stage name to compare with the status reason.

After this part, the code is straight forward, and you simply send an update request to the Business Process Flow entity (in my case mag_partyprocess) to update the stage to the correct one based off the status.

image

Summary

In summary, keeping the Business Process Flow active stage aligned with another field was a bit of a challenge and there weren’t any examples of how to do this online, so I hope this helps you to tackle this issue.