Home > Sample chapters

Customizing Microsoft Dynamics AX 2009

  • 6/10/2009

Table and Class Customization

By default, Dynamics AX 2009 comes with nine default inventory dimensions. (The user can define additional inventory dimensions.) Dimensions describe the characteristics of items or item lots. Item dimensions might include characteristics such as configuration, model, and size. Item lots might have storage dimensions, such as site, warehouse, location, or pallet, or they might be identified by a serial number and batch number. The site dimension is new in Dynamics AX 2009.

The following customization scenario describes how to customize tables and classes used by the inventory dimension feature to implement two new item dimensions that describe a specific bicycle configuration: frame size and wheel size. This description isn’t an exhaustive list of elements that you must change; instead, it offers guidelines for finding the elements necessary to customize the full implementation of a new inventory dimension.

Creating New Dimension Types

When implementing new inventory dimensions, your first task is to create extended data types for each of the dimensions. Doing so provides the following benefits:

  1. To apply the inventory dimensions to multiple tables, you define the type just once and then apply it to each table.

  2. The Label property, the HelpText property, and a few constraints can be defined on the data type, ensuring consistent behavior and appearance of fields of the same type.

  3. If the type is declared as a parameter or a return type for a method, you can declare variables of the type in X++ code to optimize IntelliSense responsiveness and to improve the readability of the code.

This scenario defines a table in which a field of the specific type is part of the primary key. You can define the relationship to this table on the extended data type and subsequently instruct the application runtime to provide lookups and Go To The Main Table Form support.

In this example, you enter the Data Dictionary in the Application Object Tree (AOT) and create a BikeFrameSize extended data type and a BikeWheelSize extended data type. Table 5-1 lists the property settings that deviate from the default settings.

Table 5-1. BikeFrameSize and BikeWheelSize Property Settings

Property

BikeFrameSize

BikeWheelSize

Type

Real

Real

Label

Frame size

Wheel size

HelpText

Frame size in inches

Wheel size in inches

AllowNegative

No

No

ShowZero

No

No

NoOfDecimals

0

0

Figure 5-1 shows the property sheet for the BikeFrameSize extended data type, accessible by clicking Properties on the context menu for the type.

Figure 5-1

Figure 5-1. Property sheet for the BikeFrameSize extended data type

Next, create two tables, named BikeFrameSizeTable and BikeWheelSizeTable, in which the frame and wheel sizes for each item can be stored. In addition to the specific inventory dimension types, the tables also contain an ItemId field and a Name field. The ItemId and dimension in each table constitute the table’s primary index.

Table 5-2 lists the BikeFrameSizeTable property settings that deviate from the default settings. (The property settings for BikeWheelSizeTable are identical except for the BikeWheelSize field and its extended property type.)

Table 5-2. Field Property Settings

Property

ItemId

BikeFrameSize

Name

Type

String

Real

String

ExtendedDataType

ItemId

BikeFrameSize

Name

Mandatory

Yes

Yes

No (default)

AllowEdit

No

No

Yes (default)

Create a unique index on both tables. For BikeFrameSizeTable, name the index FrameIdx and make it contain the ItemId field and the BikeFrameSize field. For BikeWheelSizeTable, name the index WheelIdx and make it contain the ItemId field and the BikeWheelSize field. Declare the indexes as the PrimaryIndex on the respective tables. In the AOT, the fields and the indexes appear as shown in Figure 5-2.

Figure 5-2

Figure 5-2. BikeFrameSizeTable definition

In addition to the fields and index shown in Figure 5-2, you should also set properties in the tables for caching, form references, and so on, and the table should contain field groups and methods for checking the validity of the fields. However, it is beyond the scope of this chapter to describe these enhancements. The Microsoft Dynamics AX 2009 software development kit (SDK) contains guidelines and best practices for creating tables.

After you define the tables, you should update the extended data types to reflect their relationship to the individual tables, as shown in Figure 5-3.

Figure 5-3

Figure 5-3. Extended data type relations of BikeFrameSize

This relationship instructs the Dynamics AX runtime to provide lookup and Go To The Main Table Form functionality when fields of these types appear on forms. The application runtime uses the related table as the data source for the lookup form and also to find the main table form from the FormRef property on the table. You must therefore create forms for the BikeFrameSizeTable and BikeWheelSizeTable tables and menu items to open the forms. These menu items are added to the FormRef properties on the corresponding tables. You could design the forms to mirror the form shown in Figure 5-4. See the Microsoft Dynamics AX 2009 SDK for general information on designing forms.

Figure 5-4

Figure 5-4. Frame Sizes form

Adding New Dimensions to a Table

To store transactions with the new inventory dimensions, the dimensions must be added to the InventDim table. You do this by creating two new fields, BikeFrameSize and BikeWheelSize, of the corresponding type on the InventDim table. You should also add these fields to the unique DimIdx index because any combination of inventory dimensions can exist only once in the InventDim table.

The display of inventory dimensions in almost any form in the Dynamics AX application is based on field groups and where the content of the field group in the form is built at run time. The forms runtime in Dynamics AX builds the group from the list of fields in the associated field group defined on the InventDim table. Therefore, by adding the new fields to the InventoryDimensions field group on the InventDim table, you make the two new fields available in almost any form that displays inventory dimensions. Position the fields in the field group based on where you want them to appear relative to the other dimensions, as shown in Figure 5-5.

Figure 5-5

Figure 5-5. InventDim table with customized InventoryDimensions field group

Figure 5-5 shows usr flags on the AutoReport and ItemDimensions field groups, indicating that the custom fields have been added to these groups as well. The AutoReport group is modified so that it prints the new dimensions if you create an auto report by clicking Print on a form; the ItemDimensions group is modified because the new dimensions are considered to be item dimensions.

Although the inventory dimensions are now available in any form because of the interpretation of the field groups by the Dynamics AX forms runtime, the fields still aren’t visible or editable because they aren’t enabled in any inventory dimension group. Moreover, the two new inventory dimensions automatically appear in the Dimension Groups form because the inventory dimension feature also interprets the InventoryDimensions field group on the InventDim table to find all the currently available inventory dimensions. To make the form work with the new dimensions, you merely state whether the new dimensions are item dimensions. You do this by adding the new dimensions to the isFieldItemDim method on the InventDim table, as shown in the following X++ code. The added lines are shown in bold.

static public boolean isFieldIdItemDim(fieldId dimFieldId)
{
    ;
    #InventDimDevelop

    switch (dimFieldId)
    {
        case (fieldnum(InventDim,ConfigId))         :
        case (fieldnum(InventDim,InventSizeId))     :
        case (fieldnum(InventDim,InventColorId))    :
        case (fieldnum(InventDim,BikeFrameSize))    : // Frame size added
        case (fieldnum(InventDim,BikeWheelSize))    : // Wheel size added
            return true;

        case (fieldnum(InventDim,InventSiteId))     :
        case (fieldnum(InventDim,InventLocationId)) :
        case (fieldnum(InventDim,InventBatchId))    :
        case (fieldnum(InventDim,wMSLocationId))    :
        case (fieldnum(InventDim,wMSPalletId))      :
        case (fieldnum(InventDim,InventSerialId))   :
            return false;
    }

    throw error("@SYS70108");
}

The new dimensions will be available for setup in the Dimension Groups form, which is reached through the navigation pane under Inventory Management\Setup\Dimensions\ Dimension Groups. The dimensions are located in the Item Dimensions grid, as shown in Figure 5-6.

Figure 5-6

Figure 5-6. Dimension Groups form with new item dimensions

Enabling New Dimensions in Forms

You can enable new dimensions by setting up dimension groups, but you won’t see them yet in the forms. The inventory dimension feature uses a temporary table named InventDimParm to carry certain information, such as whether a dimension has the following attributes:

  • Is enabled

  • Is an item dimension

  • Is a primary stocking dimension

  • Is visible

  • Serves as a filter-by term

  • Serves as a group-by term

  • Serves as an order-by term

The dimension groups are enabled and controlled by reflecting each inventory dimension as a boolean flag field on the InventDimParm table and then matching the corresponding fields in the X++ code. For example, when a dimension group is queried to determine which dimensions are active, an InventDimParm record is returned where the corresponding flag field is set to true for the active dimensions. The remaining flags are set to false. You must therefore add a frame-size flag and a wheel-size flag to the InventDimParm table, as shown in Table 5-3.

Table 5-3. BikeFrameSizeFlag and BikeWheelSizeFlag Property Settings

Property

BikeFrameSizeFlag

BikeWheelSizeFlag

Type

enum

enum

Label

Frame size

Wheel size

HelpText

View by frame size

View by wheel size

ExtendedDataType

NoYesId

NoYesId

Enum

NoYes

NoYes

You should also add the new fields to the FixedView and View field groups defined on the InventDimParm table, because they are used in forms from which it is possible to specify whether a dimension should be visible.

When you add fields to the table and field groups, you must map the new fields on the InventDim table to the corresponding fields on the InventDimParm table in the X++ code. To do this, you modify the dim2DimParm method on the InventDim table, as shown in the following X++ code. The added mappings of BikeFrameSize and BikeWheelSize appear in bold.

static public fieldId dim2dimParm(fieldId dimField)
{
    ;
    #InventDimDevelop

    switch (dimField)
    {
        case (fieldnum(InventDim,ConfigId))         :
            return fieldnum(InventDimParm,ConfigIdFlag);
        case (fieldnum(InventDim,InventSizeId))     :
            return fieldnum(InventDimParm,InventSizeIdFlag);
        case (fieldnum(InventDim,InventColorId))    :
            return fieldnum(InventDimParm,InventColorIdFlag);
        case (fieldnum(InventDim,InventSiteId))     :
            return fieldnum(InventDimParm,InventSiteIdFlag);
        case (fieldnum(InventDim,InventLocationId)) :
            return fieldnum(InventDimParm,InventLocationIdFlag);
        case (fieldnum(InventDim,InventBatchId))    :
            return fieldnum(InventDimParm,InventBatchIdFlag);
        case (fieldnum(InventDim,wMSLocationId))    :
            return fieldnum(InventDimParm,WMSLocationIdFlag);
        case (fieldnum(InventDim,wMSPalletId))      :
            return fieldnum(InventDimParm,WMSPalletIdFlag);
        case (fieldnum(InventDim,InventSerialId))   :
            return fieldnum(InventDimParm,InventSerialIdFlag);
        case (fieldnum(InventDim,BikeFrameSize))    : // Add mapping
            return fieldnum(InventDimParm,BikeFrameSizeFlag);
        case (fieldnum(InventDim,BikeWheelSize))    : // Add mapping
            return fieldnum(InventDimParm,BikeWheelSizeFlag);
    }

    throw error(strfmt("@SYS54431",funcname()));
}

You must make the same modification to the dimParm2Dim method on the same table to map InventDimParm fields to InventDim fields.

Customizing Other Tables

The customizations made so far allow the new dimensions to be enabled on dimension groups and presented in forms. However, you should also consider customizing the following tables by adding inventory dimensions to them:

  • BOMTmpUsedItem2ProducedItem

  • InventCostTmpTransBreakdown

  • InventDimCombination

  • InventSumDateTrans

  • InventSumDeltaDim

  • PBADefault

  • PBATreeInventDim

  • PriceDiscTmpPrintout

  • InterCompanyInventDim

Whether and how you should customize these tables depends on the functionality you’re implementing. Be sure to examine how the inventory dimensions are implemented and used for each of the tables before you begin customizing.

Adding Dimensions to Queries

Because of the generic implementation of the inventory dimension concept using the InventDim and InventDim Parm tables, a substantial number of queries written in X++ use just a few patterns to select, join, and filter the inventory dimensions. So that you don’t have to repeatedly copy and paste the same X++ code, these patterns exist as macros that you can apply in your code. To modify these queries, you simply customize the macros and then recompile the entire application to update the X++ code with the new dimensions.

You should customize the following macros:

  • InventDimExistsJoin

  • InventDimGroupAllFields

  • InventDimJoin

  • InventDimSelect

The bold text in the following X++ code shows the changes that you must make to the InventDimExistsJoin macro to enable the two new dimensions for all exists joins written as statements involving the InventDim table.

/* %1 InventDimId           */
/* %2 InventDim             */
/* %3 InventDimCriteria     */
/* %4 InventDimParm         */
/* %5 Index hint            */

exists join tableId from %2
    where
  (%2.InventDimId      == %1) &&
  (%2.ConfigId         == %3.ConfigId         || ! %4.ConfigIdFlag)         &&
  (%2.InventSizeId     == %3.InventSizeId     || ! %4.InventSizeIdFlag)     &&
  (%2.InventColorId    == %3.InventColorId    || ! %4.InventColorIdFlag)    &&
  (%2.BikeFrameSize    == %3.BikeFrameSize    || ! %4.BikeFrameSizeFlag)    &&
  (%2.BikeWheelSize    == %3.BikeWheelSize    || ! %4.BikeWheelSizeFlag)    &&
  (%2.InventSiteId     == %3.InventSiteId     || ! %4.InventSiteIdFlag)     &&
  (%2.InventLocationId == %3.InventLocationId || ! %4.InventLocationIdFlag) &&
  (%2.InventBatchId    == %3.InventBatchId    || ! %4.InventBatchIdFlag)    &&
  (%2.WMSLocationId    == %3.WMSLocationId    || ! %4.WMSLocationIdFlag)    &&
  (%2.WMSPalletId      == %3.WMSPalletId      || ! %4.WMSPalletIdFlag)      &&
  (%2.InventSerialId   == %3.InventSerialId   || ! %4.InventSerialIdFlag)

#InventDimDevelop

The three remaining macros are just as easy to modify. Just remember to recompile the entire application after you make your changes.

Adding Lookup, Validation, and Defaulting X++ Code

In addition to macro customizations and the customizations to the previously mentioned methods on the InventDim table, you must implement and customize lookup, validation, and defaulting methods. These include methods such as the InventDim::findDim lookup method, the InventDim.validateWriteItemDim validation method, and the InventDim.initFromInventDimCombination defaulting method. The necessary changes in the InventDim::findDim lookup method for the new inventory dimensions are shown in bold in the following X++ code.

server static public InventDim findDim(InventDim _inventDim,
                                       boolean   _forupdate = false)
{
    InventDim inventDim;
    ;
    if (_forupdate)
        inventDim.selectForUpdate(_forupdate);

    select firstonly inventDim
        where inventDim.ConfigId         == _inventDim.ConfigId
           && inventDim.InventSizeId     == _inventDim.InventSizeId
           && inventDim.InventColorId    == _inventDim.InventColorId
           && inventDim.BikeFrameSize    == _inventDim.BikeFrameSize
           && inventDim.BikeWheelSize    == _inventDim.BikeWheelSize
           && inventDim.InventSiteId     == _inventDim.InventSiteId
           && inventDim.InventLocationId == _inventDim.InventLocationId
           && inventDim.InventBatchId    == _inventDim.InventBatchId
           && inventDim.wmsLocationId    == _inventDim.wmsLocationId
           && inventDim.wmsPalletId      == _inventDim.wmsPalletId
           && inventDim.InventSerialId   == _inventDim.InventSerialId;

    #inventDimDevelop

    return inventDim;
}

Notice the use of the inventDimDevelop macro in the preceding method. This macro contains only the following comment:

/* used to locate code with direct dimension references */

Performing a global search for use of the inventDimDevelop macro should be sufficient to find all the X++ code that you must consider when implementing a new dimension. This search returns all the methods that require further investigation. Figure 5-7 shows results of a search for the use of the macro on all tables.

Figure 5-7

Figure 5-7. Search results for the inventDimDevelop macro

Most of the methods you find when searching for the macro are lookup, validation, and defaulting methods, but you also see methods that aren’t in these categories. Such methods include those that modify the Query object, such as the InventDim::queryAddHintFromCaller method, and methods that describe dimensions, such as InventDimParm.isFlagSelective. You should also review these methods when investigating the X++ code.