Some organization required to have (approve/reject/request change) documents/Vouchers through workflow in a bulk. Higher management wants to (approve/reject/request change) multiple voucher in once so we need to customize workflow approval process.
Problem Solution:
Before we get into the solution we need to create a model and a project. We can also extend Application Foundation Suit Model because most of the AOT object of workflow are present in this model.
Workflow Action Status:
Step 1 is to create a base enums which contains values as approve, reject and request change. This enum help us to distinguish between different types of action need to take during user input and also help us in finding workflow in AOT.
Work Assigned Action Menu Item:
Step two is to create action menuitem for individual bulk action, which hold the details of the action to be perform during process.
Note: In action menu item, set the following property as:
Enum Type Parameter: (Enter Enum name as create before in step1)
Enum Parameter: (Action need to perform) (e.g. Reject)
Object: (class name which is responsible for bulk action process) (we will create class in upcoming step).
Object Type: Class
Next step is to create a form extension of "WorkflowWorkList" form to add button group and button on action panel. From which user can perform respective action on it.
Note: In property of every button mark multiselect option to 'YES' for bulk action.
Before implementing the logic for Bulk action copying the event handler from all three buttons and write code as mentioned below.
create a event handler class and write the logic for calling the bulk action class. The function
bulkActionProcess is responsible for calling the bulk action class for all three buttons where
_workflowActionStatus, menuitemName create a difference for perform a specific action.
For data passing (i.e. comment, userid), I used contract class SLD_BulkWorkflowActionContract which is responsible to set and get the user specific input which then pass in args.parmObject as object.
WorkflowWorkList Form EventHandler:
/// <summary>
/// Event Handler class on form button on click method
/// </summary>
final class SLD_WorkflowWorkList_EventHanlder
{
/// <summary>
/// The Method is used to pass args in bulk action process class
/// </summary>
/// <param name = "_WorkflowWorkItemTable_ds">form data source</param>
/// <param name = "menuitemName">action menu item name</param>
/// <param name = "_workflowActionStatus">action workflow status</param>
public void bulkActionProcess(FormDataSource _WorkflowWorkItemTable_ds,
str menuitemName,
SLD_WorkflowActionStatus _workflowActionStatus)
{
WorkflowWorkItemTable workflowWorkItemTableget;
MenuFunction menuFunction;
args args = new Args();
MultiSelectionHelper helper = MultiSelectionHelper::construct();
//workflow dialog form
Dialog dialog;
DialogField userId,
comment;
//data contract class
SLD_BulkWorkflowActionContract bulkContract = new SLD_BulkWorkflowActionContract();
helper.parmDatasource(_WorkflowWorkItemTable_ds);
workflowWorkItemTableget = helper.getFirst();
dialog = new Dialog(strFmt("@SLD_Label:WorkFlow_ApproveAndRejectAll", _workflowActionStatus));
//for request change take input form user to enter userid for assignment.
if(_workflowActionStatus ==SLD_WorkflowActionStatus::RequestChange)
{
userId = dialog.addField(extendedTypeStr(UserId), "@ApplicationFoundation:SysUserManagementUserId");
}
comment = dialog.addField(extendedTypeStr(WorkflowComment), "@SYS35300");
dialog.run();
if(dialog.closedOk())
{
if(_workflowActionStatus == SLD_WorkflowActionStatus::RequestChange &&
userId.value() == null)
{
error("@SLD_Label:Workflow_UserIdValidation");
}
else
{
while (workflowWorkItemTableget.RecId != 0)
{
args.record(workflowWorkItemTableget);
//passing contract class object as param object in args
bulkContract.parmComment(comment.value());
if(_workflowActionStatus == SLD_WorkflowActionStatus::RequestChange)
{
bulkContract.parmUserId(userId.value());
}
else
{
bulkContract.parmUserId(CurUserId());
}
args.parmObject(bulkContract);
menuFunction = new menufunction(menuitemName, MenuItemType::Action);
menuFunction.run(args);
workflowWorkItemTableget = helper.getNext();
}
_WorkflowWorkItemTable_ds.research();
}
}
}
/// <summary>
/// The method is used to call bulk approval process by passing values and menufunction in action menuitem
/// </summary>
/// <param name = "sender">Form control name</param>
/// <param name = "e">Form control eveent argument</param>
[FormControlEventHandler(formControlStr(WorkflowWorkList, WorkflowAssignedActionStatusApprove), FormControlEventType::Clicked)]
public static void WorkflowAssignedActionStatusApprove_OnClicked(FormControl sender, FormControlEventArgs e)
{
FormDataSource WorkflowWorkItemTable_ds = sender.formRun().dataSource(FormDataSourceStr(WorkflowWorkList, WorkflowWorkItemTable));
SLD_WorkflowWorkList_EventHanlder workflowWorkList = new SLD_WorkflowWorkList_EventHanlder();
workflowWorkList.bulkActionProcess(WorkflowWorkItemTable_ds,
menuitemActionStr(SLD_WorkflowAssignedActionStatusApprove),
SLD_WorkflowActionStatus::Approve);
}
/// <summary>
/// The method is used to call bulk approval process by passing values and menufunction in action menuitem
/// </summary>
/// <param name = "sender">Form control name</param>
/// <param name = "e">Form control eveent argument</param>
[FormControlEventHandler(formControlStr(WorkflowWorkList, WorkflowAssignedActionStatusReject), FormControlEventType::Clicked)]
public static void WorkflowAssignedActionStatusReject_OnClicked(FormControl sender, FormControlEventArgs e)
{
FormDataSource WorkflowWorkItemTable_ds = sender.formRun().dataSource(FormDataSourceStr(WorkflowWorkList, WorkflowWorkItemTable));
SLD_WorkflowWorkList_EventHanlder workflowWorkList = new SLD_WorkflowWorkList_EventHanlder();
workflowWorkList.bulkActionProcess(WorkflowWorkItemTable_ds,
menuitemActionStr(SLD_WorkflowAssignedActionStatusReject),
SLD_WorkflowActionStatus::Reject);
}
/// <summary>
/// The method is used to call bulk approval process by passing values and menufunction in action menuitem
/// </summary>
/// <param name = "sender">Form control name</param>
/// <param name = "e">Form control eveent argument</param>
[FormControlEventHandler(formControlStr(WorkflowWorkList, WorkflowAssignedActionStatusRequestChange), FormControlEventType::Clicked)]
public static void WorkflowAssignedActionStatusRequestChange_OnClicked(FormControl sender, FormControlEventArgs e)
{
FormDataSource WorkflowWorkItemTable_ds = sender.formRun().dataSource(FormDataSourceStr(WorkflowWorkList, WorkflowWorkItemTable));
SLD_WorkflowWorkList_EventHanlder workflowWorkList = new SLD_WorkflowWorkList_EventHanlder();
workflowWorkList.bulkActionProcess(WorkflowWorkItemTable_ds,
menuitemActionStr(SLD_WorkflowAssignedActionStatusRC),
SLD_WorkflowActionStatus::RequestChange);
}
}
The contract class is used to pass comment and userId (if any) to bulk action process class.
BulkWorkFlowAction Contract Class:
/// <summary>
/// The class is used to save the comment made by user against bulk approval/reject and request changes
/// </summary>
[DataContractAttribute]
class SLD_BulkWorkflowActionContract
{
WorkflowComment comment;
UserId userId;
/// <summary>
/// Save user comment
/// </summary>
/// <param name = "_comment">pass comment as arg</param>
/// <returns>return save arg</returns>
[DataMemberAttribute]
public WorkflowComment parmComment(WorkflowComment _comment = comment)
{
comment = _comment;
return comment;
}
/// <summary>
/// Save user userId
/// </summary>
/// <param name = "_userId">pass userId as arg</param>
/// <returns>return save arg</returns>
[DataMemberAttribute]
public UserId parmUserId(UserId _userId = userId)
{
userId = _userId;
return userId;
}
}
WorkFlowAssignedActionStatus Class For Bulk Action Process:
/// <summary>
/// The class is used to perform related action against bulk workflow
/// </summary>
class SLD_WorkflowAssignedActionStatus extends RunBase
{
SLD_WorkflowActionStatus slWorkflowActionStatus;
WorkflowWorkItemTable workflowWorkItemTable;
WorkflowTrackingStatusTable workflowTrackingStatusTable;
WorkflowComment Comment;
UserId UserId;
#define.WorkflowAction('workflowaction')
#Properties
#AOT
#define.SLPropertyWorkflowApproveMenuItem('Approve')
#define.SLPropertyWorkflowRejectMenuItem('Reject')
#define.SLPropertyWorkflowRequestChangeMenuItem('RequestChange')
str path, workflowActionPath;
WorkflowTypeName workflowTypeName;
/// <summary>
/// Describes whether the class is designed for execution in a new session.
/// </summary>
/// <returns>
/// true if the class is designed for execution the operation in a new session; otherwise, false.
/// </returns>
protected boolean canRunInNewSession()
{
return true;
}
/// <summary>
/// perform workflow bulk action accodring to requirement
/// </summary>
/// <param name = "_workflowActionType">action type</param>
/// <param name = "_ret">validation flag</param>
/// <returns></returns>
private boolean bulkAction(WorkflowWorkItemActionType _workflowActionType,
boolean _ret)
{
TreeNode workflowTreeNode;
WorkflowTable workflowTable;
WorkflowWorkItemTable WorkflowWorkItemTableget;
this.getWorkflowstatusTable(workflowWorkItemTable);
select WorkflowWorkItemTableget
where WorkflowWorkItemTableget.UserId == CurUserId() &&
WorkflowWorkItemTableget.Type == WorkflowWorkItemType::WorkItem &&
WorkflowWorkItemTableget.Status == WorkflowWorkItemStatus::Pending &&
WorkflowWorkItemTableget.RecId == workflowWorkItemTable.RecId;
workflowTable = WorkflowTable::findRecId(WorkflowVersionTable::find(WorkflowWorkItemTableget.ConfigurationId).WorkflowTable);
workflowTypeName = workflowTable.TemplateName;
workflowTreeNode = this.getActionTreeNode(slWorkflowActionStatus);
if(workflowTreeNode !=null)
{
WorkflowWorkItemActionManager::dispatchWorkItemAction(WorkflowWorkItemTableget,
Comment,
UserId,
_workflowActionType,
findProperty(workflowTreeNode.AOTgetProperties(), #PropertyActionMenuItem));
}
return _ret;
}
/// <summary>
/// find the path of workflow exist
/// </summary>
/// <param name = "_workflowActionStatus">work flow status</param>
/// <returns>workflow action path</returns>
private TreeNode getActionTreeNode(SLD_WorkflowActionStatus _workflowActionStatus)
{
TreeNode currentNode;
TreeNode workflowTreeNode;
TreeNodeIterator treeNodeIterator;
int counter;
path = #WorkflowTypesPath + #AOTDelimiter + workflowTypeName + #AOTDelimiter + #WorkflowTypeElementsPath;
treeNodeIterator = TreeNode::findNode(path).AOTiterator();
counter = TreeNode::findNode(path).AOTchildNodeCount();
while (counter)
{
currentNode = treeNodeIterator.next();
workflowActionPath = #WorkflowApprovalTasksPath + #AOTDelimiter + currentNode.treeNodeName() + #AOTDelimiter + #WorkflowOutcomesPath + #AOTDelimiter;
workflowTreeNode = TreeNode::findNode(workflowActionPath + enum2str(_workflowActionStatus));
if (workflowTreeNode != null)
{
break;
}
counter--;
}
return workflowTreeNode;
}
/// <summary>
/// fetch workflow tracking status table
/// </summary>
/// <param name = "_workflowWorkItemTable"> workflow item table</param>
public void getWorkflowstatusTable(WorkflowWorkItemTable _workflowWorkItemTable)
{
if(_workflowWorkItemTable)
{
select workflowTrackingStatusTable
order by workflowTrackingStatusTable.createdDateTime desc, workflowTrackingStatusTable.TrackingStatus asc
where workflowTrackingStatusTable.ContextRecId == _workflowWorkItemTable.RefRecId &&
workflowTrackingStatusTable.ContextTableId == _workflowWorkItemTable.RefTableId;
}
}
/// <summary>
/// intialize the class varaibles
/// </summary>
/// <param name = "_workflowWorkItemTable">table buffer</param>
/// <param name = "_workflowActionStatus">action status</param>
/// <param name = "_workflowContract">save args</param>
void new(WorkflowWorkItemTable _workflowWorkItemTable,
SLD_WorkflowActionStatus _workflowActionStatus,
SLD_BulkWorkflowActionContract _workflowContract)
{
super();
slWorkflowActionStatus = _workflowActionStatus;
workflowWorkItemTable = _workflowWorkItemTable;
Comment = _workflowContract.parmComment();
UserId = _workflowContract.parmUserId();
}
public container pack()
{
return connull();
}
/// <summary>
/// Contains the code that does the actual job of the class.
/// </summary>
void run()
{
WorkflowWorkItemTable workflowWorkItemTableget;
boolean ret = true;
try
{
ttsbegin;
workflowWorkItemTableget = workflowWorkItemTable::findRecId(workflowWorkItemTable.RecId, true);
switch (slWorkflowActionStatus)
{
case SLD_WorkflowActionStatus::Approve :
ret = this.bulkAction(WorkflowWorkItemActionType::Complete,
ret);
break;
case SLD_WorkflowActionStatus::Reject :
ret = this.bulkAction(WorkflowWorkItemActionType::Return,
ret);
break;
case SLD_WorkflowActionStatus::RequestChange:
ret = this.bulkAction(WorkflowWorkItemActionType::RequestChange,
ret);
break;
}
if (!ret)
{
throw Exception::Error;
}
ttscommit;
info(strfmt("%1 - workflow has been updated.", workflowTrackingStatusTable.Document));
}
catch
{
error(strfmt("%1 - workflow has not been updated due to error.", workflowTrackingStatusTable.Document));
}
}
public boolean unpack(container packedClass)
{
return true;
}
client server static ClassDescription description()
{
return "@SLD_Label:WorkFlow_ChangeAction";
}
client static void main(Args args)
{
SLD_WorkflowAssignedActionStatus WorkflowAssignedActionStatus;
FormDataSource WorkflowWorkItemTable_ds;
SLD_BulkWorkflowActionContract workflowContract = new SLD_BulkWorkflowActionContract();
if (!args)
{
throw error("@SYS30498");
}
if (args.record().tableId != tableNum(WorkflowWorkItemTable))
{
throw error("@SYS30498");
}
WorkflowWorkItemTable_ds = args.record().dataSource();
workflowContract = args.parmObject() as SLD_BulkWorkflowActionContract;
WorkflowAssignedActionStatus = new SLD_WorkflowAssignedActionStatus(args.record(), args.parmEnum(), workflowContract);
WorkflowAssignedActionStatus.run();
}
}
Very good explanation. Thank you for sharing.
ReplyDeleteMS Dynamics AX Online Training
Microsoft Dynamics 365 - The Definitive Guide: Workflow - Bulk Action Processing On Workflow In Microsoft Dynamics 365 >>>>> Download Now
Delete>>>>> Download Full
Microsoft Dynamics 365 - The Definitive Guide: Workflow - Bulk Action Processing On Workflow In Microsoft Dynamics 365 >>>>> Download LINK
>>>>> Download Now
Microsoft Dynamics 365 - The Definitive Guide: Workflow - Bulk Action Processing On Workflow In Microsoft Dynamics 365 >>>>> Download Full
>>>>> Download LINK 3S
Very good explanation. Thank you for sharing.
ReplyDeleteMS Dynamics Operations Trade & Logistics Online Training
Microsoft Dynamics 365 - The Definitive Guide: Workflow - Bulk Action Processing On Workflow In Microsoft Dynamics 365 >>>>> Download Now
ReplyDelete>>>>> Download Full
Microsoft Dynamics 365 - The Definitive Guide: Workflow - Bulk Action Processing On Workflow In Microsoft Dynamics 365 >>>>> Download LINK
>>>>> Download Now
Microsoft Dynamics 365 - The Definitive Guide: Workflow - Bulk Action Processing On Workflow In Microsoft Dynamics 365 >>>>> Download Full
>>>>> Download LINK