web
You’re offline. This is a read only version of the page.
close
Skip to main content

Notifications

Announcements

No record found.

Community site session details

Community site session details

Session Id :
Supply chain | Supply Chain Management, Commerce
Answered

How to find current store/channelId for Product Searchview to show inventory

(0) ShareShare
ReportReport
Posted on by 1,457

I need to modify the product search view to add the Inventory, Reserved and Ordered columns for the current store which is the channelId for the user logged in. I am trying to use: docs.microsoft.com/.../pos-view-extension

I went through all the view extension examples under "..\RetailSDK\POS\Extensions\SampleExtensions\ViewExtensions" but cannot find an example.

If I look at example CustomInventoryByStoreListColumns.ts then it seems it is just a list of stores with their quantities. If I look on POS on the inventory lookup page, after one entered a product on the product search on the side, it does give me a current store quantity.

6170.InventoryLookupview.jpg

But the above is standard code and thus locked down. So I have no idea how it was done.

So for the channelId of the user that is logged in, I need to show the following for each product in the searchview:

7446.ProdInventoryLookupview.jpg

 

Any ideas on how to accomplish what I need to do? I am new to Retail development so any help is much appreciated. 

Have to say I have a cloud-hosted development environment with D365FO v10.0.6 PU30 and I am customizing CPOS incase that makes a difference.

I have the same question (0)
  • Oksana Kovaliova Profile Picture
    3,597 on at

    Hi,

    Please, confirm my understanding: you want to put information from 'Inventory lookup' to 'Product search' view, right?

    Taking current POS store as 'inventory warehouse' for availability search

  • Retha Profile Picture
    1,457 on at

    Hi Oksana,

    Yes. And I have no idea how to do it.  

    I contacted Microsoft and the guy told me they doesn't recommend real time calls from the searchview due to speed, but if that is the only way for us to get the values in the columns then I have to do it.

    Microsoft did give me the following but I still don't know how to put them together.

    He gave the following when he told me that they do not recommend RTC's in SearchView:

    /*

    orgUnitManager.SearchProductAvailability(productId, new OrgUnitAvailabilitySearchCriteria { OrgUnitNumber = storeId }

    orgUnitManager.GetProductAvailability(productId, resultSettings);

    */

    But the above is very cryptic and I have no idea where to put it.

    When I asked for explanation, he gave me the following and I still can't see how to call it from a PosExtension. All the ViewExtensions examples make calls to entities in Proxyentities. His suggestion is a call to what looks like a Retail server API. I can't find an example that shows me how to make the call from my POSExtension to a RetailServer.

    /*

    First, you need to decide which API you need to call depending your request. I don’t have any specific example to share about getting product availabilities. But in the retail proxy you will see ProductManager class through which you can call the GetProductAvailabilities. This API call goes through Channel DB and does not query inventory from RTS. It will query the table ax.RetailInventAvailability for the latest known Available Physical value from headquarters, then sum the recent sales transactions are subtract that value.

               /// <summary>

               /// GetProductAvailabilities method.

               /// </summary>

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

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

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

               /// <returns>

               /// The Collection of ProductAvailableQuantity.

               /// </returns>

               public Task<PagedResult<ProductAvailableQuantity>> GetProductAvailabilities(IEnumerable<long> itemIds, long channelId, QueryResultSettings queryResultSettings)

               {

                   return Task.Run(() => Runtime.Client.InventoryManager.Create(CommerceRuntimeManager.Runtime).GetProductAvailabilities(

                       queryResultSettings,

                       itemIds,

                       channelId));

               }

    Something like this:

               QueryResultSettings queryResultSettings = new QueryResultSettings();

               queryResultSettings.Paging = new PagingInfo { Top = (long)pageSize, Skip = skipCount };

               managerFactory = ManagerFactory.Create(context);

               IProductManager productManager = managerFactory.GetManager<IProductManager>();

               PagedResult<ProductSearchResult> productSearchResults = await productManager.SearchByCategory(ChannelId, 0, categoryId, queryResultSettings);

    */

    All the documents I can find on custom columns only add columns that are on the entity that is used for the standard columns already shown.

    If I look at the below link, then it showed how to add a custom field to CustomerAddEdit and not CustomerSearchView, although it mentions customersearchView.

    If I look at the PosApi for SearchView then I cannot see that it can accept method calls like Constructor and Init.

    community.dynamics.com/.../custom-field-isn-t-reflected-to-searchview-and-customeraddedit-form

    The following one is for custom controls and SearchView doesn't allow custom controls. However I thought the way it makes the call for the productavailability using a function, I might use to use the code Microsoft gave me but I still can't see how to do it.

    docs.microsoft.com/.../pos-control-non-screen

    So if you can help me with Microsoft's suggestion then I will really appreciate it or if you have another idea on how to accomplish what I need to do then I'll take that too.

  • Suggested answer
    Oksana Kovaliova Profile Picture
    3,597 on at

    Hi,

    There are multiple steps in your requirement

    CRT extension

    Main goal: add extension properties for 'Product search' rows - data about availability. 

    Possible implementation: 

    1)  Add post-triggers for Request & Response, executed by CRT when product search is requested by POS

    I would have a look at the following requests first, but can be more requests to add triggers to: GetProductSearchResultsDataRequest, GetProductSearchResultsByProductIdsDataRequest

    2) Responses for above requests will contain collections of  ProductSearchResult objects - you can loop through these objects and add Availability properties to the list of ExtensionProperties

    3) Decide how to calculate availability:

         a) I agree with Microsoft, your search will become slow and D365 will receive high load, if you start querying D365 i real-time for availability - but if you still think it is a good option, you can call D365 for availability calculation in real-time from CRT

         b) Another way is to query from CRT table [ax].RETAILINVENTAVAILABILITY from retail database to fetch availability details. This availability data is not real-time, it is updated periodically by 1130 job and to make it calculated, you need to configure P-job, 'Calculate product availability' and 'Post inventory' batch jobs running often. 

           I usually prefer working with [ax].RETAILINVENTAVAILABILITY - yes, its data is not real time, but for most of the retailers that is fine. And this way is fast, because it is just SQL query 

    POS extension

    Main goal: display extension properties populated in CRT. 

    1) CustomProductSearchColumns sample from POS samples explain how to add new columns

    2) You need to access ProductSearchResult.ExtensionProperties to get your availability value 

    Hope this helps

  • Retha Profile Picture
    1,457 on at

    Thanks Oksana . I will try your option 3b.

  • Verified answer
    Oksana Kovaliova Profile Picture
    3,597 on at

    1 and 2 are mandatory steps too :)

    Really good if for your customer option 3b works - because it will not influence on performance of POS search

  • Retha Profile Picture
    1,457 on at

    Hi Oksana, here is what I did. 

    1) CRT side:

    a) Post trigger:

    I add the extension property here. note I did not create a channel DB table because I do not want the available physical quantity to persist in the DB. I just want to show it in the grid in POS. I also call view: ITEMAVAILABILITYVIEW because I need to show the quantity for the existing store only.

    ****

    namespace TMC

    {

       using System;

       using System.Collections.Generic;

       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;

       public class GetProductSearchTriggers : IRequestTrigger

       {

           /// <summary>

           /// Gets the supported requests for this trigger.

           /// </summary>

           public IEnumerable<Type> SupportedRequestTypes

           {

               get

               {

                   return new[] { typeof(GetProductSearchResultsDataRequest) };

               }

           }

           /// <summary>

           /// Post trigger code to retrieve extension properties.

           /// </summary>

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

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

           public void OnExecuted(Request request, Response response)

           {

               ThrowIf.Null(request, "request");

               ThrowIf.Null(response, "response");

               var product = ((SingleEntityDataServiceResponse<Product>)response).Entity;

               if (product == null)

               {

                   return;

               }

               CommerceProperty availPhys = new CommerceProperty();

               availPhys.Key = "Inventory";

               var query = new SqlPagedQuery(QueryResultSettings.AllRecords)

               {

                   DatabaseSchema = "crt",

                   Select = new ColumnSet("RECID", "AVAILPHYSICAL"),

                   From = "ITEMAVAILABILITYVIEW",

                   Where = "ITEMID = @itemId AND INVENTLOCATIONID = @channelLocation AND DATAAREAID = @companyId"

               };

               query.Parameters["@itemId"] = product.ItemId;

               query.Parameters["@channelLocation"] = request.RequestContext.GetChannelConfiguration().InventLocation;

               query.Parameters["@companyId"] = request.RequestContext.GetChannelConfiguration().InventLocationDataAreaId;

               using (var databaseContext = new DatabaseContext(request))

               {

                   PagedResult<TMC.ProductAvailQty> extensions = databaseContext.ReadEntity<TMC.ProductAvailQty>(query);

                   var resultObj = extensions.Results;

                   var qty = 0;

                   foreach (TMC.ProductAvailQty obj in extensions.Results)

                   {

                       qty += obj.AvailQty;

                   }

                   availPhys.Value = qty.ToString();

               }

               product.ExtensionProperties.Add(availPhys);

           }

           public void OnExecuting(Request request)

           {

               throw new NotImplementedException();

           }

       }

    }

    ****

    b) I then created the following class to have a commerce entity to put the data in when SQL returns the quantities. I couldn't find an example where the call the channel DB did not make use of a persisted table in the channel DB.

    namespace TMC

    {

       using System.Runtime.Serialization;

       using Microsoft.Dynamics.Commerce.Runtime.ComponentModel.DataAnnotations;

       using Microsoft.Dynamics.Commerce.Runtime.DataModel;

       class ProductAvailQty : CommerceEntity

       {

           private const string AvailColumn = "AVAILPHYSICAL";

           private const string IdColumn = "RECID";

           //

           // Summary:

           //     Initializes a new instance of the TMC.ProductAvailQtyy

           //     class.

           public ProductAvailQty()

               : base("ProductAvailQty")

           {

           }

           /// <summary>

           /// Gets or sets the physical available quantity.

           /// </summary>

           [DataMember]

           [Column(AvailColumn)]

           public int AvailQty

           {

               get { return (int)this[AvailColumn]; }

               set { this[AvailColumn] = value; }

           }

           /// <summary>

           /// Gets or sets the id.

           /// </summary>

           [Key]

           [DataMember]

           [Column(IdColumn)]

           public long Id

           {

               get { return (long)this[IdColumn]; }

               set { this[IdColumn] = value; }

           }

       }

    }

    On a side note, the way I populate the above commerce entity, will it only contain data for the specific user during the scope of the request or will it contain other users' data as well meaning that when multiple users access the product search that this entity will not contain the quantity just for their search request but also that of the other users and thus showing the wrong quantity.

    c) I have built my project and it created a DLL for me. I placed the dll in K:\\RetailServer\WebRoot\bin\Ext\

    Dll name: "TMC_6433_ProdSearchInventValuesCRT.dll"

    d) The RetailSDK that I develop in, is on the C-drive. And I have added my DLL to the CommerceRuntime.Ext.Config:

    <add source="assembly" value="TMC_6433_ProdSearchInventValuesCRT" />

    2) POS side:

    On the CloudPos solution, under Pos.Extensions, I created the directory structure: TMCPosExtensions\SearchViewextensions\ProductSearchView.

    In here I have created a ts-file:

    a) TS-file with the Inventory custom column. I used CustomProductSearchColumns.ts as the base and used CustomOrderListColumns.ts as an example how to read extension properties to populate the column value. Maybe I did something wrong here but ti compiled without errors:

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

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

    import { CurrencyFormatter } from "PosApi/Consume/Formatters";

    import { ProxyEntities } from "PosApi/Entities";

    import { ObjectExtensions, StringExtensions } from "PosApi/TypeExtensions";

    /**

        * Gets the property value given the column name.

        * @param {ProxyEntities.CommerceProperty[]} extensionProperties The extension properties collection.

        * @param {string} column The column name of the property value to be retrieved.

        * @returns The property value.

        */

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

       return [

           {

               title: "Item ID_CUSTOMIZED",

               computeValue: (row: ProxyEntities.ProductSearchResult): string => { return row.ItemId; },

               ratio: 20,

               collapseOrder: 3,

               minWidth: 120

           }, {

               title: "Name",

               computeValue: (row: ProxyEntities.ProductSearchResult): string => { return row.Name; },

               ratio: 40,

               collapseOrder: 2,

               minWidth: 200

           }, {

               title: "Inventory",

               computeValue: (row: ProxyEntities.ProductSearchResult): string => {

                   if (!ObjectExtensions.isNullOrUndefined(row.ExtensionProperties)) {

                       let inventProperties: ProxyEntities.CommerceProperty[] = row.ExtensionProperties.filter(

                           (value: ProxyEntities.CommerceProperty): boolean => {return value.Key === "Inventory";});

                            return inventProperties.length > 0 ? inventProperties[0].Value.StringValue : StringExtensions.EMPTY;

                           }

                           return StringExtensions.EMPTY;

               },

               ratio: 20,

               collapseOrder: 4,

               minWidth: 100,

               isRightAligned: true

           }, {

               title: "Price",

               computeValue: (row: ProxyEntities.ProductSearchResult): string => { return CurrencyFormatter.toCurrency(row.Price); },

               ratio: 20,

               collapseOrder: 1,

               minWidth: 100,

               isRightAligned: true

           }

       ];

    };

    b) Then in the manifest.json file under TMCPosExtensions I already has 1 custom which is working and showing when I run CloudPOS. So I just added the view extension. I used the manifest.json under Pos\Extensions\SampleExtensions\ as an example.

           "SearchView": {

             "productListConfiguration": { "modulePath": "SearchViewExtensions/ProductSearchView/ProductSearchViewColumns" }

           }

    c) Then with my previous custom I added already TMCPosExtensions to the extensions.json which is under Pos.Extensions:

    {

     "extensionPackages": [

       {

           "baseUrl": "TMCPosExtensions"

       }

     ]

    }

    d) Lastly I added previously my extension also to tsconfig.json under Exclude:

    "exclude": [

       //"TMCPosExtensions",

       "AuditEventSignatureSample",

       "B2BSample",

       "CreateCashManagementTransactionSample",

       "CreateTenderCountingTransactionSample",

       "CustomerSearchWithAttributesSample",

       "DualDisplaySample",

       "FiscalRegisterSample",

       "PaymentSample",

       "PrefixUserIdSample",

       "PromotionsSample",

       "SalesOrdersSample",

       "SalesTransactionSignatureNorway",

       "SalesTransactionSignatureSample",

       "SalesTransBuildNumberSample",

       "SampleExtensions",

       "SampleExtensions2",

       "SequentialSignature",

       "StoreHoursSample",

       "SuspendTransactionReceiptSample",

       "WarrantyAndReturnSample"

     ],

    Output:

    1777.pastedimage1576790615995v1.png

    I am very excited to see my column but it has no values. It might be the view I'm using so I logged into SSMS and ran:

    select *

    from [crt].ITEMAVAILABILITYVIEW

    It returned nothing. I then ran a for each on [ax].RETAILINVENTAVAILABILITY and it also returned nothing.

    I ran in D365 under Retail in the distribution schedule job 1130 using button "run now" and it looks like it is doing nothing because it returns very quickly. I even ran it in a batch and it finished the job in 2 seconds. So it is clearly not running. So how on earth do I get data into that channel DB on my Dev box? 

    There are products in [ax]/EcoResProduct and inventdimIds in [ax].InventDim. I ran job 1040 and it waits awhile before it returns to the screen which means it is running. IT is just job 1130 that has an issue it seems.

  • Oksana Kovaliova Profile Picture
    3,597 on at

    Hi, did you do the following steps from the list: 'you need to configure P-job, 'Calculate product availability' and 'Post inventory' batch jobs running often.' ?

  • ram shenkar Profile Picture
    515 on at

    Hi

    You can use the existing request to get the inventory of any product. You have to just pass the Item Id and variant (if applicable)

    GetStoreAvailabilityRealtimeRequest

    Remember, As Oksana stated earlier it may impact performanace little bit.

  • Retha Profile Picture
    1,457 on at

    Oksana with 'Calculate product availability' I assume you are talking about the distribution schedule 1130? Because it is on my Dev box there isn't a lot of change in the inventory. So I ran it a few times by clicking on the "Run now" button for job 1130 in the disctribution schedule. That should have populated the RetailInventAvailability table if I understood you correctly.

    However it is not doing anything. When I click on History it just states "No data".

    2161.pastedimage1577140836935v1.png

    I couldn't find anything under Microsoft.Docs that indicate there is another "Calculate product availability" P-job.

    I then in the end manually through SSMS added 1 record for a specific product in [ax].RetailInventAvailability table.

    However my code doesn't even show for this specific item the availPhysical.

    I am trying to debug my CRT code. However I have no idea how. I will log another question on that so that this topic doesn't contain 2 issues' answers.

    I changed my CRT as follow:

    ***

    namespace TMC
    {
        using System;
        using System.Collections.Generic;
        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 Microsoft.Dynamics.Commerce.Runtime.Services.Messages;

        /// <summary>
        /// Class that implements a post trigger for the GetProductSearchResultsDataRequest request type.
        /// </summary>
        public class GetProductSearchTriggers : IRequestTrigger
        {
            /// <summary>
            /// Gets the supported requests for this trigger.
            /// </summary>
            public IEnumerable<Type> SupportedRequestTypes
            {
                get
                {
                    /*
                    return new[]
                        {
                            typeof(GetProductSearchResultsByProductIdsDataRequest),
                            typeof(GetProductSearchResultsDataRequest),
                        };
                        */
                    return new[] { typeof(GetProductSearchResultsDataRequest) };
                }
            }
            /// <summary>
            /// Post trigger code to retrieve extension properties.
            /// </summary>
            /// <param name="request">The request.</param>
            /// <param name="response">The response.</param>
            public void OnExecuted(Request request, Response response)
            {
                ThrowIf.Null(request, "request");
                ThrowIf.Null(response, "response");

                var productsList = ((EntityDataServiceResponse<ProductSearchResult>)response);
                if (productsList == null)
                {
                    return;
                }
                foreach (ProductSearchResult product in productsList)
                {
                    CommerceProperty availPhys = new CommerceProperty();
                    availPhys.Key = "Inventory";
                    var query = new SqlPagedQuery(QueryResultSettings.AllRecords)
                    {
                        DatabaseSchema = "ext",
                        Select = new ColumnSet("RECID", "AVAILPHYSICAL"),
                        From = "TMCITEMSTOREAVAILABILITYVIEW",
                        Where = "ITEMID = @itemId AND INVENTLOCATIONID = @channelLocation AND DATAAREAID = @companyId"
                    };
                    query.Parameters["@itemId"] = product.ItemId;
                    query.Parameters["@channelLocation"] = request.RequestContext.GetChannelConfiguration().InventLocation;
                    query.Parameters["@companyId"] = request.RequestContext.GetChannelConfiguration().InventLocationDataAreaId;
                    var qty = 0;
                    using (var databaseContext = new DatabaseContext(request))
                    {
                        PagedResult<TMC.ProductAvailQty> extensions = databaseContext.ReadEntity<TMC.ProductAvailQty>(query);
                        var resultObj = extensions.Results;
                        foreach (TMC.ProductAvailQty obj in extensions.Results)
                        {
                            qty += obj.AvailQty;
                        }
                        availPhys.Value = qty.ToString();
                    }
                    
                    product.ExtensionProperties.Add(availPhys);
                   
                }
            }
           public void OnExecuting(Request request)
            {
                if (request == null)
                {
                    throw new ArgumentNullException("request");
                }
            }

        }
    }

    ***

    Note that I had to switch to a custom view because the standard view: ITEMAVAILABILITYVIEW, repeats the 1 line in [ax].RetailInventAvailability 3 times if I run a select on it in SSMS.

    View RETAILINVENTAVAILABILITYVIEW on the other hand shows the entry only once which is correct but I cannot call this view because it doesn't give me the option to run it for a specific store.. My custom view is just a copy of ITEMAVAILABILITYVIEW but without the join that cause the repetition.

    Can you see anything wrong in my code above?

  • Retha Profile Picture
    1,457 on at

    Hi Ram, I first need to try Oksana's way because the reason why we switch for the order desk from D365 full client to Retail thin client is because the whole sales order creation, completion and advanced product search is very slow on the full client.

    So it won't help if I make the product search slow again by calling realtime

Under review

Thank you for your reply! To ensure a great experience for everyone, your content is awaiting approval by our Community Managers. Please check back later.

Helpful resources

Quick Links

Responsible AI policies

As AI tools become more common, we’re introducing a Responsible AI Use…

Neeraj Kumar – Community Spotlight

We are honored to recognize Neeraj Kumar as our Community Spotlight honoree for…

Leaderboard > Supply chain | Supply Chain Management, Commerce

#1
Laurens vd Tang Profile Picture

Laurens vd Tang 299 Super User 2025 Season 2

#2
Siv Sagar Profile Picture

Siv Sagar 183 Super User 2025 Season 2

#3
André Arnaud de Calavon Profile Picture

André Arnaud de Cal... 118 Super User 2025 Season 2

Last 30 days Overall leaderboard

Product updates

Dynamics 365 release plans