headerquery

 

PROBLEM STATEMENT:

This tutorial will tell you how to create a new custom line level workflow. A simple and easy guide to create a new timesheet approval workflow. By following this guide, the reader can implement a custom workflow on both header and line level in AX 2012 with ease.

I am developing line level workflow for a timesheet that are in the new custom module ‘Advanced payroll’.

OBJECT:

New created timesheet should be approved through workflow.

To develop a new workflow, following artifacts or objects need to be created / modified:

  • Workflow Categories
  • Workflow Templates
  • Workflow Query (Document)
  • Workflow Approvals and Tasks (Tasks are optional)
  • Enabling the workflows on the form
  • Workflow submission classes

 

STEPS:

  1. Create an AOT query for both line and header. Here, SISTimesheetTable is the main table and SISWorkerTimesheetDetail contains the lines.headerquery2
  2. Make sure that an enumeration field is added to both line and header tables that will keep track of the current workflow state. In my case, I have added a base-enum ‘SISTimesheetStatus’ on both line and header tables.3
  3. Override ‘canSubmitToWorkflow’ method on both the table (line and header). I wrote the following code on header.
    public boolean canSubmitToWorkflow(str _workflowType = '')
    {
        boolean ret;
        SISWorkerTimesheetDetail    sisTimesheetLine;
         ret = this.RecId != 0 && this.TimesheetStatus == SISTimesheetStatus::Draft;
    select firstonly RecId from sisTimesheetLine where sisTimesheetLine.TimesheetNumber == this.TimesheetNumber;
    
        if (!sisTimesheetLine)
        {
            ret = false;
        }
        return ret;
    }

    And on line table,

    public boolean canSubmitToWorkflow(str _workflowType = '')
    
    {
        boolean ret;
        ret = super(_workflowType);
    
        if (this.AXPSISTimesheetStatus == SISTimesheetStatus::Draft)
    
            return true;
    
        else
    
            return false;
    }
  4. Now, we have to write a method that actually updates the status of the workflow. This method will be called from event handlers that we will create later on. I create the following static method on both tables. This method simply updates the workflow status.
    public static void axpUpdateLineWorkflowState(RefRecId _timesheetRecId, SISTimesheetStatus _status)
    {
    SISWorkerTimesheetDetail sisTimesheetLine;
     sisTimesheetLine = SISWorkerTimesheetDetail::findByRecId(_timesheetRecId,true);
     ttsBegin;
     sisTimesheetLine.AXPSISTimesheetStatus = _status;
     sisTimesheetLine.update();
     ttsCommit;
    }
  5. Now as I mentioned, I am enabling workflow to a custom module ‘Advanced Payroll’, I have to add new element to the base-enum ‘ModuleAxapta’ and named it as the name of the module for which you are creating workflow.
  6. I have to create a new workflow category as well. Right click on node in AOT>Workflow>Workflow categories and click New Workflow Category and set its properties.1
  7. I have also created a menu item to access workflow list page for the new module.2
  8. Now we have to create new workflow type through wizard available in AX2012, Right click AOT>Workflow>Workflow Types and select Add-Ins>Workflow type wizard.3Specify Wizard values and by Clicking on next, wizard will create workflow event handlers class and menu items for you.45Update the code in for event handler class as:
    public void started(WorkflowEventArgs _workflowEventArgs)
    {
        if(_workflowEventArgs.parmWorkflowContext().parmTableId() == tableNum(SISTimesheetTable))
        {
            SISTimesheetTable::axpUpdateTimesheetWorkflowState(_workflowEventArgs.parmWorkflowContext().parmRecId(),SISTimesheetStatus::Submitted);
        }
        else if(_workflowEventArgs.parmWorkflowContext().parmTableId() == tableNum(SISWorkerTimesheetDetail))
        {
            SISWorkerTimesheetDetail::axpUpdateLineWorkflowState(_workflowEventArgs.parmWorkflowContext().parmRecId(),SISTimesheetStatus::Submitted);
        }
    }
    public void completed(WorkflowEventArgs _workflowEventArgs)
    {
        if(_workflowEventArgs.parmWorkflowContext().parmTableId() == tableNum(SISTimesheetTable))
        {
            SISTimesheetTable::axpUpdateTimesheetWorkflowState(_workflowEventArgs.parmWorkflowContext().parmRecId(),SISTimesheetStatus::Approved);
        }
        else if(_workflowEventArgs.parmWorkflowContext().parmTableId() == tableNum(SISWorkerTimesheetDetail))
        {
            SISWorkerTimesheetDetail::axpUpdateLineWorkflowState(_workflowEventArgs.parmWorkflowContext().parmRecId(),SISTimesheetStatus::Approved);
        }
    }
    public void returned(WorkflowElementEventArgs _workflowElementEventArgs)
    {
        if(_workflowElementEventArgs.parmWorkflowContext().parmTableId() == tableNum(SISTimesheetTable))
        {
            SISTimesheetTable::axpUpdateTimesheetWorkflowState(_workflowElementEventArgs.parmWorkflowContext().parmRecId(),SISTimesheetStatus::Rejected);
        }
        else if(_workflowElementEventArgs.parmWorkflowContext().parmTableId() == tableNum(SISWorkerTimesheetDetail))
        {
            SISWorkerTimesheetDetail::axpUpdateLineWorkflowState(_workflowElementEventArgs.parmWorkflowContext().parmRecId(),SISTimesheetStatus::Rejected);
        }
    }
    public void canceled(WorkflowEventArgs _workflowEventArgs)
    {
        if(_workflowEventArgs.parmWorkflowContext().parmTableId() == tableNum(SISTimesheetTable))
        {
            SISTimesheetTable::axpUpdateTimesheetWorkflowState(_workflowEventArgs.parmWorkflowContext().parmRecId(),SISTimesheetStatus::Draft);
        }
        else if(_workflowEventArgs.parmWorkflowContext().parmTableId() == tableNum(SISWorkerTimesheetDetail))
        {
            SISWorkerTimesheetDetail::axpUpdateLineWorkflowState(_workflowEventArgs.parmWorkflowContext().parmRecId(),SISTimesheetStatus::Draft);
        }
    }
  9. Now, create workflow approval using approval creation wizard in AX2012. Right click AOT>Workflow>Approvals and select Add-Ins>Approval wizard.5Specify Wizard values and by Clicking on next, wizard will create workflow element event handlers class and menu items for you.5I renamed the new event handler class to ‘AXPSISTimesheetElemEventHandler’. Update each event handler using previous code. For some reasons, I am not using ‘Deny’ event handler so I deleted its menu item as well.
  10. Add the following code to submit manager class.
    public static void main(Args args)
    {
        // Variable declaration.
        recId _recId = args.record().RecId;
        WorkflowCorrelationId _workflowCorrelationId;
        // Hardcoded type name
        workflowTypeName _workflowTypeName = workFlowTypeStr("AXPSISTimesheet");
        // Initial note is the information that users enter when they
        // submit the document for workflow.
        WorkflowComment _initialNote = "";
        WorkflowSubmitDialog workflowSubmitDialog;
    
    
        // Opens the submit to workflow dialog.
        workflowSubmitDialog = WorkflowSubmitDialog::construct(args.caller().getActiveWorkflowConfiguration());
        workflowSubmitDialog.run();
    
    
        if (workflowSubmitDialog.parmIsClosedOK())
        {
               _recId = args.record().RecId;
            // Get comments from the submit to workflow dialog.
            _initialNote = workflowSubmitDialog.parmWorkflowComment();
            try
            {
                ttsbegin;
                // Activate the workflow.
                _workflowCorrelationId = Workflow::activateFromWorkflowType(_workflowTypeName, _recId, _initialNote, NoYes::No);
                ttscommit;
                args.caller().updateWorkflowControls();
                args.caller().close();
            }
    
    
            catch(exception::Error)
            {
                info("Error on workflow activation.");
            }
        }
    }
  11. Add the following code to resubmit manager class.
    public static void main(Args args)
    {
        // Variable declaration.
        recId _recId = args.record().RecId;
        //WorkflowCorrelationId _workflowCorrelationId;
        WorkflowWorkItemActionManager workflowWorkItemActionManager = new WorkflowWorkItemActionManager();
        // Hardcoded type name
        workflowTypeName _workflowTypeName = workFlowTypeStr("AXPSISTimesheet");
        // Initial note is the information that users enter when they
        // submit the document for workflow.
        WorkflowComment _initialNote = "";
        //WorkflowSubmitDialog workflowSubmitDialog;
        SISWorkerTimesheetDetail    timeSheetDetail;
        SISTimesheetTable           timesheetTable;
    
    
               _recId = args.record().RecId;
            // Get comments from the submit to workflow dialog.
    
    
            try
            {
                if (args.record().TableId == tableNum(SISWorkerTimesheetDetail))
                {
                    ttsbegin;
                    timeSheetDetail = SISWorkerTimesheetDetail::findByRecId(_recId, true);
                    timeSheetDetail.AXPSISTimesheetStatus = SISTimesheetStatus::Submitted;
                    if(timeSheetDetail)
                    {
                        timeSheetDetail.doUpdate();
                    }
                    ttscommit;
                }
                else if (args.record().TableId == tableNum(SISTimesheetTable))
                {
                    ttsbegin;
                    timesheetTable = SISTimesheetTable::findRecId(_recId, true);
                    timesheetTable.TimesheetStatus = SISTimesheetStatus::Submitted;
                    if(timesheetTable)
                    {
                        timesheetTable.doUpdate();
                    }
                    ttscommit;
                }
                // Activate the workflow.
                workflowWorkItemActionManager.parmArgs(args);
                workflowWorkItemActionManager.parmCaller(args.caller());
                workflowWorkItemActionManager.run();
                args.caller().updateWorkflowControls();
                args.caller().close();
            }
    
    
            catch(exception::Error)
            {
                info("Error on workflow activation.");
            }
    }
  12. Repeat steps 8 and 9 for creating line level workflow type and approval element as well. As we already have made classes and menu items for event handlers and element event handler, we will reuse these classes and menu items for line workflow as well, so delete any additional event handler classes and menu items. Both private project will now have following items as shown below.5
  13. Drag and drop the header and line approval element to ‘Supported Element’ node of line and header types respectively.5
  14. Now we will make relation of line workflow with header. Right click on ‘Line Item Workflows’ node of header and click ‘New Line Item Workflow’. Make sure that all properties are set as below. Also add new line item workflow type in ‘Line Item Workflow Types’ node and specify the line level workflow type.5
  15. Make sure properties values are all set on both workflow types and approvals outcomes. 55
  16. To enable workflow on the form on which you want to use workflow, SISTimesheetDetail>Design, set properties as below. Make sure you set header level workflow type in WorkflowType property.5
  17. Additionally, I have to set assignment type to role based participant, i.e. project related participants. For that, I added line workflow type to AOT>Workflow>Providers>Participant Assignment>ProjWorkflowParticipantProvider. Also, I made some changes to related provider class as well.5
  18. Now generate incremental CIL and after that restart the AOS service. This will avoid any possible issues (if any).
  19. Now we will create a simple workflow. Click on new workflow and first select line type workflow. If your newly created workflows are not appearing on this form, you have to restart the AOS service.5
  20. Now configure workflow. Select participant in assignment type, and select the participant. I selected ‘Project Manager’. Activate the new version.5
  21. Now create a header type workflow. I configured it as is shown in screenshot below.55
  22. Furthermore, I you want to use a single form for updating header and lines workflow states, you have to make some changes in the form code as well. I am using ‘SISTimesheetDetail’ form for submitting and approving lines and header workflow. Create a new method on the form (code below).
    private boolean  formControlIsDataBound(Object  _formControl)
    {
        FormControlType formControlType = SysFormRun::controlType(classIdGet(_formControl));
        boolean         ret             = false;
        if (formControlType == FormControlType::CheckBox
        ||  formControlType == FormControlType::ComboBox
        ||  formControlType == FormControlType::ListBox
        ||  formControlType == FormControlType::RadioButton
        ||  formControlType == FormControlType::String
        ||  formControlType == FormControlType::StaticText
        ||  formControlType == FormControlType::Date
        ||  formControlType == FormControlType::Integer
        ||  formControlType == FormControlType::Real
        ||  formControlType == FormControlType::Time
        ||  formControlType == FormControlType::Image
        ||  formControlType == FormControlType::Grid
        ||  formControlType == FormControlType::TabPage)
        {
            ret = true;
        }
        return ret;
    }

    Override method ‘selectControl’ on the form (code below).

    public boolean selectControl(FormControl _control)
    {
        boolean ret;
        ret = super(_control);
    
    
        if (ret  &&  _control  &&  element.formControlIsDataBound(_control))
        {
            element.toggleWorkflow_ds(_control);
        }
        return ret;
    }

    Add new method which toggles the workflow between line and header when a respective control  came in focus.

    public void  toggleWorkflow_ds(Object  _formControl)
    {
        Object  parentControl;
    
    
        int     dataSourceId;
        int     oldWorkflow_ds;
        // The reference fields does not point to a datasource in the design,
        // but the reference group (parent control) does
        parentControl = _formControl.parentControl();
        if (parentControl  &&  parentControl is FormReferenceGroupControl)
        {
            dataSourceId = parentControl.dataSource();
        }
        else
        {
            dataSourceId = _formControl.dataSource();
        }
        oldWorkflow_ds = element.design().workflowDatasource();
    
    
        // Return if the form datasource isn't changed or it is empty
        if (!dataSourceId || dataSourceId == oldWorkflow_ds)
        {
            return;
        }
        // Distinguish between header and line-item workflow
        if (dataSourceId == SISTimesheetTable_ds.id())
        {
            // Return if the workflow datasource isn't changed
            if (oldWorkflow_ds == SISTimesheetTable_ds.id())
            {
                return;
            }
            element.design().workflowDatasource(SISTimesheetTable_ds.id());
            element.design().workflowType(workFlowTypeStr(AXPSISTimesheet));
            element.updateWorkflowControls();
        }
        else
        {
            // Return if the workflow datasource isn't changed
            if (oldWorkflow_ds == SISWorkertimesheetDetail_ds.id())
            {
                return;
            }
            // Only enable "line-items" when the header already has been submitted
            if (!element.canSubmitToWorkflow())
            {
                element.design().workflowDatasource(SISWorkerTimesheetDetail_ds.id());
                element.design().workflowType(workFlowTypeStr(AXPSISTimesheetLine));
                element.updateWorkflowControls();
            }
        }
    }
  23. You are done with development and configuration of workflow.

Leave a Reply

Recent Comments

    Archives

    Categories