Wednesday, March 27, 2019

Common mistake, when creating Purchase Orders from X++

I would like to warn my fellow Axapta/AX/D365FO bloggers, that blind copy-pasting from each other seems innocent, but it fact may result in spreading bugs.

E.g., the following piece of code is very popular in "How to create Purchase Order from X++" posts.

numberSeq = NumberSeq::newGetNumFromCode(purchParameters::numRefPurchaseOrderId().NumberSequence,true);

Purchtable.PurchId = numberSeq.num();

The problem is numRefPurchaseOrderId returns the number sequence for purchase order confirmation number. For purchase order number, numRefPurchId should have been used. Using the wrong number sequence may result in duplicate key issues.

Thursday, December 6, 2018

Controlling grid line colors

Can see some folks have found the DisplayOptionInitialize event, still they are in doubt on how to control the displayOption settings based on the selected line.


Here is the solution:


X++:
 [FormDataSourceEventHandler(formDataSourceStr(Form1, SalesTable), FormDataSourceEventType::DisplayOptionInitialize)]
    public static void SalesTable_OnDisplayOptionInitialize(FormDataSource sender, FormDataSourceEventArgs e)
    {
        FormDataSourceDisplayOptionInitializeEventArgs args = e as FormDataSourceDisplayOptionInitializeEventArgs;
        RecId currentRecId = args.record().RecId;
        int rgbValue = 255 - (20 * (currentRecId mod 2));
        args.displayOption().backColor(WinAPI::RGB2int(rgbValue, rgbValue, rgbValue));
    }



Tuesday, November 6, 2018

Create LedgerDimension from main account and financial dimensions

The following code will (hopefully) keep generating valid LedgerDimension values, even after account structures or advanced rule structures are modified.

In the example below, there is an account structure with MainAccount-BusinessUnit-Department segments and an advanced rule with Project segment.

class TestLedgerDim
{        
    public static DimensionDynamicAccount generateLedgerDimension(
        MainAccountNum _mainAccountId,
        str _departmentId,
        str _businessUnit,
        str _projectId)
    {
        DimensionAttributeValueSetStorage dimensionAttributeValueSetStorage 
            = new DimensionAttributeValueSetStorage();

        void addDimensionAttributeValue(
            DimensionAttribute _dimensionAttribute, 
            str _dimValueStr)
        {
            DimensionAttributeValue dimensionAttributeValue;

            if (_dimValueStr != '')
            {
                dimensionAttributeValue = 
                    DimensionAttributeValue::findByDimensionAttributeAndValueNoError(
                        _dimensionAttribute,
                        _dimValueStr);
            }

            if (dimensionAttributeValue.RecId != 0)
            {
                dimensionAttributeValueSetStorage.addItem(dimensionAttributeValue);
            }
        }

        DimensionAttribute dimensionAttribute;

        while select dimensionAttribute
            where dimensionAttribute.ViewName == tableStr(DimAttributeOMDepartment)
               || dimensionAttribute.ViewName == tableStr(DimAttributeOMBusinessUnit)
               || dimensionAttribute.ViewName == tableStr(DimAttributeProjTable)
        {
            switch (dimensionAttribute.ViewName)
            {
                case tableStr(DimAttributeOMDepartment):
                    addDimensionAttributeValue(dimensionAttribute, _departmentId);
                    break;

                case tableStr(DimAttributeOMBusinessUnit):
                    addDimensionAttributeValue(dimensionAttribute, _businessUnit);
                    break;

                case tableStr(DimAttributeProjTable):
                    addDimensionAttributeValue(dimensionAttribute, _projectId);
                    break;
            }            
        }

        RecId defaultDimensionRecId = dimensionAttributeValueSetStorage.save();

        return LedgerDimensionFacade::serviceCreateLedgerDimension(
            LedgerDefaultAccountHelper::getDefaultAccountFromMainAccountRecId(
                MainAccount::findByMainAccountId(_mainAccountId).RecId),
            defaultDimensionRecId);
    }

    /// <summary>
    /// Runs the class with the specified arguments.
    /// </summary>
    /// <param name = "_args">The specified arguments.</param>
    public static void main(Args _args)
    {   
        // Prints 110110-001-022-000002
        info(
            DimensionAttributeValueCombination::find(
                TestLedgerDim::generateLedgerDimension(
                    '110110', 
                    '022', 
                    '001', 
                    '000002')).DisplayValue);
    }

}

Tuesday, November 29, 2016

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.