次の方法で共有


販売時点管理 (POS) デュアル ディスプレイ ビューの拡張

この記事では、ユーザー情報が表示されるように、販売時点管理 (POS) デュアル ディスプレイ ビューを拡張する方法について説明します。 この記事は、Microsoft Dynamics 365 Finance 7.2 または Microsoft Dynamics 365 Retail 7.2 および KB 4091080 とそれ以降のバージョンに適用されます。

POS デュアル ディスプレイ ビューを拡張するには、カスタム コントロールを追加します。 カスタム コントロールでは、カスタム情報を表示する画像、POS データ リスト、ラベルなどを追加できます。

メモ

POS デュアル ディスプレイ ビューのみを拡張するには、カスタム コントロールを追加します。 カスタム コントロールは、POS デュアル ディスプレイ ビューに表示される標準的な内容を上書きします。 デュアル ディスプレイ カスタム コントロールとデュアル ディスプレイに関連するその他の拡張機能の詳細情報は、拡張機能の詳細ビューには表示されません。

必要なステップ

POS デュアル ディスプレイ ビューをカスタマイズするために必要な手順の概要を示します。

  1. デュアル ディスプレイを有効にするハードウェア プロファイルをコンフィギュレーションします。
  2. POS.Extensions プロジェクトに、POS デュアル ディスプレイ ビューの拡張ためのフォルダを作成します。
  3. カスタム情報を含む新しいカスタム コントロールを追加します。
  4. POS デュアル ディスプレイ ビューの拡張子を使用して、manifest.json ファイルと extensions.json ファイルを更新します。
  5. 変更を配置し、カスタマイズを検証します。

シナリオまたはビジネスの問題

POS デュアル ディスプレイ ビューにカスタム コントロール列を追加して、カートの詳細と顧客および店舗の従業員に関する情報を表示します。

デュアル ディスプレイを有効にするハードウェア プロファイルをコンフィギュレーションします

  1. クライアントにサイン インします。
  2. Retail とコマース > チャネル設定 > POS 設定 > POS プロファイル > ハードウェア プロファイルの順に移動します。
  3. レジスタにリンクしているハードウェア プロファイルを選択します。
  4. デュアル ディスプレイタブで、デュアル ディスプレイの使用オプションをはいに設定します。
  5. Retail とコマース > Retail とコマース IT > 配送スケジュールの順に移動します。
  6. レジスター (1090) ジョブを選択し、今すぐ実行を選択します。

メモ

エンド ツー エンド (E2E) サンプルを \RetailSDK\POS\拡張機能\DualDisplaySample で検索できます。

POS デュアル ディスプレイ ビューの拡張子の新しいカスタム コントロールを追加

  1. Microsoft Visual Studio 2015 を管理者モードで起動します。

  2. ModernPOS ソリューションを …\RetailSDK\POS から開きます。

  3. POS.Extensions プロジェクトで、DualDisplayExtension というフォルダーを作成します。

  4. DualDisplayExtension フォルダーで、CustomControl というフォルダーを作成します。

  5. CustomControl フォルダーで、DualDisplayCustomControl.html という HTML ファイルを作成します。

    HTML ファイルで、買い物カゴの詳細を表示するデータ リスト コントロール、合計金額、顧客名、従業員名、およびサインイン ステータスを表示するテキスト コントロールを追加します。

  6. 次のコードをコピーして、DualDisplayCustomControl.html ファイルに貼り付けます。

    <!DOCTYPE html>
    <html lang="en" xmlns="http://www.w3.org/1999/xhtml">
    <head>
    <meta charset="utf-8" />
    <title></title>
    </head>
    <body>
    <!-- Note: The element ID is different from the ID generated by the POS extensibility framework. This 'template' ID is not used by the POS extensibility framework. -->
    <script id="Microsoft_Pos_Extensibility_Samples_DualDisplay" type="text/html">
    <div class="height100Percent width100Percent">
        <div class="height700 width100Percent no-shrink col marginBottom20">
            <div class="col grow marginBottom48">
                <div id="dualDisplayDataListSample" data-bind="msPosDataList: cartLinesDataList">
                </div>
            </div>
        </div>
        <div class="marginBottom20">
            <h2 class="marginLeft8" data-bind="text: cartTotalAmountLabel">Total Amount:</h2>
            <h2 class="marginLeft8" data-bind="text: cartTotalAmount"></h2>
            <h2 class="marginLeft8" data-bind="text: customerNameLabel">Customer Name:</h2>
            <h2 class="marginLeft8" data-bind="text: customerName"></h2>
            <h2 class="marginLeft8" data-bind="text: customerAccountNumberLabel">Customer Account Number:</h2>
            <h2 class="marginLeft8" data-bind="text: customerAccountNumber"></h2>
            <h2 class="marginLeft8" data-bind="text: isLoggedOn() ? 'logged in' : 'logged out'"></h2>
            <h2 class="marginLeft8" data-bind="text: employeeNameLabel">Employee Name:</h2>
            <h2 class="marginLeft8" data-bind="text: employeeName"></h2>
        </div>
    </div>
    </script>
    </body>
    </html>
    

    次に、フィールド名をローカライズするために使用されるリソース ファイルを追加します。

  7. DualDisplayExtension フォルダーで、Resources というフォルダーを作成します。

  8. Resources フォルダーで、Strings というフォルダーを作成します。

  9. Strings フォルダーで、en-US というフォルダーを作成します。

  10. en-US フォルダで新しいリソース ファイルを追加し、ファイル拡張子を resources.resjson に変更し、および resources.resjson ファイルの中身をコピーし以下のリソース文字列に貼り付けます。

    //======================================================================================================
    //======================================= Sample comment. ==============================================
    //======================================================================================================
    
    {
        //======================== extensions strings. ========================
    
        "string_0" : "ID",
        "_string_0.comment" : "Item ID label text",
    
        "string_1" : "Name",
        "_string_1.comment" : "Item name label text",
    
        "string_2" : "Quantity",
        "_string_2.comment" : "Item cost label text",
    
        "string_3" : "Discount",
        "_string_3.comment" : "Total amount label text",
    
        "string_4" : "Cost",
        "_string_4.comment" : "Item cost label text",
    
        "string_5" : "Total Amount:",
        "_string_5.comment" : "Total amount label text",
    
        "string_6" : "Customer Name:",
        "_string_6.comment" : "Customer name label text",
    
        "string_7" : "Customer Account Number:",
        "_string_7.comment" : "Customer account number label text",
    
        "string_8" : "Employee Name:",
        "_string_8.comment" : "Employee name label text"
    }
    
  11. CustomControl フォルダーで、DualDisplayCustomControl.html という TypeScript ファイル (.ts file) を作成します。

  12. 次のコードをコピーして、DualDisplayCustomControl.ts ファイルに貼り付けます。 このコードは関連するエンティティおよびコンテキストをインポートします。

    import {
        DualDisplayCustomControlBase,
        IDualDisplayCustomControlState,
        IDualDisplayCustomControlContext,
        CartChangedData,
        CustomerChangedData,
        LogOnStatusChangedData
    } from "PosApi/Extend/DualDisplay";
    import { DataList, IDataListState, SelectionMode } from "PosUISdk/Controls/DataList";
    import { ObjectExtensions, StringExtensions } from "PosApi/TypeExtensions";
    import { ProxyEntities } from "PosApi/Entities";
    
  13. DualDisplayCustomControl.ts ファイルで、DualDisplayCustomControl というクラスを作成し、DualDisplayCustomControlBase クラスから拡張します。 DualDisplayCustomControlBase クラスから拡張し、デュアル ディスプレイで公開されているすべてのイベントを取得します。

    export default class DualDisplayCustomControl extends DualDisplayCustomControlBase { }
    
  14. DualDisplayCustomControl クラス内で、買い物カゴ、顧客、および従業員の詳細を取得するのに次の変数を追加します。

    private static readonly TEMPLATE_ID: string = "Microsoft_Pos_Extensibility_Samples_DualDisplay";
    
    // The data list to bind against and display with cart line information.
    
    public readonly cartLinesDataList: DataList<ProxyEntities.CartLine>;
    
    // Computed values for binding against.
    
    public readonly cartTotalAmount: Computed<number>;
    public readonly customerName: Computed<string>;
    public readonly customerAccountNumber: Computed<string>;
    public readonly isLoggedOn: Computed<boolean>;
    public readonly employeeName: Computed<string>;
    
    // Labels for binding against.
    
    public readonly cartTotalAmountLabel: string;
    public readonly customerNameLabel: string;
    public readonly customerAccountNumberLabel: string;
    public readonly employeeNameLabel: string;
    
    // Observable values used for keeping track of state.
    
    private readonly_cart: Observable<ProxyEntities.Cart>;
    private readonly_cartLinesObservable: ObservableArray<ProxyEntities.CartLine>;
    private readonly_customer: Observable<ProxyEntities.Customer>;
    private readonly_loggedOn: Observable<boolean>;
    private readonly_employee: Observable<ProxyEntities.Employee>; private_selectedTenderLines: ProxyEntities.TenderLine[];
    
  15. クラスのコンストラクター メソッドを作成して、すべての変数を初期化します。

    constructor(id: string, context: IDualDisplayCustomControlContext) {
        super(id, context);
    
        // Initializes labels.
    
        this.cartTotalAmountLabel = this.context.resources.getString("string_5");
        this.customerNameLabel = this.context.resources.getString("string_6");
        this.customerAccountNumberLabel = this.context.resources.getString("string_7");
        this.employeeNameLabel = this.context.resources.getString("string_8");
    
        // Initializes observable and computed values.
    
        this._cart = ko.observable(null);
        this._cartLinesObservable = ko.observableArray([]);
        this._customer = ko.observable(null);
        this._loggedOn = ko.observable(false);
        this._employee = ko.observable(null);
        this.cartTotalAmount = ko.computed(() => {
            return ObjectExtensions.isNullOrUndefined(this._cart()) ? 0.00 : this._cart().TotalAmount;
        });
        this.customerName = ko.computed(() => {
            return ObjectExtensions.isNullOrUndefined(this._customer()) ? StringExtensions.EMPTY : this._customer().Name;
        });
        this.customerAccountNumber = ko.computed(() => {
            return ObjectExtensions.isNullOrUndefined(this._customer()) ? StringExtensions.EMPTY : this._customer().AccountNumber;
        });
        this.isLoggedOn = ko.computed(() => {
            return this._loggedOn();
        });
        this.employeeName = ko.computed(() => {
            return ObjectExtensions.isNullOrUndefined(this._employee()) ? StringExtensions.EMPTY : this._employee().Name;
        });
        this.cartChangedHandler = (data: CartChangedData) => {
            this._cart(data.cart);
            this._cartLinesObservable(ObjectExtensions.isNullOrUndefined(data.cart) ?[] : data.cart.CartLines)
        };
        this.customerChangedHandler = (data: CustomerChangedData) => {
            this._customer(data.customer);
        };
        this.logOnStatusChangedHandler = (data: LogOnStatusChangedData) => {
    
            // Displays the busy indicator here, even though it's not necessary, in order to showcase and test the scenario.
    
            this.isProcessing = true;
            window.setTimeout(() => {
                this.isProcessing = false;
            }, 1000);
            this._loggedOn(data.loggedOn);
            this._employee(data.employee);
        }
    
        // Initializes the cart lines data list
    
        let cartLinesDataListOptions: IDataListState<ProxyEntities.CartLine> = {
            selectionMode: SelectionMode.None,
            itemDataSource: this._cartLinesObservable,
            columns:[
                {
                    title: context.resources.getString("string_0"), // ID
                    ratio: 20,
                    collapseOrder: 2,
                    minWidth: 50,
                    computeValue: (cartLine: ProxyEntities.CartLine): string => {
                        return ObjectExtensions.isNullOrUndefined(cartLine.ItemId) ? StringExtensions.EMPTY : cartLine.ItemId;
                    }
                },
                {
                    title: context.resources.getString("string_1"), // Name
                    ratio: 50,
                    collapseOrder: 4,
                    minWidth: 100,
                    computeValue: (cartLine: ProxyEntities.CartLine): string => {
                        return ObjectExtensions.isNullOrUndefined(cartLine.Description) ? StringExtensions.EMPTY : cartLine.Description;
                    }
                },
                {
                    title: context.resources.getString("string_2"), // Quantity
                    ratio: 10,
                    collapseOrder: 3,
                    minWidth: 50,
                    computeValue: (cartLine: ProxyEntities.CartLine): string => {
                        return ObjectExtensions.isNullOrUndefined(cartLine.Quantity) ? StringExtensions.EMPTY : cartLine.Quantity.toString();
                    }
                },
                {
                    title: context.resources.getString("string_3"), // Discount
                    ratio: 10,
                    collapseOrder: 1,
                    minWidth: 50,
                    computeValue: (cartLine: ProxyEntities.CartLine): string => {
                        return ObjectExtensions.isNullOrUndefined(cartLine.DiscountAmount) ? StringExtensions.EMPTY : cartLine.DiscountAmount.toString();
                    }
                },
                {
                    title: context.resources.getString("string_4"), // Cost
                    ratio: 10,
                    collapseOrder: 5,
                    minWidth: 50,
                    computeValue: (cartLine: ProxyEntities.CartLine): string => {
                        return ObjectExtensions.isNullOrUndefined(cartLine.TotalAmount) ? StringExtensions.EMPTY : cartLine.TotalAmount.toString();
                    }
                }
            ]
        };
        this.cartLinesDataList = new DataList(cartLinesDataListOptions);
    
        // Logs the completion of constructing the DualDisplayCustomControl.
    
        this.context.logger.logInformational("DualDisplayCustomControl constructed", this.context.logger.getNewCorrelationId());
    }
    
  16. onReady メソッドを追加して、コントロールを指定した HTML 要素にバインドします。

    /**
    * Binds the control to the specified element.
    * @param {HTMLElement} element The element to which the control should be bound.
    */
    
    public onReady(element: HTMLElement): void {
        ko.applyBindingsToNode(element, {
            template: {
                name: DualDisplayCustomControl.TEMPLATE_ID,
                data: this
            }
        });
    }
    
  17. すべてのコントロールを初期化する init メソッドを追加します。

    /**
    * Initializes the control.
    * @param {IDualDisplayCustomControlState} state The initial state of the page used to initialize the control.
    */
    
    public init(state: IDualDisplayCustomControlState): void {
        this._cart(state.cart);
        this._customer(state.customer);
        this._loggedOn(state.loggedOn);
        this._employee(state.employee)
    }
    

    全体のクラスがどのように見えるかを次に示します。

    import {
        DualDisplayCustomControlBase,
        IDualDisplayCustomControlState,
        IDualDisplayCustomControlContext,
        CartChangedData,
        CustomerChangedData,
        LogOnStatusChangedData
    } from "PosApi/Extend/DualDisplay";
    import { DataList, IDataListState, SelectionMode } from "PosUISdk/Controls/DataList";
    import { ObjectExtensions, StringExtensions } from "PosApi/TypeExtensions";
    import { ProxyEntities } from "PosApi/Entities";
    export default class DualDisplayCustomControl extends DualDisplayCustomControlBase {
        private static readonly TEMPLATE_ID: string = "Microsoft_Pos_Extensibility_Samples_DualDisplay";
    
        // The data list to bind against and display with cart line information.
    
        public readonly cartLinesDataList: DataList<ProxyEntities.CartLine>;
    
        // Computed values for binding against.
    
        public readonly cartTotalAmount: Computed<number>;
        public readonly customerName: Computed<string>;
        public readonly customerAccountNumber: Computed<string>;
        public readonly isLoggedOn: Computed<boolean>;
        public readonly employeeName: Computed<string>;
    
        // Labels for binding against.
    
        public readonly cartTotalAmountLabel: string;
        public readonly customerNameLabel: string;
        public readonly customerAccountNumberLabel: string;
        public readonly employeeNameLabel: string;
    
        // Observable values used for keeping track of state.
    
        private readonly_cart: Observable<ProxyEntities.Cart>;
        private readonly_cartLinesObservable: ObservableArray<ProxyEntities.CartLine>;
        private readonly_customer: Observable<ProxyEntities.Customer>;
        private readonly_loggedOn: Observable<boolean>;
        private readonly_employee: Observable<ProxyEntities.Employee>;
        constructor(id: string, context: IDualDisplayCustomControlContext) {
            super(id, context);
    
            // Initializes labels.
    
            this.cartTotalAmountLabel = this.context.resources.getString("string_5");
            this.customerNameLabel = this.context.resources.getString("string_6");
            this.customerAccountNumberLabel = this.context.resources.getString("string_7");
            this.employeeNameLabel = this.context.resources.getString("string_8");
    
            // Initializes observable and computed values.
    
            this._cart = ko.observable(null);
            this._cartLinesObservable = ko.observableArray([]);
            this._customer = ko.observable(null);
            this._loggedOn = ko.observable(false);
            this._employee = ko.observable(null);
            this.cartTotalAmount = ko.computed(() => {
                return ObjectExtensions.isNullOrUndefined(this._cart()) ? 0.00 : this._cart().TotalAmount;
            });
            this.customerName = ko.computed(() => {
                return ObjectExtensions.isNullOrUndefined(this._customer()) ? StringExtensions.EMPTY : this._customer().Name;
            });
            this.customerAccountNumber = ko.computed(() => {
                return ObjectExtensions.isNullOrUndefined(this._customer()) ? StringExtensions.EMPTY : this._customer().AccountNumber;
            });
            this.isLoggedOn = ko.computed(() => {
                return this._loggedOn();
            });
            this.employeeName = ko.computed(() => {
                return ObjectExtensions.isNullOrUndefined(this._employee()) ? StringExtensions.EMPTY : this._employee().Name;
            });
            this.cartChangedHandler = (data: CartChangedData) => {
                this._cart(data.cart);
                this._cartLinesObservable(ObjectExtensions.isNullOrUndefined(data.cart) ?[] : data.cart.CartLines)
            };
            this.customerChangedHandler = (data: CustomerChangedData) => {
                this._customer(data.customer);
            };
            this.logOnStatusChangedHandler = (data: LogOnStatusChangedData) => {
    
                // Displays the busy indicator here, even though it's not necessary, in order to showcase and test the scenario.
    
                this.isProcessing = true;
                window.setTimeout(() => {
                    this.isProcessing = false;
                }, 1000);
                this._loggedOn(data.loggedOn);
                this._employee(data.employee);
            }
    
            // Initializes the cart lines data list
    
            let cartLinesDataListOptions: IDataListState<ProxyEntities.CartLine> = {
                selectionMode: SelectionMode.None,
                itemDataSource: this._cartLinesObservable,
                columns:[
                    {
                        title: context.resources.getString("string_0"), // ID
                        ratio: 20,
                        collapseOrder: 2,
                        minWidth: 50,
                        computeValue: (cartLine: ProxyEntities.CartLine): string => {
                            return ObjectExtensions.isNullOrUndefined(cartLine.ItemId) ? StringExtensions.EMPTY : cartLine.ItemId;
                        }
                    },
                    {
                        title: context.resources.getString("string_1"), // Name
                        ratio: 50,
                        collapseOrder: 4,
                        minWidth: 100,
                        computeValue: (cartLine: ProxyEntities.CartLine): string => {
                            return ObjectExtensions.isNullOrUndefined(cartLine.Description) ? StringExtensions.EMPTY : cartLine.Description;
                        }
                    },
                    {
                        title: context.resources.getString("string_2"), // Quantity
                        ratio: 10,
                        collapseOrder: 3,
                        minWidth: 50,
                        computeValue: (cartLine: ProxyEntities.CartLine): string => {
                            return ObjectExtensions.isNullOrUndefined(cartLine.Quantity) ? StringExtensions.EMPTY : cartLine.Quantity.toString();
                        }
                    },
                    {
                        title: context.resources.getString("string_3"), // Discount
                        ratio: 10,
                        collapseOrder: 1,
                        minWidth: 50,
                        computeValue: (cartLine: ProxyEntities.CartLine): string => {
                            return ObjectExtensions.isNullOrUndefined(cartLine.DiscountAmount) ? StringExtensions.EMPTY : cartLine.DiscountAmount.toString();
                        }
                    },
                    {
                        title: context.resources.getString("string_4"), // Cost
                        ratio: 10,
                        collapseOrder: 5,
                        minWidth: 50,
                        computeValue: (cartLine: ProxyEntities.CartLine): string => {
                            return ObjectExtensions.isNullOrUndefined(cartLine.TotalAmount) ? StringExtensions.EMPTY : cartLine.TotalAmount.toString();
                        }
                    }
                ]
            };
            this.cartLinesDataList = new DataList(cartLinesDataListOptions);
    
            // Logs the completion of constructing the DualDisplayCustomControl.
    
            this.context.logger.logInformational("DualDisplayCustomControl constructed", this.context.logger.getNewCorrelationId());
        }
    
        /**
        * Binds the control to the specified element.
        * @param {HTMLElement} element The element to which the control should be bound.
        */
    
        public onReady(element: HTMLElement): void {
            ko.applyBindingsToNode(element, {
                template: {
                    name: DualDisplayCustomControl.TEMPLATE_ID,
                    data: this
                }
            });
        }
    
        /**
        * Initializes the control.
        * @param {IDualDisplayCustomControlState} state The initial state of the page used to initialize the control.
        */
    
        public init(state: IDualDisplayCustomControlState): void {
            this._cart(state.cart);
            this._customer(state.customer);
            this._loggedOn(state.loggedOn);
            this._employee(state.employee)
        }
    }
    
  18. DualDisplayExtension フォルダーで、manifest.json という名前の JavaScript Object Notation (JSON) ファイル (.json ファイル) を作成します。

  19. 次のコードをコピーして、manifest.json ファイルに貼り付けます。 このコードをコピーする前に、既定で生成されたコードを削除してください。

    {
        "$schema": "../manifestSchema.json",
        "name": "Pos_Extensibility_DualDisplaySample",
        "publisher": "Microsoft",
        "version": "7.2.0",
        "minimumPosVersion": "7.2.0.0",
        "components": {
            "resources": {
                "supportedUICultures": [ "en-US" ],
                "fallbackUICulture": "en-US",
                "culturesDirectoryPath": "Resources/Strings",
                "stringResourcesFileName": "resources.resjson"
            },
            "dualDisplay": {
                "customControl": {
                    "controlName": "DualDisplayCustomControl",
                    "htmlPath": "CustomControl/DualDisplayCustomControl.html",
                    "modulePath": "CustomControl/DualDisplayCustomControl"
                }
            }
        }
    }
    
  20. extensions.jsonファイルを POS.Extensions プロジェクトで開いて、DualDisplayExtension サンプルで更新します。 このようにして、実行時に POS にこの拡張が含まれます。

    {
        "extensionPackages": [
            {
                "baseUrl": "SampleExtensions"
            },
            {
                "baseUrl": " SampleExtensions2"
            },
            {
                "baseUrl": " DualDisplayExtension"
            }
        ]
    }
    
  21. tsconfig.json ファイルを開いて、拡張パッケージ フォルダーを除外リストでコメント アウトします。 POS では、このファイルを使用して、拡張機能を追加または除外します。 既定では、リストにすべての除外された拡張子が含まれています。 拡張機能を POS の一部として含めるには、次に示すように、拡張フォルダーの名前を追加し、拡張リストの拡張をコメント アウトします。

    "exclude": [
        "AuditEventExtensionSample",
        "B2BSample",
        "CustomerSearchWithAttributesSample",
        "FiscalRegisterSample",
        "PaymentSample",
        "PromotionsSample",
        "SalesTransactionSignatureSample",
        //"SampleExtensions2",
        "SampleExtensions",
        "StoreHoursSample",
        "SuspendTransactionReceiptSample"
        //"SampleExtensions",
        //"DualDisplayExtension"
    ],
    
  22. プロジェクトをコンパイル、およびリビルドします。

カスタマイズの検証

  1. オペレーター ID として 000160、パスワードとして 123 を使用し、Store Commerce アプリにサインインします。
  2. ようこそ画面で、現在のトランザクションボタンを選択します。
  3. 品目をトランザクションに追加します。 たとえば、品目番号 0005 を追加します。
  4. 顧客をトランザクションへ追加します。 たとえば、Karen Berg を追加します。
  5. デュアル ディスプレイには、買い物カゴ、合計、従業員、および顧客の詳細が表示される必要があります。