Friday, 18 November 2011

Cumulative update pack 2 for Microsoft Dynamics AX 2012

Cumulative update pack 2 for Microsoft Dynamics AX 2012 has been released.

This pack contains close to 70 fixes for various bugs. The pack also contains the cumulative update pack 1 for Microsoft Dynamics AX 2012 which was released earlier.

The build number after applying this pack will be 6.0.947.280

You can download the pack and read more about the fixes in the pack here.

Thursday, 17 November 2011

Set financial dimension values through code in AX 2012

In Dynamics AX 2012, the dimensions framework has gone for a complete makeover. We can now have an unlimited number of financial dimensions. In Dynamics AX 4 and Dynamics AX 2009, the dimensions were actually stored as an array of string fi?>elds. This has changed in Dynamics AX 2012 where the dimension is stored as a recid. Recently I came across a requirement where if the CostCenter dimension was set to a particular value, the department dimension's value should change to a different value and vice versa.


I developed the code and thought it will be a good idea to share it here.

My requirement was in the ProjTable form but you can use this code in any of your form which uses the default financial dimensions.

Override the DefaultDimension field's dataChanged() method on your main datasource and add the below code.
// Financial dimension change code - Zubair - Begin
public void dataChanged()
{
    DimensionAttribute  dimToBeChanged;
    DimensionAttributeValue dimAttributeValue;

    boolean             triggerDimensionChange;
    str        10       valueToBeDefaulted;

    DictTable           dictTable;
    Common              common;

    FormStringControl   dimensionControl = element.selectedControl();

    super();

 // Our dimension change will trigger this method again, we dont want to skip it then.
    if (!dimensionValueChanged)
    {
        if (dimensionControl.name() == 'DimensionValue1' && dimensionControl.valueStr() == #CostCenterValue)
        {
            dimToBeChanged = dimAttributeDepartment;
            triggerDimensionChange = true;
            valueToBeDefaulted = #DepartmentValue;
        }

        if (dimensionControl.name() == 'DimensionValue2' && dimensionControl.valueStr() == #DepartmentValue)
        {
            dimToBeChanged = dimAttributeCostCenter;
            triggerDimensionChange = true;
            valueToBeDefaulted = #CostCenterValue;
        }

        if (triggerDimensionChange)
        {
            dictTable = new DictTable(dimToBeChanged.BackingEntityType);
            common = dictTable.makeRecord();

            select common where common.(fieldName2id(dimToBeChanged.BackingEntityType, #DimensionValueFieldName)) == valueToBeDefaulted;

   // Find the DimensionAttributeValue record for the dimension being changed
            dimAttributeValue = DimensionAttributeValue::findByDimensionAttributeAndEntityInst(dimToBeChanged.RecId, common.RecId, false, true);

            // Set the new dimension value
            dimensionDefaultingController.setDimensionAttributeValue(dimToBeChanged, dimAttributeValue.RecId, valueToBeDefaulted);

            dimensionValueChanged = true;
        }
    }
    else
    {
        dimensionValueChanged = false;
    }
}
// Financial dimension change code - Zubair - End

Add the following lines in the init() method of your form after the call to super().
// Financial dimension change code - Zubair - Begin
    dimAttributeCostCenter = DimensionAttribute::findByName(#CostCenterDimension);
    dimAttributeDepartment = DimensionAttribute::findByName(#DepartmentDimension);
// Financial dimension change code - Zubair - End

Add the following lines in the classDeclaration of the form.
// Financial dimension change code - Zubair - Begin
    DimensionAttribute  dimAttributeCostCenter;
    DimensionAttribute  dimAttributeDepartment;

    boolean             dimensionValueChanged;

    #define.CostCenterDimension('CostCenter')
    #define.DepartmentDimension('Department')
    #define.CostCenterValue('OU_3569')
    #define.DepartmentValue('OU_2561')
    #define.DimensionValueFieldName('Value')
// Financial dimension change code - Zubair - End

Thats it. Now try changing the value of the CostCenter dimension to OU_3569. Notice that the value in Department to OU_2561.

Hope this snippet will be useful if you have a similiar requirement or atleast you know how it can be done now.

Feel free to mail me or comment here if you need help with understanding the code.

Friday, 28 October 2011

“Day in the Life Benchmark” available for download

Microsoft has recently released the Day in the Life Benchmark documents on PartnerSource.

You can read more about it on the Dynamics Ax Performance Team Blog.

The benchmark numbers shared there are really encouraging and truly shows that Dynamics AX 2012 is a robust and agile erp platform to base your business on.

Note: PartnerSource or CustomerSource login is required.

Wednesday, 19 October 2011

Get selected records in Dynamics AX 2012

In my earlier post, we discussed how we can retrieve all selected records on a list page. You can read that post here.

Retrieving all selected records from a caller form is very easy. The code for this can be found in a number of places in standard Dynamics AX codebase. The general approach is to use the dataSource.getFirst(1) statement to get the first selected record and loop through the remaining using dataSource.getNext().

In Dynamics AX 2009, a new class was introduced for this purpose. This was the MultiSelectionHelper class.

In Dynamics AX 2012, another new class has been introduced to retrieve the selected records. This is the MultiSelectionContext class.

The MultiSelectionHelper class is an application class. It presents two constrcutors. The standard constructor method just creates a new instance of the MultiSelectionHelper class. When this construct is used, you will have to explicitly set the dataSource to be used for fethcing selected records. This can be done in the following way,
MultiSelectionHelper multiSelectionHelper; 

multiSelectionHelper = MultiSelectionHelper::construct();
multiSelectionHelper.parmDatasource(element.dataSource());
This way, the class is initialized and the datasource is specified.

The other constructor method this class offers is the createFromCaller() method. This method takes a formRun as an argument. When you want to retrieve the selected records from the caller form, you can pass the caller form as an argument here.

Once the class has been initialized, you can traverse through the selected records using the getFirst() & getNext() method.

The MultiSelectionHelper class provides the added benefit of caching the selected records if the object is initialized on the server.

The other new class that has been introduced in Dynamics AX 2012 for selected records handling is MultiSelectionContext. This is a kernel class. An object of this class exists in the xArgs class. This class also provides the getFirst() and getNext() methods to traverse through the selected records.

Below is a code sample on how these two classes can be used to implemented in code.
public void init()
{
    FormRun                 callerForm;
    FormDataSource          formDataSource;

    MultiSelectionHelper    multiSelectionHelper;
    MultiSelectionContext   multiSelectionContext;

    ProjTable       projTable;

    super();

    if (!element.args().caller())
    {
        throw error("@SYS22539");
    }

// ************** The standard way of doing it in Dynamics AX 4 and Dynamics AX 2009 **************
    callerForm = element.args().caller();
    formDataSource = callerForm.dataSource();

    ListView.add("Approach 1");

    for(projTable = formDataSource.getFirst(1); projTable; projTable = formDataSource.getNext())
    {        
        ListView.add(strFmt("%1 - %2",projTable.ProjId, projTable.Name));
    }

// ************** Using the new MultiSelectionHelper class in Dynamics AX 2012 **************
    multiSelectionHelper = MultiSelectionHelper::createFromCaller(element.args().caller());

    projTable = multiSelectionHelper.getFirst();
    ListView.add("Approach 2");

    while (projTable)
    {        
        ListView.add(strFmt("%1 - %2",projTable.ProjId, projTable.Name));
        projTable = multiSelectionHelper.getNext();
    }

// ************** Using the new MultiSelectionContext class in Dynamics AX 2012 **************
    multiSelectionContext = element.args().multiSelectionContext();

    projTable = multiSelectionContext.getFirst();
    ListView.add("Approach 3");

    while (projTable)
    {        
        ListView.add(strFmt("%1 - %2",projTable.ProjId, projTable.Name));
        projTable = multiSelectionContext.getNext();
    }

    if (!ListView.getCount())
    {
        throw error("No projects selected");
    }
}
The below image shows which part of the code produced the what output.
Going forward, I'll recommend that we start using these helper classes for dealing with selected records.

Some of you may have a question as to which class to use and in what scenarios. Let me clarify that for you.

Like I stated above, the MultiSelectionHelper class provides us the benefit of record caching provided you intantiate the object on the server tier. Now, because of this, you will have to call this class from within an object(not a form as forms run on the client tier) which is running on the server tier. This is because the RunOn property of the MultiSelectionHelper class is CalledFrom. So if you call it directly from a form, the object will be created on the client tier itself and your selected records will not be cached.

But if you have designed your code such a way that your form calls a table or a class running on the server tier, and then this class in turns calls the MultiSelectionHelper class, we can then make use of the caching capabilities.

So if your code is designed in such a way, I'll advice to make use of the MultiSelectionHelper class else you should go for the MultiSelectionContext class.

You can download the project I've developed for this demo here. Click on the Project details button on the Projects list page to open the new form.

I hope this post was helpful. Please feel free to provide your comments.

Thats all for today, do check back soon for more.

Saturday, 15 October 2011

New security model in Dynamics AX 2012

In Dynamics AX 2012, one of the key area that has gone a total overhaul is Security. In Dynamics AX 4.0 and Dynamics AX 2009, the security model was module based. Users were assigned to user groups which grouped permissions to the various objects. These permissions were controlled by security keys. The biggest drawback of this model was that you could not have the same security user group apply across multiple companies. You still had to create the same user group across different groups.

With Dynamics AX 2012, all this has changed. The security model in Dynamics AX 2012 is role based. This means that you can relate the day to day business processes to roles. Users are assigned to roles. Roles contain a group of duties or privileges. It is logically very easy now to configure security in Dynamics AX 2012.

The Security keys in the AOT are now obsolete in Dynamics AX 2012. It is present for backward compatibility. If you right click on the Security keys node, you dont have an option to create a new security key.

Instead, in the AOT, you will find a new node - Security. This further has sub nodes - Privileges, Duties and Roles. Let us better understand these by relating to a real world example.

Let us take an example of a school. A school needs to do a lot of day to day activities like teaching students, conducting exams, counselling, issuing books, buying new books etc. All these are privileges. These privileges can be group as duties. So teaching students and conducting exams can be grouped under one duty of learning enablement. At the same time issuing books and buying new books can be grouped under the book management duty. Now not everyone can perform these duties. These duties have to be assigned to some role. In some cases, multiple duties can be assigned to a single role.

Take a look at the below image to better understand this.
In my opinion, the new role based security model is very logical and aimed at enabling business analysts to configure security. Of course, a developer's help may still be required but once the privileges are created, it is just a matter of group them under duties and assinging the right duties to the correct roles.

The new security model is strictly enforced in Dynamics AX 2012. There are a bunch of best practice errors around this. Let us look at these.

Every new menu item should be added as an entry point in a privilege. If this is not the case, the following best practice error is thrown.

Entry point is not in any privilege

In case, you create a new privilege and add your menu item to it, and forget to add the privilege to a duty, you get the following best practice error.

Privilege is not in any duty

And finally, if you created a new duty and didnt add it to any role, you will get these best practice error.

All duties should be part of a role

All duties should be part of a process cycle

So, there are four best practice errors coaxing the user to complete the security modelling. In a way, this is a good thing as this ensures that there are no orphan or unused objects existing in the AOT.

When configuring security, ensure that you analyze the out of the box duties and roles present in Dynamics AX 2012. Often, your new entry points should be easily sit in one of these. If they dont, then you can go ahead and create your own duties or roles.

I'll encourage you to further go and read more about the new Security model in Dynamics AX 2012 on the What's New: Security [AX 2012] page on MSDN.

Also, take a look at this nice post by Brandon - AX 2012 and the impact on Design with the new Security Model

That is all for today. In future posts, I'll cover some more topics on Security. Check back soon for more.

Tuesday, 11 October 2011

How to get list page's datasource in Dynamics AX 2012?

Recently, I had a requirement where I needed to disable a button on a list page if the records selected did not meet a particular condition. Let me explain you this with an example. Say I have a new button on the Projects list page. This button should be enabled only if all projects selected have the same project contract ID. If the contract ID is different for the selected projects, the button should be disabled.
So how can we achieve this? On a form, this can be done in a very straight forward way. Just loop through all the selected records and check if the project contract IDs are different. If they are, just disable the button. But on a list page, the form datasource is not readily available. After some investigation, I found out a way of doing this.

As you must be knowing, all list pages in Dynamics AX 2012 are controlled via interaction classes. This is the new framework model. If you are new to this and dont know about these, I'll recommend you learn more on this on the How to: Create a List Page Form page on MSDN.

Coming back to the post, the interaction class has a method selectionChanged() which is fired every time a record is selected or deselected on the list page. We can use this method to check the project contract ID of the selected projects. So, our logic should go into this method.

Now how to retrieve the form's datasource? To do that, I first got hold of the active record on the list page and then extracted the datasource object associated with it. The code to do that is

this.listPage().activeRecord(queryDataSourceStr(ProjTable_NoFilter, ProjTable)).dataSource();

Notice how I have used the intrinsic function queryDataSourceStr() here to get the datasource. You can read more about this function in my previous post on queryDataSourceStr.

I've created a new helper method called enableNewButton to return me a boolean value to enable/disable the button.

public boolean enableNewButton(FormDataSource  _formDataSource)
{
    ProjTable   projTableLocal;

    boolean     ret = true;

    for (projTableLocal = _formDataSource.getFirst(1); projTableLocal; projTableLocal = _formDataSource.getNext())
    {
        if (selectedProjects.elements() && !selectedProjects.in(projTableLocal.ProjInvoiceProjId))
        {
            ret = false;
            break;
        }

        selectedProjects.add(projTableLocal.ProjInvoiceProjId);
    }

    return ret;
}

I call this method from the selectionChanged() method.

public void selectionChanged()
{
    super();

    this.enableButton(newButtonName,
    this.enableNewButton(this.listPage().activeRecord(queryDataSourceStr(ProjTable_NoFilter, ProjTable)).dataSource()));
}

That is it. You can download the objects I've used in this sample from here.

Thats all for today, do check back soon for more.

Monday, 10 October 2011

QueryDataSourceStr - new intrinsic function in Dynamics AX 2012

There is a new intrinsic function introduced in Dynamics AX 2012. This is the QueryDataSourceStr() function. This function returns a string of the datasource name.

The queryDataSourceStr() function checks if the supplied datasource name exists in the query in the AOT or not.

An important point to note here is that all the intrinsic functions like formStr, classStr, queryStr and queryDataSourceStr invoke real time compiler check. This means that the compiler checks for the existence of the object in the AOT as soon as the function call is completed. In case the object name is not found, an error is shown immediately by displaying a red squiggly line.