Sunday, November 8, 2009

Thursday, November 5, 2009

Issue with installing AX2009 Reporting Extensions on Windows Server 2008 R2 and workaround

Hello all

I just came across an issue where the partner was trying to install Reporting Extensions on a Windows Server 2008 R2 Standard edition, and it fails during the installation of the pre-req IIS components, the error we get is:

[ServerManagerCmd] Error (Id=0) ArgumentNotValid: Feature not valid: 'NET-XPS-Viewer'. The name of the feature was not found.


AX setup uses the following file from the installation media/source \Support\ServerManagerCmdInputIIS.xml to install IIS components and it indeed does have a feature listed


However if you run the command, “servermanagercmd –query”, you will find that on Windows 2008 R2 this feature does not exist, it has been renamed to “XPS-Viewer” (on Windows 2008 it was indeed called “NET-XPS-Viewer”



We were able to workaround the issue by editing the ServerManagerCmdInputIIS.xml, and installation does proceed and complete.


On the partners system there was NO Role Centers and Enterprise Portal (EP) installed although they did have IIS installed but with partial components. The issue only manifests if AX Setup determines it needs to install IIS components if it finds one of them missing or if IIS is not installed at all.

As you can guess the question that was next raised is that is this a known issue and is it issue documented anywhere by any chance? Was it picked up as part of the validation of Windows 2008 R2? Anyone else come across a similar issue? Why do we do a check for the XPS-Viewer feature? What AX extension uses that?

The issue is simple to replicate. Just remove the Web Server (IIS) Role Service and attempt installing the Reporting Extensions, it should fail with an error when it comes to installing the IIS components as per the attached screen shot and log files.

Wednesday, November 4, 2009

How to: Debug User Controls

You can use Visual Studio to debug the code used in User Controls. You can debug when the User Control is being run in the ASP.NET Development Server environment or in Enterprise Portal.

[http://i.msdn.microsoft.com/Global/Images/clear.gif] Debugging in the ASP.NET Development Server Environment

To debug a User Control in the ASP.NET Development Server environment, you must create a Dynamics AX Webpart Page that will run the User Control. For more information, see How to: Test User Controls in Visual Studio.

To debug in the ASP.NET Development Server environment

1. Start Visual Studio.

2. Open the Web project that contains the User Control that you want to debug.

3. View the code for the User Control. Do this by right-clicking the User Control in Solution Explorer, and then clicking View Code.

4. Add breakpoints to the appropriate locations in the code.

5. In the Debug menu, click Start Debugging.

6. In the Web browser window that is displayed, click the file name for the Dynamics AX Webpart page (.aspx) that contains the User Control that you want to debug. Visual Studio should stop execution at the designated breakpoints.

[http://i.msdn.microsoft.com/Global/Images/clear.gif] Debugging in Enterprise Portal

To debug a User Control that is running in Enterprise Portal, you must configure Web project appropriately and enable debugging for the Enterprise Portal Web site.

To debug in Enterprise Portal

1. Start Visual Studio.

2. Open the Web project that you want to use for debugging.

3. Add the User Control to the Web project.

4. If the User Control uses any proxies to access X++ code, right-click the App_Code node in Solution Explorer, and then click Generate Proxies. The proxies that are required to access X++ code will be added to the Web project.

5. Copy the complete URL for the page in Enterprise Portal that contains the User Control that you want to debug. This URL is needed when configuring Visual Studio.

6. Select the Web site in Solution Explorer.

7. In the Website menu, click Start Options.

8. Set the Start action to Start URL. Specify the complete URL for the page that contains the User Control.

9. Set Server to Use custom server. Specify the Base URL for the server that is running Enterprise Portal. For example, http://daxep.

10. Click OK.

11. To enable debugging for the Enterprise Portal installation, right-click the Web site in Solution Explorer. Click Enable Debugging in SharePoint.

[Important]Important

If you do not see the Enable Debugging in SharePoint item in the menu, debugging may have already been enabled for the Web site. You might also have to restart Visual Studio and re-load the Web project for the menu item to be visible.


12. In the dialog box displayed, indicate that you want to enable debugging and click OK.

[Important]Important

Enabling debugging will modify the web.config file for the Enterprise Portal site. We do not recommend that you enable debugging in production environments for Enterprise Portal.


13. View the code for the User Control. Do this by right-clicking the User Control in Solution Explorer, and then clicking View Code.

14. Add breakpoints to the appropriate locations in the code.

15. In the Debug menu, click Start Debugging. The page that you specified will be loaded, and Visual Studio should stop execution at the designated breakpoints.

Friday, September 11, 2009

How to: Access the Active Query on Forms

http://msdn.microsoft.com/en-us/library/aa659696.aspx

How to call a form method from method of other form

void B()
{
Object formAobj = element.args( ).caller( );
;
// Check that this form was opened from FormA
If ( formAobj && formAobj.name( ) == formstr(FormA) )
{
// Call method A on FormA
formAobj.A()
}
Return;
}

How can I pass parameter from a form to a class

static void main(Args _args)
{
PurchTable purchTable;
;

if (_args.dataset( ) != tablenum(PurchTable )
throw error(strfmt( "Method %1 was called with an invalid record", funcname())) ;

purchTable = _args().record( );

}

How can I pass parameter from a form to a class

static void main(Args _args)
{
PurchTable purchTable;
;

if (_args.dataset( ) != tablenum(PurchTable )
throw error(strfmt( "Method %1 was called with an invalid record", funcname())) ;

purchTable = _args().record( );

}

How to sow a webpage on ax 3.0

These are the steps to solve your problem..
STEPS-->
1) Create a new Form in AOT
2) In design of that form
A) Add-->ActiveX
B) Set Autodeclaration property of that ActiveX to "Yes"
C) Set Height & Width property as per your requirement
3) Override "Init" method of Form u have created in step-1)
public void init()
{
;
super();
WebPage.Navigate( "http://www.google. co.in");
}
4) Run the form

Monday, August 24, 2009

Writing a quine

While reading through Wikipedia, I came across something called a quine, and here is its definition:

In computing, a quine is a computer program which produces a copy of its own source code as its only output.

I was inspired by this and decided to make a quine in X++, the programing language of Microsoft Dynamics AX.

static void quine(Args _args)
{
#AOT;
print TreeNode::findNode(strfmt(#JobPath,"quine")).AOTgetSource();
pause;
}
It’s a job that finds it’s own tree node in the AOT, gets the source of that node and prints it. I originally wrote it all on one line, because I like the look of that better, but I didn’t do that here for readability.

Now I’m not entirely sure if this qualifies as a quine, because a quine can have no input, and using the method AOTgetSource could be seen as cheating. But anyway, it reproduces itself, it’s short, and it made me happy

AOS crashed when importing XPO

Dynamics AX client crashed when clinking on show details. No message was shown, the application just quit. It was also impossible to reconnect to the Application Object Server because the AOS had crashed as well. When you restarted the AOS, you could easily reproduce this scenario by repeating the above steps.

Sometimes, the following message was logged in the event log on the client, basically informing you that the AX client had crashed:

Faulting application ax32.exe, version 5.0.1000.52, stamp 490c41fb, faulting module unknown, version 0.0.0.0, stamp 00000000, debug? 0, fault address 0×0bbf0b3c.

In the event log on the server an other message was logged, but not every time the AOS crashed:

Object Server 16: RPC error: Client provided an invalid session ID 3

From what I understand, this message is logged when a client sends an RPC request to a server that has ended the session for this client, which would be the case if the AOS crashed.

Once again, the event log is pretty useless. The real problem lies in the application files, more specifically the Microsoft Dynamics AX Label Index files. These are the files that have the extension .ali. For some reason, one or more of these index files are corrupt, and this causes the import process to fail, and the AOS to crash. At the time, the AOS server was low on diskspace, and that could be the cause of the corrupt index files. Luckily, index files such as ali files can be deleted when the AOS is down, and will be rebuilt when the AOS is started.
After all label index files were deleted, and the AOS restarted, the xpo could be imported, and the AOS didn’t crash anymore.

So, when the AOS crashed when you are trying to import an xpo file, stopping the AOS, deleting the ali files from the application directory and restarting the AOS might be the solution

Change security permissions for admin group

In Dynamics AX, it is not possible to change the user group permissions for admin group. This is because users in this group should always have full control over the Dynamics AX setup.

But sometime, you’ll have to change it, whatever you reason may be. One reason why you might need to change these, is because the user group permissions setup has been messed up by adding one or more new security keys, or by importing them from an xpo file. The security keys will be set to ‘No access’, and because you can’t change these for the admin group, this poses a problem.

This can easily be solved by changing the method isAdmin() on the form SysUserGroupSecurity to return false.

#admin
boolean isAdmin()
{
/*if (userGroupInfo.Id == #AdminUserGroup &&
(domainInfo.Id == #AdminDomain || !useDomains))
{
return true;
}*/
return false;
}

Gender information in Employees-form and the Global Address Book (GAB)

You can open the information about employees with the Global Address Book (Basic/Global Address Book) and with the Employees form (Basic/Employees).
Normally it is no problem to modify data in both forms, but unfortunately this is not true for the ‘gender’ property. This information is stored in the DirPersonPartyDetail (Gender) and EmplTable (EmplGender) table. The particularity here is, that the EmplGender property is of type ‘EmplGender’ which has two states (0:Male;1:Female) and the enum ‘Gender’ has three states (0:unknown;1:male;2:female).
If you know change the value for the gender in the GAB it won’t change the value for ‘EmplGender’ and if you change it through the Employees-form it won’t change the value for ‘Gender’.
This is why the gender information of employees must be modified with the Employees-form and not with the GAB.
If you need to synchronize the employees information with the GAB, you can add at the end of the method (tables\EmplTable\ValidateField), the following code :
1: //MODIFICATION(04/08/2009): Empl-form is not synchronizing some values with the GAB
2: case(fieldnum(EmplTable, emplGender)):
3: dirPersonPartyDetail = DirPersonPartyDetail::find(this.PartyId, true);
4: if(dirPersonPartyDetail)
5: {
6: dirPersonPartyDetail.Gender = enum2int(this.EmplGender);
7: dirPersonPartyDetail.update();
8: }
9: break;
10: //END MODIFICATION

Template Wizard does create SheetNames with more than 30 characters

The Template Wizard (Administration/Periodic/Data export/import/Excel spreadsheets/Template Wizard) creates unfortunately SheetNames that might exceed 30 characters, which is not allowed by Excel and results in annoying message boxes that are requesting a new SheetName with less than 31 characters.

The following code change in the method SysDataExcelDef/buildTmpExcelWorksheet will resolve this problem:

1: //modifications done for working with long tablenames
2: // str tempSheetName;
3: // str suffixName;
4: // int suffixlength;
5: //original code:
6: //tmpExcelWorksheet.SheetName = tmpExcelWorksheet.TableName + '_' + int2str(sysExpImpTable.OccurrenceId) + '-1';
7:
8: tempSheetName = tmpExcelWorksheet.TableName + '_' + int2str(sysExpImpTable.OccurrenceId) + '-1';
9:
10: if (strlen(tempSheetName) > 30)
11: {
12: tempSheetName = tmpExcelWorksheet.TableName;
13: suffixName = '_' + int2str(sysExpImpTable.OccurrenceId) + '-1'; //as this is done in the original code
14: suffixLength = strlen(suffixName);
15: tempSheetName = substr(tempSheetName, 0, 30 - suffixLength);
16: //assigning the new name
17: tmpExcelWorksheet.SheetName = tempSheetName + suffixName;
18: }
19: else
20: {
21: tmpExcelWorksheet.SheetName = tempSheetName;
22: }

Monday, August 17, 2009

Manage Cues on Role Center pages for Web parts

Cues Web parts display a visual representation of a user's workload and remaining work items, such as sales leads or overdue activities. You can configure or edit Cues, and then import or export them to other computers.

For information about how to add a Cues Web part, modify the properties of a Cue, create Cues, or modify Cue filters, see the Applications and Business Processes help, available from the Microsoft Dynamics AX Help menu.

Edit a Cue
You can configure several settings for a Cue, including the minimum and maximum values, the query used for the Cue, and which user profiles a Cue is available for.

Click Basic > Setup > Role Center > Edit Cues.

Select a Cue ID.

Click the General tab.

Enter a caption to be displayed for the Cue in the Web part.

Enter the minimum and maximum count information, which determines how the icon that is displayed for each Cue represents the number of records.

Specify the threshold. If the threshold is not met, a warning icon will be displayed for the Cue.

Select the totals to display for Cues that display currency information.

Select which users will have access to this Cue. If you select Specified Profiles, select which profiles will have access to the filtered view. This setting applies across all companies.

Click OK.

How to import or export Cues
If you edit Cues and you want to make those Cues available on other computers, you can import or export those Cues.

Click Basic > Setup > Role Center > Manage Cues.

Select the Cue IDs to import or export.

Click Import or Export and click the AOT or file option.

If you are importing or exporting Cues on the same computer, select the AOT option.

If you are importing or exporting Cues on another computer, select the file option.

Close the form to save your changes.

Tuesday, August 11, 2009

Sending attachments using the "mailto:" protocol

It works with Outlook at least. I think this example job is enough

static void Test_MailTo(Args _args){ str recepient='mbelugin@gmail.com'; str subject = 'Test'; str attachment = @'c:\AUTOEXEC.BAT'; str url = strFmt( 'mailto:%1?Subject=%2&attachments=""%3""', recepient, subject, attachment );; WinApi::shellExecute(url);}

Sunday, August 9, 2009

Problem solving in AX

Sometimes, when developing, AX doesn’t work as expected, or behaves weird.
Here are some of the things you can try if you run out of ideas, roughly in the order I do:

Try again
You probably already did, but make sure you can reproduce the problem. If it only occurred once, it’s not a problem.

Check your code again
Check your code carefully for errors, and maybe ask a colleague’s opinion.

Compile
Your project might contain compile errors, so compile it to be sure.

Close the debugger
Sometimes, when the debugger is active, AX will keep executing ‘old’ code. Close the debugger to make sure the latest code is executing.

Compile forward
When you have modified a class that is inherited by other classes, you might want to compile forward this class.

Synchronize data dictionary
You may have made changes to application objects that haven’t been synchronized with the database. Open the AOT, right click on the Data Dictionary node and choose Synchronize.

Restart AX client
Simple as that, close the AX client and start it again.

Reset usage data
Go to options screen (AX button > Extra > Options) and click the Usage Data button. Click reset to remove this data.

Check the application event log for clues
Open the event viewer on the AOS to see if the AOS service has logged something in it. Messages here can help you a great deal. You can also check the event log on the database server or your client pc.

Google it
If you receive an error, just copy and paste it in Google. Most likely you are not the only one having that problem.

Check your client version
Check your AX client version. You might for example be connecting to a SP1 application with an SP0 client. You can check this in the about screen: AX button > Help > About. The kernel version indicates the client version, below that is the application version.

Refresh AOD, Dictionary and Data
You can flush cashed information using three option in the Tools > Development tools menu: refresh AOD, refresh Dictionary and refresh Data. This can be useful when you just imported an xpo file, or when you are developing for the enterprise portal.

Delete AUC file
The application Unicode object cache file, if there is one, is located at C:\Documents and Settings\[USERNAME]\Local Settings\Application Data for xp, or C:\Users\USERNAME\AppData\Local for vista. Delete this file while the AX client is closed.

Check if other users are having the same problem
Knowing whether you are the only one that’s having that problem or if it’s a general problem is a big step towards solving the problem. For example, if you alone have the problem, restarting the AOS probably won’t solve it, but removing usage data might.

Check security settings
When only some users have a problem, big changes are that it has something to do with security settings. Security can be set up from Administration > Setup > Security, on the Permissions tab.

Check configuration key setup
Some features of AX won’t work if a configuration key is disabled, be aware of this.

Full compile
Open the AOT, right click the AOT node and select compile.

Restart AOS
Sometimes restarting the AOS solves your problem just fine. If you can, it’s worth the try as this doesn’t take long.

Remove .aoi file
When the AOS is stopped, you can delete the .aoi file from the application files. This file will be rebuilt when the AOS starts.

Add leading zeros

You want to show a integer with a fixed length (for example 2characters: 1 –> 01). You don’t have to write your own for-loop to ad the zero’s in front or something like that. The function strRFix does this for you automaticly.

info(strRFix(int2str(1), 2, '0'));

Class RunBaseReport

When you create a class that extends from RunBaseReport and you try to modify the printer preferences it won’t have any effect. This small bug can be fixed very easy.

Edit the class RunBaseReportDialog, method main and check lines 14 and 15:
static void main(Args args)
{
RunBaseReportDialog reportDialog = new RunBaseReportDialog(args.caller());
RunBaseReport runBaseReport = args.caller().runbase();
ReportRun reportRun = runBaseReport.reportRun();
Report report = reportRun.report();
boolean oldInteractive;
boolean res;
Dialog dialog;
;
// We must invoke the SysPrintForm via the report object so that we honor an prompt overrides.
oldInteractive = report.interactive();
report.interactive(true);
//res = reportRun.prompt(); // COMMENTED
res = reportDialog.prompt(); // CHANGE OF CODE
report.interactive(oldInteractive);
if (!res)
return;
dialog = Dialog::getDialogFromCaller(args.caller());
if (dialog)
{
dialog.updateServer();
}
runBaseReport.dialogUpdatePrinterSettings(dialog);
reportDialog.run();
}

Hide/destroy annoying Content Pane in AX2009

Here is a small job, which hides content pane in Dynamics Ax 2009. This content pane can be useful for users, but as a developer it can be very annoying

static void hideAnnoyingContentPane(Args _args)
{
HWND contentPane = WinApi::findWindowEx(
WinAPI::findWindowEx(infolog.hWnd(), 0, 'MDIClient', ''),
0,
'ContentFrame',
''
);
;
if (contentPane)
WinApi::destroyWindow(contentPane);
}

Type of Anytpe enum

Normaly you try to avoid using AnyType variables, but sometimes you are forced to use them. When you use a AnyType enum and you want to cast it to it’s actual data type, be sure you cast it to the right type to prevent stack-traces. You can get the enumId from a AnyType-enum by using the DictEnum::value2id(AnyType _value) -function. Then you can compare it with the enumnum(EnumType) to check if it is the right type.

In code this check could look like this:

static void testAnyTypeEnumCasting(Args _args)
{
DocumentStatus docStatus;
Anytype any = DocumentStatus::Invoice;
;
switch (DictEnum::value2id(any))
{
case enumnum(DocumentStatus) :
docStatus = any;
break;
case enumnum(...) :
...
}
}

Select Table/Field/User/… (PickList)

Sometimes you want your user to select a sertain table/field/… When you want to program this selection all by yourself you have to make a table, override the lookup()-method, …

There is also a easy way to do this. You can use the pic**** methodes in the Global class:
pickclasses
pickdatamethord...etc

How do you use these classes (you can find this example in \Forms\DocuOptionTable\Designs\Design\[Tab:TableTab]\[TabPage:Overview]\[Grid:TableOverviewGrid]\StringEdit:DocuTableName\Methods\lookup):
void lookup()
{
tableId id;
;
id = pickTable();
if (! id)
return;
docuTable.DocuTableId = id;
docuTable_ds.refresh();
}

The result:
Table : Field:
When you want to create you own customized pick-dialog you can by entering the following code:

static void testPickList(Args _args)
{
Object formRun;
container con;
;
formRun = classfactory.createPicklist();
formRun.init();

//fill container con

formRun.choices(con); // you can add a secundary parameter wth the imiges
formRun.caption("TEST"); // Make label
formRun.run();
formRun.wait();
if (formRun.choice())
{
print formRun.choice();
}
pause;
}

Security key disabled, even for the Admin

When you import a new security key trough a XPO, this new key will be disabled even for the admin group. Since you can’t adjust the security setting for the admin group you can’t use the keys functionality.

There is a verry simple solution for this problem. Open the method \Forms\SysUserGroupSecurity\Methods\isAdmin and force this method to return false.

#admin
boolean isAdmin()
{
/* if (userGroupInfo.Id == #AdminUserGroup &&
(domainInfo.Id == #AdminDomain || !useDomains))
{
return true;
}*/
return false;
}

Customizing EditorScripts

Ax had a few interesting scripts for the developer. When you have an idea for a new useful script you can extend the the EditorScripts Class (\Classes\EditorScripts) with your new class.

Here is a example where you automate some custom comment while developing:


void comments_MyComment(Editor e)
{
e.unmark();
e.gotoLine(1);
e.gotoCol(1);
e.insertLines(strfmt('//--> ProjectRef - By:%1 @ %2\n', curuserid(), systemdateget()));
}

Monday, June 15, 2009

Thursday, May 21, 2009

Inline PDF viewer for Portal

Get PrintJobSettings From User

To get printjobsettings from user by clicking a button on AX, you can put the below code to clicked method of button
void clicked()
{
PrintJobSettings printJobSettings;
;
super();
printJobSettings = new PrintJobSettings(printset);

printJobSettings.printerSettings(formstr(SysPrintForm));

printset = printJobSettings.packPrintJobSettings();
}

Encode Barcode String For Any BarcodeType

Encode barcode just in a few lines of code:

Barcode barcode;
;
barcode = Barcode::construct(BarcodeType::Code39);
barcode.string(true, "Barcode_String");
barcode.encode();
itemBarcode = barcode.barcodeStr();

Un/Reservation By Code

server static void inventReservation(InventTransId _inventTransId,
InventSerialId _inventSerialId,
Qty _qty ,
boolean _unreserve=false)
{
InventUpd_Reservation inventUpd_Reservation;
inventMovement inventMovement;
InventSerial invserial;
inventdim inventDim,iDimNew;
SalesLine salesLine;
;
try
{
ttsbegin;
salesLine = SalesLine::findInventTransId(_inventTransId);
if (!salesline)
throw error('Sales Order Line could not be found !');
invSerial = InventSerial::find(_inventSerialId,salesLine.ItemId);
if (!invSerial)
throw error('Inventory Serial Number could not be found !');
inventDim = salesLine.inventDim();
inventdim.inventSerialId = _inventSerialId;
iDimNew = inventDim::findOrCreate(inventDim);
inventMovement = InventTrans::findTransId(salesLine.InventTransId).inventMovement(true);
inventUpd_Reservation = InventUpd_Reservation::newInventDim(inventMovement,iDimNew,_unreserve ? _qty : -_qty);
inventUpd_Reservation.updateNow();
ttscommit;
}
catch (Exception::Deadlock)
{
retry;
}
}

AX 2009 – Adding Display method to Custom Lookup

AX 2009 brings capability to add display methods as well as adding table fields to custom lookups.

SysTableLookup.addLookupMethod(identifierstr(DisplayMethod));


So you can create a display method that gets only date of datetime field and display it in Lookup

Display bitmap on report

display Bitmap bitmap()
{
ResourceNode resNode=SysResource::getResourceNode('ResourceName');
container nodeData;
Image img;
;
if (resNode != null)
{
resNode.AOTload();
nodeData = SysResource::getResourceNodeData(resNode);
img = new Image(nodeData);
}
return img.getData();
}

Get the label description for any language

Label filed contains the description for each defined language. And it will automatically displayed in user's language on the forms (for reports it can be changed by code). But if you want to get that label's decription other than user's language. You will need to use below command that returns description that in given parameter languageId.
syslabel::labelId2String(literalstr('@LabelId'),'LanguageId')

How to add a range for the Array EDT to a query?

For the most cases dimensions have to be used in ranges. To do that you can use below code to add a range to a array element of that Dimension

qbrDimension = this.query().dataSourceNo(1).addRange(fieldid2ext(fieldNum(myTableName, Dimension), 1));

How many time user logged on to AX at the time (for 3 tier structure)

You can find the user logging count by using SysUsersOnline class as below.

container con1,con2;
;
con1 = conpeek(SysUsersOnline::getAllOnlineUserInfo(), 1);

for(counter1 = 1; counter1 <= conlen(con1); counter1++)
{
con2 = conpeek(con1, counter1);

if (conpeek(con2, 2) == curuserid())
{
countUser++;
}
}


Cookies usage on AX Portal

Cookies are important on web applications. You may want to keep some informations on client side, so you have to use cookies for Portal application. To set and get cookies from client side, below code will help you.

//Set Cookie with expiraton date&time
webSession().response().cookies().itemWriteCookie("InformationId=Value; expires=Fri, 31-Dec-2013 00:00:00 GMT");

//Get cookie
iis = new IISRequest();
webSession().writeTxt(iis.cookies().itemTxt(("InformationId")));

Sending XML file to webservice

Nowadays, using webservice for connecting different based systems is very popular. If you have to send xml data file to a webservice you firstly have to create a Microsoft.XMLHttp COM object variable to put xml string and to specify webservice address.

static void sendXML(Args _args)
{
url webServiceUrl;
COM myXmlHttp;
str s = '\r\n';
;
s += '\r\n';
s += 'V1.0\r\n';
s += '\r\n';
s += '1\r\n';
s += '';
myXmlHttp = new COM('Microsoft.XMLHTTP');
webServiceUrl = "http://webservice/webservice.aspx";
myXmlHttp.open("GET",webServiceUrl,false);
myXmlHttp.send(s);
}

What are the objects Dict* and How can we use them?

In some cases we need to write some codes in Dict objects. If we look under AOT\System Documentation\Classes node, here are the objects we will discuss in this post.

Dict objects are generally used for creating a object of related type (for DictClass e.g. CustBalanceList class), getting information about that object (for DictClass e.g. is class defined Abstract, Final or is Runnable {has main method} ) etc.

DictClass

Object can be constructed by “new” word (Params: ClassId). Mostly and effectively used for creating a child class (if we know classId of it).

Example usage in AX Classes\VenOutPaymRecord\newVendOutPaymRecord
Example code

//A child class will be created by using dictclass

DictClass dictClass;
VendOutPaymRecord vendOutPaymRecord; // parent class is defined
;

if (! _vendPaymModeSpec.classId)
{ // Payment mode specification has the child class id
return null;
}

if (! SysDictClass::isSuperclass(_vendPaymModeSpec.classId, classNum(VendOutPaymRecord)))
{ //Be sure if child class is really child
return null;
}

dictClass = new DictClass(_vendPaymModeSpec.classId); //Initialize dictclass object by child class id
vendOutPaymRecord = dictClass.makeObject(); // Create real child class by calling makeObject method

if (! vendOutPaymRecord) // Check if class was made or not
{
return null;
}

DictConfigurationKey

Object can be constructed by “new” word (Params: ConfigurationKeyId). Mostly used for checking if configuration key is enabled or not

Example usage in AX Classes\PurchLineType\interCompanyMirror
Example code

//Check if SalesDeliveryDateControl configuration key is enabled

if (new DictConfigurationKey(configurationkeynum(SalesDeliveryDateControl)).enabled())

DictEnum

Object can be constructed by “new” word (Params: EnumId). Mostly used for returning all of the elements of any enum object and accessing name of them.

Example usage in AX Classes\InventItemType\valueCanBeProduced
Example code

//Check if SalesDeliveryDateControl configuration key is enabled

DictEnum dictEnum;
Counter i;
InventItemType inventItemType;
str itemTypeTxt;
;

dictEnum = new DictEnum(enumnum(ItemType)); // ItemType is a variable that value determined at outer of method earlier
for (i=0;i// This block will loop until the end element of enum
{
inventItemType = InventItemType::construct(i);
if (inventItemType.canBeProduced())
{
itemTypeTxt += itemTypeTxt ? ',' : '';
itemTypeTxt += queryValue(dictEnum.value2Name(i)); // Here, enum name is extracting from enum value
}
}

return itemTypeTxt ? itemTypeTxt : SysQuery::valueEmptyString();

DictField

Object can be constructed by “new” word (Params: TableId, FieldId). Mostly used for finding basetype or label etc of any table field.

Example usage in AX Classes\SysApplCheck\checkFieldDefaultReport
Example code

// This example will show some functionalities of DictField class

#MacroLib.DictField // will be used for flag testing
DictField dictField = new DictField(Tablenum(ContactPerson), FieldNum(ContactPerson,Name));
;
dictField.enumId(); // If that field was the type of enum, that method would return enumid (now is zero)
dictField.arraySize(); // In default array size is 1 (for EDT Dimension , array size bigger than 1)
if (bitTest(dictField.flags(),#DBF_CHANGE))
info('Allow edit property of the field is true');
dictField.relationObject(); // can be used for determining main table of EDT of the field. That expression will return dictRelation object
dictField.label(); // label of the field
dictField.stringLen(); // if field is the typeof string, that will return length of string (nonzero) value

DictFieldGroup

Object can be constructed by “new” word (Params: TableId, FieldGroupName). Mostly used for determining the fields in the fieldgroup of a table.

Example usage in AX

Classes\SysReportWizard\fieldFillContainer
Example code

// This example will show some functionalities of DictFieldGroup class

dictFieldGroup = new DictFieldGroup(TableNum(CustTable), ‘Address’); // Will Process Address fieldgroup of CustTable
fieldGroupName = dictFieldGroup.name(); // will return the string ‘Address’
fieldGroupElement = connull();
lastFieldElement = 0;

for (j=1; j <= dictFieldGroup.numberOfFields(); j++) //Determine the # of fileds in group
{
methodName = dictFieldGroup.methodName(dictFieldGroup.field(j)); //If the element in group is display or edit method ‘methodname’ variable will be the name of the method
if (methodName) //If the element is method then do something…
{

}
else // otherwise this is a table field
{
sysDictField = new SysDictField(dictTable.id(), dictFieldGroup.field(j)); // get dictfield and do something…
for(k = 1; k <= sysDictField.arraySize(); k++)
{

}
}
}

DictIndex

Object can be constructed by “new” word (Params: TableId, IndexId). Mostly used for finding the fields of the index or checking if it allow duplicates or not.

Example usage in AX

Classes\SysDictField\isUnique

Example code

// This example will show some functionalities of DictIndex class

DictTable dictTable;
DictIndex dictIndex;
int indexId;
int i;
fieldId fieldId;

if (this.mandatory())
{
dictTable = new DictTable(this.tableid());
fieldId = fieldExt2Id(this.id());
if (dictTable.primaryKeyField() == fieldId)
return true;

for (indexId = dictTable.indexNext(0); indexId; indexId = dictTable.indexNext(indexId)) // loop index of table
{
dictIndex = dictTable.indexObject(indexId); // get dictIndex from dictTable
if (!dictIndex.allowDuplicates()) // check if index property allowDuplicates is true or false
{
for (i=1; i<=dictIndex.numberOfFields(); i++) // loop thru fields of index
{
if (dictIndex.field(i) == fieldId) // get fieldId of the current field
return true;
}
}
}
}
return false;

Dictionary

Object can be constructed by “new” word (no Params required). Dictionary class is the general class for getting some informations about every AOT object.

Example usage in AX

Classes\ProjTableType\returnClass

Example code

// This example will show some functionalities of Dictionary class (Only for class related methods).

Dictionary dict = new Dictionary();
int i;
;
for (i = 1; i<=dict.classCnt(); i++) // Loop thru the classes in AOT
{
print dict.classCnt2Id(i); // classid of class
print dict.classFlush(); // Restore class object
print dict.className(dict.classCnt2Id(i)); // name of class
print dict.className2Id(dict.className(dict.classCnt2Id(i))); // will print same value as dict.classCnt2Id(i)
print dict.classNext(dict.classCnt2Id(i)); // classid of the next class after the current class
print dict.classObject(dict.classCnt2Id(i)); // constructs dictClass of the current class object
}
pause;

DictLicenseCode

Object can be constructed by “new” word (Params: LicenseCodeId). Mostly used for finding licensecode group

Example usage in AX

Classes\SysLicenseCodeReadFile\createCodes

Example code

// This example will show some functionalities of DictLicenseCode class

sysDictLicenseCode = new DictLicenseCode(dictionary.licenseCodeCnt2Id(i)); //construct licensecode object
if (sysDictLicenseCode) // if constructed
{
switch (sysDictLicenseCode.group()) // get group of license code ie. System,Partner,Web etc
{
case LicenseCodeGroup::Language:
print sysDictLicenseCode.id(); // get licensecode id and print

break;
}
}

DictMethod

Object can be constructed by “new” word (Params: UtilElementType, Id, Name). Mostly used for accessing method properties such as return type, parameters etc.

Example usage in AX

Classes\SysApplCheck\checkTableFieldPnameMustBeUnique

Example code

// This example will show some functionalities of DictMethod class

DictMethod dictMethod;
;
// If you want to create dictMethod of the type
//
// ClassInstanceMethod
// ClassStaticMethod
// then 2nd parameter will be ClassId
//
// TableInstanceMethod
// TableStaticMethod
// then 2nd parameter will be TableId
dictMethod = new DictMethod(UtilElementType::ClassInstanceMethod,
classNum(CustInPaym),
identifierstr(loadVoucherNum));
print dictMethod.returnType(); // prints return type (void)
print dictMethod.isAbstract(); // prints if the method is abstract (false)
print dictMethod.parameterCnt(); // prints parameter count of method (0)
print dictMethod.accessSpecifier(); // prints access specifier (public)
print dictMethod.returnId(); // prints EDT id of return type (in this case 0 = void)
print dictMethod.noParms(); // prints 1 which means there is no parameter required for that method
print dictMethod.parentId(); // in this case prints classid (72) (because of method is class instance method)
pause;

DictRelation

Object can be constructed by “new” word (Params: TableId). Mostly used for determining relation of two tables.

Example usage in AX

Classes\SysSeachResultForm\getMainRecord

Example code

// This example will show some functionalities of DictRelation class

private static Common getMainRecord(int iPMainTableId, int iPLinesTableId, Common CPLinesTableRecord)
{
DictRelation CDictRelation;
int i;
DictField CMainDictField;
DictField CLineDictField;
Query CQuery = new Query();
QueryRun CQrun;
Common CMainTableRecord;

;

CDictRelation = new DictRelation( iPLinesTableId ); // construct dictrelation according to line table
CDictRelation.loadTableRelation( iPMainTableId ); // give tableid to be found relation

// get list of fields that one table has to another
// and build query to main table
CQuery.addDataSource( iPMainTableId );
if (CDictRelation.lines())
{
for (i = 1; i <= CDictRelation.lines(); i++)
{
CMainDictField = new DictField(iPMainTableId, CDictRelation.lineExternTableValue(i)); // Here is the point, that job of dictrelation begins. CDictRelation.lineExternTableValue(i) returns maintable relation field
CLineDictField = new DictField(iPLinesTableId, CDictRelation.lineTableValue(i)); // And CDictRelation.lineTableValue(i) returns line table relation field
CQuery.dataSourceTable( iPMainTableId ).addRange( CMainDictField.id() ).value( queryValue( CPLinesTableRecord.( CLineDictField.id() ) ) );
}
}
CQrun = new QueryRun(CQuery);
CQrun.next();
CMainTableRecord = CQrun.get(iPMainTableId);

return CMainTableRecord;
}

DictSecurityKey

Object can be constructed by “new” word (Params: SecurityKeyId). Mostly used for checking if the user has securitykey access.

Example usage in AX

Classes\Global\hasSecurityKeyAccess

Example code

// Below method will explains DictSecurityKey class briefly

static boolean hasSecuritykeyAccess(securityKeyId securityKeyId, AccessType neededAccessLevel)
{
DictSecurityKey dsk=new DictSecurityKey(securityKeyId); // Construct class
;

if (dsk == null) // if not constructed then there is no such a securityKey
return false;
else
return dsk.rights() >= neededAccessLevel; // rights method will return current user’s AccessType (eg. NoAccess or View etc) to SecurityKey
}

DictTable

Object can be constructed by “new” word (Params: TableId). Mostly used for getting information and calling methods of table.

Example usage in AX

Classes\SysApplCheck\checkForm

Example code

// Below method will explains DictTable class briefly

DictTable dictTable;
CustTable custTable;
CustAccount accountNum = 'CustomerNo1';
;
select custTable
where custTable.AccountNum == accountNum; // we will use that record below
// we can construct table, map, view
dictTable = new DictTable(tablenum(CustTable)); // Construct dictTable class (here we will use CustTable as an example)
print dictTable.callObject(identifierstr(countryName),custTable); // callObject will execute 'countryName' instance method using the selected record above and returns value/object (according to the method return type)
print dictTable.callStatic(identifierstr(blocked),accountNum); // this will execute 'blocked' static method and returns value/object (according to the method return type)
print dictTable.fieldGroupCnt(); // prints count of fieldgroup in that table (CustTable)
CustTable = dictTable.makeRecord(); // makes a new record
dictTable.isMap(); //is object type of Map?
dictTable.isView(); //is object type of View?
dictTable.isTmp(); // (if it is table) is it temporary table?
print dictTable.titleField1(); // prints titlefield1 (fieldnum) property of table (is it is table)
pause;

DictType

Object can be constructed by “new” word (Params: extendedTypeId). Mostly used for getting information about any type.

Example usage in AX

Classes\AifMessage\validateString

Example code

// Below method will explains DictType class briefly

DictType dictType = new DictType(extendedTypeNum(CustAccount));
;
print global::extendedTypeId2name(dictType.extend()); // prints CustVendAC (ie. extend type of dictType)
print dictType.label(); // prints "Customer account" which is label of dictType
dictType.relationObject(); // returns DictRelation which contains relation to table which is specified in EDT
dictType.stringLen(); // if it is the type of string, then prints string length of dictType
dictType.configurationKeyId(); // returns "configurationKeyId" of dictType
pause;

Round function

Round function gets two parameters, first one is variable type of real which is the number to be rounded and the other one is decimal which will be used as sensitivity constant. What does mean sensitivity? Let’s dive into examples.

    round(23.79, 0.1) = 23.80 . The explanation of the result is:
      Closest multiple to the number of 0.1 is on the left : 23.70 and on the right : 23.80. But the closest one is 23.80 ie. the result.
    round(23.79, 0.5) = 24.00 . The explanation of the result is:
      Closest multiple to the number of 0.5 is on the left : 23.50 and on the right : 24.00. But the closest one is 24.00 ie. the result.
    round(23.79, 10) = 20.00 . The explanation of the result is:
      Closest multiple to the number of 10 is on the left : 20.00 and on the right : 30.00. But the closest one is 20.00 ie. the result.
    round(23.5, 0.1) = 24.00 . The explanation of the result is:
      Closest multiple to the number of 0.1 is on the left : 23.00 and on the right : 24.00. Which have equal distance to the number. In this case bigger one will be taken into account which is 24.00 ie. the result.
    round(23.5, 50) = 0.00 . The explanation of the result is:
      Closest multiple to the number of 50 is on the left : 0.00 and on the right : 50.00. But the closest one is 0.00 ie. the result.

<<<<<<<<<<<<<<<<<<<<<< >>>>>>>>>>>>>>>>>>>>>>

Above shape shows behavior of round function. Thus, There are two boundaries, lower and upper, rounded number will be lower boundary if number is closer to lower boundary and vice versa. Special case the middle boundary ( (lower+upper) / 2 ), which the result will be upper one

Stack Class

Stack Class

Stack Class

Stack Class

Web Popup Weblet

Web popup weblet

KeySum Class

KeySum class is useful for summing data linked to keys. For example you may want to sum Invoice Amount while looping in CustInvoiceJour with grouping in Customer Group. So I recommend to use that handy class. No need to explain in theoretic let’s go practical.

KeySum keySum; // Define keySum class
;
// Initializing class:
// First Param: NumOfKeys : Length of key.
// If you specify that value 2, you have to give it a container which length of it is 2.
// Second Param: NumOfData : Length of data.
// If you specify that value 2, you have to give it a container which length of it is 2.
// Third Param: Sorting
// -1 : Descending order according to key
// 0 : It does not sort
// 1 : Ascending order according to key
keySum = new KeySum(1, 1, 0); // Initialize
keySum.updateNow(1, 15); // keySum = [1 -> 15]
keySum.updateNow(2, 47); // keySum = [1 -> 15, 2 -> 47]
keySum.updateNow(1, 100); // keySum = [1 -> 115, 2 -> 47]
keySum.updateNow(3, 15); // keySum = [1 -> 115, 2 -> 47, 3 -> 15]
keySum.updateNow(7, 47); // keySum = [1 -> 115, 2 -> 47, 3 -> 15, 7 -> 47]
keySum.updateNow(4, 100); // keySum = [1 -> 115, 2 -> 47, 3 -> 15, 7 -> 47, 4 -> 100]
print keySum.key2Data(1); // prints 115 = which is sum of 15 + 100 i.e. the data we loaded earlier for the key 1
print keySum.total(); // prints 324 which is grand total of all the data
print keySum.numOfTrans(); // prints 5 = which is the count of the keys (1,2,3,4,7)
keySum.keyDelete(2); // deletes key 2 -> 47. So total is decreased to 324-47 = 277
keySum.addKeySum(keySum); // addKeySum method can be merged with another keySum object. In this case we did with itself
print keySum.total(); // total is doubled so 277*2 = 554
pause;

Tuesday, May 19, 2009

Working with Dynamics AX Table Meta Data

Background
Recently I had to create a custom database log for a client. Dynamics AX does come with a database log feature (with the proper license key). However, my client wanted something different to allow easier reporting and tracking of changes. The log must be searchable, filterable, and sortable based on any field. For instance, if the CustTable updates were being logged, the user must be able to search, filter, and/or sort by CustGroup, AccountNum, or any other field that was logged. The standard log does not have this capability.

It was decided that in order to achieve these goals, each table to be logged (source) should have its own log table. The log table would mirror the table structure of the source table, with some additional log specific fields, such as log type, date and time of log, changed by, etc. The custom log needed to be able to record the same table events as the standard log (inserts, updates, deletes, and rename keys).

Problem
I had to have a way to perform a field-to-field data transfer from the source table to the log table (ie. source.AccountNum --> log.AccountNum). My initial thought was to copy the data using the intrinsic "data" method on table buffer object. This method allows a table buffer (record) to be passed to the "data" method of another (empty) table buffer to be copied field for field, with the exception of system fields like RecId, RecVersion, etc. It turns out that a field-to-field data copy only works if the two table buffers are of the same table. Unfortunately, even if the field names were the same, the "data" method does not copy the data if the table buffers objects were for different tables.

I needed to write my own data copy function. My function had to be generic, because I didn't want to hard-coded the field name and the mappings between the source and log tables. Additionally, I didn't want to have to update my code each time a new field was added or removed from the source table. In order to make the log function in a generic way, I needed to the meta data within Dynamics AX.

Solution
Below is the code for the static method I created to transfer the data from the source table to the log table. I've placed comments in red to explain the interesting parts of the code.

Basically the method loops through the fields of the source table, and assigns it to the corresponding field in the log table. By using the SysDictTable class, I'm able to retrieve the field list for the source table. This allows me to "see" all the fields, including newly added fields. I also do not have to deal with deleted fields, because they won't appear in the field list.

The challenging part of this method was dealing with array fields. These are fields that are based on an extended data type that is an array. A prime example of this type of field is the "Dimension" field found in many master and transactions tables. Each array element translates to a single field in the table in the database. In Dynamics AX, an array field appears as a single field would. It has a field ID just like any other field, with one exception. An array field has what's called an "extended field ID". This is the combination of the field's ID and the element's position or index in the array. Therefore, to properly retrieve the data for an array field, you must properly address the field's array element to get the correct field ID. This can be done via the "fieldId2Ext" function.

static void copyDataToLog(Common _fromTable, Common _toTable)
{
SysDictTable fromDictTable;
SysDictField fromDictField;
SysDictTable toDictTable;
SysDictField toDictField;
int fields;
int fieldIndex;
int arraySize;
int arrayIndex;
int fromFieldId;
int toFieldId;

;

// Instantiate the SysDictTable object. The SysDictTable class
// extends the DictTable class. By using the SysDictTable class,
// instead of the DictTable class, you get the ability override methods,
// and get the field list, among other things.
//
// I use the SysDictField class in the same way as I use the
// SysDictTable class to process and the retrieve meta data
// about the fields. It provides information about each field.

fromDictTable = new SysDictTable(_fromTable.TableId);
toDictTable = new SysDictTable(_toTable.TableId);

// Loop through the field list for the source table.
// Note that the loop excludes the system fields, such as
// RecId, RecVersion, CreatedDate, CreatedBy, etc. Each
// table will have its own set of system fields, which will be
// populated by Dynamics AX when the record is inserted.

fields = fromDictTable.fieldCnt();

for (fieldIndex = 1; fieldIndex <= fields; fieldIndex++)
{
fromDictField = new SysDictField(fromDictTable.id(),fromDictTable.fieldCnt2Id(fieldIndex));

if (!fromDictField.isSystem())
{
// Some tables contain fields that inherit from array extended data
// types. An example of an array field is the "Dimension" field,
// found in tables throughout the database.
// Each element in the array field corresponds to a real field
// in the database. The concept of field ID still exists for array
// fields, but with a little twist. The field ID for each
// array element is called an "extended field ID". This extended
// field ID is the combination of the field ID and the array index
// for the element.
// The function "fieldId2Ext" provides a way to get an array field's
// field ID.

if (fromDictField.arraySize() > 1)
{
// Field is an array (ie. Dimension).
arraySize = fromDictField.arraySize();

// Loop through each array field's element to copy its value
// to the corresponding log array field's element.

for (arrayIndex = 1; arrayIndex <= arraySize;arrayIndex++)
{
fromFieldId = fieldId2Ext(fromDictField.id(),arrayIndex);
toFieldId = fieldId2Ext(fieldName2Id(_toTable.TableId, fromDictField.name()), arrayIndex);

// Note that field values may be assigned and retrieved using
// the field ID instead of the field name.

_toTable.(toFieldId) = _fromTable.(fromFieldId);
}
}
else
{
// Use the "fieldName2Id" function to convert the field name
// from the source table to the corresponding field ID for the
// log table. This is possible because the field names in the log
// table are the same as the source table.

toFieldId = fieldName2Id(_toTable.TableId,fromDictField.name());

_toTable.(toFieldId) = _fromTable.(fromDictField.id());
}
}
}
}




Conclusion
Using this static method, I'm able to copy the source table fields to the log table fields. This method illustrates one possible usage of system meta data as provided by the "Dict*" classes to perform generic work. These classes provided the necessary information about the table and fields to perform the task without the need to hard-code table or field names. There are several other "Dict" classes that provide information about other application objects.

Some ref to utilelement table and creation of shared project

As far my experience utilelements table get updated when the service is started.
It may also get updated when the customisation is imported into the application.

The code shown below how to delete a shared project

treenode c1,c2;
;

c1 = systemnode::getsharedproject();
c2 = c1.aotfindchild("project1");

c2.aotdelete();
c2.aotrefresh();
c2.aotsave();

Sunday, April 19, 2009

Find out the string length of an extendedDataType

static void TestJob(Args _args)

{

Dictionary dict;

DictType dictType;

;

dict = new Dictionary();

dictType = dict.typeObject(dict.typeName2Id(extendedtypestr(AccountName)));

info(strfmt("Name : %1 \nId: %2 \nStringLength: %3 \nAdjustment: %4 \nLabel: %5 \nHelp: %6 \nBasetype: %7", dictType.name(), dictType.id(), dictType.stringLen(), dictType.stringRight() ? "Right" : "Left", dictType.label(), dictType.help(), int2str(dictType.baseType()) + " - " + enum2Value(dictType.baseType())));
}

Tuesday, March 31, 2009

how to pass the parameter form form a to form b

I have two forms, Form1 and Form2. I call the Form2 on the click event of button from Form1. Now, from Form2 I want some values to be transfered to Form1. How would I do that?

I can easily pass value from Form1 to Form2 using:

args.parm("hello" );

and can get value on Form2 using:

info(element. args().parm( ));

But how would I pass value from Form3 to Form1? How can I pass more than one values from Form3 to Form1?

You can use args.parmObject( ) to pass object as a parameter,
or pass reference of form1 by args.caller( )

Thursday, March 5, 2009

How to create a crash dump for Axapta processes ?

All clients lose connection to the AOS Server. If the Axapta AOS or client process crashes for any reason, it is recommended to create a crash dump, of the failing process.

Here are the steps to do so:

  1. Download the Microsoft debugging tools, from here: 
    http://www.microsoft.com/whdc/devtools/debugging/installx86.Mspx 
    The 64-bit Version is available here: 
    http://www.microsoft.com/whdc/devtools/debugging/install64bit.mspx
  2. Install the debugging tools on the problem machine.
  3. Switch to the install folder of the debugging tools using the command line.
  4. Attach the tracing tool(adplus) to the crashing process. 
  5. Use the following command line options to attach the debugger. 
    adplus –crash –pn ax32.exe
    adplus –crash –pn ax32serv.exe

    or
    adplus –crash –p 

    To enable the Process ID, go to Task Manager\View\Select Columns… 
    PID (Process Identifier)
  6. Now the tracing tool is active and should generate a crash dump, once the process  crashes.
  7. You will find the dump file in the installation folder of the debugging tools.
  8. A correct crash dump file should look like this: 
    PID-4580__AX32.EXE__2nd_chance_UnknownException__full_1810_2008-12-04_20-53-48-528_11e4.dmp 
    Please focus on the 2nd_chance in the title. Please reattach the tracing tool again in case you get only 1st_chance exception, or just a log file(has a *.log extension). 
    Example: 
    example1 
  9. Once you have created a second chance crash dump file, send it to the Microsoft MBS support to get the file analyzed in detail.

Creation of new financial dimension

Many a times there is requirement from many customers to create a new financial dimension in AX apart from the three standard dimensions "Department", "Cost center" and "Purpose". Here is a step by step description of creating a new financial dimension.
 
To create a new financial dimension modify following objects one by one
 
    • Base enum "SysDimension" : Find this base enum and right click on this base enum -> Select option "New element". In properties window give a name to this element say "TestDim" and label as "Test dimension". Save the base enum.
    • Extended data type "Dimension" : Find this EDT and then add a new array element in this EDT. Name this array element as "TestDim". In properties window specify label as "Test dimension". Now in the "Relations" tab of this new array element add a new "Normal"  relation first. To this normal relation open properties window and set property Table as "Dimensions" and Related field as "Num". Now add another relation of type "Related field fixed". To this related field fixed relation open properties window and set Related field as "DimensionCode" and property value as "3" (this is the value of the new enum element created in SysDimension). Save the EDT.
    • Extended data type "DimensionCriteria" : Repeat the process of modification as done for EDT "Dimension" above.
    • Extended data type "XMLMapDimension" : Find this EDT in AOT and then create a new array element for this EDT. Label this array element as "Test document value". Save the EDT.
    • Extended data type "MandatoryDimension" : Find this EDT in AOT and then create a new array element for this EDT. Label this array element as "Validate test dimension". Save the EDT.
    • Extended data type "DimensionLedgerJournal" : Find this EDT in AOT and then create a new array element for this EDT. Label this array element as "Test dimension". Save the EDT.
    • Extended data type "DimensionKeepFromTransaction" : Find this EDT in AOT and then create a new array element for this EDT. Label this array element as "Keep transaction test dimension". Save the EDT.
    • Extended data type "COSAllowDimensions" : Find this EDT in AOT and then create a new array element for this EDT. Label this array element as "Test dimension". Save the EDT.
    • Extended data type "DimensionPriority" : Find this EDT in AOT and then create a new array element for this EDT. Label this array element as "Test dimension". Save the EDT.
    • Extended data type "DimensionAllocation" : Find this EDT in AOT and then create a new array element for this EDT. Label this array element as "Test dimension". Save the EDT.
    • Table "LedgerJournalTrans" : Find this table in AOT and add a new relation in the relations tab as follows. Create a new relation in relations tab and name it as say "interCoDimension3" (You can see three more similar relations with suffix 0, 1 and 2 for three standard dimensions). Now set the property table of this relations as "Dimensions". Create a new "Normal" relation under this realtion tab and set the property "Field" = "OffsetCompany" and property "RelatedField" = "dataAreaId". Create another "Normal" relation under this relation tab and set the property "Field" = "InterCoDimension[4]" and property "RelatedField" = "Num". Now create a new "Related field fixed" relation under this tab and set property "Value" = 3 (this is the value of the new enum element created in SysDimension) and property "Related field" = "DimensionCode". Save the changes.

The new financial dimension is successfully created in AX and can be viewed in different forms through out the AX where ever dimensions are used.

Wednesday, February 4, 2009

Extracting data from Ax3.0 to Xml

Sample code to get XML from table:
============ ========= ==
static XML getAllDlvMode( )
{
DlvMode dlvMode;
XML xml;
;
xml = '';
while select Code, txt
from dlvMode
where dlvMode.dataAreaId == 'dat'
{
xml += dlvmode.xml( );
}
xml += '
';
return xml;
}
============ ========= ========= =======

Sample code to load and parse XML file:
============ ========= ========= =======
XMLDocument XMLDOM;
XMLNodeList XMLNodes_ItemOut;
TextBuffer tbPurchOrder;
boolean blnXMLLoaded;
str nodeText;
;
XMLDOM = new XMLDocument( ) ;
XMLDOM.async( false);
XMLDOM.setProperty( "ServerHTTPReque st", true);
strUNCFileName_ reg = strfmt("%1", _strFileName) ;
tbPurchOrder = new TextBuffer() ;
tbPurchOrder. fromFile( strRTrim( strLTrim( strUNCFileName_ reg)));
temp = tbPurchOrder. getText() ;
blnXMLLoaded = XMLDOM.loadXML( temp);

if(blnXMLLoaded)
{
//parse the XMLDOM
nodeText = strXMLDOM.selectSin gleNode(" path/nodeName" ).text
();
}

============ ========= ========= ========= ========= =========


Sample code to write a file
============ ========= ========= ========= ========= ========= =========
AsciiIo oAsciiIo;
str strFilePath, strMode, strLog;
;
//oAsciiIo = new AsciiIo(strFileName , "A");
oAsciiIo = new AsciiIo(strFilePath , strMode);
oAsciiIo.write( strLog);
/*
You may use WinAPI static methods to create and manage folders.
e.g.: if (!WinAPI::folderExi sts(strFolderPat h)) 
WinAPI::createDirec tory(strFolderPa th);
*/

============ ========= ========= ========= ========= ========= ========

Working with Dynamics AX Table Meta Data

Background
Recently I had to create a custom database log for a client. Dynamics AX does come with a database log feature (with the proper license key). However, my client wanted something different to allow easier reporting and tracking of changes. The log must be searchable, filterable, and sortable based on any field. For instance, if the CustTable updates were being logged, the user must be able to search, filter, and/or sort by CustGroup, AccountNum, or any other field that was logged. The standard log does not have this capability.

It was decided that in order to achieve these goals, each table to be logged (source) should have its own log table. The log table would mirror the table structure of the source table, with some additional log specific fields, such as log type, date and time of log, changed by, etc. The custom log needed to be able to record the same table events as the standard log (inserts, updates, deletes, and rename keys).

Problem
I had to have a way to perform a field-to-field data transfer from the source table to the log table (ie. source.AccountNum --> log.AccountNum). My initial thought was to copy the data using the intrinsic "data" method on table buffer object. This method allows a table buffer (record) to be passed to the "data" method of another (empty) table buffer to be copied field for field, with the exception of system fields like RecId, RecVersion, etc. It turns out that a field-to-field data copy only works if the two table buffers are of the same table. Unfortunately, even if the field names were the same, the "data" method does not copy the data if the table buffers objects were for different tables.

I needed to write my own data copy function. My function had to be generic, because I didn't want to hard-coded the field name and the mappings between the source and log tables. Additionally, I didn't want to have to update my code each time a new field was added or removed from the source table. In order to make the log function in a generic way, I needed to the meta data within Dynamics AX.

Solution
Below is the code for the static method I created to transfer the data from the source table to the log table. I've placed comments in red to explain the interesting parts of the code.

Basically the method loops through the fields of the source table, and assigns it to the corresponding field in the log table. By using the SysDictTable class, I'm able to retrieve the field list for the source table. This allows me to "see" all the fields, including newly added fields. I also do not have to deal with deleted fields, because they won't appear in the field list.

The challenging part of this method was dealing with array fields. These are fields that are based on an extended data type that is an array. A prime example of this type of field is the "Dimension" field found in many master and transactions tables. Each array element translates to a single field in the table in the database. In Dynamics AX, an array field appears as a single field would. It has a field ID just like any other field, with one exception. An array field has what's called an "extended field ID". This is the combination of the field's ID and the element's position or index in the array. Therefore, to properly retrieve the data for an array field, you must properly address the field's array element to get the correct field ID. This can be done via the "fieldId2Ext" function. 

static void copyDataToLog(Common _fromTable, Common _toTable)
{
SysDictTable fromDictTable;
SysDictField fromDictField;
SysDictTable toDictTable;
SysDictField toDictField;
int fields;
int fieldIndex;
int arraySize;
int arrayIndex;
int fromFieldId;
int toFieldId;

;

// Instantiate the SysDictTable object. The SysDictTable class
// extends the DictTable class. By using the SysDictTable class,
// instead of the DictTable class, you get the ability override methods,
// and get the field list, among other things.
//
// I use the SysDictField class in the same way as I use the
// SysDictTable class to process and the retrieve meta data
// about the fields. It provides information about each field.

fromDictTable = new SysDictTable(_fromTable.TableId);
toDictTable = new SysDictTable(_toTable.TableId);

// Loop through the field list for the source table.
// Note that the loop excludes the system fields, such as
// RecId, RecVersion, CreatedDate, CreatedBy, etc. Each
// table will have its own set of system fields, which will be
// populated by Dynamics AX when the record is inserted.

fields = fromDictTable.fieldCnt();

for (fieldIndex = 1; fieldIndex <= fields; fieldIndex++)
{
fromDictField = new SysDictField(fromDictTable.id(),fromDictTable.fieldCnt2Id(fieldIndex));

if (!fromDictField.isSystem())
{
// Some tables contain fields that inherit from array extended data
// types. An example of an array field is the "Dimension" field,
// found in tables throughout the database.
// Each element in the array field corresponds to a real field
// in the database. The concept of field ID still exists for array
// fields, but with a little twist. The field ID for each
// array element is called an "extended field ID". This extended
// field ID is the combination of the field ID and the array index
// for the element.
// The function "fieldId2Ext" provides a way to get an array field's
// field ID.

if (fromDictField.arraySize() > 1)
{
// Field is an array (ie. Dimension).
arraySize = fromDictField.arraySize();

// Loop through each array field's element to copy its value
// to the corresponding log array field's element.

for (arrayIndex = 1; arrayIndex <= arraySize;arrayIndex++)
{
fromFieldId = fieldId2Ext(fromDictField.id(),arrayIndex);
toFieldId = fieldId2Ext(fieldName2Id(_toTable.TableId, fromDictField.name()), arrayIndex);

// Note that field values may be assigned and retrieved using
// the field ID instead of the field name.

_toTable.(toFieldId) = _fromTable.(fromFieldId);
}
}
else
{
// Use the "fieldName2Id" function to convert the field name
// from the source table to the corresponding field ID for the
// log table. This is possible because the field names in the log
// table are the same as the source table.

toFieldId = fieldName2Id(_toTable.TableId,fromDictField.name());

_toTable.(toFieldId) = _fromTable.(fromDictField.id());
}
}
}
}




Conclusion
Using this static method, I'm able to copy the source table fields to the log table fields. This method illustrates one possible usage of system meta data as provided by the "Dict*" classes to perform generic work. These classes provided the necessary information about the table and fields to perform the task without the need to hard-code table or field names. There are several other "Dict" classes that provide information about other application objects.

How to identify the user that was used to change an object from AOT in AX2012

Get the object name for which we need to track these (user and date&time) information's. Login to SQL Server Management Studio an...