Tuesday, 20 October 2015

How to Create/Update/Find a phone number for a customer/vendor in X++ [AX 2012]

As a follow-up to my last post about finding phone numbers, here is sample code of how you can properly find/create/update a phone number for a customer/vendor.

Case- 1: Creating or updating phone number for a customer/vendor


static void CreatePhoneExample(Args _args)
{
    CustTable                           custTable = CustTable::find('100013'); // TODO - Change to your customer
    LogisticsElectronicAddress          logisticsElectronicAddress;
    container                           defaultRole = map2Con(LogisticsLocationEntity::getDefaultLocationRoleFromEntity(tableNum(DirPartyTable)));
    
    setPrefix(strFmt("Creating/Updating number for customer %1", custTable.AccountNum));
    
    // This will find/create a number for a customer
    ttsBegin;
    logisticsElectronicAddress.Type = LogisticsElectronicAddressMethodType::Phone;
    logisticsElectronicAddress.Locator = '555-555-5555';
    
    logisticsElectronicAddress.Location = DirPartyLocation::findOrCreate(custTable.Party, 0).Location;

    // This will find or create the new logisticsElectronicAddress
    // If it does not find it, it will do a .insert() which will only persist these fields (Location, Type, Locator, LocatorExtension)
    // so if you want to set the Description or if it's primary or not, you will need to update the record after this call
    logisticsElectronicAddress = LogisticsElectronicAddress::findOrCreate(logisticsElectronicAddress);
    
    // We re-select it for update in case this isn't a new number and it found an existing
    // because the "find" doesn't "select for update"
    logisticsElectronicAddress = LogisticsElectronicAddress::findRecId(logisticsElectronicAddress.RecId, true);
    
    logisticsElectronicAddress.Description = "New Primary Phone";
    
    // If you set the number to primary, during the insert/update it will handle unassigning previously
    // marked primary numbers if they exist
    logisticsElectronicAddress.IsPrimary = NoYes::Yes;
    
    logisticsElectronicAddress.update();
    
    // At this point, we need to mark the "purpose" of the number.  I'm just using the default role, which should be "Business"
    LogisticsEntityLocationRoleMap::createEntityLocationRoles(tableNum(LogisticsElectronicAddressRole), logisticsElectronicAddress.RecId, conPeek(defaultRole, 1), true);
    
    info(strFmt("Created/updated phone number [%1] for customer %2.", logisticsElectronicAddress.Locator, custTable.AccountNum));
    ttsCommit;
}

Case- 1: Finding phone number for a customer/vendor

Method-1:
Display LogisticsElectronicAddressLocator Phone() { LogisticsElectronicAddress logisticsElectronicAddress; //return LogisticsElectronicAddress::findByLocation(DirPartyLocation::findOrCreate(this.Party, 0).Location).Locator; select Location, Type, Locator from logisticsElectronicAddress where logisticsElectronicAddress.Location == DirPartyLocation::findOrCreate(this.Party, 0).Location && logisticsElectronicAddress.Type == LogisticsElectronicAddressMethodType::Phone; Return logisticsElectronicAddress.Locator; }
Note: Here this.Party may refer to MSDirPartyPostalAddressView.Party or CustTable/VendTable.Party;
Method-2:
Display Description PhoneDescription() { LogisticsElectronicAddress logisticsElectronicAddress; //return LogisticsElectronicAddress::findByLocation(DirPartyLocation::findOrCreate(this.Party, 0).Location).Locator; select Location, Type, Locator from logisticsElectronicAddress where logisticsElectronicAddress.Location == DirPartyLocation::findOrCreate(this.Party, 0).Location && logisticsElectronicAddress.Type == LogisticsElectronicAddressMethodType::Phone; Return logisticsElectronicAddress.Description; }
Note: Here also this.Party may refer to MSDirPartyPostalAddressView.Party or CustTable/VendTable.Party;
Happy DAXing.....

The Table.Field does not have a corresponding parm-method on the AxBC-class

If you add new field in a table and getting "The Table.Field does not have a corresponding parm-method on the AxBC-class" error message, you can run following Job to fix this issue. This job will add corresponding parm-method for newly added field in a table.

static void CreateAxBCParmMethodJob(Args _args)
{ 
    axGenerateAxBCClass axGenerateAxBCClass; 

    axGenerateAxBCClass = AxGenerateAxBCClass::newTableId(tablenum(CustTable));
    axGenerateAxBCClass.run(); 
 
}

Friday, 9 October 2015

The Table.Field does not have a corresponding parm-method on the AxBC-class

If you add new field in a table and getting "The Table.Field does not have a corresponding parm-method on the AxBC-class" error message, you can run following Job to fix this issue. This job will add corresponding parm-method for newly added field in a table.

static void CreateAxBCParmMethodJob(Args _args)
{ 
    axGenerateAxBCClass axGenerateAxBCClass; 

    axGenerateAxBCClass = AxGenerateAxBCClass::newTableId(tablenum(CustTable));
    axGenerateAxBCClass.run();  
}

Happy DAXing....

Thursday, 1 October 2015

How to get/ fetch Sales Price (SalesPrice) from sales Trade agreement in AX 2012

Hi Guys,
Now a days I am working on Interfaces like Import order from xml into AX 2012 Sales order so here, I will share with you the logic for picking Sales price from trade agreement in ax 2012.

Firstly, I tested the values through a job, like:
static void priceFromAgreement(Args _args)
{
    PriceDiscTable      priceDiscTable;
    //PriceDiscAdmTrans   _trans;
    ItemId              itemRelation = "021-08221";
    CustAccount         accountRelation = "1652-000007";
    real                salesPrice1, salesPrice2, salesPrice3;
    PriceType           relation = PriceType::PriceSales;
    NoYes               _RelationExist = NoYes::No;

    /// First if  trade agreement exist for Item for customer
    select firstonly priceDiscTable
        where priceDiscTable.Relation           == relation
           && priceDiscTable.ItemCode           == TableGroupAll::Table
           && priceDiscTable.ItemRelation       == itemRelation
           && priceDiscTable.AccountCode        == TableGroupAll::Table
           && priceDiscTable.AccountRelation    == accountRelation;

    salesPrice1 = priceDiscTable.Amount;
    info(strFmt("salesPrice1: %1", salesPrice1));

    if (priceDiscTable !=null)
    {
        info(strFmt("found Price1: %1", priceDiscTable.Amount));
    }

    // trade agreement exist for particular item for particular customer
    select firstonly priceDiscTable
        where priceDiscTable.Relation           == relation
           && priceDiscTable.ItemCode           == TableGroupAll::Table
           && priceDiscTable.ItemRelation       == itemRelation
           && priceDiscTable.AccountCode        == TableGroupAll::GroupId
           && priceDiscTable.AccountRelation    == "ROC";

    salesPrice2 = priceDiscTable.Amount;
    info(strFmt("salesPrice2: %1", salesPrice2));

    if (priceDiscTable !=null)
    {
        info(strFmt("found Price2: %1", priceDiscTable.Amount));
    }

    // third possibility is that when Customer have to all product with same price of amount.
    select firstonly priceDiscTable
        where priceDiscTable.Relation           == relation
           && priceDiscTable.ItemCode           == TableGroupAll::Table
           && priceDiscTable.ItemRelation       == itemRelation
           && priceDiscTable.AccountCode        == TableGroupAll::All
           && priceDiscTable.AccountRelation    == " ";

    salesPrice3 = priceDiscTable.Amount;
    info(strFmt("salesPrice3: %1", salesPrice3));

    if (priceDiscTable !=null)
    {
        info(strFmt("found Price3: %1", priceDiscTable.Amount));
    }
}


Now testing is done, we can start our development, Steps are as:

Step-1: Create one method to find PriceDiscTable object

public static PriceDiscTable findPriceDiscTable(PriceDiscAccountCode        _accountCode,
                                                PriceDiscAccountRelation    _accountRelation,
                                                PriceDiscItemCode           _itemCode,
                                                PriceDiscItemRelation       _itemRelation)
{
    PriceDiscTable      priceDiscTable;
    InventSiteId        inventSiteId;
    PriceType           relation = PriceType::PriceSales;

    inventSiteId = InventDim::find(priceDiscTable.InventDimId).InventSiteId;

    select firstonly priceDiscTable
        where priceDiscTable.Relation           == relation
           && priceDiscTable.AccountCode        == _accountCode
           && priceDiscTable.AccountRelation    == _accountRelation
           && priceDiscTable.ItemCode           == _itemCode
           && priceDiscTable.ItemRelation       == _itemRelation
           && priceDiscTable.ToDate             == dateNull();

    return priceDiscTable;
}

Step-2: Create another method to specify scenarios (cases) which may be described and will run on the basis of that trade agreement. Create cases as required as:

public static PriceDiscTable checkPriceDiscTable(PriceDiscAccountRelation    _accountRelation,
                                                 PriceDiscItemRelation       _itemRelation)
{
    PriceDiscTable              priceDiscTableRec;
    TableGroupAll               accountCode, ItemCode;
    PriceDiscAccountRelation    accountRelation;
    PriceDiscItemRelation       itemRelation;

    int     totalcases = 3;
    int     prioritycase = 0;

    while(prioritycase < totalcases)
    {
        switch (priorityCase)
        {
            case 0:
                accountCode         = TableGroupAll::Table;
                accountRelation     = _accountRelation;
                itemCode            = TableGroupAll::Table;
                itemRelation        = _itemRelation;

                priceDiscTableRec = MSSalesInterfaceLaffvlo::findPriceDiscTable(accountCode, accountRelation,
                                                                                itemCode, itemRelation);
                if(priceDiscTableRec.recId)
                {
                    return priceDiscTableRec;
                }
                break;

            case 1:
                accountCode         = TableGroupAll::GroupId;
                accountRelation     = _accountRelation;
                itemCode            = TableGroupAll::Table;
                itemRelation        = _itemRelation;

                priceDiscTableRec = MSSalesInterfaceLaffvlo::findPriceDiscTable(accountCode, accountRelation,
                                                                                itemCode, itemRelation);
                if(priceDiscTableRec.recId)
                {
                    return priceDiscTableRec;
                }
                break;

            case 2:
                accountCode         = TableGroupAll::All;
                accountRelation     = " ";
                itemCode            = TableGroupAll::Table;
                itemRelation        = _itemRelation;

                priceDiscTableRec = MSSalesInterfaceLaffvlo::findPriceDiscTable(accountCode, accountRelation,
                                                                                itemCode, itemRelation);
                if(priceDiscTableRec.recId)
                {
                    return priceDiscTableRec;
                }
                break;
        }
        prioritycase++;
    }

    return priceDiscTableRec;
}


Step-3: Add logic in Insert method from where values are getting inserted....

public static void insertLaffvloLineData(Container _value, MSSalesTable _mSSalesTable)
{
    MSSalesLine         salesline;
    InventTable         inventTable;
    InventDim           inventDim, inventDimLoc;
    PriceDiscTable      priceDiscTable;

    salesline.ItemId        = conPeek(_value, 22);
    salesline.ProductId     = conPeek(_value, 22);
    salesline.ProductName   = conPeek(_value, 7);
    salesline.Qty           = conPeek(_value, 9);
    salesline.CurrencyCode  = _mSSalesTable.CurrencyCode;
    salesline.MSSalesTable  = _mSSalesTable.RecId;  
 
    inventTable             = InventTable::find(salesline.ItemId);
    inventDim.InventSiteId  = inventTable.inventItemSalesSetup().inventDim().InventSiteId;
    inventDimLoc            = InventDim::findOrCreate(inventTable.inventItemSalesSetup().inventDim());

    priceDiscTable = MSSalesInterfaceLaffvlo::checkPriceDiscTable(_mSSalesTable.CustAccount, salesline.ItemId);
    salesline.SalesPrice = priceDiscTable.Amount;
 
    /*salesline.SalesPrice   = conPeek(PriceDisc::findItemPriceAgreement(ModuleInventPurchSales::Sales,
                                        salesline.ItemId,
                                        inventdim,
                                        "EA",
                                        today(),
                                        salesline.Qty,
                                       _mSSalesTable.CustAccount,
                                        'USD',
                                        CustTable::find(_mSSalesTable.CustAccount).PriceGroup), 1);*/

    if (!salesline.SalesPrice)
    {
        salesline.SalesPrice = InventItemPrice::findCurrent(salesline.ItemId,
                                                            CostingVersionPriceType::Sales,
                                                            inventdim.InventDimId,
                                                            today(),
                                                            inventdim.InventSiteId).Price;

        if (!salesline.SalesPrice)
            salesline.SalesPrice = InventTable::find(salesline.ItemId).salesPcsPrice();
    }

    salesline.LineAmount = salesline.Qty * salesline.SalesPrice;

    salesline.insert();
}


Note: Yeah, let me clear you on the SalesPrice scenarios

  1. If Trade agreement is created what we need then it should be 1st priority to fetch price from there
  2. If not then SalesPrice should be picked from Costing Version
  3. If not then lastly, SalesPrice should be picked up from Item base price (Item master- released products)
I tried to clarify from my best.

Thanx.
Happy DAXing.....

Friday, 11 September 2015

Address Import in AX 2012

Here, I got the job for importing address masters like CountryId, State, ZipCode, City... I am sharing with DAX techies hope it will help you.

static void AddressImport(Args _args)
{
    SysExcelApplication               application;
    SysExcelWorkbooks               workbooks;
    SysExcelWorkbook                workbook;
    SysExcelWorksheets              worksheets;
    SysExcelWorksheet               worksheet;
    SysExcelCells                        cells;
    COMVariantType                  type;
    Name                                     name;
    FileName                               filename;
    InventPosting                         InventPosting;

    DimensionAttributeValueCombination DimensionAttributeValueCombination;

    int row =1;
    LogisticsAddressZipCode          zipCode;
    LogisticsAddressZipCodeId        zipCodeId;
    LogisticsAddressCity             city;
    LogisticsAddressStateId          stateId;
    LogisticsAddressState            state;
    LogisticsAddresssCity            cityRecord;
    LogisticsAddressCountyName       countyName;
    LogisticsAddressCountyId         countyId;
    LogisticsAddressCounty           county;

    boolean                          badRecord;
    int                              i=1;
    int                              numProcessedRecords=0;
    LogisticsAddressCountryRegionId  _countryRegionId;

    //LogisticsAddressStateId          stateId;
    //LogisticsAddressCountyId         countyId;
    //LogisticsAddressCountyName       countyName;
   
    application = SysExcelApplication::construct();
    workbooks   = application.workbooks();
   
    //specify the file path that you want to read
    filename    = "C:\\Users\\v-vimsin\\Documents\\Vimal\\DIXF\\LogisticsPostalAddress - Copy.xlsx";
    try
    {
        workbooks.open(filename);
    }
    catch (Exception::Error)
    {
        throw error("File cannot be opened.");
    }
   
    ttsbegin;
    workbook    = workbooks.item(1);
    worksheets  = workbook.worksheets();
    worksheet   = worksheets.itemFromNum(1); //Here 3 is the worksheet Number
    cells       = worksheet.cells();
   
    do
    {
        row++;
        zipCodeId        = cells.item(row, 4).value().bStr();
        if (zipCodeId == "")
            zipCodeId = int2str(cells.item(row, 4).value().double());
       
        stateId          = cells.item(row, 3).value().bStr();
        city             = cells.item(row, 2).value().bStr();
        _countryRegionId = cells.item(row, 1).value().bStr();
        countyId         = cells.item(row, 5).value().bStr();
       
        if (stateId != '')
            state= LogisticsAddressState::find(_countryRegionId, stateId);  
            if (!state.RecId)
            {
                state.CountryRegionId   = _countryRegionId;
                state.StateId           = stateId;
                state.insert();
            }
            // Check if county exists
            //if(countyId != '')
            county = LogisticsAddressCounty::find(_countryRegionId, stateId, countyId);
   
            if (city != '')
            {
                select firstonly cityRecord where
                    cityRecord.Name             == city &&
                    cityRecord.StateId          == stateId &&
                    cityRecord.CountryRegionId  == _countryRegionId &&
                    cityRecord.CountyId         == countyId;
            }

            if (!cityRecord.RecId)
            {
                cityRecord.CountryRegionId  = _countryRegionId;
                cityRecord.Name             = city;
                cityRecord.StateId          = stateId;
                cityRecord.CountyId         = countyId;
                cityRecord.insert();
            }

            select firstonly zipCode where
                zipCode.ZipCode         == zipCodeId &&
                zipCode.State           == stateId &&
                zipCode.County          == countyId &&
                zipCode.CountryRegionId == _countryRegionId &&
                zipCode.CityRecId       == cityRecord.RecId;

            if (!zipCode.RecId)
            {
                zipCode.ZipCode         = zipCodeId;
                zipCode.City            = city;
                zipCode.CityRecId       = cityRecord.RecId;
                zipCode.CountryRegionId = _countryRegionId;
                zipCode.State           = stateId;
                zipCode.County          = countyId;
                zipCode.insert();

                numProcessedRecords++;
            }

        type = cells.item(row+1, 1).value().variantType();
    }

    while (type != COMVariantType::VT_EMPTY);

    info(strFmt("%1 Inserted",numProcessedRecords));

    ttsCommit;

    application.quit();
}

Happy DAXing.....

Friday, 17 July 2015

Dynamics AX 2012 Workflow receives "Failed to find workflow" error

We have run across a few situations in Dynamics AX 2012 where the workflow batch job fails with the error "Failed to find workflow" message.  When the customer received this error it prevented the remaining workflow messages in the queue from being processed.  In order to process all of the items that were queued up we needed to complete the following steps:

1.  Identify the record(s) that are causing the batch to fail.
Select a.RECID from SYSWORKFLOWMESSAGETABLE a Where a.ROOTCORRELATIONID notin(Select b.ROOTCORRELATIONID from SYSWORKFLOWTABLE b)and a.MESSAGELIFECYCLESTATE = 1
2.  Dequeue the SYSWORKFLOWMESSAGETABLE records that do not have a corresponding SYSWORKFLOWMESSAGETABLE record.
Update SYSWORKFLOWMESSAGETABLE Set MESSAGELIFECYCLESTATE = 2 where RECID =<RECID returned from statement above>
The procedures above will dequeue the message causing the error and allow the batch to complete.

Note:  As always, ensure you do a proper backup of your existing SYSWORKFLOWMESSAGETABLE prior to attempting this fix.

Forms not opening in local client.

In support we have seen a number of cases described along the lines of “Forms opens on remote desktop client but not on local AX client.”
Or “Dialog boxes fail to open.“ Historically we have found that the forms that fail to open are not out of the box forms. Instead they are USR or ISV layer objects.

What we have found is that the “Slide open combo boxes” under Performance options needs to be checked.


On server operating systems this is not turned on by default.

You can find this setting by right clicking on Computer -> Properties -> Advanced system settings -> Performance -> Settings.

You receive the error: “The transactions on voucher xxxxxxx do not balance as per xx/xx/xxxx. (Company currency: -x.xx - secondary currency: x.xx)” when attempting to post an invoice proposal in Project management and accounting.

One possible cause of this issue is the duplication of number sequences.  In Project management and accounting, there are separate number sequence setups available for On-Account Invoice, Invoice, On-Account Credit Note and Credit Note document types.  If the number sequences are not uniquely defined, in the posting process, when the records are fetched from the projProposalJour\ProjInvoiceJour it is possible that posting a credit would fetch the corresponding invoice with the same number sequence and vice versa.  This would then result in a situation where the merger of the documents results in an out of balance transaction, and then you would receive the error: "The transactions on voucher xxxxxxx do not balance as per xx/xx/xxxx. (Company currency: -x.xx - secondary currency: x.xx)".
To resolve this issue, you have a few options:
  1. Uniquely define the number sequences with a character segment specific to each document type.
  2. Use the same number sequence for the document types.
  3. You could also resolve the error by bumping up the next number to something that would not be a duplicate.  However, this would be a temporary solution as you would likely run into the error in the future on other documents.

How to enable the setup for user specific font Settings in MS Dynamics AX 2012

Found the following way on how to customize user depended fonts for reports and forms in AX 2012 client:
(1) Open a new developer workspace
(2) Navigate in the AOT to forms, SysUserSetup, designs, design(sys), [tab:tab](sys)], right click [TabPage.Fonts] (sys) and select properties
(3) Set the property visible by default No to Yes
(4) Save and compiling you the form change.
(5) In the AX 2012 client you can now see menu file (Note: blue button top left), tools, options the fonts and set the setting there user specifically

How to force complete CIL recreation in MS Dynamics AX 2012

“Microsoft provides programming examples for illustration only, without warranty either expressed or implied, including, but not limited to, the implied warranties of merchantability or fitness for a particular purpose. This mail message assumes that you are familiar with the programming language that is being demonstrated and the tools that are used to create and debug procedures.”
Prerequisite: Make sure that a full X++ compile was run without errors before

1) Stop all relevant AOS server(s)

2) On your relevant AOS server(s) navigate to the following folder (default) using Windows Explorer:
C:\Program Files\Microsoft Dynamics AX\60\Server\MicrosoftDynamicsAX\bin\XppIL

3) Make sure you create a safe copy of the XPPIL folder content to another new local folder on the AOS computer (example: C:\XPPIL_SAVE)

4) Now delete all folders and files inside the folder “C:\Program Files\Microsoft Dynamics AX\60\Server\MicrosoftDynamicsAX\bin\XppIL” but keep the folder “XPPIL” itself.

5) Start all AOS server(s)

6) Run a full CIL creation from AOT. This will create a fresh rebuild of all files/ folders inside the XPPIL folder

Happy DAXing
.........