How to create custom workflow in standard form-Dynamics 365 F&O

Custom Workflow in standard form


Problem statement

Requirement is to create custom workflow in “All assets” (standard form) which is available in the Asset management module. Workflow should trigger when it is in draft state & Active field set as yes.


Resolution

  • Create custom workflow in standard form
  • Create enum
  • Create new field in table
  • Write code for canSubmitWorklflow() method
  • Write code for UpdateWorkflow()
  • Create query
  • Create workflow category
  • Create workflow type
  • Write logic in submit manager
  • Create workflow approval
  • Write logic in approval event handler
  • Enabling the workflow in form
  • Configuration of workflow

1. Detailed step by step explanation of the resolution

Workflows are designed to streamline business processes while giving clear instructions for document flows through the system by showing the steps needed to process and approve it along with who needs to process it. 

This Blog will explain every step needed to create your custom workflow in Standard form in Dynamics 365 F&O

2. Workflow status Enumeration:

To define the status of the workflow, we need to use the base enumeration which contains basic statuses such as draft, submitted, change request, rejected, and approved. Provide proper labels for each element.



Create new field on the table

Create extension of Asset table (standard), add new field using enumeration and make the field as non-editable.


Table method

Usually we need to override the canSubmitWorkflow() method in custom table. Since we are creating workflow in standard form, we need to write COC in table level. Create a class using the Asset object table. Write the below code which shows the condition when workflow status in draft state and active should be yes then Boolean true means workflow can trigger else it will not trigger


[ExtensionOf(tableStr(EntAssetObjectTable))]

final class SCCEntAssetObjectTable_Extension

{

    public boolean canSubmitToWorkflow(str _workflowType)

    {

        boolean ret = next canSubmitToWorkflow(_workflowType);

        if(this.SCCAssetObjectWFStatus == SCCAssetObjectWFStatus::Draft

            && this.ObjectActive == NoYes::Yes)

        {

            ret = boolean::true;

        }

        else

        {

            ret = boolean::false;

        }

        return ret;

    }

}
        

We should add a method for updating the workflow status using a record identifier. Use below code in same COC “SCCEntAssetObjectTable_Extension”


public static void updateWorkflowStatus(RecId _entAssetObjectTableRecId, SCCAssetObjectWFStatus _status)

    {

        ttsbegin;

        EntAssetObjectTable entAssetObjectTable;

        update_recordset    entAssetObjectTable

        setting             SCCAssetObjectWFStatus       = _status

        where               entAssetObjectTable.RecId   == _entAssetObjectTableRecId;

        ttscommit;

    }
        

Query

Create new query for Asset table. It defines the tables and field that the workflow will use. For this example, we will define a query that returns all fields of our Asset table. Here I used standard query “EntAssetObjectTableQuery” Set Dynamics queries as yes


Workflow Category

We need to create workflow category to setting up the module property, it determines which module the workflow should appear. In this example, the category will use Asset management module. So obviously it’s necessary to extend this Menu (Asset management) in our project Click on Add à New Item à Business Process and Workflow à Workflow Category àEnter a proper name.


Set the module as “Asset management”


Workflow Type

The Workflow type describes that all the elements available in the workflow. While creating it, we need to fill the category, query & display menu item what we created. To create workflow type, Click on Add > New Item > Business Process and Workflow > Workflow Type


Category: Select the workflow category which we created 

Query: Select the query created for the Asset table

Document menu item: Select the menu item for the Asset table’s form


After completing the wizard, Visual Studio creates objects in our project.


Open Submit Manager class and write the below code. Workflow calls this class when submitting a document to workflow. We can use this class for submission and resubmission of the workflow.


///The SCCAssetObjectWFTypeSubmitManager menu item action event handler.

public class SCCAssetObjectWFTypeSubmitManager 

{

    private EntAssetObjectTable     entAssetObjectTable;

    private WorkflowVersionTable    versionTable;

    private WorkflowComment         comment;

    private WorkflowWorkItemTable   workItem;

    private SysUserId               userId;

    private boolean                 isSubmission;

    private WorkflowTypeName        workflowType;

    /// Main method

    /// calling arguments

    public static void main(Args _args)

    {

        if (_args.record().TableId != tableNum(EntAssetObjectTable))

        {

            throw error(‘Error attempting to submit document’);

        }

        EntAssetObjectTable entAssetObjectTable = _args.record();

        FormRun             caller              = _args.caller() as FormRun;

        boolean             isSubmission        = _args.parmEnum();

        MenuItemName        menuItem            = _args.menuItemName();

        FormDataSource      fds                 = caller.dataSource();

        SCCAssetObjectWFTypeSubmitManager manager = SCCAssetObjectWFTypeSubmitManager::construct();

        manager.init(entAssetObjectTable, isSubmission, caller.getActiveWorkflowConfiguration(), caller.getActiveWorkflowWorkItem());

        if (manager.openSubmitDialog(menuItem))

        {

            manager.performSubmit(menuItem);

        }

        caller.updateWorkflowControls();

        fds.research(true);

    }

    ///Construct method

    /// new instance of submission manager

    public static SCCAssetObjectWFTypeSubmitManager construct()

    {

        return new SCCAssetObjectWFTypeSubmitManager();

    }

    /// parameter method for document

    /// new document value

    /// current document

    public EntAssetObjectTable parmDocument(EntAssetObjectTable _entAssetObjectTable = entAssetObjectTable)

    {

        entAssetObjectTable = _entAssetObjectTable;

        return entAssetObjectTable;

    }

    /// parameter method for version

    /// new version table value

    /// current version table

    public WorkflowVersionTable parmVersionTable(WorkflowVersionTable _versionTable = versionTable)

    {

        versionTable = _versionTable;

        return versionTable;

    }

    /// parameter method for comment

    /// new comment value

    /// current comment value

    public WorkflowComment parmComment(WorkflowComment _comment = comment)

    {

        comment = _comment;

        return comment;

    }

    /// parameter method for work item

    /// new work item value

    /// current work item value

    public WorkflowWorkItemTable parmWorkItem(WorkflowWorkItemTable _workItem = workItem)

    {

        workItem = _workItem;

        return workItem;

    }

    /// parameter method for user

    /// new user value

    /// current user value

    public SysUserId parmUserId(SysUserId _userId = userId)

    {

        userId = _userId;

        return userId;

    }

    /// parameter method for isSubmission flag

    /// flag value

    /// current flag value

    public boolean parmIsSubmission(boolean _isSubmission = isSubmission)

    {

        isSubmission = _isSubmission;

        return isSubmission;

    }

    /// parameter method for workflow type

    /// new workflow type value

    /// current workflow type

    public WorkflowTypeName parmWorkflowType(WorkflowTypeName _workflowType = workflowType)

    {

        workflowType = _workflowType;

        return workflowType;

    }

    /// Opens the submit dialog and returns result

    /// true if dialog closed okay

    protected boolean openSubmitDialog(MenuItemName _menuItemName)

    {

        if (isSubmission)

        {

            return this.openSubmitDialogSubmit();

        }

        else

        {

            return this.openSubmitDialogResubmit(_menuItemName);

        }

    }

    /// Open submission dialog

    /// true if dialog closed okay

    private boolean openSubmitDialogSubmit()

    {

        WorkflowSubmitDialog submitDialog = WorkflowSubmitDialog::construct(this.parmVersionTable());

        submitDialog.run();

        this.parmComment(submitDialog.parmWorkflowComment());

        return submitDialog.parmIsClosedOK();

    }

    /// Open resubmit dialog

    /// true if dialog closed okay

    private boolean openSubmitDialogResubmit(MenuItemName _menuItemName)

    {

        WorkflowWorkItemActionDialog actionDialog = WorkflowWorkItemActionDialog::construct(workItem, WorkflowWorkItemActionType::Resubmit, new MenuFunction(_menuItemName, MenuItemType::Action));

        actionDialog.run();

        this.parmComment(actionDialog.parmWorkflowComment());

        this.parmUserId(actionDialog.parmTargetUser());

        return actionDialog.parmIsClosedOK();

    }

    /// initializes manager

    /// document

    /// calling menu item

    /// workflow version

    /// workflow item

    protected void init(EntAssetObjectTable _entAssetObjectTable, boolean _isSubmission, WorkflowVersionTable _versionTable, WorkflowWorkitemTable _workItem)

    {

        this.parmDocument(_entAssetObjectTable);

        this.parmIsSubmission(_isSubmission);

        this.parmVersionTable(_versionTable);

        this.parmWorkItem(_workItem);

        this.parmWorkflowType(this.parmVersionTable().WorkflowTable().TemplateName);

    }

    /// perform workflow submission

    protected void performSubmit(MenuItemName _menuItemName)

    {

        if (isSubmission)

        {

            this.performSubmitSubmit();

        }

        else

        {

            this.performSubmitResubmit(_menuItemName);

        }

    }

    /// perform workflow submit

    private void performSubmitSubmit()

    {

        if (this.parmWorkflowType() && EntAssetObjectTable::findRecId(entAssetObjectTable.RecId).SCCAssetObjectWFStatus == SCCAssetObjectWFStatus::Draft)

        {

            Workflow::activateFromWorkflowType(workflowType, entAssetObjectTable.RecId, comment, NoYes::No);

            EntAssetObjectTable::updateWorkflowStatus(entAssetObjectTable.RecId, SCCAssetObjectWFStatus::Submit);

        }

    }

    /// perform workflow resubmit

    private void performSubmitResubmit(MenuItemName _menuItemName)

    {

        if (this.parmWorkItem())

        {

            WorkflowWorkItemActionManager::dispatchWorkItemAction(workItem, comment, userId, WorkflowWorkItemActionType::Resubmit, _menuItemName);

            EntAssetObjectTable::updateWorkflowStatus(entAssetObjectTable.RecId, SCCAssetObjectWFStatus::Submit);

        }

    }

}
        

Write below code in EventHandler class: This class handles the type-specific events. The wizard creates methods to which we can add our business logic.


       /// The SCCAssetObjectWFTypeEventHandler workflow event handler.

public class  SCCAssetObjectWFTypeEventHandler implements WorkflowCanceledEventHandler,  

WorkflowCompletedEventHandler,

WorkflowStartedEventHandler

{

    public void started(WorkflowEventArgs _workflowEventArgs)

    {

        RecId entAssetObjectTableRecId = _workflowEventArgs.parmWorkflowContext().parmRecId();

        EntAssetObjectTable::updateWorkflowStatus(entAssetObjectTableRecId, SCCAssetObjectWFStatus::Submit);

    }

    public void canceled(WorkflowEventArgs _workflowEventArgs)

    {

        RecId entAssetObjectTableRecId = _workflowEventArgs.parmWorkflowContext().parmRecId();

        EntAssetObjectTable::updateWorkflowStatus(entAssetObjectTableRecId, SCCAssetObjectWFStatus::Rejected);

    }

    public void completed(WorkflowEventArgs _workflowEventArgs)

    {

        RecId entAssetObjectTableRecId = _workflowEventArgs.parmWorkflowContext().parmRecId();

        EntAssetObjectTable::updateWorkflowStatus(entAssetObjectTableRecId, SCCAssetObjectWFStatus::Approved);

    }

}
        

Fill the properties in submit action menu item, set the proper label and needs to have a value of NoYes::Yes in the enum value parameter. Also, we need to check the target of the SubmitMenuItem action menu item to call this class. Do compile the objects created till now.


Workflow Approval

After the build, create Workflow approval where approval is a workflow element that we can add to a workflow. In this case, an approval is a step where one or more people need to approve something. To create a workflow approval, simply click on Add > New item > Business process and workflow > Workflow Approval


Provide workflow document & document menu item. Field group is optional


Open the action menu items and set the labels, for resubmit menu item set the parameter NoYes::No


For this example, we will be adding business logic. Open approval event handler and use the below code. If any business logic needs to add at the time of approval, we can use this event handler to achieve it. There are methods available for every outcome like Reject, Cancel, Approve based on the requirement we can add the logic inside the respective methods.


/// The SCCAssetObjectWFApprovalEventHandler workflow outcome event handler.

public final class SCCAssetObjectWFApprovalEventHandler implements WorkflowElementCanceledEventHandler,

WorkflowElemChangeRequestedEventHandler,

WorkflowElementCompletedEventHandler,

WorkflowElementReturnedEventHandler,

WorkflowElementStartedEventHandler,

WorkflowElementDeniedEventHandler,

WorkflowWorkItemsCreatedEventHandler

{

    public void started(WorkflowElementEventArgs _workflowElementEventArgs)

    {

        RecId entAssetObjectTableRecId = _workflowElementEventArgs.parmWorkflowContext().parmRecId();

    }

    public void canceled(WorkflowElementEventArgs _workflowElementEventArgs)

    {

        RecId entAssetObjectTableRecId = _workflowElementEventArgs.parmWorkflowContext().parmRecId();

        EntAssetObjectTable::updateWorkflowStatus(entAssetObjectTableRecId, SCCAssetObjectWFStatus::Rejected);

    }

    public void completed(WorkflowElementEventArgs _workflowElementEventArgs)

    {

        RecId entAssetObjectTableRecId = _workflowElementEventArgs.parmWorkflowContext().parmRecId();

        EntAssetObjectTable::updateWorkflowStatus(entAssetObjectTableRecId, SCCAssetObjectWFStatus::Approved);

    }

    public void denied(WorkflowElementEventArgs _workflowElementEventArgs)

    {

        RecId entAssetObjectTableRecId = _workflowElementEventArgs.parmWorkflowContext().parmRecId();

        EntAssetObjectTable::updateWorkflowStatus(entAssetObjectTableRecId, SCCAssetObjectWFStatus::Rejected);

    }

    public void changeRequested(WorkflowElementEventArgs _workflowElementEventArgs)

    {

        RecId entAssetObjectTableRecId = _workflowElementEventArgs.parmWorkflowContext().parmRecId();

        EntAssetObjectTable::updateWorkflowStatus(entAssetObjectTableRecId, SCCAssetObjectWFStatus::ChangeRequest);

    }

    public void returned(WorkflowElementEventArgs _workflowElementEventArgs)

    {

        // TODO:  Write code to execute once the workflow is returned.

    }

    public void created(WorkflowWorkItemsEventArgs _workflowWorkItemsEventArgs)

    {

        // TODO:  Write code to execute once work items are created.

    }

}
        

Next step, we need to add the workflow approval to workflow type. In workflow type, add new workflow element reference under Supported elements node. Provide workflow approval name under properties.


Enabling the workflow

Finally, we need to enable the workflow in the form. Create extension of Asset object table form. On the details node of the Asset object table’s form, change the Workflow data source, Workflow enabled, and Workflow type properties.


Configuration of workflow

After the build, open the UI through Microsoft edge since internet explorer will not support to get the workflow editor. Go to your module à setups à workflow for respected modules available. Here we customize the Asset workflow menu item which is helpful to configure the workflow


In the configuration form, we click the new button and select our workflow type from the list.



When asked, we enter our credentials and the workflow editor will open.


In the workflow editor, we add the workflow approval to the workflow (drag & drop). Next, we connect the start node to the approval and the approval to the end node.


Double click the workflow approval, click basic setting, fill subject, instructions then assign the user whom you want to approve the workflow. Finally, we resolve the warnings and click Save and close. When asked, activate the workflow.


Output

Workflow enabled in All asset form, once record is created and active set to Yes



/* H1 style */ h1 { font-size: 24px; /* Adjust the size as needed */ font-weight: normal; /* You can use 'bold', 'lighter', 'bolder', or a number */ } /* H2 style */ h2 { font-size: 20px; /* Adjust the size as needed */ font-weight: normal; /* Adjust the weight as needed */ } /* H3 style */ h3 { font-size: 18px; /* Adjust the size as needed */ font-weight: normal; /* Adjust the weight as needed */ }