Insight into AX 2012 Product Dimensions

Situation:  Configurable product masters (Constraint-based configuration) exist without the required standard configuration

Probable cause: Setup was missing during import of product master.

Solution: Requires a script as the default configuration should have been created automatically and the form required to add configurations manually is write protected for constraint-based configurations. The creation of product variants and activation as standard configuration would be possible manually, but is covered by the script as well.

Script Overiew

  • Create product specific configuration  (Main tables involved: EcoResProductDimensionGroupProduct)
  • Create product variant (Main tables involved: EcoResDistinctProductVariant, EcoResProductVariantConfiguration; Helper Class: EcoResProductVariantManager)
  • Release product variant to company (Main tables involved: InventDimCombination, InventDim; Helper Class: EcoResProductVariantReleaseManager)
  • Set variant as default on item (Main tables involved: InventTable)
static void ecoResProductMasterConfiguration_fix(Args _args)
{
    EcoResConfiguration                 c = EcoResConfiguration::findByName(EcoResProductParameters::getConfigurationName());
    EcoResProduct                       p;
    EcoResProductMasterConfiguration    productMasterConfiguration, productMasterConfiguration_notExists;
    EcoResProductDimensionGroupProduct  productDimensionGroupProduct;
    EcoResProductDimensionGroup         productDimensionGroup = EcoResProductDimensionGroup::findByDimensionGroupName("CONFIG_GRP");
    EcoResDistinctProductVariant        distinctProductVariant, distinctProductVariant_notExists;
    RefRecId                            productVariant;
    RefRecId                            attribute = EcoResProductDimensionAttribute::inventDimFieldId2DimensionAttributeRecId(fieldNum(InventDim,configId));
    EcoResProductVariantReleaseManager  ecoResProductVariantReleaseManager;
    InventTable                         inventTable;
    InventDimCombination                inventDimCombination_notExists;
    int                                 i,u;
    ;
    //Add config to product master (this cannot be done manually - error during import)
    While select p join productDimensionGroupProduct where productDimensionGroupProduct.Product == p.RecId && productDimensionGroupProduct.ProductDimensionGroup == productDimensionGroup.RecId
        notExists join productMasterConfiguration_notExists where productMasterConfiguration_notExists.ConfigProductMaster == p.RecId && productMasterConfiguration_notExists.Configuration == c.RecId
    {
        info(p.productNumber());
        ttsBegin;
        productMasterConfiguration.initValue();
        productMasterConfiguration.ConfigProductMaster = p.RecId;
        productMasterConfiguration.Configuration = c.RecId;
        productMasterConfiguration.ConfigProductDimensionAttribute = EcoResProductDimensionAttribute::inventDimFieldId2DimensionAttributeRecId(fieldNum(InventDim,ConfigId));
        productMasterConfiguration.insert();
        ttsCommit;
        i++;
    }
    //create product variant
    While select p //debug: where p.RecId == EcoResProduct::findByProductNumber("0052999").RecId
        join productDimensionGroupProduct where productDimensionGroupProduct.Product == p.RecId && productDimensionGroupProduct.ProductDimensionGroup == productDimensionGroup.RecId
        notExists join distinctProductVariant_notExists where distinctProductVariant_notExists.ProductMaster == p.RecId //not precise, but sufficient to identify missing standard variant
    {
        info(p.productNumber());
        ttsBegin;
        productVariant = EcoResProductVariantManager::createProductVariant(p.RecId,p.SearchName,[[attribute,c.RecId]]);
        EcoResProductTranslation::createOrUpdateTranslation(productVariant, p.productName(), '');
        ttsCommit;
        i++;
    }
    //release product variant to current company
    While select forupdate inventTable
        join p where inventTable.Product == p.RecId //debug: && p.RecId == EcoResProduct::findByProductNumber("0052999").RecId
        join distinctProductVariant where distinctProductVariant.ProductMaster == p.RecId
        exists join productDimensionGroupProduct where productDimensionGroupProduct.Product == p.RecId && productDimensionGroupProduct.ProductDimensionGroup == productDimensionGroup.RecId
        notExists join inventDimCombination_notExists where inventDimCombination_notExists.ItemId == inventTable.itemId //not precise, but sufficient to identify not released standard variant
    {
        info(p.productNumber());
        ecoResProductVariantReleaseManager = EcoResProductVariantReleaseManager::construct();
        ecoResProductVariantReleaseManager.parmEcoResProduct(distinctProductVariant);
        ecoResProductVariantReleaseManager.parmLegalEntity(CompanyInfo::current());
        ecoResProductVariantReleaseManager.release();
        i++;
    }
    //set default configuration
    ttsBegin;
    While select forupdate inventTable where !inventTable.StandardConfigId
        join p where inventTable.Product == p.RecId
        exists join productDimensionGroupProduct where productDimensionGroupProduct.Product == p.RecId && productDimensionGroupProduct.ProductDimensionGroup == productDimensionGroup.RecId
    {
        info(p.productNumber());
        inventTable.StandardConfigId = c.Name;
        inventTable.update();
        u++;
    }
    ttsCommit;
    info(strFmt("%1 records inserted, %2 updated",i,u));
}

Convert Product to Product master

Changing the table inheritance of a record must be done on SQL level – the following code does this. Of course, related data must be handled as well.

static void Job1(Args _args)
{
    //change to product master
    class1::changeInheritance('EcoResProduct',tableNum(EcoResProductMaster),EcoResProduct::findByDisplayProductNumber('0119').RecId);
    //change back
    //class1::changeInheritance('EcoResProduct',tableNum(EcoResDistinctProduct),EcoResProduct::findByDisplayProductNumber('0119').RecId);
}
//Important: this code must be run on the server
public static server void changeInheritance(str _tableName, tableId _newTableId, RefRecId   _refRecId)
{
    Connection      connection;
    Statement       statement;
    str             query;
    ;
    // create connection object
    connection = new Connection();
    // create statement
    statement = connection.createStatement();
    // Set the SQL statement
    query = strfmt("update %1 set %1.InstanceRelationType ='%2' where %1.RecId = %3;", _tableName, _newTableId, _refRecId);
    info(query);
    // assert SQL statement execute permission
    new SqlStatementExecutePermission(query).assert();
    //BP Deviation documented
    statement.executeUpdate(query);
    // limit the scope of the assert call
    CodeAccessPermission::revertAssert();
}

Better Default Number Sequences

I’m not sure what you think, but I don’t like that most of the number sequences the AX 2012 Wizard creates contain the company.

The following job removes the company from all number sequences:

 

static void NumberSequenceSegments_RemoveCompany(Args _args)
{
    str                 annotatedFormat;
    str                 format;
    container           segments;
    NumberSequenceTable numberSequenceTable;
    boolean             companyFound;
    int                 i, j;
    str                 company;
    ;
    ttsBegin;
    while select forUpdate numberSequenceTable
    {
        companyFound = false;
        segments = NumberSeq::parseAnnotatedFormat(numberSequenceTable.AnnotatedFormat);
        for(i=1;i<=conLen(segments);i++)
        {
            if(conPeek(conPeek(segments,i),1) == 0)
            {
                company = conPeek(conPeek(segments,i),2);
                segments = conDel(segments,i,1); //remove company from number sequence
                companyFound = true;
                i--;
            }
            else if(companyFound && conPeek(conPeek(segments,i),1) == -1 && conPeek(conPeek(segments,i),2) == "-")
            {
                segments = conDel(segments,i,1); //remove "-" after company from number sequence
                i--;
                break;
            }
        }
        if(companyFound)
        {
            annotatedFormat = NumberSeq::createAnnotatedFormatFromSegments(segments);
            format = NumberSeq::createAnnotatedFormatFromSegments(segments, false);

            if(company)
            {
                numberSequenceTable.Txt = strFmt("%1 (%2)",numberSequenceTable.Txt,company);
            }
            numberSequenceTable.AnnotatedFormat = annotatedFormat;
            numberSequenceTable.Format = format;
            numberSequenceTable.update();
            j++;
        }
    }
    ttsCommit;
    info(strFmt("@SYS74545",j,tableId2pname(tableNum(numberSequenceTable))));
}

Enable Database Log on all Parameter- and Group-Tables

I want to make sure that I know who changed what and when on all parameter- and group-tables.

The following script will enable the database log on all those tables:

static void enableDatabaselogOnParameterTables(Args _args)
{
    #AOT
    DatabaseLog         databaseLog;
    TreeNode            treeNode;
    SysDictTable        sysDictTable;
    Name                name;
    ;
    ttsbegin;
    treeNode = TreeNode::findNode(#TablesPath);
    treeNode = treeNode.AOTfirstChild();
    while (treeNode)
    {
        name = treeNode.AOTname();
        sysDictTable = SysDictTable::newTableId(treeNode.applObjectId());
        if((sysDictTable.tableGroup() == TableGroup::Parameter || sysDictTable.tableGroup() == TableGroup::Group)
            && SysQueryForm::hasValidCountryCode(sysDictTable.id()))
        {
            select firstOnly databaseLog where databaseLog.logTable == sysDictTable.id(); //do not modify existing settings
            if(!databaseLog)
            {
                databaseLog.LogTable = sysDictTable.id();
                databaseLog.LogField = 0;
                databaseLog.LogType = DatabaseLogType::Update;
                databaseLog.insert();
                databaseLog.LogType = DatabaseLogType::EventUpdate;
                databaseLog.insert();
                if(sysDictTable.tableGroup() != TableGroup::Parameter)
                {
                    databaseLog.LogType = DatabaseLogType::Delete;
                    databaseLog.insert();
                    databaseLog.LogType = DatabaseLogType::EventDelete;
                    databaseLog.insert();
                    databaseLog.LogType = DatabaseLogType::RenameKey;
                    databaseLog.insert();
                    databaseLog.LogType = DatabaseLogType::EventRenameKey;
                    databaseLog.insert();
                    databaseLog.LogType = DatabaseLogType::Insert;
                    databaseLog.insert();
                    databaseLog.LogType = DatabaseLogType::EventInsert;
                    databaseLog.insert();
                }
            }
        }
        treeNode = treeNode.AOTnextSibling();
    }
    ttscommit;
    SysFlushDatabaseLogSetup::main();
}

How to get radio buttons on a dialog

There is no dialog.addRadioButtonContol(…) function so how do you do it?

static void Job1(Args _args)
{
    Dialog                dialog;
    FormRadioControl      formRadioControl;
    FormBuildRadioControl formBuildRadioControl;
    FormBuildGroupControl formBuildGroupControl;
    int                   formBuildRadioControlId;
    ;
    //Build dialog with radio buttons
    dialog = new Dialog("Test Dialog");
    formBuildGroupControl = dialog.mainFormGroup();
    formBuildRadioControl = formBuildGroupControl.addControl(FormControlType::RadioButton,'radiobuttons');
    formBuildRadioControlId = formBuildRadioControl.id();
    //set number of buttons
    formBuildRadioControl.items(3);
    //add descriptions
    formBuildRadioControl.item(1);
    formBuildRadioControl.text("Radiobutton 1");
    formBuildRadioControl.item(2);
    formBuildRadioControl.text("Radiobutton 2");
    formBuildRadioControl.item(3);
    formBuildRadioControl.text("Radiobutton 3");
 
    if(dialog.run())
    {
        if(formBuildRadioControl)
        {
            //get control
            formRadioControl = dialog.formRun().control(formBuildRadioControlId);
            //get index
            info(strfmt("Radiobutton %1 selected",formRadioControl.selection()+1));
        }
    }
}