LibrarySites.Banner

Sitecore Experience Commerce 9 – Workflow

The pricing & promotions features in Sitecore Experience Commerce have always had workflow built in to allow an approval process to take place before data changes are reflected on the storefront. With the release of version 9.0.2 there is a new customisable workflow included that allows you to apply custom workflow patterns to the entities you store.

The functionality works in two steps, first you declare your workflow pattern e.g. the steps needed for your approval process, and then you choose the entity types to apply the workflow to. The advantage to having this split process is that it means you can have multiple workflow patterns existing in the same system, so you could for example have a more stringent approval process for changes to catalog structure (Catalogs & Categories), compared to the actual Sellable Item themselves.

States & Commands

The feature has two key models that provide the functionality these are States & Commands. The states represent buckets where content changes can reside and the commands are actions that the users can perform to move the content changes between the different buckets. A typical example for this would be an entity residing in a Draft state, then a user executing a Submit command to move it to the next state where it will wait for approval. So let’s take a look at how this has been implemented out of the box.

Default Commerce Workflow

There is a default workflow pattern included with 9.0.2 called the DefaultCommerceWorkflow which has been applied to the Catalog, Category & Sellable Item entities. This workflow is a simple pattern made up of 3 states and 3 commands, which you can see in the diagram below, here the blue boxes represent the states & the red arrows represent the commands.

The process is started by a user creating new version of an entity that they would like to edit, you can read about versioning in my other post here. This version will be created in the Draft state, the user will then make the changes required and execute the Submit action which will move the entity into the Awaiting Approval state. This state then has two possible actions associated with it Approve and Reject, selecting the Approve action will move the entity in to the final Approved state and the changes will then be reflected on the storefront, or if the user isn’t happy then they can choose to execute the Reject action and this will move the entity back to the draft state for the process to begin again.

Let’s take a look at how this looks from within the Business Tools, once you’ve finished making your changes and want to execute the action to move the entity to the next state in the workflow, you do this using the drop menu on the Summary panel for the entity, here you will see the new Promote to next workflow state button.

Clicking this will present you with a popup that tells you the current state that the entity is in, and lets you choose from the available commands you want to execute. This is the UI that allows you to push your changes through the various workflow states you have configured.

The DefaultCommerceWorkflow is setup as part of the Environment Initialization step (for more information about Environment Initialization check out our DevOps Guide) when you first create your environments. The Workflow plugin registers an InitializeDefaultWorkflowBlock into run in the InitializeEnvironmentPipeline to handle the setup of this for you, this is where the workflow itself is defined.

How to enable workflow for other entities

As I mentioned above the default workflow is enabled on the Catalog, Category & Sellable Item entities, but it’s also really simple to apply this default workflow to other entity types, for example any custom entity types that you might create.

If you take a look in the PlugIn.Workflow.PolicySet-1.0.0.json configuration file that is created with Experience Commerce 9.0.2 you will see multiple instances of the WorkflowPolicy which is used to control which workflow is assigned to which entities, here you’ll see where the workflow is applied to the Catalog, Category & Sellable Item types. To enable the default workflow for one of your custom entities all you need to do is to add your own WorkflowPolicy that references your custom type:

{
  "$type": "Sitecore.Commerce.Plugin.Workflow.WorkflowPolicy, Sitecore.Commerce.Plugin.Workflow",
  "TypeFullName": "<<YOUR CUSTOM TYPE>>",
  "WorkflowId": "Entity-Workflow-DefaultCommerceWorkflow"
}

After you add the record for your custom type, you Bootstrap (for more information about bootstrapping check out our DevOps Guide) these changes into the database and you’re done, your custom type will then have the default workflow enabled, as easy as that!

How to create new workflows

Now so far this post has been covering the DefaultCommerceWorkflow which is provided for you out of the box, but as I mentioned before you can create your own workflows to represent your specific business needs. You might for example require a multi-step approval process where there are different tiers of approval before changes are reflected on the storefront. Well if you need to create your own custom workflow it’s really simple, so let’s take a look at how we’d implement a dual approval flow that looks like this:

As you can see we’ve added another workflow state here to give a dual approval process, but how would we actually code this? Well the example below shows how simple it is to build up the Workflow object that represents this flow.

var draftState = new WorkflowState
{
  Name = "Draft",
  DisplayName = "Draft",
  Commands = new List<WorkflowCommand>
  {
    new WorkflowCommand
    {
      Name = "Submit",
      NextState = "AwaitingApproval"
    }
  }
};

var awaitingApprovalState = new WorkflowState
{
  Name = "AwaitingApproval",
  DisplayName = "Awaiting Approval",
  Commands = new List<WorkflowCommand>
  {
    new WorkflowCommand
    {
      Name = "Approval",
      NextState = "Awaiting Confirmation"
    },
    new WorkflowCommand
    {
      Name = "Reject",
      NextState = "Draft"
    }
  }
};

var awaitingConfirmationState = new WorkflowState
{
  Name = "AwaitingConfirmation",
  DisplayName = "Awaiting Confirmation",
  Commands = new List<WorkflowCommand>
  {
    new WorkflowCommand
    {
      Name = "Confirmation",
      NextState = "Confirmed"
    },
    new WorkflowCommand
    {
      Name = "Reject",
      NextState = "Draft"
    }
  },
};

var confirmedState = new WorkflowState
{
  Name = "Confirmed",
  DisplayName = "Confirmed",
  FinalState = true
};

var customWorkflow = new Sitecore.Commerce.Plugin.Workflow.Workflow
{
  Id = $"{CommerceEntity.IdPrefix<Sitecore.Commerce.Plugin.Workflow.Workflow>()}.CustomWorkflow",
  FriendlyId = "CustomCommerceWorkflow",
  Name = "CustomCommerceWorkflow",
  DisplayName = "Custom Commerce Workflow",
  States = new List<WorkflowState>
  {
    draftState,
    awaitingApprovalState,
    awaitingConfirmationState,
    confirmedState
  },
  Components = new List<Component>()
  {
    new ListMembershipsComponent()
    {
      Memberships = new List<string>()
      {
        CommerceEntity.ListName<Sitecore.Commerce.Plugin.Workflow.Workflow>()
      }
    }
  }
};

You can see that we’ve created each of the 4 different workflow state models each one containing its required commands, we then create a Workflow entity and add each of these to its states collection, then finally we add the Workflow entity to the Workflow managed list. You could drop this code into an initialize block and use the PersistEntityPipeline to store the entity after you’ve created it, then when your environment is initialized this workflow will be created and ready to be assigned to your types.

So let’s go back to the example from the beginning of the post where we were happy for the default single step workflow to be used for changes to Sellable Items, but we want changes to the catalog structure to require an extra level of approval. The last thing we need to do to achieve this is to amend the Workflow Policy entries in the PlugIn.Workflow.PolicySet-1.0.0.json configuration file to reference the correct workflow definitions:

"Policies": {
  "$type": "System.Collections.Generic.List`1[[Sitecore.Commerce.Core.Policy, Sitecore.Commerce.Core]], mscorlib",
  "$values": [
  {
    "$type": "Sitecore.Commerce.Plugin.Workflow.WorkflowPolicy, Sitecore.Commerce.Plugin.Workflow",
    "TypeFullName": "Sitecore.Commerce.Plugin.Catalog.Catalog",
    "WorkflowId": "Entity-Workflow-CustomCommerceWorkflow"
  },
  {
    "$type": "Sitecore.Commerce.Plugin.Workflow.WorkflowPolicy, Sitecore.Commerce.Plugin.Workflow",
    "TypeFullName": "Sitecore.Commerce.Plugin.Catalog.Category",
    "WorkflowId": "Entity-Workflow-CustomCommerceWorkflow"
  },
  {
    "$type": "Sitecore.Commerce.Plugin.Workflow.WorkflowPolicy, Sitecore.Commerce.Plugin.Workflow",
    "TypeFullName": "Sitecore.Commerce.Plugin.Catalog.SellableItem",
    "WorkflowId": "Entity-Workflow-DefaultCommerceWorkflow"
  }
 ]
}

So as simple as that we would now have two different Workflow patterns setup in the system and being used for different entities, forcing different levels of approval to be required for different changes that the merchandisers try to make.