この記事では、Microsoft Dynamics 365 Commerce 本社、POS (POS)、コマース ランタイム (CRT) で注文の属性値を直接編集および設定する方法について説明します。
以前は、属性フレームワークは、オンライン注文でのみ属性をサポートしていました。 ただし、このフレームワークでは、現金持ち込みトランザクション、顧客注文、コール センター注文の属性がサポートされるようになりました。 この拡張機能を使用すると、コマース本社、POS、および CRT で注文の属性値を直接編集および設定できます。
本社には、属性値を編集したり更新したりするためのページが含まれます。これは、headquarters でコール センターの注文の値を設定できることを意味します。 POS では、属性パネルを使用して、POS で属性の値を設定または更新できます。 ユーザー インターフェイスを必要とせず、ビジネス ロジックを追加するだけの場合、ビジネス ロジックを CRT に直接追加できます。 headquarters コンフィギュレーションを使用することにより、新しい属性を作成することができます。 データベースの変更は必要ありません。 以前は、headquarters とチャネル データベースで新しいテーブルを作成するには、それらのテーブルを変更する必要がありました。
注文属性の追加が必要な理由および場合
現金売りトランザクション、顧客注文、またはコールセンターの注文に新しいフィールドを追加し、POS または headquarters で情報をキャプチャする場合は、注文属性を使用します。 以前は、現金売りトランザクション (トランザクション ヘッダーまたは行) または POS の顧客注文に新しいフィールドを追加するには、headquarters とチャネル データベースで新しい拡張テーブルを作成し、CRT および POS コードにインライン変更を加えて各種画面および操作を処理する必要がありました。 また、チャネル データベースと headquarters 間でデータを同期するように Commerce Data Exchange を構成する必要がありました。 ただし、注文属性はコンフィギュレーションを通してこれらすべてのアクションを完了できます。 コードを記述またはカスタム拡張機能テーブルを作成する必要はありませんが、主要なビジネス ロジックおよび POS UI を作成する必要があります。
この最初のバージョンは String 属性タイプのみをサポートしますが、将来のバージョンは他の属性タイプをサポートします。 データをマスター テーブルから取得し、そのデータに X++ の複雑な検索ロジックとコア ビジネス ロジックが含まれている場合は、拡張プロパティを使用します。
メモ
属性は、顧客注文と現金持ち込みトランザクションでのみサポートされます。 他のトランザクションの種類では属性はサポートされません。
属性タイプを定義
属性の種類を定義し、それらに有効な範囲を割り当てるには、次の手順に従います。
- コマース本社で、製品情報管理>セットアップ>カテゴリーと属性>属性の種類に移動します。
- [ 属性の種類] で、[ 新規 ] を選択して新しい属性の種類を追加します。
- 属性タイプ名を入力します。
- [ 全般 ] クイック タブの [ 種類 ] フィールドで、このデータ型に割り当てられている属性に対してユーザーが入力できるデータの種類を選択します。
- 属性タイプが少数点または整数の場合、測定単位を選択します。
- 属性の種類が Text の場合は、その値の固定リストを定義します。 固定リスト チェック ボックスをオンにし、値クイック タブで、値の一覧を入力します。
- 属性タイプに有効な値の範囲を定義するには、値の範囲チェック ボックスをオンにします。 次に、範囲クイックタブで、値の有効範囲を入力します。
属性の定義
属性を定義するには、定義する属性ごとに次の手順に従います。
- 本社で、製品情報管理>セットアップ>カテゴリと属性>属性に移動します。
- [ 属性] で、[ 新規 ] を選択して新しい属性を追加します。
- 属性の名前、フレンドリ名、および説明を入力します。 さらに、属性のユーザーに表示するヘルプ テキストを入力します。
- 属性タイプ フィールドで、属性に割り当てる属性タイプを選択します。
- 属性の種類に応じて、[ 既定値 ] フィールドに、属性がチャネルに割り当てられるときに既定で表示する値の値または範囲を入力します。
- 翻訳 を選択します。 次に、[ テキスト翻訳 ] ページで、属性の名前、説明、フレンドリ名、ヘルプ テキストを他の言語で入力します。
属性グループの定義
属性グループを定義するには、次の手順に従います。
- 本社で、製品情報管理>セットアップ>カテゴリと属性>属性グループに移動します。
- [ 属性グループ] で、[ 新規 ] を選択して新しい属性グループを追加します。
- 属性グループ名を入力します。 その後、全般クイックタブで、属性グループのフレンドリ名、説明、およびヘルプ テキストを入力します。
- 属性クイック タブで、追加を選択して、属性グループに属性を追加します。 [ 既定値 ] フィールドに、選択した属性の既定値を入力します。
- 翻訳 を選択します。 次に、[ テキスト翻訳 ] ページで、属性グループの説明、フレンドリ名、ヘルプ テキストを他の言語で入力します。
属性グループのチャネルへのリンク
属性グループをチャネルにリンクするには、次の手順に従います。
本社で、 Retail and Commerce>Channels>Stores>All stores に移動します。
チャネルページ上の属性をリンクしたいチャネルを選択します。
設定タブで、属性グループの販売注文属性を選択します。
販売注文属性グループで、[新規] を選択して、属性グループをチャネルにリンクします。
名前フィールドで属性グループを選択してチャネルにリンクします。
属性の適用先フィールドで、次のいずれかのオプションを選択します。
- ヘッダー – 属性はトランザクション ヘッダーにのみ適用されます。
- 行 – 属性はトランザクション行にのみ適用されます。
- 既定値 – 属性は、トランザクション ヘッダーとトランザクション行の両方に適用されます。
保存 を選択します。
配送ジョブを実行
ディストリビューション ジョブを実行するには、次の手順に従います。
- 本社で、小売とコマース>小売とコマースのIT>配送スケジュールに移動します。
- 製品 (1040) を選択し、アクション ペインで 今すぐ実行 を選択します。 メッセージが表示されたら、[はい]選択します。 このステップは、新しい属性、属性タイプ、または属性グループを追加した場合にのみ必要です。
- チャネル コンフィギュレーション ジョブ (1070) を選択し、アクション ペインで 今すぐ実行 を選択します。 メッセージが表示されたら、[はい]選択します。
属性コントロールを使用して POS トランザクション画面に注文属性を表示します (この機能はバージョン 8.1.3 以降で使用できます)。
本社
- 本社で、小売およびコマース > チャネルのセットアップ > POS セットアップ > POS > 画面レイアウトに移動します。
- 画面レイアウト ページで、[ 新規 ] を選択して新しい画面レイアウトを作成するか、既存の画面レイアウトを選択します。
- スクリーン レイアウトの名前と ID を入力します。
- レイアウト サイズ クイック タブで、追加ボタン選択して、POS の新しいレイアウト サイズを追加します。
- 名前フィールドで POS 画面の解像度を選択します。
- [ レイアウト サイズ ] クイック タブで、[ レイアウト デザイナー ] ボタンを選択します。
- プロンプトが表示されたら、はいを選択して、インストール/実行ボタンを使用してデザイナー ホストをダウンロードおよびインストールします。
- メッセージが表示されたら、Microsoft Dynamics 365 のユーザー名とパスワードを入力してデザイナーを起動します。
- デザイナーが起動したら、画面レイアウト デザイナーの任意の場所に [属性] パネルをドラッグし、画面の幅に応じてサイズを調整します。
- 完了したら、[ OK] を 選択して変更を保存します。
- 右上隅にある [ 閉じる ] ボタン (X) を選択して、画面レイアウト デザイナーを閉じます。 メッセージが表示されたら、[ はい ] を選択して変更を保存します。
- 小売とコマース > 小売とコマース IT > 配送スケジュールに移動します。
- レジスター ジョブ (1090) を選択し、アクション ペインで 今すぐ実行 を選択します。 メッセージが表示されたら、[はい] を選択します。
POS(販売時点情報管理)
- POS を起動し、任意の品目をトランザクションに追加します。 ヘッダーと明細行の両方のコンフィギュレーション済の属性と共に、トランザクション画面に属性パネルが表示されます。
- 属性パネルで [編集] アイコンを選択して属性値を更新します。
- 属性パネルでヘッダーまたは行タブを選択して、ヘッダーまたは行属性を表示します。
- lines 属性は、トランザクションで選択された行に基づいて自動的に更新されます。
コール センターの注文の属性値を設定
チャンネルの注文属性を構成した後、顧客サービスまたはすべての販売注文、およびコール センターの新しい注文を作成します。
- 顧客注文の作成後または作成中に、トランザクション ヘッダーの属性値を設定する場合は、アクション ウィンドウで、コマース タブに移動し、小売属性を選択します。
- [ 販売注文の属性値 ] ページで、属性の値を設定します。 このページの属性のリストは、チャネル用にコンフィギュレーションした属性グループに基づいています。
- 行レベルで属性値を設定するには、販売注文ページで 行ビューを選択し、属性値を設定する行を選択します。 販売注文明細行グループで、小売および商取引>属性を選択します。
- 値を設定するすべての販売明細行に対して手順 3 を繰り返します。
本社で現金取引の属性値を表示
配布ジョブを実行し、現金持ち込みトランザクションを本社にプルすると、そのトランザクションの属性値を表示できます。 POS は、注文の属性を表示するための UI を提供しません。 したがって、注文の属性値を表示するには、POS を拡張する必要があります。
- 小売>照会およびレポート>店舗のトランザクションの順に移動します。
- トランザクション ヘッダー属性を表示するには、アクション ウィンドウで属性を選択します。
- トランザクション ヘッダー行の属性を表示するには、アクション ウィンドウで トランザクション>販売トランザクション を選択します。
- 販売トランザクションページで、すべての明細行を選択し、アクション ウィンドウで、属性を選択して、明細行の属性を表示します。
メモ
属性グループの一部として構成し、チャネルへのリンクとして構成した属性のみが、本社 UI に表示されます。
CRT でビジネス ロジックを追加するには、属性を拡張します。
Retail SDK にサンプルが追加され、CRT の注文属性のビジネス ロジックが追加され、ビジネス ロジックのコードのみが含まれます。 属性に対する読み取りおよび書き込み処理は自動化されているため、属性を保存したり読み取ったりする方法は示しません。
このサンプルでは、次のシナリオを実装しています。カートを中断するには、属性値を設定します。 カートを再開すると、その値がクリアされます。 このサンプルでは、 SuspendCartRequest のプリトリガーを追加し、ビジネス ロジックを書き込みます。 CRT で任意のトリガーを拡張または任意の要求をオーバーライドして、シナリオに基づきロジックをセットすることができます。
メモ
カートに属性を追加する前に、属性がカートまたはカートラインに既に存在するかどうかを確認します。 属性が既に存在する場合は、もう一度追加するのではなく、属性を更新します。 カートまたはカートの行に重複する属性を追加すると、CRT にランタイム エラーが表示されます。 このシナリオのサンプル コードは、次のサンプル コード セクションにあります。
完全なサンプル コードは Retail SDK\SampleExtensions\CommerceRuntime\Extensions.TransactionAttributesSample の Retail SDK で見つけることができます。
- 新しい C# ポータブル クラス ライブラリ プロジェクトを作成し、次のコードを貼り付けます。
public class CustomSuspendCartTrigger : IRequestTrigger
{
// summary
// Gets the list of supported request types.
public IEnumerable<Type> SupportedRequestTypes
{
get
{
return new [ ] { typeof(SuspendCartRequest)};
}
}
// Pre-trigger code.
// summary
// param name="request" The request param
public void OnExecuting(Request request)
{
ThrowIf.Null(request, "request");
Type requestedType = request.GetType();
if (requestedType == typeof(SuspendCartRequest))
{
SuspendCartRequest suspendCartRequest = request as SuspendCartRequest;
// Get the cart.
var getCartServiceRequest = new GetCartServiceRequest(
new CartSearchCriteria(suspendCartRequest.CartId), QueryResultSettings.SingleRecord);
Cart cart = request.RequestContext.Execute<GetCartServiceResponse>(getCartServiceRequest).Carts.Single();
// Update the transaction header attribute for customer order.
if (cart.CartType == CartType.CustomerOrder)
{
bool cartUpdated = CustomCartHelper.CreateUpdateTransactionHeaderAttribute(
cart, reserveNow: false, updateAttribute: true);
if (cartUpdated)
{
// Save the cart after updating the header attribute.
var saveCartRequest = new SaveCartRequest(cart);
request.RequestContext.Execute<SaveCartResponse>(saveCartRequest);
}
}
}
}
// Post-trigger code.
// summary
// param name="request" The request param
// param name="response" The response param
public void OnExecuted(Request request, Response response)
{
}
// Sample code to check for the duplicate attribute, before adding attributes to the cart check whether the attribute already exists if so then don’t add the attribute again, instead update it.
public static class CustomCartHelper
{
/// <summary>
/// Updates the transaction header attribute.
/// </summary>
/// <param name="cart">The cart.</param>
/// <param name="reserveNow">The value of the transaction header attribute.</param>
/// <param name="updateAttribute">A flag indicating whether or not to override an existing attribute value.</param>
/// <returns>A flag indication whether or not the cart was updated.</returns>
public static bool CreateUpdateTransactionHeaderAttribute(Cart cart, bool reserveNow, bool updateAttribute)
{
ThrowIf.Null(cart, "cart");
bool cartUpdated = false;
IList<AttributeValueBase> transactionAttributes = cart.AttributeValues;
string reserveNowAttributeName = "Reserve now";
string reserveNowAttributeValue = reserveNow ? "Yes" : "No";
AttributeValueBase reserveNowAttribute = transactionAttributes.SingleOrDefault(attribute => attribute.Name.Equals(reserveNowAttributeName));
if (reserveNowAttribute == null)
{
transactionAttributes.Add(new AttributeTextValue() { Name = reserveNowAttributeName, TextValue = reserveNowAttributeValue });
cartUpdated = true;
}
else if (updateAttribute && !((AttributeTextValue)reserveNowAttribute).TextValue.Equals(reserveNowAttributeValue))
{
((AttributeTextValue)reserveNowAttribute).TextValue = reserveNowAttributeValue;
cartUpdated = true;
}
return cartUpdated;
}
}
Dynamics 365 Commerce eコマースサイトを拡張して、カート内の注文属性の値を設定し
このコードを使用して、カート内の注文属性の値を設定します。
public _addOrUpdateSalesOrderAttributes = (cart: Cart): void => {
// Create the array of attribute and add attributes
const attributeArr: AttributeValueBase[] = [];
let attributeObj = {
// @ts-ignore
'@odata.type': '#Microsoft.Dynamics.Commerce.Runtime.DataModel.AttributeTextValue',
Name: 'Brand',
ExtensionProperties: [],
TextValue: 'OscarBrand-2',
TextValueTranslations: []
};
attributeObj.Name = 'Brand';
attributeArr.push(attributeObj);
attributeObj = {
// @ts-ignore
'@odata.type': '#Microsoft.Dynamics.Commerce.Runtime.DataModel.AttributeTextValue',
Name: 'Connector',
ExtensionProperties: [],
TextValue: 'OscarConnector-2',
TextValueTranslations: []
};
attributeObj.Name = 'Connector';
attributeArr.push(attributeObj);
cart.AttributeValues = attributeArr;
updateAsync({ callerContext: this.props.context.actionContext}, cart)
.then(newCart => {
console.log('Success');
this.props.context.actionContext.update(new GetCheckoutCartInput(this.props.context.request.apiSettings), newCart);
})
.catch(error => {
console.log(error);
});
};
POS でビジネス ロジックを行うには、属性を拡張します。
メモ
次の変更は、バージョン 8.1.2 以前のアプリケーションを実行している場合にのみ必要です。 8.1.3 以降は、属性パネルを使用して、POS で属性の値を設定または更新できます。 このコントロールを使用すると、追加のコードを記述したり、POS で属性値を設定するための UI を作成したりする必要がなくなりました。 属性コントロール UI が追加され、属性値を設定または更新します。 詳細については、このドキュメント の「属性制御」セクションを使用して POS トランザクション画面で注文属性を表示 するを参照してください。
Retail SDK に追加された新しいサンプルでは、POS の注文属性のビジネス ロジックが設定されます。 この例には、ビジネス ロジックのコードのみが含まれています。 属性に対する読み取りおよび書き込み処理は自動化されているため、属性の値を保存したり読み取ったりする方法は示しません。 属性の値は、シナリオに基づき、CRT または POS のいずれかで設定することができます。 値が顧客の入力に基づいている場合、POS クライアントで設定します。 一部のビジネス ロジックが含まれている場合は、CRT で値を設定します。
このサンプルでは、次のシナリオを実装しています。企業間 (B2B) 注文を作成し、 顧客からのフィードバックに基づいて B2B 属性の値を設定する場合 (Yes または No)。 PreEndTransactionTrigger は値を設定するために POS で拡張されました。 必要に応じて任意の POS トリガーを拡張、または要求をオーバーライドすることができます。
完全なサンプル コードは Retail SDK\POS\Extensions\B2BSample の Retail SDK で見つけることができます。
メモ
コードで属性と属性値を設定または追加した場合でも、構成された属性のみが本社 UI に表示されます。 チャネルにリンクした属性グループの一部ではない属性は、本社 UI には表示されません。
Retail SDK から ModernPOS.sln\CloudPos.sln を開きます。
Retail SDK で、POS.Extension プロジェクトに新しい拡張フォルダーを作成します。
新しい拡張フォルダーに、新しい manifest.json ファイルを追加します。
次のコードに貼り付けます。
{ "$schema": "..manifestSchema.json", "name": "Pos_Extensibility_B2BSample", "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" }, "extend": { "triggers": [ { "triggerType": "PreEndTransaction", "modulePath": "TriggerHandlers/PreEndTransactionTrigger" } ] } } }新しい TypeScript ファイルを作成して PreEndTransactionTrigger を実装し、次のコードを追加します。
/** * Executes the trigger functionality. * @param {Triggers.IPreEndTransactionTriggerOptions} options The options provided to the trigger. */ public execute(options: Triggers.IPreEndTransactionTriggerOptions): Promise<ClientEntities.ICancelable { console.log("Executing PreEndTransactionTrigger with options " + JSON.stringify(options) + "."); let currentCart: ProxyEntities.Cart; return this.context.runtime.executeAsync<GetCurrentCartClientResponse>(new GetCurrentCartClientRequest()) then((getCurrentCartClientResponse: ClientEntities.ICancelableDataResult<GetCurrentCartClientResponse>): Promise<ClientEntities.ICancelableDataResult<GetCustomerClientResponse>> => { currentCart = getCurrentCartClientResponse.data.result; // Gets the current customer. let result: Promise<ClientEntities.ICancelableDataResult<GetCustomerClientResponse>>; if (!ObjectExtensions.isNullOrUndefined(currentCart) && !ObjectExtensions.isNullOrUndefined(currentCart.CustomerId)) { let getCurrentCustomerClientRequest: GetCustomerClientRequest<GetCustomerClientResponse = new GetCustomerClientRequest(currentCart.CustomerId); result = this.context.runtime.executeAsync<GetCustomerClientResponse>(getCurrentCustomerClientRequest); } else { result = Promise.resolve({ canceled: false, data: new GetCustomerClientResponse(null) }); } return result; }) .then((getCurrentCustomerClientResponse: ClientEntities.ICancelableDataResult<GetCustomerClientResponse>): Promise<ClientEntities.ICancelableDataResult<ShowMessageDialogClientResponse>> => { let currentCustomer: ProxyEntities.Customer = getCurrentCustomerClientResponse.data.result; // If the cart is a customer order with a B2B customer, then we display a dialog to determine if the order should be B2B. let result: Promise<ClientEntities.ICancelableDataResult<ShowMessageDialogClientResponse>>; if (!ObjectExtensions.isNullOrUndefined(currentCart) && currentCart.CustomerOrderModeValue === ProxyEntities.CustomerOrderMode.CustomerOrderCreateOrEdit && !ObjectExtensions.isNullOrUndefined(currentCustomer) && this.isCustomerB2B(currentCustomer)) { let yesButton: ClientEntities.Dialogs.IDialogResultButton = { id: PreEndTransactionTrigger.DIALOG_YES_BUTTON_ID, label: this.context.resources.getString("string_0"), "Yes" result: PreEndTransactionTrigger.DIALOG_RESULT_YES }; let noButton: ClientEntities.Dialogs.IDialogResultButton = { id: PreEndTransactionTrigger.DIALOG_NO_BUTTON_ID, label: this.context.resources.getString("string_1"), "No" result: PreEndTransactionTrigger.DIALOG_RESULT_NO }; let showMessageDialogClientRequestOptions: ClientEntities.Dialogs.IMessageDialogOptions = { title: this.context.resources.getString("string_2"), "B2B Order" subTitle: StringExtensions.EMPTY, message: this.context.resources.getString("string_3"), "Do you want to mark this order as a B2B order?" button1: yesButton, button2: noButton }; let showMessageDialogClientRequest: ShowMessageDialogClientRequest<ShowMessageDialogClientResponse> = new ShowMessageDialogClientRequest(showMessageDialogClientRequestOptions); result = this.context.runtime.executeAsync<ShowMessageDialogClientResponse>(showMessageDialogClientRequest); } else { result = Promise.resolve({ canceled: false, data: new ShowMessageDialogClientResponse(null) }); } return result; }) .then((showMessageDialogClientResponse: ClientEntities.ICancelableDataResult<ShowMessageDialogClientResponse>): Promise<ClientEntities.ICancelableDataResult<SaveAttributesOnCartClientResponse>> => { // Save the B2B attribute value depending on the dialog result. let messageDialogResult: ClientEntities.Dialogs.IMessageDialogResult = showMessageDialogClientResponse.data.result; let result: Promise<ClientEntities.ICancelableDataResult<SaveAttributesOnCartClientResponse>>; if (!ObjectExtensions.isNullOrUndefined(messageDialogResult)) { let attributeValue: ProxyEntities.AttributeTextValue = new ProxyEntities.AttributeTextValueClass(); attributeValue.Name = PreEndTransactionTrigger.B2B_CART_ATTRIBUTE_NAME; attributeValue.TextValue = messageDialogResult.dialogResult === PreEndTransactionTrigger.DIALOG_RESULT_YES? PreEndTransactionTrigger.B2B_ATTRIBUTE_VALUE_TRUE : PreEndTransactionTrigger.B2B_ATTRIBUTE_VALUE_FALSE; let attributeValues: ProxyEntities.AttributeValueBase[] = [attributeValue]; let saveAttributesOnCartRequest: SaveAttributesOnCartClientRequest<SaveAttributesOnCartClientResponse> = new SaveAttributesOnCartClientRequest(attributeValues); result = this.context.runtime.executeAsync(saveAttributesOnCartRequest); } else { result = Promise.resolve({ canceled: false, data: new SaveAttributesOnCartClientResponse(null) }); } return result; }); } /** * Returns whether or not the given customer is a B2B customer. * @param {ProxyEntities.Customer} customer The customer. * @returns {boolean} Whether or not the given customer is a B2B customer. */ private isCustomerB2B(customer: ProxyEntities.Customer): boolean { let isB2B: boolean = false; if (!ObjectExtensions.isNullOrUndefined(customer.Attributes)) { for (let i: number = 0; i < customer.Attributes.length; i++) { let currentAttribute: ProxyEntities.CustomerAttribute = customer.Attributes[i]; if (currentAttribute.Name === PreEndTransactionTrigger.B2B_CUSTOMER_ATTRIBUTE_NAME) { if (!ObjectExtensions.isNullOrUndefined(currentAttribute.AttributeValue.BooleanValue)) { isB2B = currentAttribute.AttributeValue.BooleanValue; } break; } } } return isB2B; } } } }