How to Extend Customer List View adding Custom Field on Dynamics 365 Retail & Commerce POS

 

Microsoft Dynamics 365 

Retail & Commerce

POS (Point of Sales)

 

How to Extend Customer List View adding Custom Field on Dynamics 365 Retail & Commerce POS

Pre-Requisite

1.    Visual Studio 19

2.    SQL Server

3.    Scale Unit project from GitHub repository

https://github.com/microsoft/Dynamics365Commerce.ScaleUnit

Verify you version and download as per match.

 

4.    SDK’s and runtime

sdk-2.1.513-windows-x64-installer

runtime-2.1.17-windows-x64-installer

.NET Core 3.1 SDK

Windows SDK (10.0.10586.0)

.NET 6.0 Runtime

.NET Core 6.0 Runtime

 

5.    After installation of sdk and runtime

Copy you scaleunit project from download to Service Volume (k)

 

 

 

 

 

 

Development

            Create MS Dynamics 365 FO project and add custom field on CustTable and Form.

 

               Result

        

               Add same custom field on ax.CustTable with script.

               Run script on database e.g: (AXDB)

               ALTER TABLE ax.CustTable Add RefNoExt nvarchar(510) Default ('')

 

       Now link this field with the acutal field. Add Resource file with XML.

              

              

              

               XML Code

               <RetailCdxSeedData Name="AX7" ChannelDBSchema="ext" ChannelDBMajorVersion="7">

       <Subjobs>

             <!--Adding additional columns to (existing) RetailTransactionTable and wants to pull it back to HQ.For upload subjobs, set the OverrideTarget property to  "false", as ilustrated below. This will tell CDX to use the table defined by TargetTableName and TargetTableSchema as extension table on this subjob.-->

             <Subjob Id="CUSTTABLE" TargetTableName ="CUSTTABLE" TargetTableSchema="ax" AxTableName="CUSTTABLE" IsUpload="false" OverrideTarget="false">

                    <ScheduledByJobs>

                           <ScheduledByJob>1010</ScheduledByJob>

                    </ScheduledByJobs>

                    <AxFields>

                           <!--If you notice the existing columns are not listed here in the <Field> tag, it's because the existing fields are already mapped in the main RetailCdxSeedData resource file, we only add the delta here. -->

                           <Field Name="RefNoExt"/>

                    </AxFields>

             </Subjob>

       </Subjobs>

</RetailCdxSeedData>

 

 

 

Now add handler class

HandlerClass - Code

internal final class HandlerClass

{

    [SubscribesTo(classstr(RetailCDXSeedDataBase), delegateStr(RetailCDXSeedDataBase, registerCDXSeedDataExtension))]

    public static void RetailCDXSeedDataBase_registerSeedDataExtension(str orgSeedDataResource, List resource)

    {

        if (orgSeedDataResource == classStr(RetailCDXSeedDataAX7))

        {

            resource.addEnd(resourceStr(CustomColumnCustTable_AX7));

        }

    }

 

}

 

Now build the project and after that you have to run this job       

 

Now open this form and select AX7 go to channel tables and search ax.CustTable

 

Now run full sync to update data from HQ to Channel. It will take some time to sync data.

Now you can see the data in table

Open Scale Unit Project from Service Volume (K) and build it.

After successful build add CustomCustomerSearchColumns.ts

 

 

 

 

 

 

 

CustomCustomerSearchColumns.ts  - Code

import { ICustomerSearchColumn } from "PosApi/Extend/Views/SearchView";

import { ICustomColumnsContext } from "PosApi/Extend/Views/CustomListColumns";

import { ProxyEntities } from "PosApi/Entities";

 

export default (context: ICustomColumnsContext): ICustomerSearchColumn[] => {

    return [

        {

            title: context.resources.getString("string_2"),

            computeValue: (row: ProxyEntities.GlobalCustomer): string => { return row.AccountNumber; },

            ratio: 15,

            collapseOrder: 5,

            minWidth: 120

        }, {

            title: context.resources.getString("string_3"),

            computeValue: (row: ProxyEntities.GlobalCustomer): string => { return row.FullName; },

            ratio: 20,

            collapseOrder: 4,

            minWidth: 200

        }, {

            title: "Ref No Ext",

            //title: context.resources.getString("string_4"),

            computeValue: (row: ProxyEntities.GlobalCustomer): string => { return row.ExtensionProperties.filter(p => p.Key === "RefNoExt")[0].Value.StringValue as string; },

            ratio: 25,

            collapseOrder: 1,

            minWidth: 200

        }, {

            title: context.resources.getString("string_5"),

            computeValue: (row: ProxyEntities.GlobalCustomer): string => { return row.Email; },

            ratio: 20,

            collapseOrder: 2,

            minWidth: 200

        }, {

            title: context.resources.getString("string_7"),

            computeValue: (row: ProxyEntities.GlobalCustomer): string => { return row.Phone; },

            ratio: 20,

            collapseOrder: 3,

            minWidth: 120

        }

    ];

};

 

 

 

 

 

 

 

Register class in manifest.json

 

Open ChannelDataServiceRequestTrigger.cs and add your code

 

 

 

 

 

 

ChannelDataServiceRequestTrigger.cs - Code

/**

 * SAMPLE CODE NOTICE

 *

 * THIS SAMPLE CODE IS MADE AVAILABLE AS IS.  MICROSOFT MAKES NO WARRANTIES, WHETHER EXPRESS OR IMPLIED,

 * OF FITNESS FOR A PARTICULAR PURPOSE, OF ACCURACY OR COMPLETENESS OF RESPONSES, OF RESULTS, OR CONDITIONS OF MERCHANTABILITY.

 * THE ENTIRE RISK OF THE USE OR THE RESULTS FROM THE USE OF THIS SAMPLE CODE REMAINS WITH THE USER.

 * NO TECHNICAL SUPPORT IS PROVIDED.  YOU MAY NOT DISTRIBUTE THIS CODE UNLESS YOU HAVE A LICENSE AGREEMENT WITH MICROSOFT THAT ALLOWS YOU TO DO SO.

 */

 

namespace Contoso.CommerceRuntime.Triggers

{

    using Microsoft.Dynamics.Commerce.Runtime;

    using Microsoft.Dynamics.Commerce.Runtime.Data;

    using Microsoft.Dynamics.Commerce.Runtime.DataModel;

    using Microsoft.Dynamics.Commerce.Runtime.DataServices.Messages;

    using Microsoft.Dynamics.Commerce.Runtime.Messages;

    using System;

    using System.Collections.Generic;

    using System.Linq;

    using System.Threading.Tasks;

 

    /// <summary>

    /// In this sample trigger, we will add property to channelConfiguartion in a thread-safe manner.

    /// That's important since channelConfiguration is a cached object, and concurrent modification on it can cause 100% CPU usage.

    /// </summary>

    public class ChannelDataServiceRequestTrigger : IRequestTriggerAsync

    {

        public static readonly string PropertyKey = "ExtConfigurationParameters";

 

        /// <summary>

        /// Gets the supported requests for this trigger.

        /// </summary>

        public IEnumerable<Type> SupportedRequestTypes

        {

            get

            {

                return new Type[]

                {

                        typeof(GetChannelConfigurationDataRequest),

                        typeof(SearchCustomersDataRequest)

                };

            }

        }

 

        /// <summary>

        /// Pre trigger code.

        /// </summary>

        /// <param name="request">The request.</param>

        public Task OnExecuting(Request request)

        {

            // It's only stub to handle async signature

            return Task.CompletedTask;

        }

 

        /// <summary>

        /// Post request trigger

        /// </summary>

        /// <param name="request">request</param>

        /// <param name="response">response</param>

        public async Task OnExecuted(Request request, Response response)

        {

            switch (request)

            {

                case GetChannelConfigurationDataRequest originalRequest:

                    var data = response as SingleEntityDataServiceResponse<ChannelConfiguration>;

                    if (data != null && data.Entity != null && data.Entity.GetProperty(PropertyKey) == null)

                    {

                        // In this example, we just put the configuration parameters as part of channelConfiguration property.

                        var configurationParameters = (await request.RequestContext.ExecuteAsync<EntityDataServiceResponse<RetailConfigurationParameter>>(new GetConfigurationParametersDataRequest(originalRequest.ChannelId)).ConfigureAwait(false)).ToList();

 

                        // The reason we need a lock here because of thread-safety.

                        // ChannelConfiguration is an object required in most crt request, and we cached in memory on the underlying ChannelDataService.

                        // In case there is concurrent crt request, without lock here, it will modify against the same ChannelConfiguration and will result as 100% CPU usage in worst case.

                        // NOTE: both SetProperty and ExtensionProperties are not thread-safe.

                        // NOTE: same situation for DeviceConfiguration, in which it is also required in most crt request and is cached in underlying DataService.

                        lock (data.Entity)

                        {

                            if (data.Entity.GetProperty(PropertyKey) == null)

                            {

                                data.Entity.SetProperty(PropertyKey, configurationParameters);

                            }

                        }

                    }

                    break;

                case SearchCustomersDataRequest getCustomerSearchResultsDataRequest:

 

                    var res = (EntityDataServiceResponse<GlobalCustomer>)response;

                    foreach (var item in res)

                    {

                        string value = "";

                        var databaseContext = new DatabaseContext(request.RequestContext);

                        ParameterSet configurationDataParameters = new ParameterSet

                        {

                            ["@AccountNum"] = item.AccountNumber

                        };

 

                        //Get tender type Id for tokenized tender line from database.

                        var configurationDataSet = await databaseContext

                            .ExecuteQueryDataSetAsync("SELECT REFNOEXT from ax.CUSTTABLE where ACCOUNTNUM = @AccountNum", configurationDataParameters);

 

                        if (configurationDataSet.Tables[0].Rows.Count > 0)

                        {

                            value = configurationDataSet.Tables[0].Rows[0][0] as string;

                        }

 

                        item.ExtensionProperties.Add(new CommerceProperty()

                        {

                            Key = "RefNoExt",

                            Value = value

                        });

                    }

                    break;

                default:

                    throw new NotSupportedException($"Request '{request.GetType()}' is not supported.");

            }

        }

    }

}

 

 

Rebuild the whole solution.

Once the build is compiled successfully Now copy the commerce runtime library.

CommerceRuntime.dll

From: “..\..\CommerceRuntime\bin\Debug\netstandard2.0”

To: “K:\RetailServer\WebRoot\bin\Ext”

 

 

Open CommerceRuntime.Ext.config file and add the following line in the composition tag.
<add source="assembly" value="CommerceRuntime" />

 

Now deploy the extension on the dev machine by copying the code.
From:..\..\ ScaleUnit\bin\Debug\netstandard2.0\CloudScaleUnitExtensionPackage\RetailCloudPOS\Code\Extensions”
To: “K:\RetailCloudPos\WebRoot\Extensions”

Now login to pos and go to setting to check package


Now go to customer

 

 

 Video Link

https://www.youtube.com/watch?v=KwjYSEHDBuw

    


 

     

The End

Comments

Post a Comment

Popular posts from this blog

How to Add Custom Control in Customer with Custom Field & Add/Update Data in D365 Commerce POS

How to Search Custom Field from Customer and Num Pad in Dynamics 365 Retail & Commerce POS