Calling methods on a caller form

This is actually something small but it comes in handy from time to time !

Due to the poor design on some form I have to make changes to, I needed this thing here. I replaced a button on the form with code behind it (yes, business logic behind a button is a BAD practice !) by a MenuItemButton. The only problem was that the form had some objects (f.e. a ledgerJournalEngine object, …) that needed to be available in the class behind it…

Now, this is what I did to get the ledgerJournalEngine object from the caller form and pass it to the class I was calling :

// Get the caller form CIMVendTransInvoicePool to get the ledgerjournalengine object
if(ClassIdGet(_args.caller()) == ClassNum(SysSetupFormrun) && (setupFormRun.name() == formStr(CIMVendTransInvoicePool)))
   {
      if (formHasMethod(_args.caller(), identifierStr(parmLedgerJournalEngine)))
      {
         ledgerJournalEngine = _args.caller().parmLedgerJournalEngine();
         DAXCancelRegisteredInvoice.parmLedgerJournalEngine(ledgerJournalEngine);
      }
   }

Inside SalesFormLetter class : ReArrange

Many times before I have made changes to the salesFormLetter classes and every time I went in there I was afraid ( as a figure of speech ) of the reArrange method. The whole thing was clear to me from a functional point of view, but the technical side of it was another pair of sleeves. As for today, I needed to add some additional functionality to the sales invoice grouping and there for I had no other options than to start digging into the code for rearranging invoices. Well so the goal of this article is clear by now, so let’s start.

For the post, I will go through the code of rearranging invoice for 2 sales orders with a different customer account but with the same invoicing account.

SalesEditLines form

When posting the invoices via Accounts Receivable à Setup à Sales Update à Invoice, we see the following when we have selected the two sales orders for invoicing.

SalesParmTable table before rearrange

Before rearranging, let’s take a look at the tables in SQL server to see what’s going on in there. As we can see in the following screen, the SalesParmTable table contains the following data for the current run. We can see that both orders have a SalesParmTable record in there.

Every time an instance of the SalesEditLines form is ran, a system sequence number is fetched to log every action that will be done for the current instance. This way we have a history for the invoice and how it has been grouped / posted. The TableRefID field will be an important aspect of the rearranging as to be seen later on.

SalesParmLine table before rearrange

The following data is currently in the SalesParmLine table. Here you will find a line for every related sales order line to be posted. Here we also see the references to the sales orders and lines and also important is the reference to the SalesParmTable records in the TableRefId field.

Hitting the rearrange button

Now we start rearranging. This happens in the SalesFormLetter_Invoice class, Method rearrange. Except from the check if the code is executed on the server, this is the first interesting piece of code.

This calls the SalesSummary classes. These are used here to build the query object with the invoice lines to be processed. So let us step into that.

SalesSummary classes

In this case, the construction of the SalesSummary class will be this case:

Additionally, inside the new method, the SalesSummaryFields is built to determine which fields will be used to sum by. The setup of these fields can be found here: Accounts Receivable à Setup à Parameters. Tab page
Summary
Updates, Button Summary update parameters.

After the construction is done, the method will be called to create a query object containing the invoice lines to be posted. The actual query building happens on the SalesPurchSummaryModel_Account class.

Basically the related order table is added and all the fields that are in the update parameters are added as sort fields to begin with. (Detail : It aren’t actually sort fields, but the ordering mode on the query is set to group by)

Looping the SalesParmSubTables

First line

After the query is built, we start looping the SalesParmSubTables and SalesParmSubLines. Important to note is that the subtables are actually a sort of copy of the parm tables to do the selections and to be able to alter the SalesParmTables while processing. At this point, this is the contents of the tables is like this:

Interesting thing to see is that the TableRefID fields are used to link the parm / sub tables together. At this point we arrive at the following code with the first SalesParmSubTable.

As we are doing this with the first line (VO000019) the UpdateParmTable is not executed here. What does happen is that a new SalesParmTable is created with a new TableRefId. (This parm table will be used to group the others on).

Now we have a SalesParmTable that will be the one where the lines will be grouped together on. The next step will be the code that moves the parm line from the original (TableRefId 00006286) to the new SalesParmTable. (TableRefId 00006288) and marking the SalesParmSubTable to be linked to the new SalesParmTable.

Second line

Now the second line from the second order is fetched and processed. First difference here is that the createNewJournal method will return false. When peeking in the insides of that we come to the following code.

This implicates that there will be no second SalesParmTable will be added. The SalesParmLine record will just be moved to the summary SalesParmTable.

The result after the second line is processed is this :

Now both the SalesParmLines and SalesParmSubLines are grouped on the newly created SalesParmTable.

What happens next is the splitting functionality for sites and delivery information but I will not go in the details of this here.

Cleanup

When the grouping is done, the SalesParmTables that have no reference anymore in the sub tables will be deleted. So the two lines are moved to a newly created SalesParmTable and then the originating SalesParmTables are erased.

Result

The result is one invoice with two lines on it. Additionally, it is also possible to check the data in the SalesParmLines and SalesParmSubTables to check the history of the grouping.

Debugging AIF on Windows 2008

For those who are using Windows 2008 as server platform and want to debug the Dynamics 2009 AIF… this will help!

We are using AIF WCF web services to communicate between a .NET portal and Dynamics Ax. And while developing we were unable to debug the code running in the Business Connector. Since we were able to do this on other customer sites I started coparing the infrastructure and the only difference was the fact that we were using Windows 2008 instead of Windows 2003…

I followed the usual steps to be able to debug the AIF code which I will elaborate in this post later on, but no success. I contacted the Microsoft support and they soon informed me about the fact that debugging the Business Connector on a Windows 2008 system is indeed a well known issue with for them! So there you have it, problem identified!

Two weeks later I was surprised as the support engineer contacted me about this issue and told me that they had a hot fix developed for this! (The thing was more like a kernel rollup since the kernel build number was already past the KR5 build number)

So to be able to debug, the first thing to do is to install the hot fix known as KB2251040 (I haven’t found this yet on the partner source knowledge base, but for those who need it, you can contact support to request this hot fix)

Installing the update

Run the update provided and you will eventually see the following screen where the installer checks the already installed parts.

Important: This update needs to be installed on all the machines if they are distributed on multiple physical servers. (In our case we updated the AOS / components on one box, and also the Business Connector on the IIS box)

Next the installer will ask you if you want to install the update on all instances of the AOS or select a specific one.

After the installer has finished, you will see the following file versions of the business connector:

Location FileName Version Filesize
Business Connector bin directory AxCliCfg.exe 5.0.1500.3076 374 kb
Business Connector bin directory Microsoft.Dynamics.BusinessConnectorNet.dll 5.0.1500.3076 13910 kb

There are several other files updated in the serverbin directory and in the clientbin but all should have the 5.0.1500.3076 version number. The list will also be complete when the KB article appears on partner source.

Steps to debug the AIF

Now that we have updated all of the components to work on the Windows 2008 platform, we can start worrying about the debugging itself. Several steps need to be taken into account to be able to debug, so let’s start.

Modify the AIFInboundProcessingService class

Firstly, imho the most important first so we do not forget this. There is method called runAsWrapper that we need to modify so that the runAs statement will not be used to run the message. Modify this so it matches the picture below.

Modify the client configuration

Open the client configuration tool and go to the developer tab. There you can check both checkboxes to enable the breakpoints in the business connector.

Modify the server configuration

Same thing on the server side. On the Application Object Server tab, make sure the checkboxes are enabled and restart the AOS service if needed.

Determine the Dynamics Ax user and enable debug mode for the user

Also very important is to set breakpoint with the right user. The user that runs the business connector will be used for setting breakpoint, etc..
This user will be specified when the logon call is used in the business connector. So make sure you debug with that user credential.

To enable debugging, open a Dynamics Ax 2009 client and set the debug mode to : When breakpoint.

Set the Business connector proxy user to the user you want to debug with everywhere

  • On the application pool
  • Inside dynamics Ax system services account

Allow the IIS windows service to interact with the desktop

This one is also crucial otherwise the IIS cannot interact with the debugger!

Add the debugging user to the Dynamics Ax Webservice Administrators

Log on to the AOS server with the debug user on the CONSOLE

Use the mstsc /admin command to log on to the AOS on the console session.

Manually start the Dynamics Ax Debugger

To finally start debugging, make sure you have opened an instance of the debugger on the machine where the business connector is running. In my case on the IIS web server. Make sure to run the debugger as Administrator

Moving objects between layers

Today I am working at a customer site and today’s task is to move all objects from the USR layer to the VAR layer. (Don’t ask why but due to licensing we could not work in the VAR layer)
There is already data present, so we cannot just move the objects without further attention.          

There are some solutions to the problem that I’ve stumbled upon on the web (f.e. http://www.axaptapedia.com/Move_DB_objects_to_another_layer ) but these did not do the job entirely.
Some problems that you may encounter and need to be addressed are:          

  • System fields that are in the range >= 60000 need to be taken into account
  • Long table names will be shortened by the SQL server backend to 30 chars (25 name + the 5 chars long Id of the table) So when changing the Id of a table that has a long name, you also need to rename the table on SQL server to contain the newly obtained ID

As always, there are several ways to travel to Rome but this is what I did. I have created a wizard that can be run before moving the table to collect dictionary data. Then you can move the object to another layer and rerun the wizard again to do the actions needed after moving the object.          

In general, this is what we need to do:          

  • Disable the synchronization code to prevent the system from executing a synchronization automatically
  • Run the wizard to gather the current dictionary data
  • Export the table and delete it from the USR layer
  • Import the table in the correct layer
  • Run the wizard again to gather the new dictionary data and update the SqlDictionary
  • Re-enable the synchronization code and perform synchronization

So let’s start! (By the way: Don’t mind the to-do’s, labels, best practices) because it is working, but will need some polishing 🙂 )         

Disable synchronization  

First we make an adjustment in the Application class, method DBSynchronize. We just make sure here the code is not executed. This is actually some stupid code and should be replaced with some kind of parameter to be able to enable / disable it.         

DBSynchronize modification

 

Now that the system will not synchronize until we make the change undone, we can start messing things up 🙂         

 Run the wizard before layer move        

 Before moving the table to another layer, we will gather all the current id’s to be able to lookup some stuff after the move to the other layer.  In the first step you need to specify the layers used and fill in the Pre-layer move option.      

Move Parameters

 

Then depending on pre/post layer move, the wizard shows a tab where you can enter tables to process when pre-layer move, or shows the results directly as in the next figure. (After the move, he uses the tables he finds in the log table)     

Wizard results

 

Then the wizard gathers everything and shows the data. In this case the new ids are empty because we are processing before the move. Just click next and finish the wizard.       

Exporting the objects  

Now that we have collected the ids we need to remember, we can export the tables (without id’s and labels) and delete the objects.       

Importing the objects  

After deleting the objects in the USR layer, we can import them in the VAR layer. This is a normal import so nothing special here.          

Run the wizard post move and update the SqlDictionary  

Now that the table is in the correct layer with the new ids generated, we need to run the wizard again to run some scripts on the SQLDictionary system table. The SQLDictionary table contains a representation of what Dynamics Ax thinks is present in the SQL database. So this table is used by the kernel to determine whether a alter table or create table statement is needed at the database backend side. 

To make sure the kernel will not try to create tables that already exist in the database with that name, we have to make sure the old id’s are replaced by the new ones. Same thing for field id’s…       

So we run the wizard again and specify the post-layer move option.       

Wizard parameters after moving table

 

Then we will not be prompted to specify tables but the wizard will process the same tables / fields as in the pre-move run of the wizard.
We get to see what will be adjusted in the SQLDictionary table in the next step.       

Results after moving the table

 

 So we have the old id’s to look for in the SQlDictionary table and the new id’s to replace them. So let’s click next and let the wizard do the processing.
This will result in the dictionary to contain the right id’s and prevent the table to be dropped and recreated by SQL server and thus keeping all the data.       

SQLDictionary results

 

Also note that the wizard takes care of long names that are shortened by SQL. The id is appended to the shortened table name so there is also a renaming needed at the backend side.      

So there you have it! Please feel free to comment on this.      

Caution: Doing stuff like this isn’t without any danger. The synchronization process does tons of stuff we (as non kernel developers) don’t know about so it might be possible there are some special cases in which this could be going wrong.
I try to process the tables in small bunches and take backups in the process so that I don’t have to run it for everything at once and en up halfway with some broken link in the SQLDictionary.      

Axaptapedia link…
http://www.axaptapedia.com/Moving_Table_between_layers
 

Download…
http://www.ksaelen.be/DynamicsAxXpo/SharedProject_KeSae_TableLayerMove.xpo

Header2Line functionality : Adding an extra field

It is possible to update fields on the purchase lines when the according field on the purchase header record changes. When you want to add a field to this functionality, this is what you need to do to make it work : 

  • Add the field to the Header2LineUpdate field group
  • HeaderToLineUpdate fieldgroup

     

  • Add field in the method LineUpdateDescription in the class PurchTable2LineField. This will take care of the text on the update parameters form.
  • Class : PurchTable2LineField, Method : LineUpdateDescription

     

When this is done, you should see the  field appear on the form with the update parameters. But if you called this once before, the map of fields will be stored in the usage data and therefore the table containing the chosen parameters will not contain the new field until you do one of the following : 

  • Call the initiate method on the PurchTable2LineParameters table. Also, you either have to delete the records in the table or skip the check if the parameters are already initiated if you want to keep the already setup values.
  • Initiate parameter records

     

  • Or erase the usage data and it will be initiated automatically (Also delete the records in the table of the check will return true that the records are already initialized.)

The result is that you have your field on the parameter form. (in my case here I added the transport field and also did some extra modification to the dialog to add 4 extra fields in one group, but this is not in scope of this post) 

Update parameters form

Dynamics AIF : Query Update Stacktrace

Today, when updating some records through AIF I got the following error :

AIF Stacktrace

This is definitely not a clear error 🙂 since the only thing was wrong is that the Update property was set to No instead of Yes on the Query’s datasource.

Update Property on the query's datasource

Thanks to a collegue of mine (from artofcreation) to point this one out !

Dynamics AIF : Null vs Empty string

Today I experienced that when using AIF through web services, you have to pay attention to the difference between an empty string and a null value.

We created a service for inventory locations and we had the InventLocationId filled in by the AIF. But when calling the service we received an error everytime telling us that the InventLocationId was mandatory but missing. (Although it was in the exemption list and there was code to fill it in)

Then we discovered that the calling .NET application specified the “” value on the inventLocationId instead of the NULL value. This results in a different XML file and the AIF thinks that the InventLocationId is filled in but with an empty value.

XML file when using null value :

AIF NULL Value

XML file when using empty string value :

AIF Empty String Value

Dynamics AIF : Working Times service

Today I created an AIF web service to expose the Working times functionality in Ax 2009. The service itself is quite simple but there was the issue with the workTimeId. The application on the other side doesn’t care about the Id of the working times and wants Dynamics to use a number sequence for the workTimeId field. This post will not deal with the creation of a number sequence reference but shows how you can fill in the WorkTimeId automatically and especially how you can link the WorkTimeLine to the WorkTimeTable via the WorkTimeId.

First things first, we create a query which contain the two tables.

AxdWorkTime Query Object

Since we are planning on updating / deleting through the service, we must not forget to set the properties on the datasources. Properties Delete and Update must be set to Yes.

Then we create the AIF service by using the Wizard available in AX 2009. Afterwards we have the following objects available :

  • AIF Service object
  • AIF Service class
  • AxWorkTimeTable entity class
  • AxWorkTimeLine entity class
  • AxdWorkTime document classWorkTimeDocument / WorkTimeDocument_WorkTimeTable / WorkTimeDocument_WorkTimeLine Classes
  • Some datacontainertype macros stuff

Created project

Now we are going to make sure the WorkTimeId is fetched from a number sequence and inserted automatically. For this we need to do 2 things :

  • Set the WorkTimeId as not mandatory (Both on the axWorkTimeTable and axWorkTimeLine entities)
  • Modify the setWorkTimeId method on the axWorkTimeTable entity to use the number sequence
protected void initMandatoryFieldsExemptionList()
{
    super();
    this.setParmMethodAsNotMandatory(methodstr(AxWorkTimeTable,parmWorkTimeId));
}
protected void setWorkTimeId()
{
    NumberSequenceReference numberSequenceReference;
    ;

    if (this.isMethodExecuted(funcName(), fieldNum(WorkTimeTable, WorkTimeId)))
    {
        return;
    }

    if (this.isSetMethodsCalledFromSave())
    {
        if (this.isFieldSetExternally(fieldnum(WorkTimeTable, WorkTimeId)))
        {
            if (!this.workTimeTable())
            {
                numberSequenceReference = NumberSeqReference::findReference(typeId2ExtendedTypeId(typeid(WorkTimeId)));
                this.checkNumber(numberSequenceReference.numberSequenceTable(),fieldNum(WorkTimeTable, WorkTimeId),this.parmWorkTimeId());
                if (numberSequenceReference.NumberSequence && numberSequenceReference.numberSequenceTable().Continuous)
                {
                    NumberSeq::newReserveNum(numberSequenceReference).reserve(this.parmWorkTimeId());
                }
            }
        }
        else
        {
            if (this.isFieldSet(fieldnum(WorkTimeTable, WorkTimeId)))
            {
                return;
            }

            if (!this.parmWorkTimeId())
            {
                this.parmWorkTimeId(NumberSeq::newGetNum(NumberSeqReference::findReference(typeId2ExtendedTypeId(typeid(WorkTimeId)))).num());
            }
        }
    }
}

And last but not least we need to link the AxWorkTimeLine to the AxWorkTimeTable via the WorkTimeId. The wizard already did some action to achieve this by putting the prepareForSave method on the AxdWorkTime document class.

public boolean prepareForSave(AxdStack _axdStack,  str _dataSourceName)
{
    AxWorkTimeTable   AxWorkTimeTable;
    AxWorkTimeLine    AxWorkTimeLine;
    ;
    switch (classidget(_axdStack.top()))
    {
        case classnum(AxWorkTimeTable) :
            AxWorkTimeTable = _axdStack.top();
            return true;

        case classnum(AxWorkTimeLine) :
            AxWorkTimeLine  = _axdStack.top();
            AxWorkTimeTable = AxWorkTimeLine.parentAxBC();
            AxWorkTimeLine.parmWorkTimeId(AxWorkTimeTable.parmWorkTimeId());
            return true;

        default :
            error(strfmt("@SYS88979",classId2Name(classidget(_axdStack.top()))));
            return false;
    }
    return false;
}

Since the parm method will call the setWorkTimeId method once, we still need to place some code there :
The code will call the parent entity’s parmWorkTimeId method.

protected void setWorkTimeId()
{
    if (this.isMethodExecuted(funcName(), fieldNum(WorkTimeLine, WorkTimeId)))
    {
        return;
    }

    if (!this.parmWorkTimeId())
    {
        this.parmWorkTimeId(this.axWorkTimeTable().parmWorkTimeId());
    }
}

The only thing still needed now is pointing out who is the parent entity. Therefore I’ve created a method that creates an axWorkTimeTable entity.

public AxWorkTimeTable axWorkTimeTable(AxWorkTimeTable _axWorkTimeTable = null)
{
    AxWorkTimeTable axWorkTimeTable;

    if (!prmisdefault(_axWorkTimeTable))
    {
        axWorkTimeTable = _axWorkTimeTable;
    }
    else
    {
        this.setWorkTimeId();
        axWorkTimeTable = WorkTimeTable::find(this.parmWorkTimeId()).axWorkTimeTable();
    }

    return axWorkTimeTable;
}
public AxWorkTimeTable axWorkTimeTable()
{
    AxWorkTimeTable axWorkTimeTable = AxWorkTimeTable::newWorkTimeTable(this);
    return AxWorkTimeTable;
}

If someone wants to add something, be my guest… comments are welcome !

Dynamics AIF : Tracing Web Service calls

When using AIF through web services, you can be in the dark when you want to know what’s really going on in there. Additionally you can have trouble debugging the thing when you are running a Windows 2008 box. (Contacted Microsoft last week and the response was that debuggin AIF webservices does not work on a Win2k8 box but they are developing a hotfix as we speak)

There is a possibility to have some tracing of what is happening there.  I found this on MSDN and will show an example here of what the result of the tracing shows. 

First, head over to the folder where the Dynamics Ax web services are generated. There you should find a web.config file. To enable tracing you need to make sure this tag is present 

 <system.diagnostics>
    <!-- This is used for enabling tracing in retail and debug builds.
           AIF Service processing stack outputs and information
           messages used for debugging.
           Possible values for the switch are Off, Warning,
             Information, Critical, Verbose.
           Change the switchValue to Information to enable tracing.
      -->
    <sources >
      <source name="AifServiceProcessing" switchValue="Information">
        <listeners >
          <add name="TextWriterListener"
               type="System.Diagnostics.TextWriterTraceListener"
               initializeData="AifServiceTrace.webinfo" >
          </add>
          <remove name="Default" />
        </listeners>
      </source>
    </sources>
  </system.diagnostics>  

After invoking the service, there should be a file named AifServiceTrace.Webinfo present with the tracing information : 

The following information can be viewed :

  • UserId’s
  • Message ID
  • Source and destination endpoint
  • Key Data XML
  • Return Data from Ax

 

AIF WCF Tracing

Dynamics AIF : Invalid child element in namespace

A couple of days ago, I encountered this problem while calling the InventInventLocation AIF web service’s create operation.

System.ServiceModel.FaultException : Invalid document schema. The following error is reported : The element ‘InventLocation’ in namespace ‘http://schemas.microsoft.com/dynamics/2008/01/documents/InventLocation’ has invalid child element ‘InventLocationIdQuarantine’ in namespace ‘http://schemas.microsoft.com/dynamics/2008/01/documents/InventLocation’. List of possible elements expected: ‘InventLocationId’ in namespace ‘http://schemas.microsoft.com/dynamics/2008/01/documents/InventLocation’. 

The error itself may actually be confusing because in this case, the InventLocationIdQuarantine field is not actually the cause of the problem. In most cases this error will appear when the order of fields is different from the one expected by the document schema. So either the field ordering is different of there are some fields missing. In this case, I was using AIF webservices (WCF services) and you can view the actual XML that was communicated to the AIF by going to the document history.

Basic – Periodic – Application Integration Framework –  Document History 

AIF Document History

 There you can view the XML by clicking the XML button.   

XML InventLocation Create Operation

Here we can see that the inventlocation field is not present in the XML.
Do note that in this Ax application we have create custom code to initiate the InventLocationId field from a numbersequence when the record is created.   

To solve the error here, we need to tell the AIF engine that the InventLocationId field is not mandatory in our XML document and make sure the value gets defaulted.
This will be done here by adding defaulting logic in the Entity class but this could also be done by putting the defaulting “behavior” on the table using a strategy pattern. 

Setting the field as not mandatory :   

protected void initMandatoryFieldsExemptionList()
{
    super();
    this.setParmMethodAsNotMandatory(methodstr(AxInventLocation,parmInventLocationId));
}

Adding the defaulting logic

protected void setInventLocationId()
{
    NumberSequenceReference numberSequenceReference;
    ;

    if (this.isMethodExecuted(funcname(), fieldnum(InventLocation, InventLocationId)))
    {
        return;
    }

    if (this.isSetMethodsCalledFromSave())
    {
        if (this.isFieldSetExternally(fieldnum(InventLocation, InventLocationId)))
        {
            if (!this.inventLocation())
            {
                numberSequenceReference = InventParameters::numRefInventLocationId();
                this.checkNumber(numberSequenceReference.numberSequenceTable(),fieldnum(InventLocation, InventLocationId),this.parmInventLocationId());
                if (numberSequenceReference.NumberSequence && numberSequenceReference.numberSequenceTable().Continuous)
                {
                    NumberSeq::newReserveNum(numberSequenceReference).reserve(this.parmInventLocationId());
                }
            }
        }
        else
        {
            if (this.isFieldSet(fieldnum(InventLocation, InventLocationId)))
            {
                return;
            }

            if (!this.parmInventLocationId())
            {
                this.parmInventLocationId(NumberSeq::newGetNum(InventParameters::numRefInventLocationId()).num());
            }
        }
    }
}

Now we do not need to specify the inventLocationId and the field will be defaulted to a new number from the number sequence reference.