Jasman's blog

Jasman's blog

Dynamics AX and life

Blog about life and work as an Dynamics AX developer.

DAX Certification

WorkPosted by Jacob Sørensen Sat, June 14, 2008 15:14:29

Friday I passed my first Microsoft Dynamics AX certification test.

After working with Microsoft Dynamics AX for 7 years as a developer, I thought i might be apropriate to get the certification :).

So now I've passed the certification test for "AX 4.0 Development Introduction", but my ambition is to reach MCBMSPDMDA (Microsoft Certified Business Management Solutions Specialist for Microsoft Dynamics AX).

So I have to pass another 3 exams. 1 more core exams and two module exams.
MorphX Solution Development in Microsoft Dynamics AX 4.0 - which is the other core exam.

Then there are two elective exams to pass:

Either

Financials in Microsoft Dynamics AX 4.0
Installation and Configuration in Microsoft Dynmaics AX 4.0 or
Trade and logistics in Microsoft Dynamics AX 4.0

However recently Microsoft releas Dynamics AX 2009 (vers. 5.0), but there are not yet any certification exams for that version yet.

  • Comments(0)//blog.fasor.dk/#post45

Time and dates in Axapta / Dynamics AX

WorkPosted by Jacob Sørensen Tue, September 04, 2007 14:45:30

Have you ever needed to find out the amount of time (secs, hours etc.) between to dates (date and time) in Axapta / Dynamics AX (DAX) ?

I've used this approach a couple of times, and it is actually something I concieved when I was working solely with XAL-coding (the 4GL programming languange in which the Microsoft XAL and Microsoft Dynamics C5 applications are written).

Dates in Axapta / DAX are can be in the interval 01.01.1901 to 31.12.2154.
A date can be converted in to an integer with the function date2num().

Time in Axapta / DAX is represented by an integer which denotes the number of seconds since midnight. There are 86400 seconds in 24 hours. Thus it is in the interval [0,86399].

A time/integer value can be converted to the textual representation by using the function time2str();

Now the trick is to calculate the date and the time in to one value, so that if you have two points in time and want to know the diff. between them, you can calculate the numerical values and subtract them from each other.

I use the method of calculating the date and time in to seconds by doing:

Axapta 3.0 and previous


static void itsAlive(Args _args)
{
date fromdate_,todate_;
timeOfDay fromtime_,totime_;
real secondsAlive;

fromDate_ = 18\01\1971;
toDate_ = today();
fromTime_ = str2time("15:00:00"); // Three o'clock p.m.
toTime_ = timenow();

secondsAlive = (date2num(todate_)*86400+totime_) - (date2num(fromdate_)*86400+fromtime_);
info(strfmt("I have been alive for approx. %1 seconds.",secondsAlive));
info(strfmt("I have been alive for approx. %1 hours.",secondsAlive / 3600)); // Centi hours
}

NOTE that the result of the calculation of diff in seconds is stored in a real variable.
This is because of the integer data types 32 bit signed limitation in Axapta 3.0 in earlier.

This little job will calculate the amount of seconds from 18 january 1971, 3 o'clock p.m (which happens to be my birthday), to current time.

It will print the number of seconds and the number of hours since my birth.
We calculate the numbers od seconds (actually the number of seconds since 01\01\1901) by doing:

(date2num(todate_)*86400+totime_)

making it into a numerical value (datetime value) that can be used for calculations.

By dividing the number of seconds by 3600 (which is the number of seconds in a hour) we get the value in (centi) hours.

This could be used for say:

Calculating the numbers of hours worked for billing.
Calculating the duration of a phone call for billing
Calculating the duration of a ship's stay in harbour for billing.
Etc

To go from a datetime-value back to date and time you can do:

static void back2datetime(Args _args)
{
real datetimevalue = (date2num(18\01\1971)*86400.00)+str2time('15:00:00');
// 2243289600 seconds;
date dateresult;
real days;
timeOfDay timeresult;
;

days = datetimevalue / 86400;
dateresult = num2date(days); // Important that it is a REAL or the daynumbers overflow
timeresult = datetimevalue - trunc(days) * 86400; // Which is actually datetimevalue MOD 86400


info(strfmt("The datetimevalue yields date: %1 %2 o'clock.",dateresult,time2str(timeresult,1,1)));

}

It is important that when initializing datetimevalue you multiply the daynumber with 86400.00. If you multiply by the integer 86400 without the zero fraction, the kernel converts the result of the multiplication to an integer causing a wrong result.

The reason why you can not do datetimevalue MOD 86400 is because of precision loss caused by the MOD operator which converts to datetimevalue to an integer causing a wrong result.


There is a slight difference in the way this is done in Axapta 3.0 and previous and Dynamics AX 4.0.

In Axapta 3.0 the integers are signed 32-bit integers (long) which means that the value of an integer variable is in the interval [-2,147,483,648 to 2,147,483,647].

In DAX 4.0 int64 was introduced, this means that we can actually use the true DIV and MOD operators.

This is the job to calculate the diff in seconds and hours between dates:
DAX 4.0 code:

static void itsAlive4_0(Args _args)
{
date fromdate_,todate_;
timeOfDay fromtime_,totime_;
real secondsAlive;
real datetimevalue;
int64 bigint;

date dateresult;
real days;
timeOfDay timeresult;


fromDate_ = 18\01\1971;
toDate_ = today();
fromTime_ = str2time("15:00:00"); // Three o'clock p.m.
toTime_ = timenow();

secondsAlive = (date2num(todate_)*86400.00+totime_) - (date2num(fromdate_)*86400.00+fromtime_);
info(strfmt("I have been alive for approx. %1 seconds.",secondsAlive));
info(strfmt("I have been alive for approx. %1 hours.",secondsAlive div 3600)); // Centi hours
}

This is the job to go back from seconds to date and time re-written for DAX 4.0:
DAX 4.0 code:


static void back2datetime4_0(Args _args)
{
date dateresult;
timeOfDay timeresult;
int64 bigint;
;

bigint = (date2num(18\01\1971)*86400.00+str2time('15:00:00'));
dateresult = num2date(bigint div 86400); // Important that it is a REAL or the daynumbers overflow
timeresult = bigint mod 86400; // Which is actually datetimevalue MOD 86400

info(strfmt("The datetimevalue yields date: %1 %2 o'clock.",dateresult,time2str(timeresult,1,1)));
}

  • Comments(0)//blog.fasor.dk/#post41

Finishing new build of APM

WorkPosted by Jacob Sørensen Tue, September 04, 2007 13:52:23

I am currently working on finishing a new build of thy:data's Preventive Maintenance and Service Management Module.

Pretty boring as it is trivial work just running through a check list of things to be done.

A to complete a really nice day, I have a dentist-appointment 16.00 o'clock.

Weeeeh. smiley

  • Comments(0)//blog.fasor.dk/#post40

Life suxx

WorkPosted by Jacob Sørensen Tue, September 04, 2007 13:46:40

We lost a good colleague to brain cancer this friday.

He was only 48, and leaves behind wife and two boys of 15 and 18.

We will be attending his funeral this friday.

smiley

R.I.P Ole - where ever you are.

  • Comments(0)//blog.fasor.dk/#post39

Subtle but important difference

WorkPosted by Jacob Sørensen Thu, June 28, 2007 14:55:35

Been tumbling with a problem for the last few days.

A form in our Dynamics AX module for Preventive Maintenance Control was not behaving.

The form has "explicit" filter fields that the user can see without having to activate the form filter (CTRL+F3), for setting up filters most commonly used in an easy way.

And this is working ok. However at this customer site, the form has been adjusted so that the user can have the form refreshed automatically periodically, and when the users at the customer site were making use of the "explicit" filter combined with the AX's normal filtering (CTRL+F3), the form simply threw away the normal form filtering.

I discovered a subtle but very important difference between writing

<formtable>_ds.executeQuery(); (which was the way our code was doing it)

and

<formtable>_ds.Research();

The difference is that research will retain the filter ranges in the forms query as they are right now.
.executeQuery will NOT.

It is mentioned in the Developer's Guide, but I guess the responsible programmer hadn't noticed that one.

Developer's guide states:

"research vs. executeQuery

If you want to refresh the form with records that were inserted in a method or job that was called, then you should use research.

If you want to change the query to show other records, perhaps based on a modified filter, then you should use executeQuery."

  • Comments(0)//blog.fasor.dk/#post35

You win some and loose some

WorkPosted by Jacob Sørensen Wed, May 09, 2007 08:54:27

We have just lost a good colleague to a competitor.
That's bad as he was a very nice guy to work with, and good at what he did. smiley


But hey, we just stole one employee right back from the same competitor. smiley
And our new colleague is in fact one of my old colleagues. We've worked together around 7 years ago from '94-'00. At that time he was my boss.

So this is exciting news.

  • Comments(0)//blog.fasor.dk/#post31

Dynamics AX overriding syslastvalue

WorkPosted by Jacob Sørensen Wed, January 17, 2007 21:25:40

Today I solved a problem, that has long puzzled me.

From Axapta 3.0 (or was it 2.5 can't quite remember) it became best pratice to wrap reports in runbase-report classes.

For a long time it has irritated me, that if you called your report class from something else than the menu, you were having trouble overriding syslastvalue, without clearing all saved last value completely.

Example:

You have a report called from a runbasereport class.
For the class you naturally have a output menuitem, that is attached to the menu. So when you call this report, and make selections in the ranges of the query of the report, they are shown in the report dialog, and saved, so that the next time you call your report, the last used selections is being restored for you in the dialog for reuse.

However sometime you experience, that the report can be called from BOTH the menu and somewhere else like a form.
When you call the report from a form, it is customary to synchronize certain ranges in the query of the report with values found on the particular record the cursor has been placed in the form.

Then what about syslastvalue in this case. Normally they would be saved as the last used values destroying the values saved when the report was called from the menu. smiley

So how do we make the report function normally saving queryvalues etc as syslastvalues when calling it from the menu, and avoid doing so when calling the report from e.g. a form.

The solution is actually fairly simple, and does not require a whole lot of work:

If you make an extra output menuitem for your report class, which includes a parameter indicating that this has NOT been called from the menu, by putting a value in e.g. the PARM property and test for this it is quite easy.

First you need a parameter method to set and return a flag indicating if or if not the class has been called from the menu:

boolean calledFromMenu(boolean _calledFromMenu = calledFromMenu)
{
calledFromMenu = _calledFromMenu;
return calledFromMenu;
}

Of course you must have a class member of the boolean type called calledFromMenu.

In your main method in the runbasereport class you would have

MyRunBaseReportObject myRunBaseReportObject;

myRunBaseReportObjec = new myRunBaseReportObject();


if (_args.parm() != '')
{
myRunBaseReportObject.calledFromMenu(false);
}
else
{
myRunBaseReportObject.calledFromMenu(true);
}

if (myRunBaseReportObject.prompt())
{
myRunBaseReportObject.run();
}

The getLast method on your wrapper class (extending RunBaseReport) must be overwritten:

public void getLast()
{
getLastCalled = true;
inGetSaveLast = true;

if (this.calledFromMenu()) // Do not save last value if not called from menu
{
xSysLastValue::getLast(this);
}
else
{
this.initParmDefault();
}
inGetSaveLast = false;
}

In this case the calledFromMenu() method returns either true or false according to the parameter passed from the menuitem, which indicates that the report is called from the menu. If it has been called from the menu, we want everything to function is normal, so we get the syslastvalues. Otherwise, we call initParmDefault to setup report with query and queryRun objects etc.

It is important that super() is NOT called.

The other method to overwrite is:

public void saveLast()
{
if (this.calledFromMenu()) // Only save last value if called from menu
{
inGetSaveLast = true;
xSysLastValue::saveLast(this);
inGetSaveLast = false;
}
}

Actually the overwriting this method with the above code, just makes saving syslastvalues dependant on the calledFromMenu, thus NOT saving anything if the report was not called from the menu.

That's it. Now your report will function as normally saving syslastvalues when called from the menu, but not doing so when you call it using the other menuitem which indicates a call from somewhere else than the menu.

  • Comments(1)//blog.fasor.dk/#post20

New boss

WorkPosted by Jacob Sørensen Mon, January 15, 2007 10:52:55

Participated in the thy:data's Company Day 2007 saturday.

Among other things we were introduced to our new CEO for the thy:data enterprise and of course vice versa.

I was quite excited to learn, that he was a bit of a family man, so he should be able to understand it when an employee might have problems on the domestic front. Not that I am experiencing anything major on that front right now.

Among other news were several technical things, that was quite interesting.

  • Comments(0)//blog.fasor.dk/#post17
Next »