Saturday, March 28, 2020

D365 FO upgrade from AX 2012 R2:

D365 FO upgrade from AX 2012 R2:

I am going to explain technical work stream for D365 FO upgrade from AX 2012 R2.

Below steps is high level points for the upgrade however I will post other articles for each major steps which required detail information. 

  1. As part of the prerequisite Install KB 4048614 and Install latest cumulative update before starting      any upgrade activity.
  2. Analyze and Identify all CRs and SSRS report which not required or deprecated in D365.
  3. Cleanup any CRs or functionality in AX 2012 which is not being used which will help to reduce upgrade cost and upgrade time as well.
  4. Configure LCS .
  5. Configure DevOps.
  6. Create test cases and process including ISV in BPML(Business process modular library )in LCS.
  1. Run upgrade analysis: estimate the AX 2012 preparation work.
  2. Run code upgrade tile: estimate the code upgrade work.
  3. Analyze LCS output report from LCS and find out over layered and conflict objects which will be base during code upgrade.
  4. Run LCS code upgrade tool with "only estimation" check box unchecked and then LCS will create report and also create work items and repository in the DevOps.
  5. Map DevOps repository with Dev VS to start code upgrade.
  6. Next is to start code upgrade and then data upgrade however this approach is good if you have less customization and you can follow water fall model. But if there is huge customization and want reduce the project duration then follow below steps.
  7. As part of data upgrade work first on schema based objects Like EDT, Enum, Table and view.
  8. Work on overlaying  and conflict objects all above related objects add all these objects in a separate model and move to Dev folder.
  9. Map this Dev folder with D365 Dev instance and start data upgrade task parallel to the code upgrade for other object type and it will save significant time in overall project and also enable and involve early in the team to start functional validation after data and code upgrade.
  10. Also we can Fast track or crash code upgrade duration by adding more resources in the project. However adding more developers for code upgrade we need extra precautions, setup and control to avoid further code conflict which I will discuss in the separate post.
  11. There are two part in code upgrade first one to work on overlaying and conflict objects by creating extension in the system ,secondly to check added code and objects after system is compiled with no error because after adding code we can not check objects from front end as there are errors in the system.
  12. During the code upgrade source ISV can be ignored and after successful code upgrade D365 compatible ISV package need to added in D365 through source control so that ISV related data can be upgraded during data upgrade.
  13. During data upgrade most of the errors are due to duplicate records and ISV related errors. So its      good practice to discuss with ISV vendor and cleanup orphan and stranded data before starting data upgrade to avoid any ISV related errors.
  14. Once code upgrade and data upgrade are completed functional testing can be started. However, if code upgrade completed before data upgrade then functional team can start CR testing with Std data so that any bug/issue can be highlighted in advance and fixed before actual functional testing.
  15. First data upgrade is the part of development data upgrade which used to identify duration of each step and any error so that these orphan or duplicate data can be cleaned in AX 2012 production before starting next data upgrade in UAT. Also this data upgrade will help for functional testing before UAT.
  16. Its always recommended to do Mock cut over to simulate actual production data upgrade.

#Stay Happy #Stay tune

Monday, July 8, 2019

How to add document note in D365


class VendAccountDocument_JKS

{



    static void main(Args _args)

    {

        VendTable vendTable;

        DocuType docuType;

        DocuRef docuRef;

        vendTable = VendTable::find('BRMF-000001');

        docuType = DocuType::find('Note');

        if (!docuType ||

            docuType.TypeGroup != DocuTypeGroup::Note)

        {

            throw error("Invalid document type");

        }

        docuRef.RefCompanyId = vendTable.dataAreaId;

        docuRef.RefTableId = vendTable.TableId;

        docuRef.RefRecId = vendTable.RecId;

        docuRef.TypeId = docuType.TypeId;

        docuRef.Name = 'Automatic note';

        docuRef.Notes = 'Added from X++';

        docuRef.insert();

        info("Document note has been added successfully");

    }



}

How to read excel and update record in AX2012 :

static void POCUpdatePurchaseDetails_JS(Args _args)
{
    SysExcelApplication     application;
    SysExcelWorkbooks       workbooks;
    SysExcelWorkbook        workbook;
    SysExcelWorksheets      worksheets;
    SysExcelWorksheet       worksheet;
    SysExcelCells           cells;
    COMVariantType          type;
    Dialog dialog = new     Dialog("Excel upload utility");
    dialogField             dialogFilename;
    int row;
    int                     totalUpdatedRecord = 0;
    FileName                filename;
    InventTable             inventTable;
    ItemId                  itemId;
    ItemGroup               itemGroup;
    InventTableModule       inventTableModule;

    ;

    application = SysExcelApplication::construct();
    workbooks = application.workbooks();
    dialogFilename = dialog.addField(extendedTypeStr(FilenameOpen),"@SYS53125");

    dialog.filenameLookupTitle("Update data from excel file");
    dialog.filenameLookupFilter(["@SYS28576",'*XLSX', "@SYS28576",'*XLS']);
    if (!dialog.run())
        return;
    filename = dialogFilename.value();
    try
    {
        workbooks.open(filename);
    }
    catch (Exception::Error)

    {
        throw error("File cannot be opened.");
    }
    workbook = workbooks.item(1);
    worksheets = workbook.worksheets();
    worksheet = worksheets.itemFromNum(1);
    cells = worksheet.cells();
    row=1;
    do
    {
        row++;
        itemId = cells.item(row, 1).value().bStr();

        ttsbegin;

        inventTable = InventTable::find(itemId,true);
        inventTableModule=InventTableModule::find(inventTable.ItemId,ModuleInventPurchSales::Purch,true);
        if (inventTable)
        {
            inventTableModule.Price=str2num(cells.item(row, 2).value().bStr());
            inventTableModule.PriceUnit=str2num(cells.item(row, 3).value().bStr());
            inventTableModule.update();
            totalUpdatedRecord++;
        }

        ttscommit;
        type = cells.item(row+1, 1).value().variantType();
    }
    while (type != COMVariantType::VT_EMPTY);
    application.quit();

    info(strFmt('Data updated, total record=%1 record',totalUpdatedRecord));

}

How to find SQL query in D365:


How to find SQL query out of the AOT query:


static void AOTQuery(Args _args)
{

    QueryRun queryRun;

    ;

    queryRun = new QueryRun(queryStr(QueryName));//add any name of query here in place of QueryName

    info(queryRun.query().toString());

}

Create Query dynamically in AOT through code:

Create Query dynamically in AOT through code:

static void CreateQueryInAOT(Args _args)
    {
        TreeNode                treeNodeObj;
        Query                   queryObj; // Extends TreeNode class.
        QueryBuildDataSource    qbds;
        QueryBuildRange         qbr;
        QueryRun                qr;
        CustTable               xrecCustTable;
        str                     queryName = "TestQuery";
        // Macro.
        #AOT
        // Delete the query from the AOT, if the query exists.
        treeNodeObj = TreeNode::findNode(#QueriesPath);
        treeNodeObj = treeNodeObj.AOTfindChild(queryName);
        if (treeNodeObj) { treeNodeObj.AOTdelete(); }
        // Add the query to the AOT.
        treeNodeObj = TreeNode::findNode(#QueriesPath);
        treeNodeObj.AOTadd(queryName);
        queryObj = treeNodeObj.AOTfindChild(queryName);
        // Further define the query.
        qbds  = queryObj.addDataSource(tablenum(CustTable));
        qbr   = qbds.addRange(fieldnum(CustTable, CustGroup));
        qbr.value("Inter");
        // Compile the query.
        queryObj.AOTcompile(1);
        queryObj.AOTsave();
        // Run the query.
        qr = new QueryRun("TestQuery");
        while ( qr.next() )
        {
            xrecCustTable = qr.GetNo(1); // 1 means first data source.
            Global::info(strFmt("%1 , %2",
                xrecCustTable.AccountNum, xrecCustTable.CustGroup));
        }

Sunday, June 23, 2019

Saving last value on the form

We can save last value on the form in AX so that when next time form will open it will show last saved value in the certain control:

We will take example to save value of below mark control on inventory counting form:


Below are the steps to enable this functionality:
1. Add below code snippet in class declaration:

AllOpenPosted allOpenPostedTrans
#define.CurrentVersion(1)
#localmacro.CurrentList
allOpenPostedTrans
#endmacro

2. Add below methods on the InventJournalTable form:

//To save default value in case if no last value available
public void initParmDefault()
{
allOpenPostedTrans= AllOpenPosted::All;
}

//To convert object to container
public container pack()
{
return [#CurrentVersion, #CurrentList];
}

//To convert container to object
public boolean unpack(container _packedClass)
{
int version = RunBase::getVersion(_packedClass);
switch (version)
{
case #CurrentVersion:
[version, #CurrentList] = _packedClass;
return true;
default:
return false;
}
return false;
}

//To save last design name
public IdentifierName lastValueDesignName
{
return element.args().menuItemName();
}

//To save last value
public IdentifierName lastValueElementName
{
return this.name();
}

//To save valueType in this case Form
public UtilElementType lastValueType()
{
return UtilElementType::Form;
}

//To store User Id
public UserId lastValueUserId()
{
return curUserId();
}

//To save Legal entity details
public DataAreaId lastValueDataAreaId()
{
return curext();
}
3. Add below code in Run(above Super) and Close(last in method) form's method:
Run:
xSysLastValue::getLast(this);
AllOpenPosted.selection(allOpenPostedTrans );


Close:
allOpenPostedTrans = AllOpenPosted.selection();
xSysLastValue::saveLast(this);


Compile and open form change value in the "show " control and close form next time form will open with last value in the show field.

I hope this will help !!!!











Create dialog run time and update respective record on Dialog

Below are the method to create Dialog dynamically, inter value, fetch value from dialog and update dialog field automatically :

class DialogDemo extends RunBase
{
    DialogField     fieldAccount;
    DialogField     fieldName; 
    DialogField     fieldGroup;
    VendAccount     vendAccount;
    VendName        vendName;
    VendGroupId     vendGroupId;
 
 }

//Convert object to container
    Public Container pack()
    { 
        return conNull();
    }

//Convert container to object
  Public boolean unpack(container _packedClass)
    {
        return true;
    }

// Dialog layout
Object dialog()
{
Dialog          dialog;
dialog = super();
dialog.caption("Customer information");
dialog.allowUpdateOnSelectCtrl(true);
fieldAccount=dialog.addField(Extendedtypestr(VendAccount),"Vendor Account");
fieldName=dialog.addField(Extendedtypestr(VendName));
fieldName.enabled(false);
dialog.addTabPage("Details");
fieldgroup=dialog.addField(Extendedtypestr(vendGroupId));
fieldgroup.enabled(false);
return dialog;

}

//Fetch value from Dialog
public boolean getFromDialog()
{
    vendAccount=fieldAccount.value(); 
    vendName=fieldName.value();
    vendGroupId=fieldGroup.value();
    return super();
}

public static void main(Args _args)
{
    DialogDemo dialogDemo= new DialogDemo();
    if(DialogDemo.prompt())
    {
    DialogDemo.run();
    }
}
//field value assignment
public void dialogSelectCtrl()
{
VendTable   vendTable;
vendTable=vendTable::find(fieldAccount.value());
fieldName.value(vendTable.name());
fieldGroup.value(vendTable.VendGroup);

}

//To execute logic
Public void run()
{
    info(strFmt("Vend Account: %1", vendAccount));
    info(strFmt("Vend Name: %1", vendName));
    info(strFmt("Vend Group: %1", vendGroupId));
}

Saturday, January 5, 2019

Error after clicking on “ Show all fields” in Dynamics AX

Recently i noticed that "show all fields" functionality is not working on Vendor Inter company form. After further investigation i came to know that this functionality is working on most of the form . However, for few forms it through below error.

I do some research and find that there is a bug in system in below method.

This is bug in system. Actually any form name can not be more than 40 char. but in our case '_ShowAllRecords_'+dictTable.name() is 55 char (InterCompanyTradingRelationSetupCustomer(40), _ShowAllRecords(15)) and hence system throw above error.

As work around I removed _ShowAllRecords and now this functionality is working for intercompany form.

Secondly there is also few KB in LCS that can be explored for permanent solution.

I hope above post help to resolve this issue.

Passing parameter between forms in Dynamics AX

How to: Pass Args from one form to another form in AX 2012

Passing parameter from one form to another is easy in AX and this concept help a lot during development and it can be achieved through Args class.
I am going to demonstrate below simple example to understand how Args works and what are the different options available in dynamics AX.

Form1: POCVehicleForm
Form2: POCInsuranceMaster  
And there is one common field vehicleId in both data source.
Task1: Pass record, string value and enum value from Form1 to Form2.
Task2: Filter data on form 2 according to the vehicle Id from form one.
Solution: Added one button on the POCVehicleForm to pass value on POCInsuranceMaster form.
Add below code snippet on "Insurance Master" clicked method on POCVehicleForm .
void clicked()
{
    MenuFunction        mf;
    Args                args;
    FormRun             formRun;
    ;
    //Initialise Args
    args= new Args();

    //Pass record
    args.record(POCVehicle);

    //Pass string value "Vehicle Id"
    args.parm(Grid_VehicleId.valueStr()); 
  
    //Pass Enam value
    args.parmEnum(VehicleType::Truck);

    //Pass enum type
    args.parmEnumType(enumNum(VehicleType));

    formRun=ClassFactory.formRunClass(args);
    formRun.init();
    formRun.run();
    formRun.wait();
}





Add below code snippet on POCInsurance’s init method.
public void init()
{
    POCVehicle              pocVehicle;
    Query                   query;
    ;

    super();
    // Get record buffer
    pocVehicle=element.args().record(); 

    // Validate if parameter is not null and record belong to right table  
    if(element.args() && element.args().record().TableId== tableNum(POCVehicle))
    {

    // Filter data according to the Vehicle ID    POCInsurenceMaster_ds.query().dataSourceName('POCInsurenceMaster').addRange(fieldNum(PO  CInsurenceMaster,vehicleID)).value(SysQuery::value(pocVehicle.VehicleId));

     // Show String and enum value                 
    info(strfmt("PARM:%1,PARMENUM:%2)", element.args().parm(),element.args().parmEnum()));  

}

Below is the result after clicking on Insurance Master button on POCVehicleForm

Below is the Args important methods:


Args Methods Details
record Get and set current or multiple record from caller table
caller Get or set the instance of the caller object
parm Get or sets string value including string field on caller form
parmEnum Get or set enum value
name Get and set the name of the application object to call
parmObject Get or set an instance of any object to pass on called object like any custom class 
parmEnumType Get or set enum name

Passing parameter from Form to Class through Args in Dynamics AX 2012