Thursday, 23 July 2020

The supplied SnapshotPoint is on an incorrect snapshot.

Hi Techies,

Before moving to the issue and resolution, I will be sharing some background:

  • Using Visual Studio 2015
  • Dynamics 365 FinOps 10.0.11 PU35
  • Azure based cloud hosted development environment
All of sudden I started getting an error "The supplied SnapshotPoint is on an incorrect snapshot." and hard crash while doing code development in VS for D365 FO AOT. VS doesn't allow to do a line change.


Resolution-1: Navigate to VS -> Tools -> Options -> Text Editor -> All languages -> X++

 


Un-check "Word wrap" and "Show visual glyphs for word wrap" check boxes and select OK.



Issue should be fixed.

Resolution-2: In case issue still exists, then go to higher version of VS or go with the latest update

This issue has been fixed and is now available in our latest update. You can download the update via the in-product notification or from here: https://visualstudio.microsoft.com/vs/ 


Happy DAXing...






Wednesday, 15 July 2020

Using Expressions in Query Ranges in Dynamics 365 FinOps

Using Expressions in Query Ranges in D365 FO

Query range value expressions can be used in any query where you need to express a range that is more complex than is possible with the usual dot-dot notation (such as 5012..5500).
For example, to create a query that selects the records from the table MyTable where field A equals x or field B equals y, do the following.
  1. Add a range on field A.
  2. Set the value of that range to the expression (if x = 10 or y = 20), as a string: ((A == 10) || (B == 20))
The rules for creating query range value expressions are:
  • Enclose the whole expression in parentheses.
  • Enclose all subexpressions in parentheses.
  • Use the relational and logical operators available in X++.
  • Only use field names from the range's data source.
  • Use the dataSource.field notation for fields from other data sources in the query.
Values must be constants in the expression, so any function or outside variable must be calculated before the expression is evaluated by the query. This is typically done by using the strFmt function.
The example above will then look like the following code example.
strFmt('((A == %1) || (B == %2))',x,y)
To get complete compile-time stability, use intrinsic functions to return the correct field names, as shown in the following code example.
strFmt('((%1 == %2) || (%3 == %4))',
fieldStr(MyTable,A), x,
fieldStr(MyTable,B), y)

 Note: Query range value expressions are evaluated only at run time, so there is no compile-time checking. If the expression cannot be understood, a modal box will appear at run time that states "Unable to parse the value."

Example of adding a range to a query

The following code programmatically adds a range to a query and uses string substitution to specify the data source and field name. The range expression is associated with the CustTable.AccountNum field; however, because the expression specifies the data sources and field names, the expression can be associated with any field in the CustTable table.

static void AddRangeToQuery3Job(Args _args)
    {
        Query q = new Query();  // Create a new query.
        QueryRun qr;
        CustTable ct;
        QueryBuildDataSource qbr1;
        str strTemp;
        ;
    
        // Add a single datasource.
        qbr1 = q.addDataSource(tablenum(CustTable));
        // Name the datasource 'Customer'.
        qbr1.name("Customer");
    
        // Create a range value that designates an "OR" query like:
        // customer.AccountNum == "4000" || Customer.CreditMax > 2500.
    
        // Add the range to the query data source.
        qbr1.addRange(fieldNum(CustTable, AccountNum)).value(
        strFmt('((%1.%2 == "4000") || (%1.%3 > 2500))',
            qbr1.name(),
            fieldStr(CustTable, AccountNum),
            fieldStr(CustTable, CreditMax)));
    
        // Print the data source.
        print qbr1.toString();
        info(qbr1.toString());
    
        // Run the query and print the results.
        qr = new QueryRun(q);
    
        while (qr.next())
        {
            if (qr.changedNo(1))
            {
                ct = qr.getNo(1);
                strTemp = strFmt("%1 , %2", ct.AccountNum, ct.CreditMax);
                print strTemp;
                info(strTemp);
            }
        }
        pause;
    }

Another Example of adding a range to a query

The following code programmatically adds a range to a query and uses string substitution to specify the data source and field name. The range expression is associated with the HSSalesTableStaging.RecID field which is linked with HSSalesOrderChargesStaging.RefRecID; however, because the expression specifies the data sources and field names, the expression can be associated with any field in the HSSalesTableStaging table.

[DataSource]
    class HSSalesTableCharges
    {
        /// <summary>
        ///
        /// </summary>
        public void executeQuery()
        {
            Query                   query = new Query();
            QueryBuildDataSource    qbd1, qbd2;

            qbd1 = query.addDataSource(tableNum(HSSalesTableStaging));
            qbd1.name("HSSalesTableStagingHeader");

            qbd2 = qbd1.addDataSource(tableNum(HSSalesOrderChargesStaging));
            qbd2.joinMode(JoinMode::InnerJoin);
            qbd2.name("HSSalesTableCharges");
            qbd2.addRange(fieldNum(HSSalesTableStaging, RecId)).value(strFmt('%1.%2 == %3.%4',qbd1.name(), fieldStr(HSSalesTableStaging, RecId), qbd2.name(), fieldStr(HSSalesOrderChargesStaging, RefRecId)));
            
            super(); 
        }
    }



Happy DAXing...


Monday, 13 July 2020

Create address at run time in Dynamics 365 FinOps

DAX Folks,

Here is a quick code to create the delivery address at runtime. You can use this while creating new Purchase requisition, Purchase order, Sales order or a person.

Today, we are going to create One-Time address and Delivery address for a particular process. Lets choose a process of sales order  and the requirement is- to create one time address or delivery address based on a flag.

Have a look on below code,
  • Create One-time address at run time in D365 FO
So we are going to use LogisticsPostalAddressEntity to get the right address with a new one or an existing one. This code should also handle if there is any update in any existing record by updating effective date stamp.

Here, xxxSalesTableStaging is the staging table which should have all the necessary address component's data like Country region, Zip code, State, City, Street and etc... 

private LogisticsPostalAddressRecId createOneTimePostalAddress(xxxSalesTableStaging _stagingTable)
    {
        LogisticsAddressing                             addressing;
        LogisticsPostalAddress                          logisticsPostalAddress;
        LogisticsPostalAddressView                      postalAddressView, newPostalAddressView;
        LogisticsPostalAddressEntity                    postalAddressEntity;
        DirPartyPostalAddressView                       newPartyAddressView;
        LogisticsPostalAddressRecId                     postalAddressRecId;
        LogisticsPostalAddressStringBuilderParameters   addressStringBuilderParameters = new LogisticsPostalAddressStringBuilderParameters();
                 
        addressStringBuilderParameters.parmCountryRegionId(_stagingTable.CountryRegionId);
        addressStringBuilderParameters.parmZipCodeId(_stagingTable.ZipCode);
        addressStringBuilderParameters.parmStateId(_stagingTable.State);
        addressStringBuilderParameters.parmCityName(_stagingTable.City);
        addressStringBuilderParameters.parmStreet(_stagingTable.Street);
             
        addressing = LogisticsPostalAddressStringBuilder::buildAddressStringFromParameters(addressStringBuilderParameters);

        select firstonly postalAddressView
            where postalAddressView.Address  == addressing;

        if (postalAddressView)
        {
            postalAddressRecId = postalAddressView.PostalAddress;
        }
        else
        {
            logisticsPostalAddress.CountryRegionId  = _stagingTable.CountryRegionId;
            logisticsPostalAddress.ZipCode          = _stagingTable.ZipCode;
            logisticsPostalAddress.State            = _stagingTable.State;
            logisticsPostalAddress.City             = _stagingTable.City;
            logisticsPostalAddress.Street           = _stagingTable.Street;

            newPartyAddressView.initFromPostalAddress(logisticsPostalAddress);
            newPartyAddressView.LocationName = _stagingTable.DeliveryName ? _stagingTable.DeliveryName : custTable.name();

            postalAddressEntity = LogisticsPostalAddressEntity::construct();

            newPostalAddressView.initFromPartyPostalAddressView(newPartyAddressView);
            logisticsPostalAddress = postalAddressEntity.createPostalAddress(newPostalAddressView);

            postalAddressRecId = logisticsPostalAddress.RecId;
        }

        return postalAddressRecId;
    }

  • Create Delivery address at run time in D365 FO

 So we are going to use DirParty and LogisticsLocationRole to get the party and role type. LogisticsPostalAddressEntity will also be used to get the right address with a new one or an existing one. This code should also handle if there is any update in any existing record by updating effective date stamp.

Here, i am getting custTable from global variable (you can take any customer account for your testing) and xxxSalesTableStaging is the staging table which should have all the necessary address component's data like Country region, Zip code, State, City, Street and etc...

private LogisticsPostalAddressRecId createDeliveryPostalAddress(HSSalesTableStaging _stagingTable)
    {
        DirParty                                        dirParty;
        container                                       roleIds;
        LogisticsAddressing                             addressing;
        LogisticsPostalAddress                          postalAddress;
        DirPartyPostalAddressView                       partyAddressView, newPartyAddressView;
        LogisticsPostalAddressRecId                     postalAddressRecId;
        LogisticsPostalAddressStringBuilderParameters   addressStringBuilderParameters = new LogisticsPostalAddressStringBuilderParameters();

        addressStringBuilderParameters.parmCountryRegionId(_stagingTable.CountryRegionId);
        addressStringBuilderParameters.parmZipCodeId(_stagingTable.ZipCode);
        addressStringBuilderParameters.parmStateId(_stagingTable.State);
        addressStringBuilderParameters.parmCityName(_stagingTable.City);
        addressStringBuilderParameters.parmStreet(_stagingTable.Street);
             
        addressing = LogisticsPostalAddressStringBuilder::buildAddressStringFromParameters(addressStringBuilderParameters);

        select firstonly partyAddressView
            where partyAddressView.Party == custTable.Party
            &&    partyAddressView.Address == addressing;

        if (partyAddressView)
        {
            postalAddressRecId = partyAddressView.PostalAddress;
        }
        else
        {
            postalAddress.clear();
            //postalAddress.initValue();
            postalAddress.Street           = _stagingTable.Street;
            postalAddress.City             = _stagingTable.City;
            postalAddress.State            = _stagingTable.State;
            postalAddress.ZipCode          = _stagingTable.ZipCode;
            postalAddress.CountryRegionId  = _stagingTable.CountryRegionId;

            newPartyAddressView.initFromPostalAddress(postalAddress);
            newPartyAddressView.Party = custTable.Party;
            newPartyAddressView.LocationName = _stagingTable.DeliveryName ? _stagingTable.DeliveryName : custTable.name();

            dirParty = DirParty::constructFromPartyRecId(CustTable.Party);                  
            roleIds = [LogisticsLocationRole::findBytype(LogisticsLocationRoleType::Delivery).RecId];

            newPartyAddressView = dirParty.createOrUpdatePostalAddress(newPartyAddressView, roleIds);
            postalAddressRecId = newPartyAddressView.PostalAddress;                                            
        }

        return postalAddressRecId;
    }


Go for a drive and revert with your question if any.

Happy DAXING...


Sunday, 13 October 2019

Use RecordInsertList in D365

Simple way to insert record list

Just for an example, we need to insert records in a regular table but for temporary purpose.

void method()
{
InventTable inventTable;
        InventDimCombination    inventDimCombination;
SMCItemPriceTmp itemPriceTmpTable;
RecordInsertList    itemPriceTmpTableList = new RecordInsertList(tableNum(SMCItemPriceTmp), false, false, false, false, false, itemPriceTmpTable);

ttsbegin;
        itemPriceTmpTable.selectForUpdate(true);
        delete_from itemPriceTmpTable;
        ttscommit;

while select inventDimCombination
            where inventDimCombination.ItemId == inventTable.ItemId
        {
            itemPriceTmpTable.RetailVariantId = inventDimCombination.RetailVariantId;
 
    ecoResProductMasterDimValueTranslation.clear();
            ecoResProductMasterColor.clear();
            ecoResProductMaster.clear();
            ecoResColor.clear();
            inventDim.clear();

            select firstonly Description from ecoResProductMasterDimValueTranslation
            exists join ecoResProductMasterColor
                where ecoResProductMasterColor.RecId == ecoResProductMasterDimValueTranslation.ProductMasterDimensionValue
            exists join ecoResProductMaster
                where ecoResProductMaster.RecId == ecoResProductMasterColor.ColorProductMaster
                &&    ecoResProductMaster.RecId == ecoResProduct.RecId
            exists join ecoResColor
                where ecoResColor.RecId == ecoResProductMasterColor.Color
            exists join inventDim
                where inventDim.InventColorId == ecoResColor.Name
                &&    inventDim.inventDimId == inventDimCombination.InventDimId;

            itemPriceTmpTable.Description = ecoResProductMasterDimValueTranslation.Description;

            itemPriceTmpTableList.add(itemPriceTmpTable);
        }

        ttsbegin;
        itemPriceTmpTableList.insertDatabase();
        ttscommit;
}

Happy DAXing...

Wednesday, 20 March 2019

Data Query ranges and query filter in AX

Scenario#01:

static void GetRangeValueOfQuery(Args _args)
{
Query query = new Query();
QueryRun queryRun;
QueryBuildDataSource qbd;
Bill_Table vendTable;
QueryBuildRange range;
int ct, i;

qbd = query.addDataSource(tablenum(Bill_Table));
queryRun = new QueryRun(query);
queryRun.prompt(); // To Prompt the dialog
ct = queryRun.query().dataSourceTable(tablenum(Bill_Table)).rangeCount();
for (i=1 ; i<=ct; i++)
{
range = queryRun.query().dataSourceTable(tablenum(Bill_Table)).range(i);
info(strfmt(“Range Field – %1, Value – %2”,range.AOTname(),range.value()));
}
range = qbd.addRange(fieldnum(Bill_Table,ItemName));
while (queryRun.next())
{
vendTable = queryRun.get(tablenum(Bill_Table));
info(strfmt(“Item – %1, Name – %2”,vendTable.ItemName, vendTable.CustName));
}
}

Scenario#02:

static void Johnkrish_GetRangeValueOfQuery(Args _args)
{
Query query;
QueryRun queryRun;
QueryBuildDataSource qbd;
GeneralJournalAccountEntry vendTable;
QueryBuildRange range;
QueryFilter qf;
GeneralJournalAccountEntry generalJournalAccountEntry;
int cnt, i;

query = new query();
qbd = query.addDataSource(tablenum(GeneralJournalAccountEntry));
queryRun = new QueryRun(query);
queryRun.prompt();
query = queryRun.query();
cnt=query.queryFilterCount();
for (i = 1; i <=cnt ; i++)
{
qf = query.queryFilter(i);
info(strFmt(“Range Field – %1: Value – %2”, qf.field(), qf.value()));
}
while (queryRun.next())
{
generalJournalAccountEntry = queryRun.get(tablenum(GeneralJournalAccountEntry));
info(strfmt(“PostingType – %1, LederAccount – %2”,generalJournalAccountEntry.PostingType, generalJournalAccountEntry.LedgerAccount));
}
}

Scenario#03:

Use the following code in the init method of datasource Test to get the latest data for every record(used groupby on customer)
public void init()
{
   QueryBuildDataSource qbds;
   super();
   qbds = this.query().dataSourceTable(tableNum(Test));
   qbds.addSelectionField(fieldNum(Test, CustAccount));
   qbds.addSelectionField(fieldNum(Test, Date), SelectionField::Max);
   qbds.addGroupByField(fieldnum(Test, CustAccount));
   qbds.orderMode(OrderMode::GroupBy);
}

Scenario#04:

public void executeQuery()
{
    this.queryBuildDataSource().validTimeStateAsOfDate(_dateValue);
    super();
}

If you have an interval, use this instead:

this.queryBuildDataSource().validTimeStateDateRange(fromDate, toDate)

Scenario#05:

public void lookup()
{
    Query query = new Query();
    QueryBuildDataSource queryBuildDataSource;
    QueryBuildRange queryBuildRange;
     QueryBuildRange queryBuildRange2;
     QueryBuildRange queryBuildRange3;

    SysTableLookup sysTableLookup = SysTableLookup::newParameters(tableNum(SalesTable), this);

    if(DateFrom.dateValue() && DateTo.dateValue())
    {
        sysTableLookup.addLookupfield(fieldNum(SalesTable, CustAccount));

        queryBuildDataSource = query.addDataSource(tableNum(SalesTable));

        queryBuildDataSource.addGroupByField(fieldNum(SalesTable, CustAccount));

        queryBuildRange = queryBuildDataSource.addRange(fieldNum(SalesTable, createdDateTime));
        queryBuildRange.value(SysQuery::range(this.dboConvertDateToDateTime(DateFrom.DateValue()), dateNull()));

        queryBuildRange = queryBuildDataSource.addRange(fieldNum(SalesTable, createdDateTime));
        queryBuildRange.value(SysQuery::range(dateNull(), this.dboConvertDateToDateTime(DateTo.dateValue())));

        queryBuildRange = queryBuildDataSource.addRange(fieldNum(SalesTable, InventSiteId));
        queryBuildRange.value(editInventSiteId.text());

        sysTableLookup.parmQuery(query);

        sysTableLookup.performFormLookup();

    }
    //super();