Store Commerce app triggers and printing
This article describes how to use triggers to capture events that occur before and after any Microsoft Dynamics 365 Commerce Store Commerce app operations.
You can use triggers to capture events that occur before or after Store Commerce app operations. Using triggers supports several business logic scenarios that enable you to do the following:
- Insert custom logic before the operation runs or after it has completed. This includes operation-specific triggers and generic triggers called the PreOperationTrigger and PostOperationTrigger, which run at the beginning and end of all POS operations.
- Continue or cancel an operation. For example, if your validation fails or returns an error, then you can cancel the operation in pre-trigger. Post-triggers are not cancelable.
- Use the post-trigger for scenarios where you want to show custom messages or insert custom fields after the standard logic is performed.
The following table lists the available triggers and denotes whether they can be canceled.
Application triggers
Trigger | Type | Description |
---|---|---|
ApplicationStartTrigger | Noncancelable | Executed after the POS application is started. You can revert or cancel whatever executed previously. |
ApplicationSuspendTrigger | Noncancelable | Executed after the POS application is suspended. |
PreLogOnTriggerTrigger | Cancelable | Executed before the POS log on. |
PostLogOnTriggerTrigger | Noncancelable | Executed after the POS log on. |
PostLogOffTrigger | Noncancelable | Executed after the POS log off. |
PreLockTerminalTrigger | Cancelable | Executed before the POS register lock. |
PostLockTerminalTrigger | Non-Cancelable | Executed after the POS register lock. |
PreUnlockTerminalTrigger | Cancelable | Executed before the POS register is unlocked. |
PostDeviceActivationTrigger | Non-Cancelable | Executed after the POS activation. |
PreElevateUserTrigger | Cancelable | This trigger is executed before the manager override and only works for non-Microsoft Microsoft Entra user authentication. If Microsoft Entra is enabled, this trigger won't work. |
PreRegisterAuditEventTrigger | Cancelable | Executed before the audit event. |
PostRegisterAuditEventTrigger | Non-Cancelable | Executed after the audit event. |
PreOpenUrlTrigger | Cancelable | Executed before the open URL operation. |
Cash management triggers
Trigger | Type | Description |
---|---|---|
PreTenderDeclarationTrigger | Cancelable | Executed before the POS tender declaration. |
PostTenderDeclarationTrigger | Noncancelable | Executed after the POS tender declaration. |
PreFloatEntryTrigger | Cancelable | Executed before the POS float entry. |
PostFloatEntryTrigger | Noncancelable | Executed after the POS float entry. |
Customer triggers
Trigger | Type | Description | Release |
---|---|---|---|
PreCustomerAddTrigger | Cancelable | Executed before the Create customer form is opened. | |
PostCustomerAddTrigger | Noncancelable | Executed after the customer is created. | |
PreCustomerClearTrigger | Cancelable | Executed before the customer cleared from the cart. | |
PostCustomerClearTrigger | Noncancelable | Executed after the customer cleared from the cart. | |
PreCustomerSetTrigger | Cancelable | Executed before the customer is added to the cart. | |
PreCustomerSearchTrigger | Cancelable | Executed before customer search is performed. | |
PostCustomerSearchTrigger | Noncancelable | Executed after customer search is performed. | |
PostIssueLoyaltyCardTrigger | Noncancelable | Executed after the loyalty card is issued. | |
PreCustomerSaveTrigger | Cancelable | Executed after the user selects the Save button, and before the customer is created or updated. The isNewCustomer value in the option indicates whether the operation is creating a new customer or editing an existing customer. |
|
PostCustomerSaveTrigger | Noncancelable | Executed after the customer is created or updated. | |
PreSaveCustomerAddressTrigger | Cancelable | Executed before the customer address is saved. | |
PreGetLoyaltyCardBalanceTrigger | Cancelable | Executed before getting the loyalty card balance. | |
PostGetLoyaltyCardBalanceTrigger | Noncancelable | Executed after getting the loyalty card balance. | |
PreDisplayLoyaltyCardBalanceTrigger | Cancelable | Executed before displaying the loyalty card balance. | |
PreCustomerEditTrigger | Cancelable | Executed before editing the Customer. | 10.0.19 |
Discount triggers
Trigger | Type | Description |
---|---|---|
PreLineDiscountAmountTrigger | Cancelable | Executed before line discount amount is added to the cart line. |
PostLineDiscountAmountTrigger | Noncancelable | Executed after line discount amount added to the cart line. |
PreLineDiscountPercentTrigger | Cancelable | Executed before line discount percent added to the cart line. |
PostLineDiscountPercentTrigger | Noncancelable | Executed after line discount percent added to the cart line. |
PreTotalDiscountAmountTrigger | Cancelable | Executed before total discount percent added to the cart. |
PostTotalDiscountAmountTrigger | Noncancelable | Executed after total amount percent added to the cart. |
PreTotalDiscountPercentTrigger | Cancelable | Executed before total discount percent added to the cart. |
PostTotalDiscountPercentTrigger | Noncancelable | Executed after total discount percent added to the cart. |
PreAddCouponTrigger | Cancelable | Executed before adding discount coupon to the cart. |
PostAddCouponTrigger | Noncancelable | Executed after adding discount coupon to the cart. |
Operation triggers
Trigger | Type | Description |
---|---|---|
PreOperationTrigger | Cancelable | Generic trigger executed before all POS operations. You can use this trigger if there is no specific trigger available for a POS operation. |
PreOperationValidationTrigger | Cancelable | Generic trigger executed before the operation validation begins. |
OperationFailureTrigger | Noncancelable | Generic trigger executed after any POS operation failed. |
PostOperationTrigger | Noncancelable | Generic trigger executed after all POS operations. You can use this trigger if there is no specific trigger available for a POS operation. |
Payment triggers
Trigger | Type | Description |
---|---|---|
PreAddTenderLineTrigger | Cancelable | Executed before the payment line is added to the cart. |
PrePaymentTrigger | Cancelable | Executed after you select the pay button in POS. |
PostPaymentTrigger | Noncancelable | Executed after all the payment processing is done. |
PreVoidPaymentTrigger | Cancelable | Executed before the payment line is voided in POS. |
PostVoidPaymentTrigger | Noncancelable | Executed after the payment line is voided in POS. |
PreTenderPaymentTrigger (10.0.21) | Cancelable | Executed after the tender amount is selected in the payment view. |
Printing Triggers
Trigger | Type | Description |
---|---|---|
PrePrintReceiptCopyTrigger | Cancelable | Executed before the receipt copy is printed from the show journal screen or receipt view screen. |
PostReceiptPromptTrigger | Noncancelable | Executed after the receipt prompt (for example, "Do you want to print the receipt?"). |
Product triggers
Trigger | Type | Description |
---|---|---|
PostGetSerialNumberTrigger | Noncancelable | Executed after the serial number is added to the cart line. |
PreProductSaleTrigger | Cancelable | Executed before the product is added to the cart. |
PostProductSaleTrigger | Noncancelable | Executed after the product is added to the cart. |
PreReturnProductTrigger | Cancelable | Executed before the return product is added to the cart. |
PostReturnProductTrigger | Noncancelable | Executed after the return product is added to the cart. |
PreSetQuantityTrigger | Cancelable | Executed before the quantity information is updated in the cart line. |
PostSetQuantityTrigger | Noncancelable | Executed after the quantity information is updated in the cart line. |
PrePriceOverrideTrigger | Cancelable | Executed before the price is overridden for a cart line. |
PostPriceOverrideTrigger | Noncancelable | Executed after the price is overridden for a cart line. |
PreClearQuantityTrigger | Cancelable | Executed before the quantity information is cleared from the cart line. |
PostClearQuantityTrigger | Noncancelable | Executed after the quantity information is cleared from the cart line. |
PreVoidProductsTrigger | Cancelable | Executed before the product is voided from the cart. |
PostVoidProductsTrigger | Noncancelable | Executed after the product is voided from the cart. |
PostPriceCheckTrigger | Noncancelable | Executed after the price check for the product is executed. |
Sales order triggers
Trigger | Type | Description |
---|---|---|
PreRecallCustomerOrderTrigger | Cancelable | Executed before the customer order is recalled. |
PostRecallCustomerOrderTrigger | Noncancelable | Executed after the customer order is recalled. |
PrePickUpCustomerOrderLinesTrigger | Cancelable | Executed before the customer order lines are picked. |
PreChangeShippingOriginTrigger | Cancelable | Executed before the shipping origin is changed during a customer order. |
PreGetFulfillmentLinesTrigger | Cancelable | Executed before the Order fulfillment lines are loaded in the Order fulfillment view. |
PreShipFulfillmentLinesTrigger | Cancelable | Executed before the shipping is done from the Order fulfillment view by selecting the Ship button. |
PostShipFulfillmentLinesTrigger | Non-Cancelable | Executed after the shipping is done from the Order fulfillment view by selecting the Ship button. |
PreMarkFulfillmentLinesAsPackedTrigger | Cancelable | Executed before the mark as packed option is triggered from the order fulfillment view by selecting the Pack button. |
PostMarkFulfillmentLinesAsPackedTrigger | Non-Cancelable | Executed after the mark as packed option is triggered from the order fulfillment view by selecting the Pack button. |
PreCreatePackingSlipTrigger | Cancelable | Executed before the create packing slip option is triggered from the order fulfillment view by selecting the Pack button. |
PostCreatePackingSlipTrigger | Non-Cancelable | Executed after the create packing slip option is triggered from the order fulfillment view by selecting the Pack button. |
PostReturnInvoicedSalesLinesTrigger | Non-Cancelable | Executed after one or more invoices selected for return. |
PreResendEmailReceiptTrigger (10.0.13) | Cancelable | Executed before sending the email from the Show journal view. |
PreRecallCustomerQuoteTrigger (10.0.18) | Cancelable | Executed before the customer quote is recalled from the recall order view. |
PostRecallCustomerQuoteTrigger (10.0.18) | Non-Cancelable | Executed after the customer quote is recalled from the recall order view. |
Shift triggers
Trigger | Type | Description | Release |
---|---|---|---|
PostOpenShiftTrigger | Noncancelable | This trigger is executed after the new shift is opened. | |
PreCloseShiftTrigger | Cancelable | This trigger is executed before the shift is closed. | |
PreResumeShiftTrigger | Cancelable | This trigger is executed before the shift is resumed. | 10.0.16 |
PostResumeShiftTrigger | Noncancelable | This trigger is executed after the shift is resumed. | 10.0.16 |
Tax override triggers
Trigger | Type | Description |
---|---|---|
PreOverrideLineProductTaxTrigger | Cancelable | Executed before the tax amount or code is overridden for a cart line. |
PostOverrideLineProductTaxTrigger | Noncancelable | Executed after the tax amount or code is overridden for a cart line. |
PreOverrideTransactionTaxTrigger | Cancelable | Executed before the tax amount or code is overridden for a cart or transaction. |
PostOverrideTransactionTaxTrigger | Noncancelable | Executed before the tax amount or code is overridden for a cart or transaction. |
Transaction triggers
Trigger | Type | Description |
---|---|---|
BeginTransactionTrigger | Noncancelable | Executed before the transaction is initialized. |
PreConfirmReturnTransactionTrigger | Cancelable | Executed before the return transaction is confirmed. |
PreReturnTransactionTrigger | Cancelable | Executed before the return transaction is processed. |
PostReturnTransactionTrigger | Noncancelable | Executed after the return transaction is processed. |
PreEndTransactionTrigger | Cancelable | Executed before the end transaction request is called to commit the changes to the database and close the transaction. |
PostEndTransactionTrigger | Noncancelable | Executed after the end transaction request is called to commit the changes to the database and close the transaction. |
PreVoidTransactionTrigger | Cancelable | Executed before the transaction is voided. |
PostVoidTransactionTrigger | Noncancelable | Executed after the transaction is voided. |
PreSuspendTransactionTrigger | Cancelable | Executed before the transaction is suspended. |
PostSuspendTransactionTrigger | Noncancelable | Executed after the transaction is suspended. |
PreRecallTransactionTrigger | Cancelable | Executed before the transaction or order is recalled. |
PostRecallTransactionTrigger | Noncancelable | Executed after the transaction or order is recalled. |
PostCartCheckoutTrigger | Noncancelable | Executed after the checkout process is completed. |
PreRecallTransactionTrigger | Cancelable | Executed before the customer order is recalled. |
PostRecallTransactionTrigger | Non-Cancelable | Executed after the customer order is recalled. |
PreSelectTransactionPaymentMethodTrigger | Cancelable | When the user selects the Totals button in the Cart view - totals panel, the available payment methods are shown and this trigger is executed before the dialog box is shown. You can use extension code to modify the available payment methods displayed from this trigger. |
PreShipSelectedCartLinesTrigger | Cancelable | Executed when the product is selected for shipping. |
Reason code triggers
Trigger | Type | Description |
---|---|---|
PostGetReasonCodeLine | Cancelable | This trigger is executed after the reason code line value is entered (before the reason code is added to the cart). |
Transfer Order triggers
Trigger | Type | Description |
---|---|---|
PreCreateTransferOrderTrigger | Cancelable | This trigger is executed before the transfer order is created (executed after the order input). |
PreUpdateTransferOrderTrigger | Cancelable | This trigger is executed before the transfer order is updated. |
Inventory triggers
Trigger | Type | Description | Release |
---|---|---|---|
PreCreateInventoryDocumentTrigger | Cancelable | This trigger is executed before the inbound/outbound document is created (executed after the order input). | 10.0.15 |
PreUpdateInventoryDocumentTrigger | Cancelable | This trigger is executed before the inbound/outbound document is updated. | 10.0.15 |
Stock count triggers
Trigger | Type | Description | Release |
---|---|---|---|
PreAdjustStockCountLineQuantityTrigger | Cancelable | This trigger is executed before the stock count for a line is adjusted. | 10.0.16 |
PreSaveStockCountJournalTrigger | Cancelable | This trigger is executed before the stock count journal is saved. | 10.0.16 |
Business scenario
In this example, a custom receipt is printed when the user suspends a transaction. This example implements the PostSuspendTransactionTrigger trigger and prints the custom receipt using the existing print peripheral API.
To implement this scenario, you must complete these steps.
- POS extension: Implement the PostSuspendTransactionTrigger trigger to get the receipt data from CRT and send it to the printer for printing.
- CRT extension: Generate the receipt data for printing.
Implement a trigger
To implement a trigger, follow these steps.
Open Visual Studio 2015 in administrator mode.
Open the ModernPOS solution from …\RetailSDK\POS.
Under the POS.Extensions project, create a new folder named SuspendReceiptSample.
Under SuspendReceiptSample, create new folder named TriggersHandlers.
In the TriggersHandlers folder, add a new Typescript file named PostSuspendTransactionTrigger.ts.
Add the following import statements to import the relevant entities and context.
import * as Triggers from "PosApi/Extend/Triggers/TransactionTriggers"; import { ObjectExtensions } from "PosApi/TypeExtensions"; import { ClientEntities, ProxyEntities } from "PosApi/Entities"; import { PrinterPrintRequest, PrinterPrintResponse } from "PosApi/Consume/Peripherals"; import { GetHardwareProfileClientRequest, GetHardwareProfileClientResponse } from "PosApi/Consume/Device"; import { GetReceiptsClientRequest, GetReceiptsClientResponse } from "PosApi/Consume/SalesOrders";
Create a new class named PostSuspendTransactionTrigger and extend it from PostSuspendTransactionTrigger.
export default class PostSuspendTransactionTrigger extends Triggers.PostSuspendTransactionTrigger { }
Implement the trigger's execute method to get the receipt profile and print the custom receipt:
public execute(options: Triggers.IPostSuspendTransactionTriggerOptions): Promise < void> { this.context.logger.logVerbose("Executing PostSuspendTransactionTrigger with options " + JSON.stringify(options) + "."); if(ObjectExtensions.isNullOrUndefined(options) || ObjectExtensions.isNullOrUndefined(options.cart)) { // This will never happen, but is included to demonstrate how to return a rejected promise when validation fails. let error: ClientEntities.ExtensionError = new ClientEntities.ExtensionError("The options provided to the PostSuspendTransactionTrigger were invalid."); return Promise.reject(error); } else { return this.context.runtime.executeAsync(new GetHardwareProfileClientRequest()) .then((response: ClientEntities.ICancelableDataResult<GetHardwareProfileClientResponse>) : Promise<ClientEntities.ICancelableDataResult<GetReceiptsClientResponse>> => { let hardwareProfile: ProxyEntities.HardwareProfile = response.data.result; // Gets the receipts. let salesOrderId: string = options.cart.Id; let receiptRetrievalCriteria: ProxyEntities.ReceiptRetrievalCriteria = { IsCopy: false, IsRemoteTransaction: false, IsPreview: false, QueryBySalesId: true, ReceiptTypeValue: ProxyEntities.ReceiptType.CustomReceipt7, HardwareProfileId: hardwareProfile.ProfileId }; let getReceiptsClientRequest: GetReceiptsClientRequest<GetReceiptsClientResponse> = new GetReceiptsClientRequest(salesOrderId, receiptRetrievalCriteria); return this.context.runtime.executeAsync(getReceiptsClientRequest); }) .then((response: ClientEntities.ICancelableDataResult<GetReceiptsClientResponse>) : Promise<ClientEntities.ICancelableDataResult<PrinterPrintResponse>> => { let receipts: ProxyEntities.Receipt[] = response.data.result; // Prints the receipts. let printerPrintRequest: PrinterPrintRequest<PrinterPrintResponse> = new PrinterPrintRequest(receipts); return this.context.runtime.executeAsync(printerPrintRequest); }).then((): Promise<void> => { // Resolves to a void result when fulfilled. return Promise.resolve(); }).catch((reason: any): Promise<void> => { // Resolves to a void result when rejected. This matches existing POS printing behavior. this.context.logger.logError("PostSuspendTransactionTrigger execute error: " + JSON.stringify(reason)); return Promise.resolve(); }); } }
The entire example should look like the following.
import * as Triggers from "PosApi/Extend/Triggers/TransactionTriggers"; import { ObjectExtensions } from "PosApi/TypeExtensions"; import { ClientEntities, ProxyEntities } from "PosApi/Entities"; import { PrinterPrintRequest, PrinterPrintResponse } from "PosApi/Consume/Peripherals"; import { GetHardwareProfileClientRequest, GetHardwareProfileClientResponse } from "PosApi/Consume/Device"; import { GetReceiptsClientRequest, GetReceiptsClientResponse } from "PosApi/Consume/SalesOrders"; export default class PostSuspendTransactionTrigger extends Triggers.PostSuspendTransactionTrigger { /** * Executes the trigger functionality. * @param {Triggers.IPostSuspendTransactionTriggerOptions} options The options provided to the trigger. */ public execute(options: Triggers.IPostSuspendTransactionTriggerOptions): Promise<void> { this.context.logger.logVerbose("Executing PostSuspendTransactionTrigger with options " + JSON.stringify(options) + "."); if (ObjectExtensions.isNullOrUndefined(options) || ObjectExtensions.isNullOrUndefined(options.cart)) { // This will never happen, but is included to demonstrate how to return a rejected promise when validation fails. let error: ClientEntities.ExtensionError = new ClientEntities.ExtensionError("The options provided to the PostSuspendTransactionTrigger were invalid."); return Promise.reject(error); } else { return this.context.runtime.executeAsync(new GetHardwareProfileClientRequest()) .then((response: ClientEntities.ICancelableDataResult<GetHardwareProfileClientResponse>) : Promise<ClientEntities.ICancelableDataResult<GetReceiptsClientResponse>> => { let hardwareProfile: ProxyEntities.HardwareProfile = response.data.result; // Gets the receipts. let salesOrderId: string = options.cart.Id; let receiptRetrievalCriteria: ProxyEntities.ReceiptRetrievalCriteria = { IsCopy: false, IsRemoteTransaction: false, IsPreview: false, QueryBySalesId: true, ReceiptTypeValue: ProxyEntities.ReceiptType.CustomReceipt7, HardwareProfileId: hardwareProfile.ProfileId }; let getReceiptsClientRequest: GetReceiptsClientRequest<GetReceiptsClientResponse> = new GetReceiptsClientRequest(salesOrderId, receiptRetrievalCriteria); return this.context.runtime.executeAsync(getReceiptsClientRequest); }) .then((response: ClientEntities.ICancelableDataResult<GetReceiptsClientResponse>) : Promise<ClientEntities.ICancelableDataResult<PrinterPrintResponse>> => { let receipts: ProxyEntities.Receipt[] = response.data.result; // Prints the receipts. let printerPrintRequest: PrinterPrintRequest<PrinterPrintResponse> = new PrinterPrintRequest(receipts); return this.context.runtime.executeAsync(printerPrintRequest); }).then((): Promise<void> => { // Resolves to a void result when fulfilled. return Promise.resolve(); }).catch((reason: any): Promise<void> => { // Resolves to a void result when rejected. This matches existing POS printing behavior. this.context.logger.logError("PostSuspendTransactionTrigger execute error: " + JSON.stringify(reason)); return Promise.resolve(); }); } } }
Create a new .json file under the SuspendReceiptSample folder and name it manifest.json.
Replace the autogenerated code in manifest.json with the following code.
{ "$schema": "../manifestSchema.json", "name": "Pos_Extensibility_SuspendTransactionReceiptSample", "publisher": "Microsoft", "version": "7.2.0", "minimumPosVersion": "7.2.0.0", "components": { "extend": { "triggers": [ { "triggerType": "PostSuspendTransaction", "modulePath": "TriggersHandlers/PostSuspendTransactionTrigger" } ] } } }
Open the extensions.json file in the POS.Extensions project and update it with the SuspendReceiptSample samples, so that during runtime POS will include this extension.
{ "extensionPackages": [ { "baseUrl": "SampleExtensions2" }, { "baseUrl": "SuspendReceiptSample" } ] }
Open the tsconfig.json file and comment out the extension package folders from the exclude list. POS will use this file to include or exclude the extension. By default, the list contains all the excluded extensions list. If you want to include an extension that is part of the POS, then add the extension folder name and comment out the extension from the extension, as shown.
"exclude": [ "AuditEventExtensionSample", "B2BSample", "CustomerSearchWithAttributesSample", "FiscalRegisterSample", "PaymentSample", "PromotionsSample", "SalesTransactionSignatureSample", "SampleExtensions", //"SampleExtensions2", "StoreHoursSample", "SuspendTransactionReceiptSample" //"POSAPIExtension", //"CustomColumnExtensions", //"EODSample", //"ProdDetailsCustomColumnExtensions", //"SerachExtension", //"SuspendReceiptSample" ],
Compile and rebuild the project.
Override the CRT receipt request to generate the receipt data
The following procedure describes how to override an existing CRT request to print a receipt for suspended transactions.
To override the CRT receipt request to generate the receipt data, follow these steps.
Start Visual Studio 2015.
On the File menu, select Open > Project/Solution. Find the template project (SampleCRTExtension.csproj).
Rename the template project Runtime.Extensions.SuspendReceiptSample.
Optional: Change the default namespace.
Rename the output assembly Contoso.Commerce.Runtime.SuspendReceiptSample.
Inside the project, add a new request class file, and name it GetCustomReceiptsRequestHandler.cs.
Copy the following code, and paste it inside the class. Before you copy it, delete the automatically generated code.
namespace Contoso { namespace Commerce.Runtime.ReceiptsSample { using System.Collections.Generic; using System.Collections.ObjectModel; using Microsoft.Dynamics.Commerce.Runtime; using Microsoft.Dynamics.Commerce.Runtime.DataModel; using Microsoft.Dynamics.Commerce.Runtime.Messages; using Microsoft.Dynamics.Commerce.Runtime.Services.Messages; using Microsoft.Dynamics.Commerce.Runtime.Workflow; /// <summary> /// The request handler for GetCustomReceiptsRequestHandler class. /// </summary> /// <remarks> /// This is an example of how to print custom types of receipts. In this example the receipt is for a transaction as opposed to /// a sales order. The implementation converts the transaction to a sales order so that existing receipt fields can be used. /// </remarks> public class GetCustomReceiptsRequestHandler : SingleRequestHandler<GetCustomReceiptsRequest, GetReceiptResponse> { } } }
Copy the following code, and paste it inside the class to add a new method to read the transaction from the cart table, because suspended transactions aren't yet created in the commerce transaction table. You must then convert the cart transaction to sales transaction. This conversion is required because the receipt object can understand only sales transactions.
Note
If you're printing a custom receipt for a completed transaction, you have don't have to get the cart transaction and convert it to a sales transaction. It has already been converted to a sales transaction, because the transaction is completed.
private SalesOrder GetSalesOrderForTransactionWithId(RequestContext requestContext, string transactionId) { SalesOrder salesOrder = new SalesOrder(); var getCartRequest = new GetCartRequest(new CartSearchCriteria(transactionId), QueryResultSettings.SingleRecord); var getCartResponse = requestContext.Execute<GetCartResponse>(getCartRequest); SalesTransaction salesTransaction = getCartResponse.Transactions.SingleOrDefault(); ; if (salesTransaction != null) { // The sales transaction is converted into a sales order so that existing receipt fields can be used. salesOrder.CopyFrom<SalesTransaction>(salesTransaction); } else { throw new DataValidationException( DataValidationErrors.Microsoft_Dynamics_Commerce_Runtime_ObjectNotFound, string.Format("Unable to get the sales transaction. ID: {0}", transactionId)); } return salesOrder; }
Copy the following code, and paste it into the class to add a new method to construct the receipt format by using the sales transaction information, based on the custom receipt format that is defined in HQ.
private Collection<Receipt> GetCustomReceipts(SalesOrder salesOrder, ReceiptRetrievalCriteria criteria) { Collection<Receipt> result = new Collection<Receipt>(); var getReceiptServiceRequest = new GetReceiptServiceRequest( salesOrder, new Collection<ReceiptType> { criteria.ReceiptType }, salesOrder.TenderLines, criteria.IsCopy, criteria.IsPreview, criteria.HardwareProfileId); ReadOnlyCollection<Receipt> customReceipts = this.Context.Execute<GetReceiptServiceResponse>(getReceiptServiceRequest).Receipts; result.AddRange(customReceipts); return result; }
Copy the following code, and paste it into the class to add a new method to override the get receipt response. This method processes the request and returns the response.
protected override GetReceiptResponse Process(GetCustomReceiptsRequest request) { ThrowIf.Null(request, "request"); ThrowIf.Null(request.ReceiptRetrievalCriteria, "request.ReceiptRetrievalCriteria"); // The sales order that we are printing receipts for is retrieved. SalesOrder salesOrder = this.GetSalesOrderForTransactionWithId(request.RequestContext, request.TransactionId); // Custom receipts are printed. Collection<Receipt> result = new Collection<Receipt>(); switch (request.ReceiptRetrievalCriteria.ReceiptType) { // An example of getting custom receipts. case ReceiptType.CustomReceipt7: { IEnumerable<Receipt> customReceipts = this.GetCustomReceipts(salesOrder, request.ReceiptRetrievalCriteria); result.AddRange(customReceipts); } break; default: // Add more logic to handle more types of custom receipt types. break; } return new GetReceiptResponse(new ReadOnlyCollection<Receipt>(result)); }
The overall code should look like this.
namespace Contoso { namespace Commerce.Runtime.ReceiptsSample { using System.Collections.Generic; using System.Collections.ObjectModel; using Microsoft.Dynamics.Commerce.Runtime; using Microsoft.Dynamics.Commerce.Runtime.DataModel; using Microsoft.Dynamics.Commerce.Runtime.Messages; using Microsoft.Dynamics.Commerce.Runtime.Services.Messages; using Microsoft.Dynamics.Commerce.Runtime.Workflow; /// <summary> /// The request handler for GetCustomReceiptsRequestHandler class. /// </summary> /// <remarks> /// This is an example of how to print custom types of receipts. In this example the receipt is for a transaction as opposed to /// a sales order. The implementation converts the transaction to a sales order so that existing receipt fields can be used. /// </remarks> public class GetCustomReceiptsRequestHandler : SingleRequestHandler<GetCustomReceiptsRequest, GetReceiptResponse> { /// <summary> /// Processes the GetCustomReceiptsRequest to return the set of receipts. The request should not be null. /// </summary> /// <param name="request">The request parameter.</param> /// <returns>The GetReceiptResponse.</returns> protected override GetReceiptResponse Process(GetCustomReceiptsRequest request) { ThrowIf.Null(request, "request"); ThrowIf.Null(request.ReceiptRetrievalCriteria, "request.ReceiptRetrievalCriteria"); // The sales order that we are printing receipts for is retrieved. SalesOrder salesOrder = this.GetSalesOrderForTransactionWithId(request.RequestContext, request.TransactionId); // Custom receipts are printed. Collection<Receipt> result = new Collection<Receipt>(); switch (request.ReceiptRetrievalCriteria.ReceiptType) { // An example of getting custom receipts. case ReceiptType.CustomReceipt7: { IEnumerable<Receipt> customReceipts = this.GetCustomReceipts(salesOrder, request.ReceiptRetrievalCriteria); result.AddRange(customReceipts); } break; default: // Add more logic to handle more types of custom receipt types. break; } return new GetReceiptResponse(new ReadOnlyCollection<Receipt>(result)); } /// <summary> /// Gets a sales order for the transaction with the given identifier. /// </summary> /// <param name="requestContext">The request context.</param> /// <param name="transactionId">The transaction identifier.</param> /// <returns>The sales order.</returns> private SalesOrder GetSalesOrderForTransactionWithId(RequestContext requestContext, string transactionId) { SalesOrder salesOrder = new SalesOrder(); var getCartRequest = new GetCartRequest(new CartSearchCriteria(transactionId), QueryResultSettings.SingleRecord); var getCartResponse = requestContext.Execute<GetCartResponse>(getCartRequest); SalesTransaction salesTransaction = getCartResponse.Transactions.SingleOrDefault(); ; if (salesTransaction != null) { // The sales transaction is converted into a sales order so that existing receipt fields can be used. salesOrder.CopyFrom<SalesTransaction>(salesTransaction); } else { throw new DataValidationException( DataValidationErrors.Microsoft_Dynamics_Commerce_Runtime_ObjectNotFound, string.Format("Unable to get the sales transaction. ID: {0}", transactionId)); } return salesOrder; } /// <summary> /// An example to get a custom receipt. /// </summary> /// <param name="salesOrder">The sales order that we are printing receipts for.</param> /// <param name="criteria">The receipt retrieval criteria.</param> /// <returns>A collection of receipts.</returns> private Collection<Receipt> GetCustomReceipts(SalesOrder salesOrder, ReceiptRetrievalCriteria criteria) { Collection<Receipt> result = new Collection<Receipt>(); var getReceiptServiceRequest = new GetReceiptServiceRequest( salesOrder, new Collection<ReceiptType> { criteria.ReceiptType }, salesOrder.TenderLines, criteria.IsCopy, criteria.IsPreview, criteria.HardwareProfileId); ReadOnlyCollection<Receipt> customReceipts = this.Context.Execute<GetReceiptServiceResponse>(getReceiptServiceRequest).Receipts; result.AddRange(customReceipts); return result; } } } }
Compile and build the project.
Go to the output directory, and copy the output assembly.
Navigate to the …\RetailServer\webroot\bin\ext folder, and paste the assembly.
Also paste the assembly in the …\RetailSDK\References folder.
Open the commerceruntime.ext.config file, and add the custom assembly information under the <composition> section.
<add source="assembly" value="Contoso.Commerce.Runtime.SuspendReceiptSample" />
Save and close the file.
Restart the Commerce Scale Unit to load the new assembly.
Add the custom receipt layout
To add the custom receipt layout, follow these steps.
- Open Dynamics 365 Commerce.
- Go to **Retail and Commerce > Channel setup > POS setup > POS > Receipts formats.
- Select New in Receipts formats.
- In the Receipt format filed field, enter the format name Suspend. In the Receipt type field, select CustomReceiptType7.
- Select Designer to open the receipt designer.
- Follow the instructions if prompted to install. Enter the Azure Active AD credentials to launch the designer.
- Drag and drop the required field into the header, lines, or footer. Or, copy from the existing receipt format by using the copy feature and then edit it.
- Select Save.
- Go to Retail and Commerce > Channel setup > POS setup > POS profiles > Receipt profiles.
- Select the Email receipt profile, or the profile you stores. Select the Add button on the General tab.
- In the Receipt type, select CustomReceiptType7 and in the Receipt format select Suspend.
- Go to Retail and Commerce > Retail and Commerce IT > Distribution schedule.
- Select Registers (1090), and then select Run now on the action bar. When prompted, select Yes to run the job.
Configure the XPS printer for quick testing
To configure the XPS printer for quick testing, follow these steps.
- Go to Retail and Commerce > Channel setup > POS setup > POS profiles > Hardware profiles.
- Select the hardware profile that your device is using. For example, in the demo data all the Houston devices uses HW002.
- Select Edit on the action bar.
- Expand the Printer FastTab. In the Printer drop-down list, select Windows driver and in the device name field enter Microsoft XPS Document Writer.
- Select Save.
- Go to Retail and Commerce > Retail and Commerce IT > Distribution schedule.
- Select Registers (1090), and then select Run now on the action bar. When prompted, select Yes to run the job.
Validate the extension
To validate the extension, follow these steps.
- Press F5 and deploy the POS to test your customization.
- After the POS starts, sign in to POS and add an item to a transaction.
- Suspend the transaction.
- The custom receipt should print.