Przeglądaj źródła

feat(core): Custom Order/Customer history entries can be defined

Relates to #1694, relates to #432
Michael Bromley 3 lat temu
rodzic
commit
d9e177022e

+ 90 - 4
packages/core/src/service/services/history.service.ts

@@ -24,7 +24,7 @@ import { RefundState } from '../helpers/refund-state-machine/refund-state';
 
 import { AdministratorService } from './administrator.service';
 
-export type CustomerHistoryEntryData = {
+export interface CustomerHistoryEntryData {
     [HistoryEntryType.CUSTOMER_REGISTERED]: {
         strategy: string;
     };
@@ -64,9 +64,9 @@ export type CustomerHistoryEntryData = {
     [HistoryEntryType.CUSTOMER_NOTE]: {
         note: string;
     };
-};
+}
 
-export type OrderHistoryEntryData = {
+export interface OrderHistoryEntryData {
     [HistoryEntryType.ORDER_STATE_TRANSITION]: {
         from: OrderState;
         to: OrderState;
@@ -108,7 +108,7 @@ export type OrderHistoryEntryData = {
     [HistoryEntryType.ORDER_MODIFIED]: {
         modificationId: ID;
     };
-};
+}
 
 export interface CreateCustomerHistoryEntryArgs<T extends keyof CustomerHistoryEntryData> {
     customerId: ID;
@@ -145,6 +145,92 @@ export interface UpdateCustomerHistoryEntryArgs<T extends keyof CustomerHistoryE
  * related to a particular Customer or Order, recording significant events such as creation, state changes,
  * notes, etc.
  *
+ * ## Custom History Entry Types
+ *
+ * Since Vendure v1.9.0, it is possible to define custom HistoryEntry types.
+ *
+ * Let's take an example where we have some Customers who are businesses. We want to verify their
+ * tax ID in order to allow them wholesale rates. As part of this verification, we'd like to add
+ * an entry into the Customer's history with data about the tax ID verification.
+ *
+ * First of all we'd extend the GraphQL `HistoryEntryType` enum for our new type as part of a plugin
+ *
+ * @example
+ * ```TypeScript
+ * import { PluginCommonModule, VendurePlugin } from '\@vendure/core';
+ * import { VerificationService } from './verification.service';
+ *
+ * \@VendurePlugin({
+ *   imports: [PluginCommonModule],
+ *   adminApiExtensions: {
+ *     schema: gql`
+ *       extend enum HistoryEntryType {
+ *         CUSTOMER_TAX_ID_VERIFICATION
+ *       }
+ *     `,
+ *   },
+ *   providers: [VerificationService],
+ * })
+ * export class TaxIDVerificationPlugin {}
+ * ```
+ *
+ * Next we need to create a TypeScript type definition file where we extend the `CustomerHistoryEntryData` interface. This is done
+ * via TypeScript's [declaration merging](https://www.typescriptlang.org/docs/handbook/declaration-merging.html#merging-interfaces)
+ * and [ambient modules](https://www.typescriptlang.org/docs/handbook/modules.html#ambient-modules) features.
+ *
+ * @example
+ * ```TypeScript
+ * // types.ts
+ * import { CustomerHistoryEntryData } from '\@vendure/core';
+ *
+ * export const CUSTOMER_TAX_ID_VERIFICATION = 'CUSTOMER_TAX_ID_VERIFICATION';
+ *
+ * declare module '@vendure/core' {
+ *   interface CustomerHistoryEntryData {
+ *     [CUSTOMER_TAX_ID_VERIFICATION]: {
+ *       taxId: string;
+ *       valid: boolean;
+ *       name?: string;
+ *       address?: string;
+ *     };
+ *   }
+ * }
+ * ```
+ *
+ * Note: it works exactly the same way if we wanted to add a custom type for Order history, except in that case we'd extend the
+ * `OrderHistoryEntryData` interface instead.
+ *
+ * Now that we have our types set up, we can use the HistoryService to add a new HistoryEntry in a type-safe manner:
+ *
+ * @example
+ * ```TypeScript
+ * // verification.service.ts
+ * import { Injectable } from '\@nestjs/common';
+ * import { RequestContext } from '\@vendure/core';
+ * import { CUSTOMER_TAX_ID_VERIFICATION } from './types';
+ *
+ * \@Injectable()
+ * export class VerificationService {
+ *   constructor(private historyService: HistoryService) {}
+ *
+ *   async verifyTaxId(ctx: RequestContext, customerId: ID, taxId: string) {
+ *     const result = await someTaxIdCheckingService(taxId);
+ *
+ *     await this.historyService.createHistoryEntryForCustomer({
+ *       customerId,
+ *       ctx,
+ *       type: CUSTOMER_TAX_ID_VERIFICATION,
+ *       data: {
+ *         taxId,
+ *         valid: result.isValid,
+ *         name: result.companyName,
+ *         address: result.registeredAddress,
+ *       },
+ *     });
+ *   }
+ * }
+ * ```
+ *
  * @docsCategory services
  */
 @Injectable()

+ 73 - 0
packages/dev-server/test-plugins/custom-history-entry/custom-history-entry-plugin.ts

@@ -0,0 +1,73 @@
+import { Args, Mutation, Resolver } from '@nestjs/graphql';
+import {
+    Ctx,
+    Customer,
+    HistoryService,
+    ID,
+    Order,
+    PluginCommonModule,
+    RequestContext,
+    TransactionalConnection,
+    VendurePlugin,
+} from '@vendure/core';
+import gql from 'graphql-tag';
+
+import { CUSTOM_TYPE } from './types';
+
+@Resolver()
+class AddHistoryEntryResolver {
+    constructor(private connection: TransactionalConnection, private historyService: HistoryService) {}
+
+    @Mutation()
+    async addCustomOrderHistoryEntry(
+        @Ctx() ctx: RequestContext,
+        @Args() args: { orderId: ID; message: string },
+    ) {
+        const order = await this.connection.getEntityOrThrow(ctx, Order, args.orderId);
+
+        await this.historyService.createHistoryEntryForOrder({
+            orderId: order.id,
+            ctx,
+            type: CUSTOM_TYPE,
+            data: { message: args.message },
+        });
+        return order;
+    }
+
+    @Mutation()
+    async addCustomCustomerHistoryEntry(
+        @Ctx() ctx: RequestContext,
+        @Args() args: { customerId: ID; name: string },
+    ) {
+        const customer = await this.connection.getEntityOrThrow(ctx, Customer, args.customerId);
+
+        await this.historyService.createHistoryEntryForCustomer({
+            customerId: customer.id,
+            ctx,
+            type: CUSTOM_TYPE,
+            data: { name: args.name },
+        });
+        return customer;
+    }
+}
+
+@VendurePlugin({
+    imports: [PluginCommonModule],
+    adminApiExtensions: {
+        schema: gql`
+            extend enum HistoryEntryType {
+                CUSTOM_TYPE
+            }
+
+            extend type Mutation {
+                addCustomOrderHistoryEntry(orderId: ID!, message: String!): Order!
+                addCustomCustomerHistoryEntry(customerId: ID!, name: String!): Customer!
+            }
+        `,
+        resolvers: [AddHistoryEntryResolver],
+    },
+    configuration: config => {
+        return config;
+    },
+})
+export class CustomHistoryEntryPlugin {}

+ 13 - 0
packages/dev-server/test-plugins/custom-history-entry/types.ts

@@ -0,0 +1,13 @@
+import { CustomerHistoryEntryData } from '@vendure/core';
+
+export const CUSTOM_TYPE = 'CUSTOM_TYPE';
+
+declare module '@vendure/core' {
+    interface OrderHistoryEntryData {
+        [CUSTOM_TYPE]: { message: string };
+    }
+
+    interface CustomerHistoryEntryData {
+        [CUSTOM_TYPE]: { name: string };
+    }
+}