Search through blog..

Wednesday, October 31, 2012

Microsoft Dynamics Ax Macros

In MorphX macros are not commonly used. A few places make use of macros such as keeping track of the list of fields stored when using dialogs. It is recommended only to use macros to define constants. Also supports code, but is not recommended as reusing code from macros is not flexible.

The main difference between a macro and a method is that a macro has no variable declaration part, and the code in a macro is not validated for errors before executed from a method. This is another reasons for not putting code in macros.

When using macros in your code, the macros must be declared after the variable declaration. And the common place to put the definition is in the ClassDeclaration of a class, form or a report. This will make the macro definition available for all parts of the object.

 
Macro commands:

For writing macros a set of simple macro commands are used. Below are few examples.


CommandDescription
#defineUsed to define a constant.

AxExample: See macro HRMConstants.

Syntax: #define.myConstant100('100')


#if.empty

Will return true if the macro has not been called with the parameter validated in the statement.

#if.notempty

Will return true if the macro has been called with the parameter validated in the statement.

AxExample: See macro InventDimJoin

Syntax: #if.notempty(%3)
print %3;
#endif

#endif

Ends a #if.empty or a #if.notempty statement.

AxExample: See macro InventDimJoin

Syntax: #endif (following any if statement)

#globalmacro

No difference whether declaring the start of a macro with #localmacro or #globalmacro. #globalmacro is not used in the standard package, consider using #localmacro instead.

#localmacro

Specify the start of a local macro.

AxExample: See macro BOM

Syntax: #localmacro.BOMDateSelect
(((BOM.fromDate <= %1 || ! BOM.fromDate) && (BOM.toDate >= %1 || ! BOM.toDate)) || ! %1 )
#endmacro

#endmacro

Ends a #LOCALMACRO or a #GLOBALMACRO.

#linenumber

Returns the current line number of the macro. Can be used while debugging, but not of must use.

#macrolib

Used to load an AOT macro from code.

AxExample: See class method BOMHierarchy.searchDownBOM()

Syntax: #macrolib.MyMacro

#undef

Undefine a constant declared with #DEFINE. A defined constant cannot be used if #undef is called with the define name.

Syntax: #define.MyConstant(100)
print #MyConstant;
#undef.MyConstant
print #MyConstant; // will fail, as #MyConstant is not defined.

 
Defining Constants:

Instead of using text in your code it is strongly recommend defining your text as constants. Often you will need an integer or a text for setting a value. If you are going to set RGB color it is not easy to read the following:
            myStringColor(255, 255, 255)

Instead you should consider defining a constant with a descriptive name:
myStringColor(#RBGColorWhite)

Using a macro as a constant rather than entering the value in code makes it easier to maintain. A good way to organize the constants used in your modifications is by creating a macro in the AOT for keeping all your constants in one place, one good example is HRMConstants.
 

Creating Macros:

Macros are either created directly in the code, or put in an AOT macro and then the AOT macro is declared in the code.

Example(1): #localmacro created in the code
static void Macros_LocalMacro(Args _args)
{
CustTable custTable;
;
#localmacro.selectCustTable  //Macro definition starts here
#ifnot.empty(%1)
while select %1
#ifnot.empty(%3)
order by %3
#endif
{
info(queryValue(%1.%2));
}
#endif
#if.empty(%1)
info("No table specified.");
#endif
#endmacro  //Macro definition ends here
#selectCustTable(CustTable, accountNum)  //Calling Macro with valid parameters
#selectCustTable  //Calling Macro with no parameters – output will be text “No table specified” as per the validation.
}
The macro will select records from a table ‘CustTable’ and print a field from the same as the table to be fetched, and the fields (%1, %2, %3) to be printed are specified in the parameters for the first Macro call.
In the second Macro call, the table has not been specified so text will be printed to the Infolog.

As you cannot declare variables in a macro, integer values prefixed with a percentage sign such as %1 are used instead. This is the common way of using the macros. The validations must be before calling the macro. Notice that you can call a macro without entering the parentheses after the macro name.
 
Example(2): Create and use an AOT Macro
If your macro is going to be used in several places it would make sense creating the macro in the AOT as you will then be able to reuse the macro.
To create a new macro in the AOT:
1.       Unfold the Macro node, right-click and choose New Macro.
2.      A new empty macro node will be created. You can rename the new macro by opening the property sheet to “MyFirstMacro”
3.      Now paste the code of the #localmacro (from above) to your new macro.
4.      Save the Macro, and it is ready to use. 

To use the macro created in the AOT the macro command #macrolib is used. Here the AOT macro is name MyFirstMacro, and it can be used in the code as shown below.
static void Macros_MacroLib(Args _args)
{
CustTable custTable;
;
#macrolib.MyFirstMacro
#selectCustTable(CustTable, accountNum)
#selectCustTable
}
Even this will result in the same output as explained above, the only difference is that we are calling a Macro created in AOT using #macrolib command.

Dynamics Ax Table collections & Virtual company

If using more than one company, sometimes, it will be useful to share data from tables with general information, like, tables storing data like zip codes and country codes. The most basic way to share data from a table among all companies is to set the table property SaveDataPerCompany to No.
This will merge data for the table and make the data accessible from all companies. In practice, the kernel will delete the system field dataAreaId for the table.

Another way to sharing data without having to change any settings in the existing tables is by using Table Collections. A table collection is just a template for tables to be shared by any number of companies and Table collections shared using a virtual company.

The form SysDataAreaVirtual is used to define virtual companies. Virtual company is a term used for sharing table collections among a set of companies and does not exist in reality, and therefore, you cannot switch to a virtual company like any normal company.

When using TC for setup tables (like customer groups), setting up a VC will be easy. But, if you are going to share data from main tables (like the inventory table), you should do more investigation as you cannot only share the table InventTable. You must include all tables which are related to InventTable.

Note: Before creating a VC you should export data for the tables used in the table collection, as existing data will be deleted from these tables when added to a virtual company.
 

Virtual Company setup:

Step 1: Create Table Collection:
Decide which tables you want to share and create a Table collection for these functionally related tables. For example; if you want to share 'Global Address Book' across companies then you can utilize the existing table collection "DirPartyCollection".

 To create a table collection, go to AOT\Data Dictionary\Table Collections and on right click select "New Table Collection", then just drag your required tables in this collection.

Step 2: Create Virtual Company, configure/attach normal companies and table collection:
Create a virtual company that will hold the shared data for normal companies.
Note: Before doing the below steps, make sure you are the Ax administrator and the only user online.

1. Go to Administration > Setup > Virtual company accounts, and create a virtual company.
2. Decide which companies needs to share data and attach those normal companies with this virtual company.
3. Attach the table collection with this virtual company and save form.

Your Ax client will re-start and you are done with setting up the virtual company account.

Now, when you have virtual company in place, all new data will be saved in this virtual company. Only companies attached to the virtual company can use this shared data. All other companies which are not attached will work normally, these companies will continue to read/write data as per company bases.


How to move existing data to virtual company?
When you setup a new virtual company, Ax does not move data automatically from normal company to virtual company. This is done by system administrator manually.

There are many ways to do this data move, let’s see only two such approaches here.



Approach 1: Ax Import / Export
This is standard Ax approach.
  1. Manually export existing normal company data from Ax.
  2. Remove duplicate records from this exported data set.
  3. Delete exported data from normal companies.
  4. Import the exported data back in Ax, while logged into one of the participating companies.
  5. Create records deleted in point 2 again in Ax using your logic. How you want to handle duplicate? For example, if you have customer 'Customer' in more than one normal company, what you want to do with this?
Approach 2: Direct SQL
Use this approach if you have good knowledge about SQL queries and Ax table structures/relationships. Below are steps you can follow.

  1. All Ax tables store data as per company unless otherwise specified. For this, Ax uses a special field called DataAreaId. In case of virtual company, it does not matter from which normal company you log-in, it is always the virtual company id which is stored in DataAreaId field of shared tables.
  2. Ax also assigns a unique 64bit number to each record in table. For this, Ax uses a special field called RecId. This RecId is unique in the table and is generated by Ax when you insert a new record in Ax. It is not related to DataAreaId / Company.
  3. For unique records between all participating normal companies, update the DataAreaId to the virtual company id.
For duplicate records, create them again in Ax using some Ax job or Ax import/export technique.

License codes and Security settings in Microsoft Dynamics Ax

License codes:

·         When purchasing Dynamics Ax you will have to decide on system settings, like, number of users, number of servers, access to MorphX and X++, which application modules you are going to use.

·         For each system setting and module you will receive a license code. All license codes will be compiled in a code letter. These license codes are used for controlling which part of Axapta you will have access to. Only modules with a valid license code will be available in the main menu for end user to use.

·         We can also create our own License codes, However, to use your own license codes you will have to contact Microsoft as they will have to generate license codes on your behalf. Creating new license codes is used by companies creating modules for the GLS layer or by partners who want to sell their own modules.
Configuration keys have a property called
LicenseCode, which is used for attaching a license code to your modifications.
 

Configuration keys:
We can have two levels of security settings in Dynamics Ax. Configuration keys are the highest level, and security keys are the second level.

·         Configuration keys are defined in a tree hierarchy where the top configuration key is related to a license code. The form SysConfiguration shows the hierarchy of configuration keys.

·         By default, Some configuration keys are disabled, generally for advanced features and country specific features.

·         When changing the settings of configuration keys, the database must be synchronized. This is because the configuration keys determine whether a table should be synchronized to the database or not.

·         Normal practice would be to attach a configuration key to each table, map, view and menu item for the modifications. This will ensure that an object where the configuration key is disabled will not show up in the menu.

·         Most of the objects in the AOT have a property for defining a configuration key. In the table SalesTable there is a field called ProjId. The extended data type for ProjId has a configuration key.
Another example is base enum FormTextType. Each of the entries added to this base enum from the GLS layer have configuration keys.

·         From X++ you can make a check for whether a configuration key is enabled. The global method isConfigurationKeyEnabled() is used to validated configuration keys.

Example (1): The following is an example for how to verify if configuration key is enabled for a particular field.
static void DataDic_ConfigurationKey(Args _args)
{
SalesTable salesTable;
ProjTable projTable;
;
select firstonly salesTable;
info(salesTable.salesId);
info(salesTable.custAccount);
if (isConfigurationKeyEnabled(configurationkeynum(ProjBasic)))
{
info(ProjTable::find(salesTable.projId).name);
}
}
Here, the name of the project related to a sales order will only be printed if the top level configuration key for the project module is enabled. If the check was left out, a blank value would have been printed, which will not make much sense to the end user.

 
Security Keys:
Where configuration keys are setting access for all users, security keys will set access for a group of users or per user.

·         The standard package uses 9 security keys for each module. One top level security key related to a configuration key, one security key for tables and the last 7 security keys are used for the groupings of the module objects as seen from the main menu. You can view the security hierarchy from the form SysUserGroupSecurity.

·         Security keys must be added to all tables, maps views and menu items. If one of these objects does not have a security key you will not be able to set access rights for the object, and the object will be accessible for all users.

·         Security keys are not only defined as enabled or disabled, instead an access level is defined. The choices of access level are no access, view, edit, add or delete.

·         At tables, maps and views you use the property MaxAccessMode to define the access level a user can retain. Tables will, by default, have MaxAccessMode set to Delete, which will allow the users to retain full access to a table.

·         Menu Items has a similar property called NeededAcccesLevel which acts just the opposite of table as you must specify the required access level to execute the menu item. The default value for NeededAccessLevel is View.

·         The best practice is adding security keys should be done as one of your last tasks when creating your modifications. At least, you should have the security keys added before doing your final test.

Tuesday, October 30, 2012

Base Enums in Microsoft Dynamics Ax

Base Enums come in handy for categorizing data. One way of categorizing is to create a related table, such as item groups, which is used for grouping inventory items.
However, if you only need a fixed number of categories or the application users are not able to define the categories, you can use a base enum, such as Item type, used for defining the type for inventory items.


  • A base enum can have a maximum of 255 entries. A good example for multiple enteries in a base enum is LedgerTransTxt. But in general there will be very few entries.
  • The value of a base enum is stored in the database as an integer value. Entry values starts by default from zero and are consecutively numbered. The property EnumValue will show you the number stored in the database for an entry.
  • We can also manually enter a particular number for an entry in base enum, this can be done if the base enum property UseEnumValues is set to Yes.
  • System enums are located in the AOT under System Documentation/Enums, an example for system enum is ‘TableGroup’ represents the entries of the table property TableGroup.
  • To refer to a base enums entry in code, the name of the base enum is entered followed by a double colon. By pressing the second colon lookup will show the available entries.
Example(1): Below is a small example on how to use Base Enums in X++
static void DataDic_BaseEnum(Args _args)
{
InventTable inventTable;
;
while select inventTable
where inventTable.itemType == ItemType::BOM || inventTable.itemType == ItemType::Service
{
info(inventTable.itemId);
}
}
Here all items of Invent types BOM and Service are fetched.

Instead of checking whether the field itemType is equal to one of the enum entries specified, ‘greater than’ or ‘equal to’ the item type BOM can also be used. However, this is not recommended as it makes the code more difficult to maintain as the condition will not be defined and result will vary with every new data record entry.
while select inventTable where inventTable.itemType >= ItemType::BOM

We could also use the base enum entry numbers instead, but it would make your code more difficult to read and maintain.


Note: The first entry of a base enum normally has the value zero - which will return false, if the first entry is validated in an if-expression. This is also the reason why fields of the type enum should not be mandatory as the first entry of an enum would be considered as not valid.

Extended Data Types in Microsoft Dynamic Ax & EDT array


·         An extended data type is extended from a base type or another extended data type. The difference is that an extended data type has a property sheet where information such as labels, length and left or right adjustment are stored.

·         EDT are a central part of MorphX. Whether you are adding fields to a table or declaring variables from X++, extended data types should always be used instead of using base types and the reason for that is Dynamics Ax provides powerful features to handle EDTs.

·         The best practice is to check before creating a new extended data type you should check whether in existing one will fulfill your needs.

·         If you have found an EDT which suits your needs, but another label or help text is required, you should create a new EDT with your requirements, but never modify existing EDT accordingly because this will impact the fields which are already using it.

·         Some EDTs are extended from system extended types (under AOT/System Documentation/Types. Regional settings like amount and types for system fields are all created as system fields.

Example(1) : Steps to create EDT for replacement of standard AccountNum EDT.

1.       Go to the node Extended Data Types, right-click and create a new string. Name the new extended data type "DataDic_AltCustAccount" using the property sheet.

2.      Enter the label "Alt. customer" and the help text "Identification for alternative customer account." for the new extended data type.

3.      Extend DataDic_AltCustAccount from AccountNum using the property Extends.

4.      Expand the nodes for DataDic_AltCustAccount and go to the node Relations. Add a new relation by right-clicking, choose New and select Normal. Use the property sheet to select MyFirstTable and the field accountNum as the relation.

5.      Save the extended data type and wait until the database has been synchronized.

6.      Go to the table CustTable and locate the field altCustAccountNum. Open the property sheet and change the ExtendedDataType property to DataDict_AltCustAccount.

7.      Locate the relations in CustTable and find the relation MyFirstTable. Press the delete key to remove the relation for MyFirstTable.

8.     Save CustTable.

9.      Change the extended data type for field accountNum in MyFirstTable as done at step 6 and save MyFirstTable.

Now we have a new EDT for the alternative customer account. The table relation which we deleted in CustTable was unnecessary as the EDT just created will now handle that relation.
It is much more flexible having the relations at the EDT level as you will not have to modify each table where the extended data type is to be used. The EDT was also changed for the field accountNum in MyFirstTable and now no lookup button will be added to accountNum as the related table is the same.
 
Note:
Both EDT and base enums can be used as extension for a field. So why to create an EDT for a base enum? This is because, Only EDTs can be used when adding a field to dialog. If a field used in a dialog is of the type enum like NoYes, even then the extended data type NoYesId extending the base enum NoYes must be used.
 

Extended Data Type Array:

  • Another powerful feature of EDTs is an EDT can also be defined as an array. And each array element of the EDT will be created a database field.
  • The node Array Elements is used when defining an EDT as an array. The first entry of the array will be the entry created when creating a standard EDT. And then array entries are created under the node Array Elements.
  • Only label and help text is specified for these subsequent array entries. All other properties are inherited from the first entry in the array. As with the first entry, separate relations can be defined for each array entry.
The most common EDT using this feature is Dimension which is used throughout the application for grouping data. Dimension consists of 3 array entries, each having their own relation.

 Example (2): Let’s see how to retrieve values from EDT array.
static void DataDic_EDTArray(Args _args)
{
CustTable custTable;
;
select firstonly custTable;
info(strfmt("%1, %2, %3", custTable.dimension[1], custTable.dimension[2], custTable.dimension[3]));
}
Output:

The same notification for declaring a variable is used when addressing the single array entries of an extended data type. In the example above, the 3 dimensions from CustTable are printed for the first record. When looking up the fields for CustTable, only the field Dimension will be listed for the array.

You must manually specify the array entry to be printed. If array entries are referred directly, such as in this example, a new array entry added will not be printed. A better solution would have been to loop all array entries rather than fix the code to print the first 3 entries.

If looking at a data source in a query, the opposite will be shown. A data source will show a field for each array entry. Dimension is always added to a field group when used in forms and reports so the dimension field group will automatically recognize a new array entry added.