Skip to main content

Develop Custom Workflow:Counting Journals workflow

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 InventJournalCountMy_Extension
{
public boolean canSubmitToWorkflow(str workflowType)
{
boolean canSubmit = next canSubmitToWorkflow(workflowType);

if (this.RecId && this.DocumentWorkflowStateCounting == DocumentWorkflowStateCounting::NotSubmitted)
{
canSubmit = true;
}
else
{
canSubmit = false;
}

return canSubmit;
}

public static void updateWorkflow(RefRecId _RecId,DocumentWorkflowStateCounting _status)
{
InventJournalTable Ijt;
select forupdate * from Ijt where Ijt.RecId==_RecId;
ttsbegin;
Ijt.DocumentWorkflowStateCounting = _status;
Ijt.update();
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.
Note:-Resubmit action menu item and class will be available after creation of workflow approval(step 10)


Also make sure that you change submit action menu items enum property as follows


9.       Update workflow type EventHandler class
You can add following code to eventhandler 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)
   {   
   }

    // 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::approved);
    }

    // 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.
 RecId documentRecId = _workflowElementEventArgs.parmWorkflowContext().parmRecId();
        InventJournalTable::updateWorkflowStatus(documentRecId, CFS_InventoryCountingWorkflow::Rejected);
   }

    public void changeRequested(WorkflowElementEventArgs _workflowElementEventArgs)
   {
          // TODO:  Write code to execute once change is requested for the workflow.
        RecId documentRecId = _workflowElementEventArgs.parmWorkflowContext().parmRecId();
        InventJournalTable::updateWorkflowStatus(documentRecId, CFS_InventoryCountingWorkflow::ChangeRequest);
   }

    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

Comments

Popular posts from this blog

Import Database from UAT/Production to cloud-hosted or dev environment | D365 Finance and operations

Update: If following method is not working have a look at this  Changes in SQL Script to restore bacpac file for D365FO Many times to debug the Production environment issues we might need live data to address that issue in such case first of all we need to refresh the UAT/Sandbox database with the Production environments database and then afterward we need to export the UAT database to the Asset library from where we can get bacpac file for that UAT database which is then to be imported in your cloud-hosted or one-box environment. This blog and video demonstration will help you to restore your production or UAT database to a cloud-hosted environment. Step 1: Rename existing db  In this step, we need to rename the existing AxDB for safety purposes. Just go to the SSMS application and execute the following command:-   ALTER DATABASE [ip_ent_site] SET SINGLE_USER WITH ROLLBACK IMMEDIATE GO ALTER DATABASE [ip_ent_site] MODIFY NAME = [ip_ent_site_new] GO ALTER DATABASE [ip_ent...

Changes in SQL Script to restore bacpac file for D365FO

There are changes in SQL db import it needs additional parameters now  Connection Security Improvements in SqlPackage 161 | Microsoft Community Hub otherwise it will throw following error *** Changes to connection setting default values were incorporated in a recent release.  More information is available at https://aka.ms/dacfx-connection *** Error importing database:Could not import package. Unable to connect to target server 'localhost'. Please verify the connection information such as the server name, login credentials, and firewall rules for the target server. A connection was successfully established with the server, but then an error occurred during the login process. (provider: SSL Provider, error: 0 - The certificate chain was issued by an authority that is not trusted.) The certificate chain was issued by an authority that is not trusted. *** The settings for connection encryption or server certificate trust may lead to connection failure if the server is not properl...

How to disable particular Financial Dimension on Desired form

Sometimes we want to disable particular financial dimension for purchase requisition line or any other form. To achieve that we need to write code on OnInitialized event handler of the required form. please follow the steps to achieve this functionality. Go to desired form and make note of control that is used for financial dimension(in our case Purchtable form and DimensionEntryControlLine control name). Now go to desired form(Purchtable in our case) and select OnInitialized event handler as follows and paste it in your class. write following code in the event handler class and provide Name field value (dimension which should be disabled) as well as DimensionEntryControl name and build the project. code :- 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 class PurchTableEventHandler { /// <summary> /// /// </summary> /// <param name="sender"></param> /// <para...