Wednesday, September 26, 2018

Workflow - Bulk Action processing on workflow in Microsoft Dynamics 365

Problem Description:

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. 


1 d365 Bulk Action Workflow - Project Description - learningd365.blogspot.com

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.


1 d365 Bulk Action Workflow - Workflow Action Status - learningd365.blogspot.com

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.





d365 Bulk Action Workflow - workflow assigned action menuitem approve detail - learningd365.blogspot.com



d365 Bulk Action Workflow - workflow assigned action menuitem Reject detail - learningd365.blogspot.com



d365 Bulk Action Workflow - workflow assigned action menuitem Request Change detail - learningd365.blogspot.com

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

Workflow Work list form extension: 

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.



d365 Bulk Action Workflow - workflowWorkList form extension - learningd365.blogspot.com

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();

    }

}



Output:

Bulk Action Approval







Bulk Action Reject:







Bulk Action Change Request:







4 comments:

  1. Replies
    1. Microsoft Dynamics 365 - The Definitive Guide: Workflow - Bulk Action Processing On Workflow In Microsoft Dynamics 365 >>>>> Download Now

      >>>>> 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

      Delete
  2. Microsoft Dynamics 365 - The Definitive Guide: Workflow - Bulk Action Processing On Workflow In Microsoft Dynamics 365 >>>>> Download Now

    >>>>> 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

    ReplyDelete

Workflow - Bulk Action processing on workflow in Microsoft Dynamics 365

Problem Description: Some organization required to have ( approve/reject/request change ) documents/Vouchers through workflow in a bul...