X++ Tips and Tricks

Some times we will be getting the BP warning as below for the labels, The simplest way to resolve the issue is:

info (strFmt(“@MyLabel”, prodShift.ShiftId));
Warning BP Rule: [BPUnusedStrFmtArgument]:The placeholder ‘%1’ to strFmt is not used in the format string.

For this warning removing add literalStr() for label string validation.

info (strFmt(literalStr(“@MyLabel”), prodShift.ShiftId));

Delete caches in F&O

Below classes used to delete the caches from F&O

D365 f&o URL/?cmp=USMF&

mi=sysclassrunner&cls=sysflushaod
mi=sysclassrunner&cls=sysflushdata
mi=sysclassrunner&cls=sysflushdictionary

Delete all the files from the below location to delete the Visual studio caches:

C:\Users\Admin4a7e2c9\AppData\Local\Microsoft\VisualStudio\16.0_e0fab5b4\ComponentModelCache

D365 – Data Entity Method Call Sequence

Here is a sequence of method’s calls during export:1. initValue2. validateField3. validateWrite4. update4.1. doUpdate4.1.1. persistEntity4.1.1.1. doPersistEntity4.1.1.1.1. initializeDataSources4.1.1.1.1.1. initializeEntityDataSourceNote: initializeDataSource is called once for each DataSource in Entity.4.1.1.1.2. mapEntityToDataSourcesNote: initializeDataSource is called once for each DataSource in Entity.4.1.1.1.3. saveDataSources4.1.1.1.3.1. updateEntityDataSource4.1.1.1.4. mapEntityToDataSource … Continue reading

Multiselect lookup in D365 FO

Below code for adding multiselect lookp field for the standard form, The field in the form is bounded to InventParameters table

Query used hear has only one table DocuType

Note: Form field properties – Autodecleration to Yes, replace on lookup – No

/// <summary>
/// Extension class for the form InventParameters
/// </summary>
[ExtensionOf(formstr(InventParameters))]
final class InventParameters_Form_Extension
{
    public SysLookupMultiSelectCtrl    msCtrl;

    /// <summary>
    /// Building the DocuType query
    /// </summary>
    /// returns the Docutype Query
    public Query buildDocuTypeQuery()
    {
        Query       docuTypeQry = new Query(queryStr(CMADocuTypeQuery));
        return docuTypeQry;
    }

    container getSelectedNotes(str _noteStr)
    {
        DocuType    docuType;
        container   tmpValues, conIds, conName;
        int         idx;
    
        if(_noteStr)
        {
            tmpValues = str2con(_noteStr, ';');
        }
    
        for(idx=1; idx<=conLen(tmpValues); idx++)
        {
            docuType = DocuType::find(conPeek(tmpValues, idx));
            conIds += docuType.RecId;
            conName += docuType.TypeId;
        }
    
        return [conIds, conName, conIds];
    }

    /// <summary>
    /// Code for the multi-lookup on form init
    /// </summary>
    /// formrun
    /// event args
    [FormEventHandler(formStr(InventParameters), FormEventType::Initialized)]
    public void InventParameters_OnInitialized(xFormRun sender, FormEventArgs e)
    {
        Query               docuTypeQry         =   this.buildDocuTypeQuery();
        FormStringControl   DocuTypes        =   this.design().controlName('InventParameters_InventDocumentTypes');
        InventParameters    inventParameters    =   InventParameters::find();

        msCtrl   = SysLookupMultiSelectCtrl::construct(this,
                                            DocuTypes,
                                            querystr(DocuTypeQuery),
                                            false,
                                            [tableNum(DocuType), fieldNum(DocuType,TypeId)]);

        msCtrl.refreshQuery(docuTypeQry);
        msCtrl.set(this.getSelectedNotes(inventParameters.InventDocumentTypes));
    }

    /// <summary>
    ///  Onmodified field of InventDocuTypes 
    /// </summary>
    /// FormDataObject
    /// form event args
    [FormDataFieldEventHandler(formDataFieldStr(InventParameters, InventParameters, _InventDocumentTypes), FormDataFieldEventType::Modified)]
    public void InventDocuTypes_OnModified(FormDataObject sender, FormDataFieldEventArgs e)
    {
        FormRun     formRun = sender.datasource().formRun();

        InventParameters    inventParameters    =   formRun.dataSource(FormDataSourceStr(InventParameters, inventParameters)).cursor() as InventParameters;
        inventParameters.InventDocumentTypes     =   con2Str(msCtrl.getSelectedFieldValues(), ';');
    }

}

D365 Integration on custom API’s

Below code contains the classes that are used in API calls in D365, In the below sample code we are using the Azure Key vault parameters for the authorization, As a prerequisite we need to setup the Azure key vault parameters.

Class – APImanagementConnection – The class that used to create request and also converts the response to the string format.

createRequestWithHeaders : Used to create a request with the available parm parameters.

readResponseData : Converts the given response to the string format.

There will be an contract class that will have all the data DataMembers with the names that are mapped with the source names.

class APImanagementConnection
{
    Str tokenValue, contentType, requestMethod;
    RecId   apiToken;
    url url;
    KeyVaultCertificateTable keyVaultCertificateTable;

    public static APImanagementConnection construct()
    {
        APImanagementConnection aPImanagementConnection = new APImanagementConnection();

        return aPImanagementConnection;
    }

    /// Creates the request with the header
    public System.Net.HttpWebRequest createRequestWithHeaders()
    {
        System.Net.HttpWebRequest    request;
        CLRObject                    clrObj;
        System.Exception             ex;
        System.IO.Stream        requestStream;
        System.Net.WebHeaderCollection httpHeader;

        try
        {
            tokenValue = KeyVaultCertificateHelper::getManualSecretValue(this.parmAPIToken());

            new InteropPermission(InteropKind::ClrInterop).assert();
            httpHeader = new System.Net.WebHeaderCollection();
            httpHeader.Add('Ocp-Apim-Subscription-Key', tokenValue);

            clrObj = System.Net.WebRequest::Create(this.parmURL());
            request = clrObj;
            request.set_KeepAlive(true);
            request.set_ContentType(this.parmContentType());
            request.set_Method(this.parmRequestMethod());
            request.set_Headers(httpHeader);

            if (this.parmRequestMethod() != 'GET')// Other than the GET method we need RequestStream to process the request
            {
                requestStream = request.GetRequestStream();
                requestStream.Close();
            }
            return request;
        }
        catch (Exception::CLRError)
        {
            ex = ClrInterop::getLastException();
            if (ex != null)
            {
                ex = ex.get_InnerException();
                if (ex != null)
                {
                    throw error(ex.ToString());
                }
            }
            throw Exception::CLRError;
        }
    }

    public static str readResponseData(System.Net.HttpWebResponse response)
    {
        int batchSize = 1024;
        System.IO.Stream receiveStream;
        System.IO.StreamReader readStream;
        System.Text.Encoding encode;
        System.Char&#091;] read;
        System.Text.StringBuilder sb;
        System.String readString;
        str contentEncoding;

        int countRead;

        if (response == null)
        {
            return "";
        }

        receiveStream = response.GetResponseStream();
        contentEncoding = response.get_ContentEncoding();
        if (contentEncoding)
        {
            encode = System.Text.Encoding::GetEncoding(contentEncoding);
        }
        else
        {
            encode = new System.Text.UTF8Encoding();
        }

        readStream = new System.IO.StreamReader(receiveStream, encode);
        read = new System.Char&#091;batchSize]();

        countRead = readStream.Read(read, 0, batchSize);

        sb = new System.Text.StringBuilder();
        while (countRead > 0)
        {
            readString = new System.String(read, 0, countRead);
            sb.Append(readString);
            countRead = readStream.Read(read, 0, batchSize);
        }

        readStream.Close();

        return sb.ToString();
    }

    public url parmURL(url _url = url)
    {
        url = _url;

        return url;
    }

    public Str parmContentType(Str _contentType = contentType)
    {
        contentType = _contentType;

        return contentType;
    }

    public Str parmRequestMethod(Str _requestMethod = requestMethod)
    {
        requestMethod = _requestMethod;

        return requestMethod;
    }

    public RecId parmAPIToken(RecId _apiToken = apiToken)
    {
        apiToken = _apiToken;

        return apiToken;
    }

}

Below sample code to use the API Management class to call the required API and get the Response:

sendRequest – Sends the request to the given API and gets the response.

class CustomAPICall
{
	System.Reflection.TargetInvocationException     ex;
	System.Net.WebException     ex1;
	System.Net.HttpWebRequest   request;
	KeyVaultCertificateTable keyVaultCertificateTable;
	str contentType = 'application/json';
	str requestMethod = 'GET';
	str responseStr;
	URL url;
	System.Net.HttpWebResponse  response;
        ContractClass    contractclass;
	
	public static void main(Args _args)
	{
		CustomAPICall customAPICall = new CustomAPICall();
		customAPICall.run(_args);
	}
	
	public void run(Args _args)
	{
		this.sendRequest(_args);
		responseStr = APImanagementConnection::readResponseData(response);
		
		if (response.StatusCode == System.Net.HttpStatusCode::OK)
		{
			contractclass = FormJsonSerializer::deserializeObject(className2Id(classStr(contractclass)), responseStr);
			this.insertintoTableName();// In this method the values from the contract class will be added into required table
		}
	}
	
	public void sendRequest(Args _args)
	{	
		try
		{
			select firstonly keyVaultCertificateTable
				where keyVaultCertificateTable.RecId == 123456789;//Assign appropriate RecId to select the required keyvault

			url = "";//Add the API URL

			APImanagementConnection aPImanagementConnection= APImanagementConnection::construct();
			aPImanagementConnection.parmURL(url);
			aPImanagementConnection.parmContentType(contentType);
			aPImanagementConnection.parmRequestMethod(requestMethod);
			aPImanagementConnection.parmApiToken(keyVaultCertificateTable.RecId);

			System.Net.ServicePointManager::SecurityProtocol = System.Net.SecurityProtocolType::Tls12 |System.Net.SecurityProtocolType::Tls11 |     System.Net.SecurityProtocolType::Tls;

			request = aPImanagementConnection.createRequestWithHeaders();
			response = request.GetResponse();
		}
		catch (Exception::CLRError)
		{
			ex = ClrInterop::getLastException();
			if (ex != null)
			{
				ex1 = ex.get_InnerException();
				if (ex1 != null)
				{
					error(ex1.ToString());
				}
			}
		}
	}
	
	public void insertintoTableName()
	{
		SalesTable salesTable;
		
		salesTable.SalesId = contractclass.parmSalesId();
		.
		.
		.
		etc.,
		salesTable.insert();
	}
}

Below is the sample class for the contract and will have the DataMember as the parm methods,

[DataContract]
class ContractClass
{
	SalesId salesId;
	
	[DataMember("SalesOrderId")]//SalesOrderId - Is same as the value which got fetched on the response file, It will be mapped to AX Sales Id field while inserting into SalesTable.
	public SalesId parmSalesId(SalesId _salesId = salesId)
	{
		salesId = _salesId;
		return salesId;
	}
}

SSRS Report Print management in D365

In order to add your customized report into Print management in D365, you needs to add an event handler to a delegate exposed by Microsoft.
Below is the example for Sales Invoice report

[SubscribesTo(classStr(PrintMgmtDocType), delegateStr(PrintMgmtDocType, getDefaultReportFormatDelegate))]
    public static void PrintMgmtDocType_getDefaultReportFormatDelegate(PrintMgmtDocumentType _docType, EventHandlerResult _result)
    {
        PrintMgmtReportFormatName formatName;
        #ISOCountryRegionCodes

        switch (_docType)
        {
            case PrintMgmtDocumentType::SalesOrderInvoice:
                // <GEEEE>
                if (SysCountryRegionCode::isLegalEntityInCountryRegion([#isoEE]))
                {
                    formatName =  ssrsReportStr(SalesInvoice, ReportEE);
                }
                // </GEEEE>
                // <GEELT>
                if (SysCountryRegionCode::isLegalEntityInCountryRegion([#isoLT]))
                {
                    formatName = ssrsReportStr(SalesInvoice, ReportLT);
                }
                // </GEELT>
                // <GEEHU>
                if (SysCountryRegionCode::isLegalEntityInCountryRegion([#isoHU]))
                {
                    formatName = ssrsReportStr(SalesInvoice, ReportHU);
                }
                // </GEEHU>
                // <GEELV>
                if (SysCountryRegionCode::isLegalEntityInCountryRegion([#isoLV]))
                {
                    formatName = ssrsReportStr(SalesInvoice, ReportLV);
                }
                // </GEELV>
                // <GEECZ>
                if (SysCountryRegionCode::isLegalEntityInCountryRegion([#isoCZ]))
                {
                    formatName = ssrsReportStr(SalesInvoice, ReportCZ);
                }
                // </GEECZ>
                // <GEEPL>
                if (SysCountryRegionCode::isLegalEntityInCountryRegion([#isoPL]))
                {
                    formatName = ssrsReportStr(SalesInvoice, ReportPL);
                }
                // </GEEPL>
                // <GTH>
                if (SysCountryRegionCode::isLegalEntityInCountryRegion([#isoTH]))
                {
                    formatName = ssrsReportStr(SalesInvoice, ReportTH);
                }
                // </GTH>
                // <GMY>
                if (SysCountryRegionCode::isLegalEntityInCountryRegion([#isoMY]))
                {
                    if (TaxParameters::find().GSTInvoiceFormat_MY == TaxGSTInvoiceFormat_MY::Full)
                    {
                        formatName = ssrsReportStr(SalesInvoice, ReportFull_MY);
                    }
                    else
                    {
                        formatName = ssrsReportStr(SalesInvoice, ReportSimplified_MY);
                    }
                }
                // </GMY>

                if (!formatName)
                {
                    formatName = ssrsReportStr(SalesInvoiceKAKA, Report);
                }

                _result.result(formatName);
        }
    }

DMF Excel Lookup Ax 2012

Some times we will not able to see or select the Excel sheets after selecting the source file, In that case we need to install one small patch given below:

Below patch needs to be installed in a machine were the DIXF is installed
Microsoft Access Database Engine 2010 Redistributable – 64 bit

https://www.microsoft.com/en-in/download/details.aspx?id=13255

D365 for Power Apps : POC

Requirement: Create and Edit the customer groups on a simple mobile Power app.

Prerequisite :
1) Mobile device with Internet
2) Login for Power App website  and D365 access.

Below steps to be followed to achieve the requirement :

Step 1: Login to Power App website with your user

Step 2: Select Dynamics 365 Phone Layout


capture11

Step 3 : In Connections tab click New Connection and select Dynamics 365 for Operations

Capture2

Step 4 : Click Create Button


Capture3

Step 5 : In Next screen It will show you all the data-set’s which you have access, select a appropriate data-set


Capture4

Step 6: Choose a table you want to use in your App and click Connect


Capture5

Step 7 : App screens are created and will be shown to you


Capture6

Step 8 : Click File and save as and then Name your app and slick save
You can save app to cloud as well as in your PC.


Capture7

Step 9 : You are done with your part of basic development in Power App web site.
Download the Power app from play store.

Step 10 : Login with the same credentials in you app, and you can see the App created in your  Power App website on your mobile PowerApps.

Sending the Customer Group records From D365 to Outlook: Logic Apps POC

Sending the Customer Group records (D365) to Outlook:

Step 1: Login to the Azure portal https://portal.azure.com with your credentials


Logicapp1

Step 2: Search Logic App, and click on it


 Logicapp2

Step 3: Click create button on the right corner below


Logicapp3

Step 4: Give Name and select an Existing resource or create a new resource group and click create


Logicapp4

Step 5: Select Blank logic app

Step 6: Perform the following steps :

                1 Create request

                2 Create Response

                3 Take Dynamics 365 operations and select get records


Logicapp5

                4 Sign in with your credentials

                5 Add instance and Entity Name

                6 In next step add office 365 outlook and sign in with your credentials

                7 Add To, Subject and Body (Give the field names that needs to be send via mail)

                8 Save the Logic App Designer


Logicapp6

                9 Click run, Once the Logic app runs successfully you will see all the green check marks


Logicapp7

 

Excel addin for D365 and Table browser for Chrome

Below steps will guide you to add D365 excel addin to your Excel

Step 1:

Browse the below link, and Click Add button

https://store.office.com/en-001/app.aspx?assetid=WA104379629&ui=en-US&rs=en-001&ad=US&appredirect=false

D365-1

Step 2:

Click on Open in Excel link

D365-2

Step 3:

Click on Open Office (desktop) as shown in below screenshot

D365-3

Step 4:

It will prompt you a window, as shown in below click yes

D365-4

Step 5:

Excel will be opened and asked for Enable, Click on Enable

D365-5

Step 6:

After clicking the Enable  a window will be opened on right side of excel to login to your D365 account,

D365-6

 

Step 6:

Paste the Same URL in server URL, click on Ok

Login to your D365 account and copy the URL till XXX.dynamics.com

D365-7

D365-8

Step 7:

It will ask you to confirm the changes, on clicking the Yes button it will be redirect you to sign in page

login with your account details which is using for D365

D365-9