To Define a Composite State in a Workflow

Using Workflow API, you can define a composite state in a workflow.

About Composite States

A composite state, which can also be referred to as a parent state, is a state that contains an ordered sequence of nested states and the transitions to and from these states. With composite states, you can specify common settings (actions, fields to be updated, and transitions) for a group of states only once—in the composite state that includes these states.

For each of the states inside the composite state, you can do the following:

  • Specify whether this state should be skipped by using a skip condition
  • Specify a transition to the next state inside the composite state instead of a transition to a specific state
  • Specify a transition to the state that is the next one after the composite state if this composite state is itself a nested state in another composite state
  • Override the settings inherited from the composite state, if needed

These capabilities make workflow customization much easier. You do not need to explicitly specify target states for transitions. Thus, if you add or remove states in the workflow or change the order of states, you do not need to modify all the affected transitions.

A record created on a form cannot be in a composite state; it can only be in one of its nested states. When a record enters any nested state in a composite state, the system checks the skip condition specified for this state, if one has been defined. If the condition is fulfilled, the system does the following for the current state:

  1. Assigns the default values for the fields as specified on the State Properties tab of the Workflow (Tree View) page for the form if the current state is the initial state of the workflow
  2. Does not check the fields that should be updated when the record enters the state and leaves it
  3. Does not check any of the workflow settings, and moves the record to the next state inside the composite state

If no skip condition is specified, the system uses the typical workflow for this state. This means that the transitions are triggered only by actions or event handlers, and the system does not check the skip condition again while the record remains in this state.

Implementing a Composite State

You add a composite state inside the AddFlowStates method by using the AddSequence method. As the type parameter of the method, specify the name of the composite state. In the lambda expression provided for the AddSequence method, you can add states that are a part of the composite state using the WithStates method. The states in the composite states are declared the same way as they are declared in the AddFlowStates method.

To specify that a state should be skipped in the composite state, call the IsSkippedWhen method in the state definition. The condition depending on which the state should be skipped is specified in the parameter of the IsSkippedWhen method.

Example of a composite state declaration is shown in the following code.

.WithFlowStates(fss =>
{
  fss.Add(initialState, flowState => flowState.IsInitial(g => g.initializeState));
  fss.AddSequence<State.HoldToBalance>(seq =>
    seq.WithStates(sss =>
    {
      sss.Add<State.hold>(flowState =>
      {
        return flowState
          .IsSkippedWhen(conditions.IsNotOnHold)
          .WithActions(actions =>
          {
            ...
          });
        });
      sss.Add<State.creditHold>(flowState =>
      {
        return flowState
          .IsSkippedWhen(conditions.IsCreditHoldChecked)
          .WithActions(actions =>
          {
            ...
          });
      });
      sss.Add<State.balanced>(flowState =>
      {
        return flowState
          .WithActions(actions =>
          {
            ...
          });
        });
    }));
}

In the code above, the HoldToBalance composite state is declared. The composite state includes the hold, creditHold, and balanced states. The hold state is skipped when the IsNotOnHold condition is true. The creditHold state is skipped when the IsCreditHoldChecked condition is true. The balanced state is never skipped.

Declaring a Transition for a Composite State

You can define the following transitions involving a composite state:

  • A transition from any state of a workflow to the composite state
  • A transition from a composite state to any state of a workflow

    All such transitions are inherited by states of a composite state.

  • A transitions from a specific state of the composite state to any state of the workflow
  • A transition from a composite state to itself so that the workflow engine can search for a proper state and check conditions again

    To define such transition, in the Add method for a transition, specify the target state that is the same as the source state.

  • A transition from one state of a composite state to the state defined after it.

    To define such transition, in the Add method for a transition, call the ToNext method.

  • If a composite state is defined inside another composite state, you can define a transition from a state in a child composite state to the next state in the parent composite state. To define such state, in the Add method for a transition, call the ToParentNext method.
Important:
A composite state cannot be an initial state of the workflow. You must specify a transition from an initial state to the composite state.

Example of the transitions including a composite state are shown in the following code.

.WithTransitions(transitions =>
{
  transitions.AddGroupFrom(initialState, ts =>
  {
    ts.Add(t => t.To<State.HoldToBalance>()
      .IsTriggeredOn(g => g.initializeState)); // To composite state
  });
  transitions.AddGroupFrom<State.HoldToBalance>(ts =>
  {
    ts.Add(t => t
      .To<State.HoldToBalance>()
      .IsTriggeredOn(g => g.OnUpdateStatus)
      .When(conditions.IsARInvoice));
    ts.Add(t => t
      .To<State.open>()
      .IsTriggeredOn(g => g.OnReleaseDocument)
      .When(conditions.IsOpen));
  }
}

In the code above, the following transitions are declared:

  • A transition from the initial state to the HoldToBalance composite state
  • A transition from the HoldToBalance composite state to itself
  • A transition from the HoldToBalance composite state

Updating an Existing Composite State

You can customize a composite state defined in source code of MYOB Acumatica. To update a composite state, use the UpdateSequence method in the WithFlowStates method. In the WithStates method of the UpdateSequence method, you can add, update, or delete a state by using Add, Update, or Delete methods respectively. To specify the location of state in the updated composite state, use the PlaceAfter method.

An example of an updated composite state is shown in the following code.

.WithFlowStates(states =>
{
  states.UpdateSequence<State.HoldToBalance>(seq =>
    seq.WithStates(sss =>
    {
      sss.Add<State.pendingApproval>(flowState =>
      {
        return flowState
          .IsSkippedWhen(conditions.IsApproved)
          .WithActions(actions =>
          {
            ...
          })
          .PlaceAfter<State.hold>();
      });
    }));
})
.WithTransitions(transitions =>
{
  transitions.AddGroupFrom<State.pendingApproval>(ts =>
  {
    ts.Add(t => t
      .To<State.HoldToBalance>()
      .IsTriggeredOn(g => g.OnUpdateStatus));
    ts.Add(t => t
      .ToNext()
      .IsTriggeredOn(aproveAction)
      .When(conditions.IsApproved));
    ts.Add(t => t
      .To<State.rejected>()
      .IsTriggeredOn(rejectAction)
      .When(conditions.IsRejected));
  });
});

In the code above, the pendingApproval state is added to the HoldToBalance composite state and placed after the hold state. Three transitions are added from the pendingApprovel state.