Friday, January 27, 2012

Delete transactions in Dynamics AX 2012

We're in the process of implementing AX 2012 and we needed to flush out all the transactions from a company. There's a nifty class in AX that's called SysDatabaseTransDelete that does exactly what we need. It loops through the tables in the AOT and, depending on their TableGroup property, will flush out the data.

After cleaning the transactions we needed to delete a few vendors. However we kept getting an error about AOS validation failing. After investigating, we figured out that transactions in VendInvoiceInfoTable still existed... This table is flagged with a new TableGroup value which is TransactionHeader!

In fact, two new TableGroup types have been added in AX2012: TransactionHeader and TransactionLine. We'll need to modify the method SysDatabaseTransDelete.handleTable to support these 2 new cases :


void handleTable(SysDictTable sysDictTable)
{
TableGroup tableGroup;

if (tableSet.in(sysDictTable.id()))
return;
tableSet.add(sysDictTable.id());

if (sysDictTable && !sysDictTable.isTmp() && !sysDictTable.isMap())
{
tableGroup = sysDictTable.tableGroup();

// Handle company specific tables to be deleted
if (sysDictTable.dataPrCompany())
{
switch(tableGroup)
{
case TableGroup::Transaction:
case TableGroup::WorksheetHeader:
case TableGroup::WorksheetLine:
//FIX - Support new AX2012 transaction table types
case TableGroup::TransactionHeader:
case TableGroup::TransactionLine:
this.handleTransTable(sysDictTable);
break;
default:
this.handleNonTransTable(sysDictTable);
break;
}
}
else
{
// Handle global tables to be deleted
switch(tableGroup)
{
case TableGroup::Transaction:
case TableGroup::WorksheetHeader:
case TableGroup::WorksheetLine:
//FIX - Support new AX2012 transaction table types
case TableGroup::TransactionHeader:
case TableGroup::TransactionLine:
this.handleGlobalTransTable(sysDictTable);
break;
default:
break;
}
}
}
}

Monday, January 24, 2011

New website!

I've been working at Haka Solutions for 5 years now and we've never really had a website I was proud to show off... But that's all over now! We've just finished adding all the content and it's now officially launched!

Take a look and let us know what you think.

Most of my AX-related blogging will probably now be featured over there but I will still be posting here also. However, my colleagues will also be contributing with blog content on our new website so I suggest adding it to your favorite or registering the RSS feed in your favorite reader.

http://www.hakasolutions.com

*Note that the website will be moving in about a week to a new host. Performance will greatly improve ^^

Thursday, August 19, 2010

Rebuilding SqlDictionary entries... (To avoid at all cost)

Whether or not this problem was the result of something I did, it seemed I just had to fix it. After the synchronization of Rollup 5 on one of our systems, we kept getting a database error for one of the tables. I just hate it when a table won't synchronize...

Since the error messages in AX tend to be so vague all the time, I still didn't know which table was failing. Next logical thing to do: fire up the SQL administration form in Administration/Periodic and run the Check/Synchronization. I quickly found the problem table was SalesParmUpdate and the info given to me was "Add table."

For a great read on data synchronization issues, take a look at this post from an old colleague of mine.

So I took a quick look in the SqlDictionary table and found absolutely no records for SalesParmUpdate (TableId: 1428)... The table existed in the AOT, it also existed in the database, but the records in SqlDictionary were missing. I decided to write a quick job that would recreate the records for a table. There's a few things to keep in mind... While normal UtcDateTime fields require two records (one for the DateTime and one for the Time Zone), the system DateTime fields don't need the Time Zone entry. Also, the RecId field uses an undocumented fieldType of 49.

I only have a few special cases in the code below (Enough for the SalesParmUpdate table) but you will probably need to tweak it a bit more to make it work with your table.

WARNING: PLEASE KNOW WHAT YOU ARE DOING BEFORE PLAYING AROUND WITH THE SQLDICTIONARY TABLE. ^^


static void rebuildSqlDictionary(Args _args)
{
SqlDictionary sqlDictionary;
SqlDictionary sqlDictionaryTZ;
SysDictTable sysDictTable;
SysDictField sysDictField;
Set tableFields;
SetIterator si;
;

ttsbegin;

sysDictTable = new SysDictTable(tablenum(SalesParmUpdate));

tableFields = sysDictTable.fields();
si = new SetIterator(tableFields);
while (si.more())
{
sysDictField = si.value();

sqlDictionary.clear();
sqlDictionary.tabId = sysDictField.tableid();
sqlDictionary.fieldId = fieldext2id(sysDictField.extendedFieldId());
sqlDictionary.array = sysDictField.arrayIndex();
sqlDictionary.name = strupr(sysDictField.name(DbBackend::Native, sysDictField.arrayIndex()));
sqlDictionary.sqlName = sysDictField.name(DbBackend::Sql, sysDictField.arrayIndex());
sqlDictionary.fieldType = sysDictField.baseType();

switch (sqlDictionary.fieldType)
{
case Types::String :
sqlDictionary.strSize = sysDictField.stringLen();
break;
case Types::UtcDateTime :
if (!sysDictField.isSystem())
{
sqlDictionaryTZ.clear();
sqlDictionaryTZ.data(sqlDictionary);
sqlDictionaryTZ.array = 2;
sqlDictionaryTZ.fieldType = Types::Integer;
sqlDictionaryTZ.sqlName += 'TZID';
sqlDictionaryTZ.doInsert();
}
break;
case Types::Int64 :
if (sysDictField.isSystem()) //RecId
{
sqlDictionary.fieldType = 49;
}
}

//sqlDictionary.flags = sysDictField.flags();
sqlDictionary.doInsert();

si.next();
}

ttscommit;
}

Thursday, June 17, 2010

COM error when exporting data...

I've been getting the following error when trying to export some data to Excel using Definition groups:

Method 'item' in COM object of class 'Range' returned error code 0x800A03EC () which means: .

At first I noticed it was only happening when adding the field RecId to the "Field setup" of the export. The problem isn't really the field itself, but actually the way it was being added. When adding a table to a definition group, it automatically pulls in all the fields of that table (except some system fields like TableId, RecId, DataAreaId, etc.). So when you hit the Setup button, it already contains most of the fields.

The table used to store that selection is SysExpImpField. There's a record for each field and it saves the selected field ids in ConvFieldId. It is important to note that it's actually the extended field id that is saved (Field id + array index). From the setup form we have 2 ways of adding more fields, either by using the lookup or by manually typing the name of the field.

If we use the lookup, it calls the Global::pickField function and returns the extended field id selected. However, if we type the name of the field, it will then use the function fieldname2id. fieldname2id returns the FieldId NOT THE EXTFIELDID...

So I added a quick hack to the edit method SysExpImpField.fieldName :


// BP Deviation Documented
edit fieldName fieldName(boolean set, fieldName name)
{
if (set)
{
this.ConvFieldId = fieldname2id(this.ConvTableId, name);
//MVaillancourt. Haka. 20100617
//fieldname2id does not return the extended fieldid. if the id is smaller than 65 536, convert to extended
if (this.convFieldId < 0x10000)
this.convFieldId = fieldid2ext(this.convFieldId, 1);
//MVaillancourt. End
}

return fieldid2name(this.ConvTableId, this.ConvFieldId);
}

Thursday, June 10, 2010

Forms not saving user layout changes

You may have come across some forms where users complained it wasn't saving the layout changes they were making. The problem is quite easy to fix if the link between the 2 forms is straightforward.

I've had to deal with this issue for a client and this is what it looked like:

A custom invoice history screen was created and it lists all the invoice lines for a customer. The invoice history screen is called from the invoice journal header form. A normal button with the call to the form in the click method was used.

void clicked()
{
FormRun fr;
Args args;
;
super();

args = new Args("CusInvoiceHistory");
args.parm(custInvoiceJour.OrderAccount);
fr = new FormRun(args);
fr.init();
fr.run();

fr.wait();
}

Although this will call the form, it will not save any user customization (Window size, column width, column order, etc.). The problem lies in the fact that no Menu Item is used. I quickly changed the Button to a MenuItemButton that points to my form, changed the init of my form to properly initialize my query and that did the trick. There is no reason a call to another form shouldn't be done with the use of a Display Menu Item (I can't think of any at the moment). It is the "standard" way of properly linking the forms together.

Have an exception to that rule? Let me know in the comments.

Friday, June 4, 2010

AX, Android, Emulation.... What now?!

So much to do... so little time!

This little blog of mine never really took off and for the most part, I'm the only one to blame. It's not that I don't want to keep it updated, it just hasn't been anywhere near my top priorities this year (or the last few years for that matter :p). Well, are things about to change? Most likely.

I'm still going to try and keep my posts in line with the work I do... that is Microsoft Dynamics AX development, but since I'm now the owner of an Android smartphone, you might see me post a few Android stuff here and there if I ever get everything setup right. First thing I'll do is probably port my Chip8 emulator to the Android. (No Hellow World nonsense for me) That should get me familiarized with the platform and I'll be able to move on to bigger projects after that.

More to come!

Friday, February 19, 2010

Modify the way AX sends reports by email

This is something I've been asked multiple times, either by customers or colleagues... The standard way AX sends reports by email is not ideal.

The two big issues are:
- Using SysINetMail which uses Outlook integration and requires the user to confirm that AX is trying to send an email using their account.
- The formating of the email is just awful and lacks relevant information.

Luckily for us, the method which sends the email is available for developers to modify. It is found at: Classes/Info/reportSendMail


void reportSendMail(PrintJobSettings p1)
{
SysINetMail m = new SysINetMail();

str fileName = 'axaptareport';

if (p1.format() == PrintFormat::ASCII || p1.format() == PrintFormat::TEXTUTF8)
fileName = fileName + '.txt';
else if (p1.format() == PrintFormat::RTF)
fileName = fileName + '.rtf';
else if (p1.format() == PrintFormat::HTML)
fileName = fileName + '.htm';
else if (p1.format() == PrintFormat::PDF || p1.format() == PrintFormat::PDF_EMBED_FONTS)
fileName = fileName + '.pdf';

m.sendMailAttach(p1.mailTo(),p1.mailCc(), p1.mailSubject(),'axapta report', true, p1.fileName(), fileName);
}



From here, instead of using SysINetMail we could use SysMailer or even System.Net.Mail. We can also change the subject, body, attachment name, etc...

Thursday, January 28, 2010

Field security and record templates

We came across an issue where a user that does not have access to edit a mandatory field on a table was not able to save a record created from a template that has the field predefined.

Consider the case where we want to allow a user to create items but we do not want him to be able to modify the item group. We prevent the user from modifying the field through security. All the items are created through templates which have an item group already defined... The problem is when the new record is created from the template, before copying the value over, it checks if the user has edit access on the field.

To avoid this problem, a simple modification can be made in ClassFactory/createRecord

The call to
sysRecordTemplate.createRecord();

needs to be modified to
sysRecordTemplate.createRecord(false);

This will ignore the AllowEditOnCreate (Edit access in security) property when transferring over the values from the template to the new record.

Thursday, April 9, 2009

Capturing key input (Update!)

Well, I finally managed to fix a few issues with my Chip8 emulator... One of them was the lousy input I used. With the help of a WinAPI call, I was able to make everything run smooth :)

First I implemented the getAsyncKeyState function from User32.dll.


client static int getAsyncKeyState(int _vKey)
{
DLL _winApiDLL = new DLL(#UserDLL);
DLLFunction _getAsyncKeyState = new DLLFunction(_winApiDLL, 'GetAsyncKeyState');
;

_getAsyncKeyState.returns(ExtTypes::DWord);
_getAsyncKeyState.arg(ExtTypes::DWord);

return _getAsyncKeyState.call(_vKey);
}


Next, Chip8 has two opcodes that polls if a key is pressed. All I had to do was use my newly implemented method and now pong has never ran so smooth. Here's how it looks (chip2vkey converts a value from 0 .. 16 to the vKey value of the key I want to map):

case 0xE :
switch (opcode & 0xFF)
{
case 0x9E :
//EX9E - Skip next instruction if key V[X] is pressed
if (WinAPI::getAsyncKeyState(this.chip2vKey(reg[#X])) & 0x8000)
PC +=2;
break;
case 0xA1 :
//EXA1 - Skip next instruction if key V[X] is not pressed
if (!(WinAPI::getAsyncKeyState(this.chip2vKey(reg[#X])) & 0x8000))
PC +=2;
break;
default :
throw error("Invalid opcode " + int2hex(opcode, 4));
}
break;

Thursday, February 21, 2008

Capturing key inputs

Following on what I said yesterday, no emulator would be good enough without the ability to capture key inputs. This is pretty easy and can be quite useful in alot of situations. An exemple of this can be found in the form tutorial_Tetris. (Yes, there is a Tetris game in DAX for those who didn't know!)

The first thing you need to do is add a Tree control to your design and set it's "AutoDeclaration" to Yes. The Tree control allows us to capture the "keyDown" events by overwriting the method with the same name. We can then use the vKey that is passed to this method and do whatever we want when a key is pressed.

boolean keyDown(int vKey, boolean ctrl, boolean shift)
{
        boolean ret;

        ret = super(vKey, ctrl, shift);

        switch (vkey)
        {
                case 49: //1 (1)
                        keyPressed = 2;
                        break;
                case 81: //4 (Q)
                        keyPressed = 5;
                        break;
                case 50: //5 (W)
                        keyPressed = 6;
                        break;
        }
        return ret;
}

So for our emulator, we want to have the emulator focused on the Tree whenever it's not processing information. So after we execute an instruction, we give back the focus to the Tree like so :

void runEmu()
{
        int opcode;
        ;

        opcode = memory[pointer]
        this.execute(opcode);
        pointer++;

        if (emuRunning)
                this.setTimeOut(identifierStr(runEmu),0,false);
        Tree.setFocus();
}

This is just a rough example, but we could call methods from here to execute a certain peice of code or change the emu status (like to pause or play the emulator).

I almost posted today about the importance of making backups of your code but was too annoyed with the fact that I lost a day and a half worth of work :p.... So remember guys, ALWAYS backup your projects, even if automatic backups are suppose to be running every night.

Tuesday, February 19, 2008

Chip8 in DAX 4.0 (continued...)

I know I haven't posted in a while... but I'm bored and this seems like a great time to continue my little adventure with emulation in Microsoft's Dynamics AX 4.0. I will probably not go as in-dept as I originally planned since the Chip8 platform is well-documented already. I'll try to focus on the challenges that the DAX platform brings into emulating a system.

Every emulator consists of a main loop that executes instructions, also known as opcodes. A typical loop in pseudocode would look a little something like this:

while emulor is running
        fetch opcode from memory at pointer's location
        execute opcode
        increase pointer
loop

In DAX this can be something as simple as (The boolean in the while statement can be changed to a variable to allow to start/stop the emulator):

while (true)
{
        opcode = this.memory[pointer];
        this.execute(opcode);
        opcode++;
}

Unfortunatly, if you try to run a loop like this, you will notice that the DAX client seems to hang as it's running an infinite loop. This prevents anything to be updated to the screen (like the Chip8 display area) and prevents any keyboard input to be captured (capturing input will be explained in another blog entry.... which I promise will not be in another month this time!). These issues can be bypassed by doing something like this :

The memory[] array, pointer and emuRunning variable would be declared in the classDeclaration in this exemple.

void runEmu()
{
        int opcode;
        ;

        opcode = memory[pointer]
        this.execute(opcode);
        pointer++;

        if (emuRunning)
                this.setTimeOut(identifierStr(runEmu),0,false);
}

Instead of using a loop, I made use of the setTimeOut method to call the same method over and over again as long as the emulator is running. This leaves enough time for the client to update itself and allow the user to move around any window that would be opened in the client.

That's it for tonight. On a last note, using Threads should work also. I haven't had any free time lately to give it a try but it's kind of like the "Find" feature in DAX. When you search for a string through code, the client will update its result window while it is searching more results.

Monday, January 7, 2008

Chip8 emulation in Dynamics AX 4.0

First thing first, this is a project I made a few months back. There is still some work that needs to be done on it, but I doubt I will be touching it again anytime soon.

Now most of you are probably wondering what exactly is Chip8... Well think Pong, Space Invaders, Brix, etc. Get the picture?

Now some of you are probably wondering what emulation is... Emulation is replicating the functions of one system through the use of another. In this case, I'm emulating Chip8 through Windows (using Dynamics AX as my coding language and interface... granted it may not be the best of choices but I wanted to see if it could be done and what performance I would be getting).

I've been involved in the emulation scene for several years now. I'm administrator on one, if not, the biggest emulation forum and have betatested several GameCube emulation projects. While I can understand the code I read and the ideas behind emulation, I have never coded an emulator myself. I guess it was time to start somewhere.... and the best place to start is to start small.... Chip8 small that is ;)

The core component in any platform is the CPU. Everything else is one way or another connected to it, either by helping the processor or by being driven by it. The Chip8 CPU only has 35 instructions making it an easy processor to emulate since it doesn't take much time to go through all of them. The isntructions (also called opcodes) are used to manipulate the data in between the registers and the memory. The Chip8 has 16 8bit registers and 4KB memory. Shouldn't be too hard for modern computer to replicate this :p

Alright, that's it for today. Next post will contain actual code for you to see and I'll explain some of the issues we have when emulating anything in DAX.

Until then, here's a little screenshot of my emulator in action :

Sunday, March 18, 2007

Stuck in Boston...

As most poeple probably heard, the US got hit by a snowstorm last friday. Unfortunatly for me, my flight back home got cancelled. Finally, I'll be able to head back to Montreal on a 7:10PM flight tonight.

Aniway, I didn't really have the time to visit Boston since I spent most of my time catching up on my sleep... I did however walk on Durchester St. saturday night. The St. Patricks Day festivities were already on their way. They sure have some lovelly ladies here :)

I'm in the hotel lobby right now waiting for the time to pass. It gave me a chance to update a phpBB forum I run with spam / bots protection. I was really surprised on how week phpBB is overall. It is missing alot of basic administration functions and is known to attract a tremandous amount of spam. Hopefully, this should be fixed right now.

I had prior experience maintaining a forum with Invision Power Board which is, in my opinion, far superior even when it was free. Too bad they don't have a free version anymore for non-commercial use.

Alright, until next time.

Friday, March 16, 2007

First entry

This may be my first and last entry in this blog...

I have attempted many times to create a blog and to actually be motivated enough to post frequently but I guess each time I failed miserably :p
Here's another go. Something a little more professional that I hope will keep me interested for a while.

I might throw in some DAX stuff here and there. I've been working on a Chip8 emulator (space invaders, pong, brix, etc...) for DAX which i'll show later. :)

Cheers,

Mathieu Vaillancourt