Sunday, October 24, 2010

Using RunBaseBatchPrintable class

RunBase framework is used when we develop a class to run an operation within Dynamics AX, such as posting a sales order or used in any data manipulation operation. If we need to batch the operation, we will commonly use the RunBaseBatch framework by extending the RunBaseBatch class.
Dialog for a class that extends RunBaseBatch
The RunBaseBatch class does not provide the access to printer such as when running a report. If you need to provide the printer support to allow users to specify the printer options in the operation, you can extend the RunBaseBatchPrintable class instead of RunBasebatch class.
Dialog for a class that extends RunBaseBatchPrintable
When extending the RunBaseBatch class, you need to override the pack() and unpack() methods. If query is used in the operation, you may need to also override the initParmDefault(), queryRun(), and showQueryValues() methods.

When extending the RunBaseBatchPrintable class, in addition to the above methods, you would also need to make the following amendments:-
1) Modify the pack() to serialise the printJobSettings object (inherits from RunBaseBatchPrintable class)
public container pack()
{
    ;
    return [#CurrentVersion,
            #CurrentList,
            queryRun.pack(),
            printJobSettings.packPrintJobSettings()  // Serialise the printJobSettings object
           ];
}
2) Modify the unpack() to deserialise the printJobSettings object
public boolean unpack(container _packedClass)
{
    Version         version = RunBase::getVersion(_packedClass);
    container       packedQuery;
    container       packedPrintJobSettings;
    ;
    switch (version)
    {
        case #CurrentVersion:
            [version, #CurrentList, packedQuery, packedPrintJobSettings] = _packedClass;
            if (packedQuery)
            {
                queryRun = new QueryRun(packedQuery);
            }
            
            // Deserialise the printJobSettings object
            if (isSwappedFromServer)  
            {
                printJobSettings = SysPrintOptions::newPrintJobSettingsOnServer(packedPrintJobSettings);
            }
            else
            {
                printJobSettings = new PrintJobSettings(packedPrintJobSettings);
            }
            break;

        default:
            return false;
    }
    return true;
}
3) To retrieve the user-selected printer settings, you can use the printJobSettings variable directly in the code. For example:-
public void run()
{
    ;
    info(strfmt('Selected printer is %1', printJobSettings.printerPrinterName()));

    // pass the printJobSettings object to run a report based on the selected printer settings
    reportRun = classFactory.reportRunClass(args);
    reportRun.init();
    reportRun.printJobSettings(printJobSettings.packPrintJobSettings());
    reportRun.run();
}
Happy DAXing!!

Tuesday, October 19, 2010

Unable to submit workflow when non form root data source is used as workflow data source

If your workflow data source is NOT the form root data source, then when you submit the workflow you might encounter the following error:-
Wrong argument types in variable assignment.
The variable assignment error is due to different record is received by the SubmitToWorkflow class rather than the specified workflow record.

Although the WorkflowDatasource property in the form has been specified properly, the SubmitToWorkflow class will always use the form root data source as the workflow data source.
After debugging, I realised that the problem actually lies in the SysWorkflowFormControls class\ initControls() method. The line below
actionBarSubmitButton.dataSource(workflowDatasource.name());
is not working properly to assign the workflowDatasource to the workflow Submit button. Hence the Submit button treats its DataSource property is empty and use the root data source.

I created a quick test to add the SubmitToWorkflow menu item button manually to the form and use the above code to assign the data source. It was still not working.
Then I created another SubmitToWorkflow menu item button, but instead of using code, I specify the data source for the button using AOT Properties window. And this time the workflow was submitted.
So I can confirm that the MenuItemButton.dataSource() method did not do its job properly.

So what’s the workaround? If the DataSource property in MenuItemButton is empty, it will first use the DataSource specified in its parent container (i.e. Group control), and continue search upward till the DataSource specified in the form Design node. If no data source found, it will use the form root/first data source by default.
So to fix the workflow problem, we can use the above behaviour to implicitly assign the data source to the SubmitToWorkflow button. One way to do this is in the SysWorkflowFormControls class\ initControls(), add the below line
actionBarGroup.dataSource(workflowDatasource.name());
to assign the data source to group control that contains the button, which will then inherited by the button.

Tuesday, September 14, 2010

Unable to find Dynamics AX Web Project template in Visual Studio 2008

Here is some of the teething problems I encountered recently when start developing on Dynamics AX 2009 Enterprise Portal (hope it won't be too late to learn EP!) :-

1) Unable to find Dynamics AX Web Project template in Visual Studio 2008
Although AX Enterprise Portal developers tools has been installed on the MOSS / SharePoint EP server, the Dynamics AX Web Project template still cannot be found in Visual Studio. This is a standard issue with AX 2009 Enterprise Portal developers tools where it only works fine on the account that doing the installation. The AX template is not automatically deployed to other user accounts. The blog post here provides a very details troubleshooting steps: http://mbsturk.blogspot.co.uk/2011/02/missing-ax-ep-web-project-in-visual.html.

2) When opening the Dynamics AX Web Project in Visual Studio, the following error message is displayed
"Unable to connect to Microsoft Dynamics AX. The Dynamics AX Enterprise Portal Tools are not available".
To resolve this issue, verify that the configuration for .NET Business Connector is correct using the AX Client Configuration Utility. One quick way I always use to verify the Business Connector configuration is to export the active configuration to an .axc file and open it directly to see whether an AX Client session could be established successfully.

3) Changes made in Visual Studio not reflected in Enterprise Portal
By default, when you save the changes made in Enterprise Portal, for example, modification to a User Control, it will automatically reflected in Enterprise Portal. If this does not work, you can manually deploy the User Control modification by go to AOT\Web Files\Web Controls, right-click the Web Controls node and choose Deploy. When you save the User Controls changes in Visual Studio, the modification is saved directly to the web controls object in AOT\Web Files\Web Controls. Be sure to restore the modified Web controls object (right-click > select Restore) first before you do the deployment. Visual Studio will use the active Client configuration to determine which layer the changes to be saved in.

Happy EP...ing!

Friday, July 23, 2010

SysQueryRangeUtil class - use method name as query criteria

This is a cool new feature in AX 2009 - you can now use a method name instead of literal value in query criteria!

For example, if you run a report every month, previously you need to change the date criteria in each month you running the report (ie. “01/06/2010..30/06/2010”,  “01/07/2010..31/07/2010”, etc).
Now you only need to enter (monthRange(0, 0)) which will automatic return the correct first day and last day of the month.

Refer to the SysQueryRangeUtil class – there are other useful methods such as dateRange(), currentEmployeeId(), etc. It’s also possible to create new method on this class for special purpose!

Thursday, July 22, 2010

Installing Dynamics AX 2009 Client on Windows 7

When I try to install Dynamics AX 2009 Client on Windows 7, the installation failed with the following error:-
RegAsm : error RA0000 : Could not load file or assembly 'System.Design, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' or one of its dependencies. The system cannot find the file specified.
...
Install: During Setup of an error occurred. Program run: C:\Windows\Microsoft.NET\Framework\v4.0.30319\regasm.exe. Parameters: C:\Program Files\Microsoft Dynamics AX\Common\MindKeyGroup.MindKey.Wheel.dll /unregister
This looks strange to me because Windows 7 is certified for running AX 2009 Client. After looking into details of the error, it seems that .NET Framework v4.0 is used during the installation.
I uninstalled the Microsoft .NET Framework 4 Client Profile and install again the AX 2009 Client. The installation then was successfully.

Tuesday, July 6, 2010

Dynamics AX 2009 Rollup 5 has been released!

Dynamics AX 2009 Rollup 5 is available for download now. Refer to the post from Dynamics AX Sustained Engineering team here:-
http://blogs.technet.com/b/dynamicsaxse/archive/2010/06/30/dynamics-ax-2009-rollup-5-has-been-released-to-partner-source-and-customer-source.aspx

Update: Direct links to download the Rollup 5 as below:
AX 2009 Rollup 5 KB982811 (Build # 5.0.593.1287)
AX 2009 SP1 Rollup 5 KB982812 (Build # 5.0.1500.2985)

strFmtLB and the newline character (\n) in label files

You want to split your info message into multiple lines, so you use the \n newline character.
You write the following code:-
info("Line1\nLine2");
You tested it's working and the output message is split into 2 lines as below:-
Line1
Line2

Now you've converted the string into a label, so you update your code such as the one below:-
info("@WKN1");
But when you re-run the code, you realised that the newline character is no longer working and printed directly on screen. The message is displayed as one line instead of two as below:-
Line1\nLine2

To display the message correctly, you need to use the global method strFmtLB:-
info(strFmtLB("@WKN1"));
If your string message contains placeholders (%1, %2, etc.), then you can use both strFmtLB and strFmt methods together:-
info(strFmtLB(strFmt("@SYS39890", "Line1", "Line2")));
Result of running the above code:-
Line1
 Line2

Saturday, June 26, 2010

Sending emails from Dynamics AX

It’s a common requirement to have Dynamics AX to send emails as part of the business processes such as delivering the invoice/statement to customers by email. Out of box Dynamics AX already has the framework provided in X++ classes for developers to send email using X++ code. There are currently three ways to do so and each has its own features/limitation as described below:-

1) SysInetMail class
This is used in the standard MorphX report if E-mail recipient is selected as Print medium. It requires an email program to be installed at the AX client computer and the mail message will be sent using the email program. The advantage using this method is it provides the flexibility for users to edit the email before it get sent out, but also means you can’t use it automate the sending of email because it requires the user’s intervention to manually click the Send button.
One workaround for this is you can modify the reportSendMail() method in the Info class. This method is called when the report’s print medium is specified as E-mail recipient. So you can modify this method to email the report automatically using the two other following options instead of SysInetMail class.
SysINetMail m = new SysINetMail();
m.sendMailAttach(..);

2) SysEmailTable::sendMail(..) table method
This method uses the SysMailer class and the underlying framework which utilise the CDO COM object to send email. It is commonly used by standard Dynamics AX system when emailing function is required such as the sending of Alert email, Workflow, etc.
To use this method, you need to set up two things:-i) SMTP relay server information in the E-mail parameters (Administration > Setup > E-mail parameters), ii) Email template that specify the subject and the body of email, supported in plain text or HTML format (Basic > Setup > E-mail templates), and the use of placeholders.
There are a lot of advantages using this way of sending email:- The email body is user-definable using Email template and placeholders, the email could be sent without user intervention, the email sending process can be scheduled in batch job and retry sending when failed, and you also can attach file to the email (although it only support one file attachment).
SysEmailId sysEmailId = 'Alerts';
str  recipientEmail = 'john@contoso.com';
;
SysEmailTable::sendMail(
    sysEmailId,
    SysEmailTable::find(sysEmailId).DefaultLanguage,
    recipientEmail,
    null,
    '',
    '',
    true,
    curuserid(),
    true);

3) System.Net.Mail .NET class
The last option is to use the .NET classes to send email. It is very simple to use and yet it provides a lot of flexibilities. You can specify multiple recipients, cc and bcc, attach multiple files, turn on the email delivery notification and read receipt notification, and you can also archive the mail message in .eml file format.
System.Net.Mail.MailMessage mailMessage;
System.Net.Mail.SmtpClient smtpClient;
System.Net.Mail.MailAddress mailFrom;
System.Net.Mail.MailAddress mailTo;
str    smtpServer;
;

mailFrom = new System.Net.Mail.MailAddress('sender@contoso.com',"Sender name");
mailTo  = new System.Net.Mail.MailAddress('recipient@contoso.com',"Recipient name");

smtpServer = SysEmaiLParameters::find(false).SMTPRelayServerName;// using the SMTP server ip setup in Email Parameters
mailMessage = new System.Net.Mail.MailMessage(mailFrom,mailTo);
mailmessage.set_Subject('This is email subject');
mailmessage.set_Body('This is email body');

smtpClient = new System.Net.Mail.SmtpClient(smtpServer);
smtpClient.Send(mailmessage); 
mailMessage.Dispose();

If you want to use Email template instead of hard code the email body, consider using the following approach:-
System.Net.Mail.MailMessage mailMessage;
SysEmailMessageTable  message;
str    messageBody;
SysEmailId   sysEmailId = 'Alerts';
Map    mappings = new Map(Types::String, Types::String);
;
...
mappings.insert('message', 'This is the message');
message = SysEmailMessageTable::find(sysEmailId, SysEmailTable::find(sysEmailId).DefaultLanguage);
messageBody = SysEmailMessage::stringExpand(message.Mail, SysEmailTable::htmlEncodeParameters(mappings));
messageBody = WebLet::weblets2Html4Help(messageBody, '');
mailMessage.set_Body(messageBody);
mailMessage.set_IsBodyHtml(true);
...
To enable the Delivery Notification feature:-
System.Net.Mail.DeliveryNotificationOptions deliveryOptions;
;
...
deliveryOptions = ClrInterop::parseClrEnum('System.Net.Mail.DeliveryNotificationOptions', 'OnSuccess');
mailMessage.set_DeliveryNotificationOptions(deliveryOptions);
...
To save the mail message to .eml file instead of sending the email:-
System.Net.Mail.SmtpClient              smtpClient;
System.Net.Mail.SmtpDeliveryMethod      stmpDeliveryMethod;
;
...
stmpDeliveryMethod = ClrInterop::parseClrEnum('System.Net.Mail.SmtpDeliveryMethod', 'SpecifiedPickupDirectory');
smtpClient.set_DeliveryMethod(stmpDeliveryMethod);
smtpClient.set_PickupDirectoryLocation('C:\\Temp');
...
smtpClient.Send(mailMessage);  // Save to file instead of sending

Now you have created the codes and want to test the email sending job. You can use the Windows SMTP virtual server for the quick test if you do not have a SMTP server. After installing the SMTP server, you need to go to IIS Manager -> Default SMTP Virtual Server -> right-click Properties -> Access tab -> Relay button -> Select All except the list below, to enable to relay all e-mail messages through the current server.
Note: For Windows Server, you need to open IIS 6.0 Manger.

To specify the SMTP settings in AX, go to Administration -> Setup -> E-mail parameters, specify the 'localhost' in Outgoing mail server and 25 in SMTP port number.
If you are using the method 2 above (SysEmailTable), after calling the sendMail(..) method, you can check the email sending status in Administration -> Periodic -> E-mail processing -> E-mail sending status. To activate the actual email sending process, go to Administration -> Periodic -> E-mail processing -> Batch.

Saturday, June 12, 2010

Using .NET enum in X++

With the CLRInterop feature, you can easily write X++ codes that instantiate and call .NET classes. One of the limitation in IntelliSense is it does not support auto displaying the .NET enumeration value. Luckily there is a useful method called ClrInterop::parseClrEnum(_clrEnumTypeName, _enumValues), which you can convert a string of the .NET enum value to a CLRInterop object.

Example of the usage:-
System.Net.Mail.MailMessage             mailMessage;
System.Net.Mail.DeliveryNotificationOptions deliveryOptions; //.NET enum
;
deliveryOptions = ClrInterop::parseClrEnum('System.Net.Mail.DeliveryNotificationOptions', 'OnSuccess');
mailMessage.set_DeliveryNotificationOptions(deliveryOptions);

Keyboard shortcuts for date field

There are two keyboard shourtcut keys that you could use to enter today's date in Dynamics AX date field, namely the 'd' and 't' keys. The difference between these two keys is 't' is the machine date from Windows and 'd' is the session date which could be changed in Dynamics AX client Tools menu -> Session date. From the development perspective, 't' is the same as the today() method, and 'd' is same as the systemDateGet() method.

Saturday, May 8, 2010

Error in project proforma invoice posting

Recently I discovered a bug in Dynamics AX proforma invoice posting in the Project module. If you post project proforma invoices (print without posting) and choose Print = After, the system will give you the error "Cannot create a record in Project invoice (ProjInvoiceJour)".

This error only happen when you post several project proforma invoices in batch (ie. using the Periodic\Post invoice function for group posting instead of posting them individually).
After diving into the X++ codes, I realised that this is kind of a design error. As with proforma invoice posting in Sales ledger and Purchase ledger, project proforma invoice posting will first insert a record in ProjInvoiceJour with blank Invoice id, and then delete this record after printing. This is not an issue when Print = Current because the record is deleted immediately after printing. But if Print = After, the system will accumulate the records in a RecordSortedList object until the posting is completed only then print and delete the proforma invoice records. In this case, when there is more than one invoice lines have the same posting/invoice date, inserting with a blank Invoice id will violate the primary key index rule in ProjInvoiceJour table, thus giving the above error message. You have to manually writing job to delete the blank Invoice id record in ProjInvoiceJour table because it will prevent any further proforma invoice posting on the same date. You cannot delete it from Table browser because the ProjInvoiceJour table is locked from user deletion.


The interesting bit here is the same error will have not happen in Sales and Purchase proforma invoice posting (or actually is printing). The system disables the Print [Current/After] checkbox if you select the Posting checkbox. And in the posting classes, it will only allow the use of Print = After when it is not proforma. I considered this is a bug, and it is found in both Dynamics AX 2009 and AX 4.0. I did not have a chance to test it on version 3.0.

Thursday, April 29, 2010

Ledger postings for Sales Tax / VAT

Below are some of the typical ledger postings related to Sales Tax / VAT.
Purchase invoice posting:-
Dr Stock / Purchase consumption (invoice amount)
Dr Sales tax receivable (tax amount)
               Cr Vendor (invoice amount + tax amount)

Sales invoice posting:-
Dr Customer (invoice amount + tax amount)
               Cr Sales revenue (invoice amount)
               Cr Sales tax payable (tax amount)

Settle the Sales tax payable against Sales tax receivable using GL journal
Dr/Cr is depending on whether the Sales tax receivable is larger or Sales tax payable is larger.
(If Sales tax payable is larger)
Dr Sales tax payable
               Cr Sales tax receivable
               Cr Sales tax settlement

Make payment to tax authorities for the balance of Sales tax payable (now stored in Sales tax settlement)
Dr Sales tax settlement
               Cr Bank

Purchase invoice posting when Use Tax is activated
If GL parameters - Apply US Sales tax and use tax rules = not selected
Dr Stock / Purchase consumption (invoice amount)
Dr Use Tax expense (use tax amount)
               Cr Vendor (invoice amount)
               Cr Use Tax payable (use tax amount)

If GL parameters - Apply US Sales tax and use tax rules = selected
Dr Stock / Purchase consumption (invoice amount + use tax amount)
               Cr Vendor (invoice amount)
               Cr Use Tax payable (use tax amount)

Common account type for the sales tax-related ledger accounts:-
Sales tax payable: Balance
Sales tax receivable: Balance
Sales tax settlement: Liability (Balance)
Use Tax expense: Profit and loss
Use Tax payable: Balance


If GL parameters - Apply US Sales tax and use tax rules = selected, you will not able to specify the Sales tax receivable and Use tax expense in the Sales tax setup\Ledger posting groups.

Patching in Microsoft Dynamics AX 2009

Recently I found this interesting information about patching in Dynamics AX 2009. If you want to understand more about how to install hotfixes and the Dynamics AX 2009 servicing architecture, Microsoft actually have published a white paper about it. Basically it explains the two types of standard hotfixes available in AX 2009: Binary hotfixes and Application hotfixes. It also explains the steps to perform the installation for these two types of hotfixes. Even better, the link below also included a video on step by step installing the hotfix.

Note: You need Partner Source login to access this information.

Wednesday, April 14, 2010

Kernel version and Application version

You can check the Kernel version and Application version in the Dynamics AX client by clicking Help > About Microsoft Dynamics AX. Usually both numbers will be the same, but this might not be the case in all time.

Kernel version
This is the kernel build number for the Dynamics AX binary executable files. The About form is only showing the kernel version of the currently running instance of AX client (Ax32.exe). To view the server kernel version, you need to check the file property for the Ax32serv.exe. The kernel version will be updated when you perform an AX upgrade or install a service pack, i.e. from AX 2009 RTM to AX 2009 SP1 that affects the binary files, or when you install a hotfix that contains binary patch (patch via msp or dll files).

Application version
This is the version number for the currently installed application. The version number is stored as static text in the class ApplicationVersion::applBuildNo(). The number will be updated when you perform an AX upgrade or install a service pack, or when you install certain hotfix that contains application patch (patch via xpo import).

The post below shows a list of all kernel version and application version from Axapta 2.5 to AX 2009 SP1. You can also use this to verify whether you have installed any service packs or hotfix roll-ups.
http://blogs.msdn.com/emeadaxsupport/archive/2009/07/01/overview-ax-kernel-build-numbers.aspx

Dick has written a sample code to check the kernel version for both client and server:-
http://axstart.spaces.live.com/Blog/cns!E82F2C8CB173C0A0!350.entry

Tuesday, April 13, 2010

Methods for refreshing form data

It is easy to confuse which method is the correct one to use for refreshing data in forms since Dynamics AX has 4 different form refresh methods:- refresh(), reread(), research() and executeQuery().

Vanya Kashperuk has posted an excellent article on explaining the usages of these methods.
http://kashperuk.blogspot.com/2010/03/tutorial-reread-refresh-research.html