Monday, October 27, 2014

Find Price and Not Lose It

A system bug in PriceDisc class that leads to losing the found price/discount. Still presented in AX 2012 R3 (Application version 6.3.164.0) The local subroutine findDisc in findDisc method is called inside the buffer loop and must be fixed as follows.
void findDisc()
    {

        if ((discDate >= localFromDate  || ! localFromDate)
            && (discDate <= localToDate || ! localToDate))
        {
            if (_relation == PriceType::EndDiscPurch ||
                _relation == PriceType::EndDiscSales )
            {
                // for end discounts, the QuantiyAmountField field contains order total amounts, not quantities
                if (this.calcCur2CurPriceAmount(localQuantityAmountFrom, priceDiscTable) <= qty &&
                    ((qty < this.calcCur2CurPriceAmount(localQuantityAmountTo, priceDiscTable)) || !localQuantityAmountTo))
                {
                    reselectBuffer();

                    discExist               = true;
                    discAmount             += this.calcCur2CurPriceAmount(priceDiscTable.Amount, priceDiscTable)/ this.priceUnit();
                    percent1               += priceDiscTable.Percent1;
                    percent2               += priceDiscTable.Percent2;
                      // Begin: Alexey Voytsekhovskiy Not to lose the buffer!
                    actualDiscTable        =  priceDiscTable.data();
                    //actualDiscTable        = priceDiscTable;
                    // End: Alexey Voytsekhovskiy
                      
                  }
            }
            else
            {
                if (localQuantityAmountFrom <= qty
                    && (qty < localQuantityAmountTo || !localQuantityAmountTo))
                {
                    reselectBuffer();

                    discExist               = true;
                    discAmount             += this.calcCur2CurPriceAmount(priceDiscTable.Amount, priceDiscTable)/ this.priceUnit();
                    percent1               += priceDiscTable.Percent1;
                    percent2               += priceDiscTable.Percent2;
                      // Begin: Alexey Voytsekhovskiy Not to lose the buffer!
                    actualDiscTable        =  priceDiscTable.data();
                    //actualDiscTable        = priceDiscTable;
                    // End: Alexey Voytsekhovskiy
                  }
            }
        }
    }

 
The subroutine findPrice in findPriceAgreement method is called inside the buffer loop and must be fixed as follows.
void findPrice()
    {
        if (((discDate >= localFromDate || ! localFromDate)
            &&(discDate <= localToDate  || ! localToDate))
        && (localQuantityAmountFrom <= absQty
            &&(localQuantityAmountTo > absQty || !localQuantityAmountTo)))
        {
            if (cacheMode)
            {
                priceDiscTable = PriceDiscTable::findRecId(localRecid);
            }

            if (this.calcCur2CurPriceAmount(priceDiscTable.calcPriceAmount(absQty),  priceDiscTable) < this.calcPriceAmount(absQty) ||
                ! priceExist)
            {
                priceUnit               = priceDiscTable.priceUnit();
                price                   = this.calcCur2CurPriceAmount(priceDiscTable.price(),  priceDiscTable);

                if (salesParameters.ApplySmartRoundingAfterConversion && (priceDiscTable.Currency != currency) &&
                    relation == PriceType::PriceSales)
                {
                    price = PriceDiscSmartRounding::smartRound(price,Currency::find(currency));
                }

                markup                  = this.calcCur2CurPriceAmount(priceDiscTable.markup(),  priceDiscTable);

                pdsCalculationId        = priceDiscTable.PDSCalculationId;

                if (priceDiscTable.DisregardLeadTime)
                {
                    this.updateLeadTime();
                }
                else
                {
                    deliveryDays        = priceDiscTable.DeliveryTime;
                    calendarDays        = priceDiscTable.CalendarDays;
                }

                // <GEERU>
                inventBaileeFreeDays    = priceDiscTable.InventBaileeFreeDays_RU;
                // </GEERU>
                  // Begin: Alexey Voytsekhovskiy, Not to lose the buffer!
                actualPriceTable        = priceDiscTable.data();
                 // actualPriceTable        = priceDiscTable;
                // End: Alexey Voytsekhovskiy
                  
                  priceExist              = true;
  
                  // <GIN>
                  // Begin: Alexey Voytsekhovskiy, ThinkMax, 04Dec13, UAP_FDD017_PriceSimulation
                uapPriceDiscTableRecId  = priceDiscTable.RecId;
                // End: Alexey Voytsekhovskiy, ThinkMax, 04Dec13, UAP_FDD017_PriceSimulation
                  if (countryRegion_IN)
                {
                    // Firstly, retrieve the MRP from the trade agreement. If there is no MRP defined in
                    // the trade agreement, the MRP should be retrieved from the item master.
                    maxRetailPrice = this.calcCur2CurPriceAmount(
                        priceDiscTable.MaximumRetailPrice_IN ?
                            priceDiscTable.MaximumRetailPrice_IN :
                            InventTableModule::find(itemId, moduleType).maxRetailPrice_IN(),
                        priceDiscTable);
                }
                // </GIN>
            }
        }
    }

 
Happy pricing!

Friday, October 24, 2014

How to iterate project group members: Tables, EDT, etc

Based on S. Kuskov's suggestion and Vania Kashperuk's article, I put down this simple job that iterates Tables and Extended Data Types groups members in a given shared project.
static void tmxIterateProjectGroupMembers(Args _args)
{
    #aot
    #properties
    Str                         projectName = "tmxEDI999";
    ProjectNode                 projectNode;
    ProjectGroupNode            ddProjectGroupNode;
    ProjectGroupNode            edtProjectGroupNode;
    ProjectGroupNode            tblProjectGroupNode;
    ProjectListNode             projectListNode;
    TreeNode                    memberTreeNode;              
    TreeNode                    projectTreeNode;
    TreeNodeIterator            projectIterator;
    
    if(projectName)
    {
        // find all shared projects
        projectListNode = SysTreeNode::getSharedProject();
        // find project with a given name
        projectNode = projectListNode.AOTfindChild(projectName);
        // open it in a separate window in AOT
        projectTreeNode = projectNode.getRunNode();
        // this is the key point after which we can iterate group members
        projectNode = projectNode.loadForInspection();
        // get nested nodes for appropriate names
        ddProjectGroupNode = projectNode.AOTfindChild('DataDictionary');
        edtProjectGroupNode = ddProjectGroupNode.AOTfindChild('Extended Data Types');
        tblProjectGroupNode = ddProjectGroupNode.AOTfindChild('Tables');
        
        // tables
        projectIterator = tblProjectGroupNode.AOTiterator();
        memberTreeNode = projectIterator.next();

        while(memberTreeNode)
        {
            info(strFmt("%1 %2", memberTreeNode.AOTname(), memberTreeNode.treeNodeName()));
            memberTreeNode = projectIterator.next();
        }

        // extended data types
        projectIterator = edtProjectGroupNode.AOTiterator();
        memberTreeNode = projectIterator.next();

        while(memberTreeNode)
        {
            info(strFmt("%1 %2", memberTreeNode.AOTname(), memberTreeNode.treeNodeName()));
            memberTreeNode = projectIterator.next();
        }
    }
}
The key method is loadForInspection. Happy iterating!

Wednesday, October 8, 2014

C# code to test EDI-XML transformation

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml;
using Microsoft.Dynamics.IntegrationFramework.Transform;
using tmxEDITransformsX12.SharedXSD;
using tmxEDITransformsX12.Transform820XSD;

namespace TransformTest
{
    class Program
    {
        static void Main(string[] args)
        {
            FileStream input = new FileStream("C:\\Test.edi", FileMode.Open);
            FileStream output = new FileStream("C:\\Output.xml", FileMode.OpenOrCreate);

            tmxEDITransformsX12.Transform820 transform = new tmxEDITransformsX12.Transform820();

            transform.Transform(input, output, "");


        }
    }
}

Wednesday, September 24, 2014

How to find in AOT all objects named like...

In fact the standard Find tool in AOT works well if you really know how to fill in all these parameters before launch it. Sometimes it is easier to use another options.

Let's say we need to find all the forms that contain 'lookup' word in the end of their names. Easy? Yes, it is! You can go directly to the SysModelElement table and work with it as with any other table: Ctrl-G to switch grid filter, 11 for Form element type, then *lookup in the name, and that's it.



This is rapid way to know the objects names but it is not possible to jump to them in AOT. Here we go with the second option. Just create a new project and filter criteria Element type name and Name for the objects. 


Simple, fast and will keep the search results for future recalls.



Happy searching in AOT!

Saturday, September 13, 2014

How to add a new relation on a table from code

If you got into troubles, like me, trying to add a new relation on a table from X++, this code will save your day. The main point here is the code 251. Read my lips: two-five-one. Because reflections work on everything when you know the code.



private void createTableRelationInAOT(TreeNode _treeNodeTableRelation, TableName _relatedTableName, FieldName _relatedFieldName)
{
#Properties    
    TreeNode            treeNodeRelations;
    TreeNode            tableRelation;
    ReferenceNode       tableRelationFields;

    if(treeNodeTableRelation && _relatedTableName && _relatedFieldName)
    {
        treeNodeRelations = _treeNodeTableRelation.AOTfindChild(#PropertyRelations);
        tableRelation = treeNodeRelations.AOTadd(relatedTableName);
        tableRelation.AOTsetProperty('Table', relatedTableName);
        //can anyone tell me how I could guess this secret code??
        tableRelationFields = tableRelation.AOTaddSubNode(251); 
        //never change the order of two these properties! it works only like follows
        tableRelationFields.AOTsetProperty(#PropertySecField, relatedFieldName);
        tableRelationFields.AOTsetProperty(#PropertyRelatedField, relatedFieldName);
    }
    _treeNodeTableRelation.AOTsave();

}


Happy deciphering!

Sasha Nazarov added:

 "251" corresponds to #NT_DBNORMALREFERENCEFIELD

in TreeNodeSysNodeType macro:
#define.NT_DBREFERENCE( 250)
#define.NT_DBNORMALREFERENCEFIELD( 251)
#define.NT_DBTHISFIXEDREFERENCEFIELD( 252) #define.NT_DBEXTERNFIXEDREFERENCEFIELD( 253)

Thursday, September 11, 2014

Problem with importing a temporary table/class/etc

I bumped into a strange problem. The admin refreshed my development environment from the TEST one; consequently, all my objects were deleted.

When I started re-importing them from an exported xpo-project, a temporary table always caused the following error:

A table, Extended Data Type, Base Enum or class called XXXXX already exists. Import of Table aborted.


The problem can be easily resolved by flushing the user cache:
Stop AX client and delete all  ax_*.auc files in "C:\Users\%USERNAME%\AppData\Local folder" 

Monday, August 25, 2014

How to fix internet speed drop via Wi-Fi connection (Cisco router)

Having read a lot of tricks and tweaks on how to fix speed drop, I found a solution in my Cisco Wi-Fi router QoS settings.