X++

Improved TFS Version Control tools

All companies should be using some form of Version Control nowadays, as Microsoft is also pushing us in that direction with Dynamics 365 for Operations already. The frameworks in AX are mostly open, ready for improvement – and there is a lot of room for that. You can find below some of my improved TFS Version Control tools below. If you have additional tools or ideas, feel free to share it.

  1. XPO Import dialog

    A new Check out button has been added, so when trying to import an object from XPO that is already under VCS, you do not have to navigate to the AOT, but can directly access it from here.
  2. Version Control Pending objects

    The Version Control Changes > Contents list already had an Open new window button for the currently selected objects, but it was missing from the Pending objects list – which is more frequently used -, until now.
  3. Version Control Check-in dialog

    IDs are now sorted in a descending order to have the most recent tasks on the top.
    I am now hiding the TFS entries in a Closed state by default and can be shown by a checkbox.
    Developers are typically working on Tasks, which was opened from a User Story using the Agile methodology setup in VCS, and check-ins are done against Tasks, so now I am hiding User Stories by default.

Please find attached the Improved TFS Version Control tools.

By |2017-05-20T10:08:38+02:00May 20th, 2017|Categories: AX 2012, TFS|Tags: , , , , , , |0 Comments

Investigating Workflow security issues

Many of us might have encountered the below error messages when trying to run a newly created workflow. I would like to provide this neat troubleshooting tip with the below code snippet for investigating workflow security issues.

Stopped (error): X++ Exception: Work item could not be created. 
Insufficient rights for user %1.

In most of the cases it is due to the lack of security access for the execution account on a menu item, for which we could add a couple of lines of code to resolve the problem.

Change \AOT\Classes\SysWorfklowDocument\assertAsUser method and for each fork of menu item type you may add the entry point in a security privilege or role, for which you need to provide access for on the user account you have been using. That means for each section of workflowPermission you need to add the appropriate menu item in the error log.

    if (workflowPermission.parmDisplayMenuItem())
    {
        permission = SysWorkflowDocument::assertMenuItem(workflowPermission.parmDisplayMenuItem(), MenuItemType::Display);
        if (!permission)
        {
            error(strFmt('Entry point %1', workflowPermission.parmDisplayMenuItem()));
            return [permission, buf2Con(rec)]; // buf2Con() needed to support packing buffers participating in SC/sc hierarchies
        }
    }

Also you need to add the following pieces to \AOT\Classes\SysWorkflowEventDispatcher\onWorkItemCreate() method:

    if (workflowPermission.parmDisplayMenuItem())
    {
        permission = SysWorkflowDocument::assertMenuItem(workflowPermission.parmDisplayMenuItem(), MenuItemType::Display);
        if (!permission)
        {
            error(strFmt('Entry point %1', workflowPermission.parmDisplayMenuItem()));
            return [permission, buf2Con(rec)]; // buf2Con() needed to support packing buffers participating in SC/sc hierarchies
        }
    }

(...)
 
    if (!SysWorkflowHelper::userHasPermission(
        _workItemContext.parmWorkflowCorrelation().parmWorkflowContext(),
        stepTable.workflowElementTable().workflowVersionTable().workflowTable().TemplateName,
        sysDictWorkflowElement.actionMenuItem(completingOutcome),
        menuItemName,
        user))
    {
        error(strFmt('Entry point %1 / Menu item action %2', menuItemName, sysDictWorkflowElement.actionMenuItem(completingOutcome)));
        throw error(strfmt("@SYS109561", user));
    }

Using the above changes you will be able to set up the missing security pieces easily by taking a look at the workflow execution history, where the infolog in the tracking history will contain the entry points causing the issues.

External references for the same issue:

https://workflowax.wordpress.com/2012/05/02/x-exception-workitem-could-not-be-created/

http://axfaq.blogspot.hu/2014/08/workflow-ax-2012-insufficient-rights.html

By |2017-05-18T12:18:42+02:00May 18th, 2017|Categories: AX 2012|Tags: , , |0 Comments

Refresh AX grid color with displayOption

We have a displayOption implemented for the Vendor transactions screen, which highlights the rows in red color where the posted transactions or the journal has a Document attachment. The requirement was that if we create a new DocuRef entry on the vendor transaction, after closing the DocuView form the grid should change color. In order to refresh AX grid color with displayOption only for the currently selected row we can use the FormDataSource.clearDisplayOption method.

Also there is a great comment on the Dynamics Community about refreshing a set of entries, instead of the current cursor:

https://community.dynamics.com/ax/f/33/t/191245

We have edited \Forms\DocuView\Methods\close as per below to achieve refreshing only the currently highlighted row:

public void close()
{
    FormRun         frm = infolog.parmLastActivatedForm().object();
    FormDataSource  fds;
 
    DocuRef::multiSelectRecordDelete();
 
    SysHelp::initWebBrowser(htmlView);
    curUrl = "";
 
    super();
 
    if (infolog.docu() && infolog.docu().docuView() && infolog.docu().docuView().object())
    {
        infolog.docu().clearDocuView(); //ensure that form is removed from infolog object
    }
 
    if (frm
        && frm.form().name() == formStr(VendTrans))
    {
        fds = frm.dataSource(1);
        fds.clearDisplayOption(fds.cursor());
    }
}
By |2017-02-20T13:32:54+01:00February 20th, 2017|Categories: AX 2012|Tags: , , , , |0 Comments

Add AX FormGroupControl runtime

Apparently you cannot populate a form data group runtime in AX. I have been able to overcome the issue by adding the controls dynamically. In this example I have created a form with InventTable as the datasource, and added a FormGroupControl called FormDataGroup with AutoDeclare = Yes. Then I have overridden the executeQuery() on InventTable_ds to call my custom method stored on the form. Here is the source to add AX FormGroupControl runtime from code:

public void WIK_addFieldGroup()
{
    #AOT
    #Properties

    TreeNode            fieldGroupRoot = TreeNode::findNode(strFmt('%1\\%2\\Field Groups\\%3',
        #TablesPath,
        tableStr(InventTable),
        tableFieldgroupStr(InventTable, Name)));
    TreeNodeIterator    tni = fieldGroupRoot.AOTiterator();
    TreeNode            fieldGroupNode = tni ? tni.next() : null;
    int                 fieldNo;
    DictField           dictField;
    FormControlType     controlType;
    Object              formControl;
        
    formDataGroup.hideIfEmpty(false);
    formDataGroup.caption(fieldGroupRoot.AOTgetProperty(#PropertyLabel));
    
    while (fieldGroupNode)
    {
        fieldNo = (select firstOnly AxId from SysModelElement
            where  SysModelElement.ParentId     == tableNum(InventTable)
                && SysModelElement.ElementType  == UtilElementType::TableField
                && SysModelElement.Name         == fieldGroupNode.AOTname()).AxId;
        
        dictField = new DictField(tableNum(InventTable), fieldNo);
        
        if (!dictField)
        {
            // display method in field group
            fieldGroupNode = tni.next();
            continue;
        }
        
        switch (dictField.baseType())
        {
            case Types::Int64 :
                controlType = FormControlType::Int64;
                break;
            case Types::Integer :
                controlType = FormControlType::Integer;
                break;
            case Types::String :
                controlType = FormControlType::String;
                break;
            case Types::Date :
                controlType = FormControlType::Date;
                break;
            case Types::UtcDateTime :
                controlType = FormControlType::DateTime;
                break;
                
            //TODO: Implement all field types
        }

        formControl = formdatagroup.addControl(controlType, strFmt('InventTable_%1', fieldGroupNode.AOTname()));
        formControl.dataSource(InventTable_ds.id());
        formControl.dataField(fieldNo);
        formControl.label(dictField.label());

        fieldGroupNode = tni.next();
    }
}

 

By |2016-09-21T12:20:54+02:00September 21st, 2016|Categories: AX 2012|Tags: , , , , |3 Comments