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