Loading...
Area: Episerver B2B Commerce

Add a custom property filter to order history in Classic

Recommended reading 

Client Side

  1. Navigate to the Admin ConsoleThemes & Content > Themes Library > Themes. 
  2. Select the Edit button for the desired theme
  3. Select the Widget Templates finger tab.
  4. Find the Widget named OrdersView and click the View Only icon .
  5. In the Details page, click the Clone button and then give your cloned OrdersView a name and group, in this case "CustomOrdersView" in group "Custom"
  6. Click the Save button.
  7. Now click on the Code tab to see the standard code that we are going to be changing.
  8. We are going to add a text input to allow the user to enter text to filter a custom property on the order history, a current text field that is similar to that is the po number filter, so let's copy and paste it where we want the new field and then change it accordingly and save your changes.

    Code Sample: CustomOrdersView

    <div ng-controller="OrderListController as vm" ng-cloak>
        <section class="accordion search-orders">
            <div class="cm">
                <input type="checkbox" id="accord-1" class="accord-check" />
                <label id="tst_ordersPage_searchOrdersBtn" for="accord-1" class="accord-head">[% translate 'Search Orders' %]</label>
                <article class="accord-content">
                    <div class="row">
                        <div class="medium-12 large-4 columns search-col-1">
                            <div class="search-ship-to">
                                <label>[% translate 'Ship To Address' %]</label>
                                <select ng-model="vm.searchFilter.customerSequence" ng-options="shipTo.customerSequence as shipTo.label for shipTo in vm.shipTos"></select>
                            </div>
                            <div class="search-status">
                                <label>[% translate 'Status' %]</label>
                                <select id="tst_ordersPage_status" ng-model="vm.searchFilter.statusDisplay">
                                    <option value="">[% translate 'Select' %]</option>
                                    <option ng-repeat="(key,value) in vm.orderStatusMappings" ng-selected="vm.searchFilter.statusDisplay === key" value="{{::key}}" ng-bind="key"></option>
                                </select>
                            </div>
                            <!-- Begin added custom text field -->
                            <div class="search-po">
                                <label>[% translate 'Custom Property' %]</label>
                                <input type="text" ng-model="vm.searchFilter.customProperty" />
                            </div>
                            <!-- End added custom text field -->
                        </div>
                        <div class="medium-12 large-4 columns search-col-2">
                            <div class="search-po" ng-if="vm.showPoNumber">
                                <label>[% translate 'PO #' %]</label>
                                <input type="text" ng-model="vm.searchFilter.ponumber" />
                            </div>
                            <div class="search-order-num">
                                <label>[% translate 'Order #' %]</label>
                                <input type="text" ng-model="vm.searchFilter.ordernumber" />
                            </div>
                        </div>
                        <div class="medium-12 large-4 columns search-col-3">
                            <div class="search-total">
                                <label>[% translate 'Order Total' %]</label>
                                <div class="row">
                                    <div class="small-6 columns">
                                        <select ng-model="vm.searchFilter.ordertotaloperator">
                                            <option value="">[% translate 'Select' %]</option>
                                            <option value="Greater Than">[% translate 'Greater Than' %]</option>
                                            <option value="Less Than">[% translate 'Less Than' %]</option>
                                            <option value="Equal To">[% translate 'Equal To' %]</option>
                                        </select>
                                    </div>
                                    <div class="small-6 columns">
                                        <input type="text" ng-model="vm.searchFilter.ordertotal" />
                                    </div>
                                </div>
                            </div>
                            <div class="search-date">
                                <label>[% translate 'Date Range' %]</label>
                                <div class="row">
                                    <div class="small-6 columns search-date-from">
                                        <em>[% translate 'From' %]</em>
                                        <input id="tst_ordersPage_fromDate" name="tst_ordersPage_fromDate" type="text" value="" class="datepicker" isc-pick-a-date="vm.searchFilter.fromDate" />
                                    </div>
                                    <div class="small-6 columns search-date-to">
                                        <em>[% translate 'To_date' %]</em>
                                        <input id="tst_ordersPage_toDate" name="tst_ordersPage_toDate" type="text" value="" class="datepicker" isc-pick-a-date="vm.searchFilter.toDate" />
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                    <div class="btns">
                        <button id="tst_ordersPage_searchBtn" type="submit" class="btn primary btn-search" ng-click="vm.search()">[% translate 'Search' %]</button>
                        <button id="tst_ordersPage_clearBtn" type="submit" class="btn secondary btn-clear" ng-click="vm.clear()">[% translate 'Clear' %]</button>
                    </div>
                </article>
            </div>
        </section>
     
        <p class="error" ng-if="vm.validationMessage" ng-bind="vm.validationMessage"></p>
     
        <div ng-show="vm.orderHistory.orders.length > 0">
     
            <h3 class="results-count">
                <span class="result-num" ng-bind="vm.pagination.totalItemCount"></span>
                <span class="result-lbl">[% translate 'orders' %]</span>
            </h3>
     
            <isc-pager pagination="vm.pagination" storage-key="vm.paginationStorageKey" update-data="vm.getOrders()"></isc-pager>
     
            <div class="overflow-table small">
                <table class="info-tbl">
                    <tbody>
                        <tr>
                            <th class="col-date">
                                <span class="sticky-first">
                                <a href="javascript:void(0)" class="sort" id="tst_myaccount_orders_sort_orderdate"
                                   ng-class="{'sort-ascending': vm.searchFilter.sort === 'OrderDate,ErpOrderNumber', 'sort-descending': vm.searchFilter.sort === 'OrderDate DESC,ErpOrderNumber DESC'}"
                                   ng-click="vm.changeSort('OrderDate,ErpOrderNumber')">
                                    [% translate 'Date' %]
                                </a>
                                </span>
                            </th>
                            <th class="col-ordernum">
                                <a href="javascript:void(0)" class="sort"
                                   ng-class="{'sort-ascending': vm.searchFilter.sort === 'WebOrderNumber', 'sort-descending': vm.searchFilter.sort === 'WebOrderNumber DESC'}"
                                   ng-click="vm.changeSort('WebOrderNumber')">
                                    [% translate 'Order #' %]
                                </a>
                            </th>
                            <th class="col-shipto">
                                <a href="javascript:void(0)" class="sort"
                                   ng-class="{'sort-ascending': vm.searchFilter.sort === 'STCompanyName', 'sort-descending': vm.searchFilter.sort === 'STCompanyName DESC'}"
                                   ng-click="vm.changeSort('STCompanyName')">
                                    [% translate 'Ship To' %]
                                </a>
                            </th>
                            <th class="col-status">
                                <a href="javascript:void(0)" class="sort"
                                   ng-class="{'sort-ascending': vm.searchFilter.sort === 'Status', 'sort-descending': vm.searchFilter.sort === 'Status DESC'}"
                                   ng-click="vm.changeSort('Status')">
                                    [% translate 'Status' %]
                                </a>
                            </th>
                            <th class="col-erp" ng-if="vm.orderHistory.showErpOrderNumber">
                                <a href="javascript:void(0)" class="sort"
                                   ng-class="{'sort-ascending':vm. searchFilter.sort === 'ERPOrderNumber', 'sort-descending': vm.searchFilter.sort === 'ERPOrderNumber DESC'}"
                                   ng-click="vm.changeSort('ERPOrderNumber')">
                                    [% translate 'ERP Order #' %]
                                </a>
                            </th>
                            <th class="col-po" ng-if="vm.showPoNumber">
                                <a href="javascript:void(0)" class="sort"
                                   ng-class="{'sort-ascending': vm.searchFilter.sort === 'CustomerPO', 'sort-descending': vm.searchFilter.sort === 'CustomerPO DESC'}"
                                   ng-click="vm.changeSort('CustomerPO')">
                                    [% translate 'PO #' %]
                                </a>
                            </th>
                            <th class="col-tot">
                                <a href="javascript:void(0)" class="sort"
                                   ng-class="{'sort-ascending': vm.searchFilter.sort === 'OrderTotal', 'sort-descending': vm.searchFilter.sort === 'OrderTotal DESC'}"
                                   ng-click="vm.changeSort('OrderTotal')">
                                    [% translate 'Total' %]
                                </a>
                            </th>
                        </tr>
                        <tr class="tst_ordersPage_orderLine" ng-repeat="order in vm.orderHistory.orders">
                            <td class="col-date"><span class="sticky-first"><a ng-href="[% urlForPage 'OrderDetailPage' %]?orderNumber={{ order.webOrderNumber || order.erpOrderNumber }}">{{ order.orderDate | date:'shortDate' }} </a></span></td>
                            <td class="col-ordernum"><a ng-href="[% urlForPage 'OrderDetailPage' %]?ordernumber={{ order.webOrderNumber || order.erpOrderNumber }}" ng-click="">{{ order.webOrderNumber || order.erpOrderNumber }}</a></td>
                            <td class="col-shipto" ng-bind="order.stCompanyName"></td>
                            <td class="col-status" ng-bind="order.statusDisplay"></td>
                            <td class="col-erp" ng-if="vm.orderHistory.showErpOrderNumber" ng-bind="order.erpOrderNumber"></td>
                            <td class="col-po"  ng-if="vm.showPoNumber" ng-bind="order.customerPO" ng-click="copyToSearch(order.customerPO)"></td>
                            <td class="col-tot" ng-bind="order.orderTotalDisplay"></td>
                        </tr>
                    </tbody>
                </table>
            </div>
            <isc-pager pagination="vm.pagination" bottom="true" storage-key="vm.paginationStorageKey" update-data="vm.getOrders()"></isc-pager>
        </div>
     
        <div class="search-no-results" ng-show="vm.orderHistory.orders.length === 0">
            <h3>[% translate 'No orders found' %].</h3>
        </div>
    </div>
  9. We now need to change the template in the CMS for the widget.
  10. Navigate to the Order History page in the storefront and open and enable editing of the CMS.
  11. Select the Edit button for the Orders View widget
  12. Uncheck the Use Default Template checkbox
  13. Select the unlock text
  14. Choose your template from the Template menu.
  15. Select Save.
  16. Select Publish.
  17. When the page refreshes after publishing, you can select the Search Orders + button and view the new "Custom Properties" field or you can sign into the storefront and navigate to My Account > Order History and open Search Orders. The new text field should be available.
  18. Now the text field needs to be wired up to do something. Close the Preview tab and return to the Admin Console.
  19. In Themes & Content click Theme Resources and search for insite.order-list.controller.
  20. Click the View Only button.
  21. In the Details page click the Clone button and give your cloned insite.order-list.controller a name and group. For this example, we use the name of "custom.order-list.controller" and put it in Custom group.
  22. Click the Save button.
  23. Now click on the Code tab to see the standard code that we will change.

    Code Sample: insite.order-list.controller

    import KeyValuePair = System.Collections.Generic.KeyValuePair;
     
    module insite.order {
        "use strict";
     
        export class OrderListController {
            orderHistory: OrderCollectionModel;
            allowCancellationRequest = false;
            showPoNumber: boolean;
            pagination: PaginationModel;
            paginationStorageKey = "DefaultPagination-OrderList";
            searchFilter: OrderSearchFilter = {
                customerSequence: "-1",
                sort: "OrderDate DESC,ErpOrderNumber DESC",
                toDate: "",
                fromDate: "",
                expand: "",
                ponumber: "",
                ordernumber: "",
                ordertotaloperator: "",
                ordertotal: "",
                status: [],
                statusDisplay: ""
            };
            appliedSearchFilter = new OrderSearchFilter();
            shipTos: ShipToModel[];
            validationMessage: string;
            orderStatusMappings: KeyValuePair<string, string[]>;
     
            static $inject = ["orderService", "customerService", "coreService", "paginationService", "settingsService"];
     
            constructor(
                protected orderService: order.IOrderService,
                protected customerService: customers.ICustomerService,
                protected coreService: core.ICoreService,
                protected paginationService: core.IPaginationService,
                protected settingsService: core.ISettingsService) {
                this.init();
            }
     
            init(): void {
                this.settingsService.getSettings().then(
                    (settingsCollection: core.SettingsCollection) => { this.getSettingsCompleted(settingsCollection); },
                    (error: any) => { this.getSettingsFailed(error); });
     
                this.customerService.getShipTos().then(
                    (shipToCollection: ShipToCollectionModel) => { this.getShipTosCompleted(shipToCollection); },
                    (error: any) => { this.getShipTosFailed(error); });
     
                this.orderService.getOrderStatusMappings().then(
                    (orderStatusMappingCollection: OrderStatusMappingCollectionModel) => { this.getOrderStatusMappingCompleted(orderStatusMappingCollection); },
                    (error: any) => { this.getOrderStatusMappingFailed(error); });
            }
     
            protected getSettingsCompleted(settingsCollection: core.SettingsCollection): void {
                this.allowCancellationRequest = settingsCollection.orderSettings.allowCancellationRequest;
                this.showPoNumber = settingsCollection.orderSettings.showPoNumber;
                this.initFromDate(settingsCollection.orderSettings.lookBackDays);
            }
     
            protected getSettingsFailed(error: any): void {
            }
     
            protected getShipTosCompleted(shipToCollection: ShipToCollectionModel): void {
                this.shipTos = shipToCollection.shipTos;
            }
     
            protected getShipTosFailed(error: any): void {
            }
     
            protected getOrderStatusMappingCompleted(orderStatusMappingCollection: OrderStatusMappingCollectionModel): void {
                this.orderStatusMappings = ({} as KeyValuePair<string, string[]>);
     
                for (let i = 0; i < orderStatusMappingCollection.orderStatusMappings.length; i++) {
                    const key = orderStatusMappingCollection.orderStatusMappings[i].displayName;
                    if (!this.orderStatusMappings[key]) {
                        this.orderStatusMappings[key] = [];
                    }
     
                    this.orderStatusMappings[key].push(orderStatusMappingCollection.orderStatusMappings[i].erpOrderStatus);
                }
            }
     
            protected getOrderStatusMappingFailed(error: any): void {
            }
     
            protected initFromDate(lookBackDays: number): void {
                this.pagination = this.paginationService.getDefaultPagination(this.paginationStorageKey);
     
                if (lookBackDays > 0) {
                    const date = new Date(Date.now() - lookBackDays * 60 * 60 * 24 * 1000);
                    this.searchFilter.fromDate = date.toISOString();
                }
     
                this.restoreHistory();
                this.prepareSearchFilter();
                this.getOrders();
            }
     
            clear(): void {
                this.pagination.page = 1;
                this.searchFilter.customerSequence = "-1";
                this.searchFilter.sort = "OrderDate DESC,ErpOrderNumber DESC";
                this.searchFilter.toDate = "";
                this.searchFilter.fromDate = "";
                this.searchFilter.ponumber = "";
                this.searchFilter.ordernumber = "";
                this.searchFilter.ordertotaloperator = "";
                this.searchFilter.ordertotal = "";
                this.searchFilter.status = [];
                this.searchFilter.statusDisplay = "";
     
                this.prepareSearchFilter();
                this.getOrders();
            }
     
            changeSort(sort: string): void {
                if (this.searchFilter.sort === sort && this.searchFilter.sort.indexOf(" DESC") < 0) {
                    this.searchFilter.sort = `${sort.replace(",", " DESC,")} DESC`;
                } else {
                    this.searchFilter.sort = sort;
                }
     
                this.getOrders();
            }
     
            search(): void {
                if (this.pagination) {
                    this.pagination.page = 1;
                }
     
                this.prepareSearchFilter();
                this.getOrders();
            }
     
            getOrders(): void {
                this.appliedSearchFilter.sort = this.searchFilter.sort;
                this.coreService.replaceState({ filter: this.appliedSearchFilter, pagination: this.pagination });
     
                delete this.appliedSearchFilter.statusDisplay;
                this.orderService.getOrders(this.appliedSearchFilter, this.pagination).then(
                    (orderCollection: OrderCollectionModel) => { this.getOrdersCompleted(orderCollection); },
                    (error: any) => { this.getOrdersFailed(error); });
            }
     
            protected getOrdersCompleted(orderCollection: OrderCollectionModel): void {
                this.orderHistory = orderCollection;
                this.pagination = orderCollection.pagination;
            }
     
            protected getOrdersFailed(error: any): void {
                this.validationMessage = error.exceptionMessage;
            }
     
            prepareSearchFilter(): void {
                for (let property in this.searchFilter) {
                    if (this.searchFilter.hasOwnProperty(property)) {
                        if (this.searchFilter[property] === "") {
                            this.appliedSearchFilter[property] = null;
                        } else {
                            this.appliedSearchFilter[property] = this.searchFilter[property];
                        }
                    }
                }
     
                if (this.appliedSearchFilter.statusDisplay && this.orderStatusMappings && this.orderStatusMappings[this.appliedSearchFilter.statusDisplay]) {
                    this.appliedSearchFilter.status = this.orderStatusMappings[this.appliedSearchFilter.statusDisplay];
                }
            }
     
            protected restoreHistory(): void {
                const state = this.coreService.getHistoryState();
                if (state) {
                    if (state.pagination) {
                        this.pagination = state.pagination;
                    }
     
                    if (state.filter) {
                        this.searchFilter = state.filter;
                        if (this.searchFilter.customerSequence === null) {
                            this.searchFilter.customerSequence = "-1";
                        }
     
                        if (this.searchFilter.statusDisplay === null) {
                            this.searchFilter.statusDisplay = "";
                        }
                    }
                }
            }
        }
     
        angular
            .module("insite")
            .controller("OrderListController", OrderListController);
    			}

    Remember, in the view change we made above, we bound our custom text field to vm.searchFilter.customProperty (ng-model="vm.searchFilter.customProperty"), but notice that searchFilter is of type OrderSearchFilter, so we need to add our customProperty field to an override of this type. Because all of our files start with insite. and pascal case is converted to lowercase with dashes between words, we can find this file by convention. Search in Theme Resources for insite.order-search-filter and find insite.order-search-filter.model.

  24. Click the View Onlybutton for insite.order-search-filter.model and clone it like we did the controller, giving it the name "custom.order-search-filter.model" and put it in the Custom group. The code for insite.order-search-filter.model is:

    Code Sample: insite.order-search-filter.model

    module insite.order {
    	"use strict";
    					
    	export class OrderSearchFilter implements order.ISearchFilter {
    		customerSequence: string;
    		sort: string;
    		toDate: string;
    		fromDate: string;
    		expand: string;
    		ponumber: string;
    		ordernumber: string;
    		ordertotaloperator: string;
    		ordertotal: string;
    		status: string[];
    		statusDisplay: string;
    	}
    }
    
  25. In our cloned version, change the name of the class to "CustomOrderSearchFilter" and, as a best practice and for upgradeability, make it inherit from OrderSearchFilter so we can just add our one property. The code should look like:

    Sample Code: custom.order-search-filter.model

    module insite.order {
        "use strict";
     
        export class CustomOrderSearchFilter extends OrderSearchFilter {
            customProperty: string;
       }
    }
  26. Now, let's go back to our cloned custom.order-list.controller. Rename the class from "OrderListController" to "CustomOrderListController" and make it extend OrderListController. Because we are inheriting from OrderListController, as a best practice and for upgradeability, we can remove the things we don't need to change and just inherit them from the standard code. The only things we need to change are the types at the top to use our CustomOrderSearchFilter type and then override the clear method to blank out our customProperty field and call the standard code. We must also tell Angular to use our CustomOrderListController instead of the standard OrderListController which is done in the registration at the very bottom.

    Code Sample: custom.order-list.controller

    module insite.order {
        "use strict";
     
        export class CustomOrderListController extends OrderListController {
           searchFilter: CustomOrderSearchFilter = {
                customerSequence: "-1",
                sort: "OrderDate DESC,ErpOrderNumber DESC",
                toDate: "",
                fromDate: "",
                expand: "",
                ponumber: "",
                ordernumber: "",
                ordertotaloperator: "",
                ordertotal: "",
                status: [],
                statusDisplay: "",
                customProperty: ""
            };
            appliedSearchFilter = new CustomOrderSearchFilter();
     
            clear(): void {
                this.searchFilter.customerProperty = "";
                super.clear();
            }
        }
     
        angular
            .module("insite")
            .controller("OrderListController", CustomOrderListController);
    }
  27. Go back into your theme and in the Scripts tab assign custom.order-search-filter.model and custom.order-list.controller to your theme.
  28. Click the Preview to make sure your typescript compiles.
  29. Sign in to the website/storefront and go to Order History.
  30. Enter a value in your custom property text box and click Search.
  31. Navigate to your browser's dev tools and find the network monitoring options. You should see it now makes a request with customProperty=text from textbox in the query string.
  32. We now have the client side set up to send the filter to the server. Go into the Theme and publish the Theme Resources and then publish the Theme.

Server Side

Overview

We have the client sending the data we want to filter on to /api/v1/orders in a query string parameter named customProperty. This request is handled by the OrdersV1Controller.Get method. This method calls the IGetOrderCollectionMapper.MapParameter method to map the incoming request data to the GetOrderCollectionParameter object that is passed into the IOrderService.GetOrderCollection method. This is the place where we want to get our customProperty parameter value from the query string and set it in the Properties collection of the GetOrderCollectionParameter object so it will be available to us in the handler chain.

  1. Create a new class that inherits from Insite.Order.WebApi.V1.Mappers.GetOrderCollectionMapper and override the MapParameter method as follows:

    Code Sample: CustomGetOrderCollectionMapper

    namespace Custom
    {
        using System.Net.Http;
        using Insite.Core.Plugins.Utilities;
        using Insite.Core.WebApi.Extensions;
        using Insite.Core.WebApi.Interfaces;
        using Insite.Order.Services.Handlers.Interfaces;
        using Insite.Order.Services.Parameters;
        using Insite.Order.WebApi.V1.ApiModels;
        using Insite.Order.WebApi.V1.Mappers;
        using Insite.Order.WebApi.V1.Mappers.Interfaces;
     
        public class CustomGetOrderCollectionMapper : GetOrderCollectionMapper
        {
            public CustomGetOrderCollectionMapper(IGetOrderMapper getOrderMapper, IObjectToObjectMapper objectToObjectMapper, IUrlHelper urlHelper, IGetOrderHelper getOrderHelper)
                : base(getOrderMapper, objectToObjectMapper, urlHelper, getOrderHelper)
            {
            }
     
            public override GetOrderCollectionParameter MapParameter(OrderCollectionParameter apiParameter, HttpRequestMessage request)
            {
                var serviceParameter = base.MapParameter(apiParameter, request);
     
                var customPropertyValue = request.GetQueryString("customProperty");
     
                if (!string.IsNullOrEmpty(customPropertyValue))
                {
                    serviceParameter.Properties.Add("CustomProperty", customPropertyValue);
                }
     
                return serviceParameter;
            }
        }
    }

This will get our customProperty value from the query string in to the handler chain. The handler chain for GetOrderCollection is:

Handler Order
GetStoredOrderCollectionQueryHandler 550
GetStoredOrderCollectionFilterHandler 600
GetRealTimeOrderCollectionMapDataSetHandler 525
GetRealTimeOrderCollectionJobDefinitionHandler 450
GetRealTimeOrderCollectionExecuteJobHandler 500
GetOrderCollectionToListHandler 750
GetOrderCollectionStatusMappingsHandler 850
GetOrderCollectionSortHandler 650
GetOrderCollectionSettingsHandler 400
GetOrderCollectionPagingHandler 700
GetOrderCollectionCurrenciesHandler 800

As a best practice and to ensure upgradeability, we do not want to override standard handlers, instead we want to inject our own handlers where necessary between standard handlers. If we were doing Real Time Order History, we would create a handler with an order between 450 and 500 and add our value to the GetOrderCollectionParameter.RealTimeJobParameters collection (or we could just do so right away in the mapper above instead of adding it to the Properties collection). In our case, we are using a refresh to get the Order History nightly and querying from the OrderHistory table. We want to create a handler between 600 and 650 so it runs after the standard filters are applied, but before the sorting is applied:

Code Sample: CustomGetStoredOrderCollectionFilterHandler

namespace Custom
{
    using System;
    using System.Linq;
    using Insite.Core.Interfaces.Data;
    using Insite.Core.Interfaces.Dependency;
    using Insite.Core.Services.Handlers;
    using Insite.Order.Services.Parameters;
    using Insite.Order.Services.Results;
 
    [DependencyName("CustomGetStoredOrderCollectionFilterHandler")]
    public class CustomGetStoredOrderCollectionFilterHandler : HandlerBase<GetOrderCollectionParameter, GetOrderCollectionResult>
    {
        public override int Order => 625;
 
        public override GetOrderCollectionResult Execute(IUnitOfWork unitOfWork, GetOrderCollectionParameter parameter, GetOrderCollectionResult result)
        {
            // if our custom property is not filtered on, just continue
            if (!parameter.Properties.ContainsKey("CustomProperty"))
            {
                return this.NextHandler.Execute(unitOfWork, parameter, result);
            }
 
            // Entity Framework does not like using this directly in the where, so use a variable instead
            var customPropertyValue = parameter.Properties["CustomProperty"];
 
            // apply the filter for the custom property value, this gets translated to SQL, so it would only be case sensitive if the database is
            parameter.OrdersQuery = parameter.OrdersQuery.Where(order => order.CustomProperties.Any(property =>
                property.Name == "CustomProperty" && property.Value == customPropertyValue));
 
            // continue the handler chain
            return this.NextHandler.Execute(unitOfWork, parameter, result);
        }
    }
}

The text box you added to the Order History list screen should now function.

Do you find this information helpful? Please log in to provide feedback.

Last updated: Dec 11, 2020

Recommended reading