In this blog, you will learn how to develop a custom workflow that is not present out of the box in D365 Finance. For this blog I’m creating a workflow for Counting Journal (Inventory management>>Journal Entries>>Item Counting>>Counting) because there is no such workflow for inventory management module. The followings are steps that are to be followed.
Steps:-
1. Create a base Enum for Document Status
2. Create a table extension and add Enum to table
3. Add document status field to form extension
4. Create query
5. Create workflow category
6. Create a workflow type
7. Now add can submit workflow and updateworkflow methods to tables class extension
8. Update submitmanager class
9. Update EventHandler class
10. Workflow approval creation
11. Add an element to workflow Type
12. Now add workflow setup form to the inventory management module
Now we are performing steps in detail
1. Create a base enum for Document Status
Make sure you have created a solution, project and assign the model to the project. Now add new item and select Base Enum. And Add Base Enum and name it as CFS_InventoryCountingWorkflow
2. Create a table extension and add enum to table
We are creating a workflow for inventory management so we need to create an extension of InventoryJournalTable and drag above created base enum to table which will create a new field of type Enum.
3. Add Document status field to form extension
Now we need to create an extension of form InventJournalCount and add newly created table field to forms grid by dragging it from data source to form grid control and assign label as approval status.
4. Create query
Now again add a new query to project and name it as CFS_InventoryCountingWorkflow and add the InventJournalTable and set properties as follows
5. Create workflow category
Now we are going to add Workflow category to project and name it as CFS_InventJournalCounting and set the properties as follows
6. Create a workflow type
After adding workflow category its time to add workflow type name it as CFS_InventoryJournalCounting and set its properties as follows
this will create new elements such as classes and action menu items for submit and all actions.
7. Now add can submit workflow and updateworkflow methods to tables
To add a method to the table we need to create a table class extension for that add a new class and name it as InventJournalTable_CFSExtension and need to add updateWorkflow and canSubmitWorkflow methods. You can use the following code
[Extensionof(tableStr(InventJournalTable))]
final class InventJournalTable_Extension
{
public boolean canSubmitToWorkflow(str _workflowType)
{
boolean ret = next cansubmitToWorkflow(_workflowType);
ret = this.CFS_InventoryCountingWorkflow == CFS_InventoryCountingWorkflow::Draft;
return ret;
}
public static void updateWorkflowStatus(RecId _documentRecId, CFS_InventoryCountingWorkflow _status)
{
ttsbegin;
InventJournalTable document;
update_recordset document
setting CFS_InventoryCountingWorkflow = _status
where document.RecId == _documentRecId;
ttscommit;
}
}
8. Update submitmanager class
Make sure you have the same name for submitting manager class or rename it as follows and following code to that
public class CFS_InventoryJournalCountingSubmitManager
{
private InventJournalTable document;
private WorkflowVersionTable versionTable;
private WorkflowComment comment;
private WorkflowWorkItemTable workItem;
private SysUserId userId;
private boolean isSubmission;
private WorkflowTypeName workflowType;
public static void main(Args args)
{
// TODO: Write code to execute once a work item is submitted.
if (args.record().TableId != tableNum(InventJournalTable))
{
throw error(‘Error attempting to submit document’);
}
InventJournalTable document = args.record();
FormRun caller = args.caller() as FormRun;
boolean isSubmission = args.parmEnum();
MenuItemName menuItem = args.menuItemName();
CFS_InventoryJournalCountingSubmitManager manager = CFS_InventoryJournalCountingSubmitManager::construct();
manager.init(document, isSubmission, caller.getActiveWorkflowConfiguration(), caller.getActiveWorkflowWorkItem());
if (manager.openSubmitDialog(menuItem))
{
manager.performSubmit(menuItem);
}
caller.updateWorkflowControls();
}
/// <summary>
/// Construct method
/// </summary>
/// <returns>new instance of submission manager</returns>
public static CFS_InventoryJournalCountingSubmitManager construct()
{
return new CFS_InventoryJournalCountingSubmitManager();
}
/// <summary>
/// parameter method for document
/// </summary>
/// <param name = “_document”>new document value</param>
/// <returns>current document</returns>
public Inventjournaltable parmDocument(Inventjournaltable _document = document)
{
document = _document;
return document;
}
/// <summary>
/// parameter method for version
/// </summary>
/// <param name = “_versionTable”>new version table value</param>
/// <returns>current version table</returns>
public WorkflowVersionTable parmVersionTable(WorkflowVersionTable _versionTable = versionTable)
{
versionTable = _versionTable;
return versionTable;
}
/// <summary>
/// parameter method for comment
/// </summary>
/// <param name = “_comment”>new comment value</param>
/// <returns>current comment value</returns>
public WorkflowComment parmComment(WorkflowComment _comment = comment)
{
comment = _comment;
return comment;
}
/// <summary>
/// parameter method for work item
/// </summary>
/// <param name = “_workItem”>new work item value</param>
/// <returns>current work item value</returns>
public WorkflowWorkItemTable parmWorkItem(WorkflowWorkItemTable _workItem = workItem)
{
workItem = _workItem;
return workItem;
}
/// <summary>
/// parameter method for user
/// </summary>
/// <param name = “_userId”>new user value</param>
/// <returns>current user value</returns>
public SysUserId parmUserId(SysUserId _userId = userId)
{
userId = _userId;
return userId;
}
/// <summary>
/// parameter method for isSubmission flag
/// </summary>
/// <param name = “_isSubmission”>flag value</param>
/// <returns>current flag value</returns>
public boolean parmIsSubmission(boolean _isSubmission = isSubmission)
{
isSubmission = _isSubmission;
return isSubmission;
}
/// <summary>
/// parameter method for workflow type
/// </summary>
/// <param name = “_workflowType”>new workflow type value</param>
/// <returns>current workflow type</returns>
public WorkflowTypeName parmWorkflowType(WorkflowTypeName _workflowType = workflowType)
{
workflowType = _workflowType;
return workflowType;
}
/// <summary>
/// Opens the submit dialog and returns result
/// </summary>
/// <returns>true if dialog closed okay</returns>
protected boolean openSubmitDialog(MenuItemName _menuItemName)
{
if (isSubmission)
{
return this.openSubmitDialogSubmit();
}
else
{
return this.openSubmitDialogResubmit(_menuItemName);
}
}
/// <summary>
/// Open submission dialog
/// </summary>
/// <returns>true if dialog closed okay</returns>
private boolean openSubmitDialogSubmit()
{
WorkflowSubmitDialog submitDialog = WorkflowSubmitDialog::construct(this.parmVersionTable());
submitDialog.run();
this.parmComment(submitDialog.parmWorkflowComment());
return submitDialog.parmIsClosedOK();
}
/// <summary>
/// Open resubmit dialog
/// </summary>
/// <returns>true if dialog closed okay</returns>
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();
}
/// <summary>
/// initializes manager
/// </summary>
/// <param name = “_document”>document</param>
/// <param name = “_menuItem”>calling menu item</param>
/// <param name = “_versionTable”>workflow version</param>
/// <param name = “_workItem”>workflow item</param>
protected void init(InventJournalTable _document, boolean _isSubmission, WorkflowVersionTable _versionTable, WorkflowWorkitemTable _workItem)
{
this.parmDocument(_document);
this.parmIsSubmission(_isSubmission);
this.parmVersionTable(_versionTable);
this.parmWorkItem(_workItem);
this.parmWorkflowType(this.parmVersionTable().WorkflowTable().TemplateName);
}
/// <summary>
/// perform workflow submission
/// </summary>
protected void performSubmit(MenuItemName _menuItemName)
{
if (isSubmission)
{
this.performSubmitSubmit();
}
else
{
this.performSubmitResubmit(_menuItemName);
}
}
/// <summary>
/// perform workflow submit
/// </summary>
private void performSubmitSubmit()
{
if (this.parmWorkflowType() && document.CFS_InventoryCountingWorkflow == CFS_InventoryCountingWorkflow::Draft)
{
Workflow::activateFromWorkflowType(workflowType, document.RecId, comment, NoYes::No);
InventJournalTable::updateWorkflowStatus(document.RecId, CFS_InventoryCountingWorkflow::Submitted);
}
}
/// <summary>
/// perform workflow resubmit
/// </summary>
private void performSubmitResubmit(MenuItemName _menuItemName)
{
if (this.parmWorkItem())
{
WorkflowWorkItemActionManager::dispatchWorkItemAction(workItem, comment, userId, WorkflowWorkItemActionType::Resubmit, _menuItemName);
InventJournalTable::updateWorkflowStatus(document.RecId, CFS_InventoryCountingWorkflow::Submitted);
}
}
}
Since we are adding code for resubmit here only we need to change the class property of resubmit action menu item as CFS_InventoryJournalCountingSubmitManager and you can also delete resubmit class for it since we are using submit manager class for resubmit action.
9. Update EventHandler class
You can add following code to event handler class and name it as CFS_InventoryJournalCountingEventHandler
public class CFS_InventoryJournalCountingEventHandler implements WorkflowCanceledEventHandler,
WorkflowCompletedEventHandler,
WorkflowStartedEventHandler
{
public void started(WorkflowEventArgs _workflowEventArgs)
{
RecId documentRecId = _workflowEventArgs.parmWorkflowContext().parmRecId();
InventJournalTable::updateWorkflowStatus(documentRecId, CFS_InventoryCountingWorkflow::Submitted);
// TODO: Write code to execute once the workflow is started.
}
public void canceled(WorkflowEventArgs _workflowEventArgs)
{
RecId documentRecId = _workflowEventArgs.parmWorkflowContext().parmRecId();
InventJournalTable::updateWorkflowStatus(documentRecId, CFS_InventoryCountingWorkflow::Rejected);
// TODO: Write code to execute once the workflow is canceled.
}
public void completed(WorkflowEventArgs _workflowEventArgs)
{
RecId documentRecId = _workflowEventArgs.parmWorkflowContext().parmRecId();
InventJournalTable::updateWorkflowStatus(documentRecId, CFS_InventoryCountingWorkflow::Approved);
// TODO: Write code to execute once the workflow is completed.
}
}
10. Workflow approval creation
Now add Workflow Approval element and name it as CFS_InventoryJournalCountingApproval and it will add new elements and set its properties as follows
Now we need to add a piece of code to approval event handlers and name element as CFS_InventoryJournalCountingApprovalEventHandler
public final class CFS_InventoryJournalCountingApprovalEventHandler implements WorkflowElementCanceledEventHandler,
WorkflowElemChangeRequestedEventHandler,
WorkflowElementCompletedEventHandler,
WorkflowElementReturnedEventHandler,
WorkflowElementStartedEventHandler,
WorkflowElementDeniedEventHandler,
WorkflowWorkItemsCreatedEventHandler
{
public void started(WorkflowElementEventArgs _workflowElementEventArgs)
{
// TODO: Write code to execute once the workflow is started.
}
public void canceled(WorkflowElementEventArgs _workflowElementEventArgs)
{
RecId documentRecId = _workflowElementEventArgs.parmWorkflowContext().parmRecId();
InventJournalTable::updateWorkflowStatus(documentRecId, CFS_InventoryCountingWorkflow::Rejected);
}
// TODO: Write code to execute once the workflow is canceled.
public void completed(WorkflowElementEventArgs _workflowElementEventArgs)
{
RecId documentRecId = _workflowElementEventArgs.parmWorkflowContext().parmRecId();
InventJournalTable::updateWorkflowStatus(documentRecId, CFS_InventoryCountingWorkflow::Rejected);
}
// TODO: Write code to execute once the workflow is completed.
public void denied(WorkflowElementEventArgs _workflowElementEventArgs)
{
// TODO: Write code to execute once the workflow is denied.
}
public void changeRequested(WorkflowElementEventArgs _workflowElementEventArgs)
{
// TODO: Write code to execute once change is requested for the workflow.
}
public void returned(WorkflowElementEventArgs _workflowElementEventArgs)
{
RecId documentRecId = _workflowElementEventArgs.parmWorkflowContext().parmRecId();
InventJournalTable::updateWorkflowStatus(documentRecId, CFS_InventoryCountingWorkflow::Rejected);
// 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.
}
}
11. Add an element to workflow Type
After creating workflow approval we need to attach that element to workflow types for that we will add a new element to workflow types and set its properties as follows
12. Now add workflow setup form to the inventory management module
Since there no workflow form for inventory management we need to perform certain steps that you can refer to from this
Link and you are all set with counting journal workflow same procedure can be used for other custom workflow development. You can also refer to my other blogs for workflow setup and issues