Friday 31 July 2015

How to get Cust account or Vend account from ledgerJournalTrans?

LedgerJournalTrans may keep information about few types of account.
Two of them may be information about customer and vendor account.

There are two ways to find a CustTable record or VendTable record from ledgerJournalTrans.

First option:
Harder but it will show how does it work.

At the beginning it's good to know that this information is stored in LedgerJournalTrans in field LedgerDimension

So before you'll start searching vendor or customer, it would be good to check what type of account is stored in LedgerJournalTrans.
You will now that from field AccountType.

LedgerDimension stores an info about record of table DimensionAttributeValueCombination
And this last table has a field which may be interesting for us: DisplayValue

So, let's make a job which will give us a record from CustTable or from VendTable

static void accountHardWay(Args _args)
{
    LedgerJournalTrans                      ledgerJournalTrans;
    DimensionAttributeValueCombination      combination;
    CustTable                               custTable;
    VendTable                               vendTable;
    
    //I assume that we already have a record 
    //from ledgerJournalTrans
    
    combination = DimensionAttributeValueCombination::find(ledgerJournalTrans.LedgerDimension);
    
    if (combination)
    {
        if (ledgerJournalTrans.AccountType == LedgerJournalACType::Cust)    
        {
            custTable = CustTable::find(combination.DisplayValue);    
        }
        else
        {
            if (ledgerJournalTrans.AccountType == LedgerJournalACType::Cust)    
            {
                vendTable = VendTable::find(combination.DisplayValue);    
            }                
        }
    }

}



Second Option:
For dummies :)

ledgerJournalTrans has a method accountDisplay()

static void accountEasyWay(Args _args)
{
    LedgerJournalTrans                      ledgerJournalTrans;
    CustTable                               custTable;
    VendTable                               vendTable;
    
    //I assume that we already have a record 
    //from ledgerJournalTrans

    if (ledgerJournalTrans.AccountType == LedgerJournalACType::Cust)    
    {
        custTable = CustTable::find(ledgerJournalTrans.accountDisplay());    
    }
    else
    {
        if (ledgerJournalTrans.AccountType == LedgerJournalACType::Cust)    
        {
            vendTable = VendTable::find(ledgerJournalTrans.accountDisplay());    
        }                
    }

}

Monday 27 July 2015

Run as different user. Strange behaviour

Dynamics AX shortcut may start different AX session for your currently user, and for user which has been chosen from menu "Run as different user"



This is because Dynamics AX is starting with default configuration which has been set in Dynamics AX Configuration utility. 
This configuration is set separately for every single user from that Windows system when Dynamics AX Client is installed.

So, when you're starting Dynamics AX for different user, but from your user session (Run as different user) it takes AX configuration from that different user, not from yours.

May be obvious, but sometimes it can be a little surprise.

Tuesday 14 July 2015

Catch insert, update or delete. Database log

Sometimes there is a need to find out, which class or other element in system is inserting, updating or deleting a record in a table which is interesting for us.

The easiest way is just to put a breakpoint on insert(), update() or delete() method of that table.
If there is no such method, it has to be added of course.
When some piece of code will call a method on table, we will see a full stack, and probably a reason why this table has been modified and where.

An example of insert() method on SalesTable and a breakpoint at the beginning.


The problem is, when a record is inserted/updated/deleted using doInsert(), doUpdate(), doDelete().
In that case, such breakpoint on table method will not work.

To find out what changes data in our table, we have to put a breakpoint in class Application
There are many methods, but for us, there are three interesting: logDelete(), logInsert() and logUpdate().

And in one of them we should put a breakpoint.

An example of breakpoint on logInsert()


But it's not enough just to put a breakpoint.

We have to go to 'System administration\Database\Database log setup'.
There will be a form with listed tables for which system is saving a log of changes and a type of those changes (Insert/Update/Delete).

Let's say that we want to find when a record is inserted to a table SalesTable using doInsert() method.

We have to press "New" button on Database log setup form
It will start a Wizard.
Just press Next.
It will take a while to build a table structure.
In my opinion it would be much more useful, just to see a list of tables with their AOT names and without any structure. Much faster, that's for sure, because this window with structure built for the first time appears after about 10 minutes! 

Please remember, that you will see a list of tables sorted by modules, and you will see their printed names. It's important because I had two AX, both in English language, but one was in UK English, and one in US English. And of course those tables had different printed names.

So, I need to catch a doInsert on SalesTable, so I have to choose a table "Sales orders"

Press Next

On next window please tick a checkbox "Track new transactions" for row with table name "Sales orders". There may be only one row, but on older working environments there may be more



Press Next, and on next (last) window of that Wizzard, please press Finish.

There was a breakpoint few lines above on that blog on logInsert() method of Application class.

So, after this database log setup has been made for SalesTable, a breakpoint will stop a trace, and it will be ease to find out what process has inserted a record to our table.


Monday 13 July 2015

Infolog levels. Setprefix and other tricks.

Sometimes we have to display some kind of treeview in info message.
We need to make somehow sub-level of infolog.

To do that we can use a setPrefix() build-in function.

As an example:

setPrefix ('A new node of infolog');
info('A sub node of infolog');

Instead of using setPrefix(), we can just use a tab '\t' to format our message

info(strFmt('A new node of infolog%1A sub node of infolog', '\t'));

Result in both cases will be the same

To have a possibility to go back to level 1, we have to use another methods, sub methods or loop statements.

So, before calling our method or loop we have an infolog level X, and after this method or statement we will have exactly the same level X of our infolog.

Please have a look in that code below. This will produce few levels of our message, and sometimes it will get back to previous level or dig deeper.

I'll use sub method in job and for loop to show how does it work.


static void Job201(Args _args)
{
    Counter i, k;
    
    void sendMessage(str _message, str _prefix = '')
    {
        if (_prefix)
        {
            setPrefix(_prefix);    
        }
        
        info(_message);
    }
    
    
    //A prefix on the very top of infolog window
    setPrefix('Build a tree');
    
    //Two lines on first level of infolog. 
    //Just to show that two of them will be on the same level
    info('Just a message on first level');
    info('Another message on the same level');    
    
    //Sub method of that job used to display sub node of 
    //that infolog and display a message 
    //under that subnode
    sendMessage('This is message on sub level 2', 'Sub level 1 prefix');
    
    //When trace went out from submethod 'sendMessage' 
    //infolog level is again on level one!!
    //Be careful. In next line there will be 
    //a little trick to display another message 
    //on sub level 2from 'sendMessage' submethod. 
    //so this line above will be displayed after 
    //this all sublevel 2. 
    //Infolog will change an order of those lines
    info('This should be a message on level 1. Where it has been displayed? :)');    
    
    //You can use a tab '\t' instead of setPrefix().
    //When you'll place exactly the same string 
    //as a prefix as it has been previously used
    //an Infolog will put not create new node, 
    //but will just put a message to previous prefix node 
    //with the same name
    info(strfmt('Sub level 1 prefix%1This is another message on sub level 2', '\t'));
    
    //and again, another sublevel 2 node, 
    //but different prefix
    //so it will be displayed as separate subnode.
    info(strfmt('Another sub level 1 prefix%1This is another message on sub level 2', '\t'));    
    
    //and third level of infolog. 
    //Just used a tab '\t' to dig there.
    info(strfmt('Another sub level 1 prefix%1This is prefix message on sub level 2%1and message on sublevel 3!', '\t', '\t'));      
    
    //and we are getting back to level 1.
    info('This should be a message on level 1');
    
    //Let's dig one level deeper.    
    setPrefix('Well, level 2 before loop');
    
    //we can use a loop instead of subMethod.
    //It will do the same for us, just close 
    //a prefix level in one statement
    //and outside of that statement infolog level will 
    //go back to level before loop.    
    
    for (i = 1;i <= 2;i++)
    {
        //next level of prefix. This will be level 3   
        setPrefix('Level 3 inside of loop');
        
        for(k = 1; k <= 2; k++)
        {
            info('Message on level 4');    
        }
    }
    
    //and after a loop, Infolog will display 
    //this message on level 3
    //because before a loop there was a setPrefix statement
    info('This should on level 3');
}

This is how it will look like after that



Thursday 9 July 2015

COM error: invalid number of parameters

Well, I've met last days a strange error message when I wanted to import data from Excel.
I'm not sure (haven't tested that) but the same problem may occur with other applications connected by COM (Outlook for example).

Error executing code: The method has been called with an invalid number of parameters

The problem was, that there was good number of parameters.

Method looked like that


    SysExcelApplication excelApp;
    SysExcelWorkbooks   excelWorkbooks;
    SysExcelWorksheets  excelWorksheets;
    SysExcelCells       excelCells;
    SysExcelWorksheet   excelWorksheet;
    ComVariant          cellValue;

....

    //line calling error
    cellValue   = excelCells.item(row, col).value();


....


I found workaround of that problem, which is just a....workaround, and nothing more. The problem is still there, and probably has to be fixed by Microsoft.


The main issue is, that this problem is not possible to replicate every time. Sometimes it is, and sometimes not.

I've added a line just before this line which is responsible for that error

     infolog.yield();

Yes, that's it!

There is another workaround for that which works, but I think that it does not give a guarantee that it will work - how can you know that 50 (or another number) will be enough?
There might a coincidence that it will work after 51st iteration.

Just add an error counter with maximum value, let's say 50.
After that, run this problematic line in try catch statement, iterating as many times as code will reach a limit gave as maximum (hardcoded)

Something like that

  Counter limitCounter;

  try
  {
    cellValue   = excelCells.item(row, col).value();
  }
  catch(Exception::Error)
  { 
     limitCounter++;

     if(limitCounter <= 50) //hardcoded!!
        retry;
     else
        throw(Exception::Error);
  }