Wednesday 2 October 2013

Physical inventory reports slow


We investigated the situation where the physical inventory reports were running slower and slower. Months ago the reports only took a couple of minutes to run, now it was over an hour. Users tended to kill the sessions which caused the report to run even slower. 

The report uses two tables in AX to build up the report data:

  • InventSumDateTable
  • InventSumDateTrans
When the report is started, the data in the tables is filled and after completing the generation of the report, the tables are emptied. However, when someone kills the sessions which is generating the report, the data in the tables isn't deleted and causes the report to generate slower on the next run. 

When you have performance issues, check if these tables contains records and if so, delete them. After we cleaned up the data, the report ran within minutes again. 

Tuesday 1 October 2013

Reset number sequences Dynamics AX

When you create a new company and copy the setup / parameter tables from another company, you'll want to reset the number sequences.

static void JLH_ResetNumberSequences(Args _args)
{
    NumberSequenceTable         numberSequenceTable;
    NumberSequenceList            numberSequenceList;
    ;

    // We want to be very sure that we are in the correct company :-)

    if (curExt() == '130')
    {
        ttsbegin;

        // First reset all number sequences

        while select forupdate numberSequenceTable
        {
            numberSequenceTable.NextRec = numberSequenceTable.Lowest;
            numberSequenceTable.doUpdate();
        }

        // Then delete exiting records in the lists

        delete_from numberSequenceList;

        ttscommit;
        info("Number sequences have been reset");
    }
    else
    info(strfmt("Number sequence of company %1 should not be reset", curExt()));

}

Wednesday 11 September 2013

Dependable batch jobs in AX with reference to external databases


There are tasks in AX which you want to run in a sequential order. For instance, you'd want the posting of the packing slips to be done before the invoice run and you want Forecast scheduling to be run prior to Master scheduling. 


When running these tasks in batch, you can can calculate some expected time frames and start task A at 4.00 AM and task B at 6.00 AM. However, when task A doesn't finish in time, it could have effect on task B. 

To prevent this from happening, you can build in some dependencies in AX. 

First create a new batch job with a recognisable description and click [View Tasks].


Here you create a new line manually, select the company account and select the classes you need:




"In order to appear in the list of selectable classes, the class needs the method canGoBatchJournal with the return value = true."
After adding a second (or third or fourth) job, you can add a dependency. In the 'Has conditions" grid you can add the condition that the Forecast needs to be 'Ended' before MRP can start. If for some reason, Forecast runs into an error, MRP will not start. 



So this principe is great, but what's even greater if we can start a batchjob which depends on data in an external database. So I created a simple class which checks an external database and copies some info to AX (how to can be found here: http://msdn.microsoft.com/en-us/library/ee677510.aspx). How cool is that :), the options are numerous, check if data is copied, check if data is present on another place, check if updates have been completed succesfully prior to the next jobs you want to run in AX.

Again setup up dependencies let forecast start when the call to the external databases has ended and let MRP start when Forecast has ended:



This way, all tasks are executed sequentially and will only start when a previous step has ended. 


Sunday 18 August 2013

Starting AX services in batch file

Just a small post regarding the starting of the AX services. I have installed AX2009 and AX2012 locally on my laptop for testing purposes. The services however take a lot of time to start up and you don't want this to be part of the bootingsequence of the laptop. So I created a bat file containing only the following lines:

@echo off

echo Starting time: %time%

net start "MSSQLSERVER"

net start "AOS50$01"

net start "AOS60$01"

echo Time finished: %time%

pause




This way you can start the services at the time you want without having to do it manually via the services. So it's a very simple and easy thing to do, but it's helpful :).

Tuesday 6 August 2013

Get Exchange Rates in Dynamics AX 2009 and 2012

Setup in Dynamics AX 2012


In DAX 2012 is now more than easy to setup the (periodic) import of Exchange Rates: 

Below is the screen with the current exchange rates [General Ledger - Setup - Currency - Currency Exchange Rates], as can be seen, it has been setup for EUR -GBP and EUR - USD and both hold one rate for the first of July. 



Create a new exchange rate provider via [GL - Setup - Currency - Configure exchange rate providers] and select one of the three default providers:


Next, import the new rates via [GL - Periodic - Import currency exchange rates]. Personally, I would uncheck "Create necessary currency pairs". It's providing new relations between currencies you haven't defined in your Exchange Rate Type. It'll only generated unwanted extra data, but hey maybe there's a good use for someone.

The forms gives you the opportunity to run this job in batch so you can update the rates whenever you prefer:


And that's it, the exchange rate is generated:



Setup in Dynamics AX 2009


Now in DAX 2009 it's a bit more difficult, there's no out of the box import available to update the rates. 
There is however a Microsoft white paper which shows how to consume a webservice which you can use to update Exchange rates, you can find the document here: http://www.microsoft.com/en-us/download/details.aspx?id=4462

The document shows how to create a button to update the exchange rate of a single currency (btw, the document mentions to add a new MenuButton, this should be a Button). The methodology however can easily be reused to run as a batch job with which you can periodically update your exchange rates in AX 2009 as well:



Friday 2 August 2013

Sync AD info with user information in AX

Recently I created this job to synchronize information from Active Directory with AX. We needed to update the e-mail address for all the employees in AX after a rebranding. 

Below you find the code, as you can see, we update the e-mail address but as an example I also update name of the employee. Also you could add the check if a user is still active in AD and if not, disable the user account in AX as well. You could extend the job to also update the email address on the employee information. 

static void JLH_SyncADwithAX(Args _args)
{
    UserInfo                            userInfo;
    xAxaptaUserManager                  axUsrMgr;
    xAxaptaUserDetails                  axUsrDet;
    Boolean                             doUpdate;
    SysUserInfo                         sysUserInfo;
    SysCompanyUserInfo                  sysCompanyUserInfo;
    #define.NewDomain("contoso.com")    // Fill in your domain
    ;

    doUpdate = Box::yesNo("Update users with new info?", DialogButton::No) == DialogButton::Yes;
    axUsrMgr = new xAxaptaUserManager();

    ttsbegin;
    setPrefix('Update AD userinfo');

    while select forupdate userInfo
    where userinfo.id != "Guest"
    {
        axUsrDet = axUsrMgr.getDomainUser(#NewDomain, userInfo.networkAlias);
        
        if(userInfo && axUsrDet)
        {
            sysUserInfo.selectForUpdate(true);
            sysUserInfo = SysUserInfo::find(userinfo.id,true);
            sysUserInfo.Email           = axUsrDet.getUserMail(0);
            userInfo.name               = axUsrDet.getUserName(0);
            userInfo.enable             = axUsrDet.isUserEnabled(0);

            if(doUpdate)
            {
                userInfo.update();
                sysUserInfo.update();
                info(strfmt("Updated for: %1, Name: %2, E-mail: %3", userInfo.Id, userInfo.name, sysUserInfo.Email));
            }
            else
            {
                info(strfmt("Not updated for: %1, Name: %2, E-mail: %3", userInfo.Id, userInfo.name, sysUserInfo.Email));
            }
        }
        else
        {
            error(strfmt("User not found: %1", userInfo.Id));
        }
    }
    ttscommit;
}