Showing posts with label bug. Show all posts
Showing posts with label bug. Show all posts

Monday, October 3, 2016

Bugs in the DateTimeUtil::getSystemDateTime()

After the current system date is changed, either from the user interface or via systemDateSet function, the DateTimeUtil::getSystemDateTime() goes out of control. Don't ever use this function for unique keys generation.

Unfortunately, they use it a lot in the DIXF.

static void printDateTimeJob(Args _args)
{
    void printDateTime()
    {
        info(strFmt('systemDateGet: %1 %2', 
            systemDateGet(), 
            time2StrHMS(timeNow())));
        info(strFmt('getSystemDateTime: %1', DateTimeUtil::getSystemDateTime()));
        info(strFmt('utcNow: %1', DateTimeUtil::utcNow()));
    }
 
    warning('Before date/time change');
 
    printDateTime();
 
    sleep(2000);
 
    info('...2 seconds later:');
 
    printDateTime();
 
    systemDateSet(systemDateGet() - 3);
 
    warning('System date changed:');
 
    printDateTime();
 
    sleep(2000);
 
    info('...2 seconds later:');
 
    printDateTime();
 
    systemDateSet(systemDateGet() + 3);
 
    warning('System date is back:');
 
    printDateTime();
 
    sleep(2000);
 
    info('...2 seconds later:');
 
    printDateTime();
}



And this is the result:


P. S.: Kernel version 6.3.4000.1745, Application version 6.3.3000.110

Monday, April 4, 2016

Error executing code: The field with ID '0' does not exist in table 'SysExtensionSerializerExtensionMap'.

If you ever get "Error executing code: The field with ID '0' does not exist in table 'SysExtensionSerializerExtensionMap'." error in CIL, it may happen it is the table extension framework + the standard buf2buf function to blame.


Somehow, if you pass a Map instead of a table to buf2buf function, the following line fails:

_to.(fieldId) = _from.(fieldId);


However, if you replace this line with:


fieldName = fieldId2name(_from.TableId, fieldId);
 _to.setFieldValue(fieldName, _from.getFieldValue(fieldName));


everything is fine both in X++ and in CIL.


P.S. I didn't dare to change the standard buf2buf method. Istead, I created another method and used it in the table extension framework logic, which failed because of the issue (\Data Dictionary\Maps\SysExtensionSerializerMap\Methods\copyExtensionTableData)


P.P.S. There are a couple of other SysExtensionSerializerMap methods that should be fixed, as they call buf2buf too.


Tuesday, March 8, 2016

DIXF: wrong field values after sales order import?

The more I work with the Data Import-Export Framework in AX 2012 R3, the more fun I have.

Never ever put an initValue() method call after any initFrom<some table name> method.


Thursday, February 20, 2014

con2buf crashing AOS

Interesting kernel issue was found today.

Developer added a new string field to the SalesTable table, which already had some custom fields created for other features. Later, a new build was released to test. In the test environment, AOS crashed each time user had to create sales order lines.

User reset his usage data, and that fixed the issue, but we wanted to avoid resetting all usage data for all users in production environment.

Thanks to the Trace Parcer, the real source of the problem was found. Somewhere deep in the sales line creation logic,  SalesTable2LineUpdatePrompt.getLast() method was called. The crash happened when unpacking a sales table buffer from a container with a con2buf function. The fix was to remove all records for this class from the SysLastValue table:

static void fixAosCrash(Args _args)
{
    sysLastValue sysLastValue;
 
    delete_from sysLastValue
        where sysLastValue.elementName == classStr(SalesTable2LineUpdatePrompt);
}

I have no explanation why this happened, and I don't know how to repro that in the demo AX virtual PC, so just be aware of that issue.

Monday, January 27, 2014

Dynamics AX 2012: orig() method fails on derived tables

Just found a bug in the table inheritance feature (kernel 6.0.1108.5781).

If you have a base table A with field B, and a derived table C with field D, then C.orig().B will return a strange value (or nil, depending on the field type).

In order to get the correct value, you will need to downcast C to A, and then call A.orig().B.

This is the job that reproes the issue:

static void reproOrigBug(Args _args)
{
    CompanyInfo     companyInfo;
    DirPartyTable   dirPartyTable;
 
    select firstOnly companyInfo;
 
    info(companyInfo.orig().DataArea);
    info(companyInfo.orig().Name);
 
    dirPartyTable = companyInfo as DirPartyTable;
    info(dirPartyTable.orig().Name);
}

And this is the output:


I will report the issue to MS Support.

Thursday, January 16, 2014

Bug in the source document framework (AX 2012 CU5)

There was an issue in SouceDocumentLineItem class, that I had to fix recently.

The failing scenario looked something like this:
  • Create a direct delivery SO with inter-company chain, with one order line
  • Set the line sales qty to 10
  • Set the line property Completed to No, so it may be partially delivered
  • After the IC chain is there, go to the IC SO and post a packing slip for qty 5
  • Verify that the IC PO is also partially delivered with the same qty
  • Go to the original SO and change the line qty from 10 to 7 (in our customization, this is done in the code)
  • Check the IC PO accounting distributions
In our case, there was one distribution line broken (click to enlarge the picture):


Apparently, the main account for the tax amount was not found.

Because of the broken accounting distribution, the PO could not be processed, and the following error message popped up:

  • One or more accounting distribution is missing a ledger account or contains a ledger account that is not valid. Use the Accounting distribution form or the Posting profile to update the ledger account.
  • The state of the source document or source document line could not be updated.
Pressing Reset button did help, but our customer was not very happy about that workaround, as there were may POs broken like this.

After some hours of debugging, I found that everything starts in this method:


Here, we have a "select forupdate", that loops through TaxUncommitted table records and calls submitSourceDocumentLine instance method:


The taxUncommitted.submitSourceDocumentLine call ends up here:

In this constructor method, the sourceDocumentLineItem class instance is first initialized from the _sourceDocumentLineImplementation parameter (which is actually a TaxUncommitted record buffer), and then put to cache (see SysTransactionScopeCache::set call).

Let's look closer at the sourceDocumentLineItem.initialize method in the debugger. The  sourceDocumentLineItem variable (which is actually TaxSourceDocSublineItem class instance), has a member-variable of Map type, which is initialized with the TaxUncommitted record buffer (selected for update in the while-loop, as we have seen before!):


The problem is that by the time the sourceDocumentLineItem class instance is fetched from the cache later in the business logic, the TaxUncommitted cursor is null, so the taxMap member-variable in the cached instance is empty and things like tax code cannot be determined anymore.

The fix was to use xRecord.data() call to separate the map from the original table buffer:


I will report the issue to MS Support.

Wednesday, December 18, 2013

Table inheritance and collection objects


Be aware that there is an issue with storing child table records in collection objects like List.

If you have a table hierarchy and add a child table record to a List and then try to get it back, information from parent tables is lost, along with InstanceRelationType field value.

The following job reproes the issue:

static void tableInheritanceAndListBug(Args _args)
{
    CompanyInfo     companyInfo;
    List            companyInfoList;
    ListEnumerator  companyInfoEnumerator;
 
    companyInfoList = new List(Types::Record);
 
    select firstOnly companyInfo;
 
    info(strFmt(
        "Orig: RecId: %1, Name: %2, InstanceRelationType: %3",
        companyInfo.RecId,
        companyInfo.Name,
        companyInfo.InstanceRelationType));
 
    companyInfoList.addEnd(companyInfo);
 
    companyInfoEnumerator = companyInfoList.getEnumerator();
    if (companyInfoEnumerator.moveNext())
    {
        companyInfo = companyInfoEnumerator.current();
        info(strFmt(
            "List: RecId: %1, Name: %2, InstanceRelationType: %3",
            companyInfo.RecId,
            companyInfo.Name,
            companyInfo.InstanceRelationType));
    }
}

Output:
Orig: RecId: 5637151316, Name: Contoso Entertainment Systems - E&G Division, InstanceRelationType: 41
List: RecId: 5637151316, Name: , InstanceRelationType: 0


The workaround is to use buf2con function to convert the table buffer to a container, save the container in the list and finally use con2buf when fetching the value with enumerator.

static void tableInheritanceAndListBugWorkaround(Args _args)
{
    CompanyInfo     companyInfo;
    List            companyInfoList;
    ListEnumerator  companyInfoEnumerator;
 
    companyInfoList = new List(Types::Container);
 
    select firstOnly companyInfo;
 
    info(strFmt(
        "Orig: RecId: %1, Name: %2, InstanceRelationType: %3",
        companyInfo.RecId,
        companyInfo.Name,
        companyInfo.InstanceRelationType));
 
    companyInfoList.addEnd(buf2Con(companyInfo));
 
    companyInfoEnumerator = companyInfoList.getEnumerator();
    if (companyInfoEnumerator.moveNext())
    {
        companyInfo = con2Buf(companyInfoEnumerator.current());
        info(strFmt(
            "List: RecId: %1, Name: %2, InstanceRelationType: %3",
            companyInfo.RecId,
            companyInfo.Name,
            companyInfo.InstanceRelationType));
    }
}

Output:
Orig: RecId: 5637151316, Name: Contoso Entertainment Systems - E&G Division, InstanceRelationType: 41
List: RecId: 5637151316, Name: Contoso Entertainment Systems - E&G Division, InstanceRelationType: 41


UPDATE: the bugs is reported to MS Support

Friday, March 22, 2013

Mandatory fields and table hierarchies

If you have a table hierarchy, and there is a mandatory field in the child table, your data may become inconsistent in some cases.

For example, there are 2 tables (TestDog extends TestAnimal):

 
 
Let's make TestDog.OwnerName field mandatory and create a record in TestDog with blank OwnerName:
 
 
A warning is shown, which is correct. Now let's close the form. Normally, you would expect a dialog "Changes have been made in the form. Save changes? Yes/No". But in our case, the records are saved in both tables, with a blank mandatory field in TestDog.

Tuesday, December 11, 2012

When good habits matter

Ever wondered why

if (x == 1)
{
    y = 2;
}
 

is better than

if (x == 1)
    y = 2;

?

Find a bug in Dynamics AX 2012 RTM then:

Sunday, November 4, 2012

Computed columns in Views by example

There is a bug in the standard AX, in the query named AxdPurchaseRequisition. The problem is here:


PurchLineAllVersions datasource is based on a View object, so PurchLineAllVersions.TableId contains table ID of the view itself, and not that of the underlying tables. As a result, no DocuRefTrans records will be found with this quiry.

The issue may be fixed by using computed columns in views - the new feature in AX 2012.

As you may already know, the PurchLineAllVersions view is based on the quiry PurchLineAllVersions, which, in turn, is a union of 2 other views: PurchLineArchivedVersions and PurchLineNotArchivedVersions.

First, add a method to the 2 views.

PurchLineNotArchivedVersions:

public static server str purchLineTableId()
{
    return SysComputedColumn::returnLiteral(tableNum(PurchLine));
} 

PurchLineArchivedVersions:

public static server str purchLineTableId()
{
    return SysComputedColumn::returnLiteral(tableNum(PurchLineHistory));
}

Add a column to the 2 views: right-click on the Fields, then "New -> Int Computed Column". Set the new fields' properties like that:


Then, add new PurchLineTableId fields to the PurchLineAllVersions query datasources and the PurchLineAllVersions view.

Finally, change the faulty link in the AxdPurchaseRequisition quiry, so that it will connect PurchLineAllVersions.PurchLineTableId and DocuRefTrans.RefTableId.

By the way, there is another, better example of how computed columns may be used: take a look at the InventValue* views, which are used by the Inventory Value report.

Thursday, November 1, 2012

xRecord.setTmp() and inactive configuration keys

Be carefull when using setTmp method on table buffers. If your table is assigned a configuration key, turning it off will make this line of code throw an error at runtime: "Table '<table name>' of type TempDB cannot be changed to table of type InMemory".

For example, \Classes\SysRecordTemplateEdit\setFormDataSourceRecord has this problem. It loops through form datasources and tries to set them all to InMemory withouth checking first if they are TempDB or not.

In order to avoid this error, you can use the following construction:

    if (!myTable.isTempDb())
    {
        myTable.setTmp();
    }

More about TempDB tables on MSDN.