Jelajahi Sumber

feat(core): Add testEligibleShippingMethods query

Allows testing mock orders against existing shipping methods, to see what options storefront customer would have for a given order and destination.
Michael Bromley 6 tahun lalu
induk
melakukan
bc860e0c32

+ 11 - 0
packages/common/src/generated-types.ts

@@ -2658,6 +2658,7 @@ export type Query = {
   shippingEligibilityCheckers: Array<ConfigurableOperationDefinition>,
   shippingCalculators: Array<ConfigurableOperationDefinition>,
   testShippingMethod: TestShippingMethodResult,
+  testEligibleShippingMethods: Array<ShippingMethodQuote>,
   taxCategories: Array<TaxCategory>,
   taxCategory?: Maybe<TaxCategory>,
   taxRates: TaxRateList,
@@ -2828,6 +2829,11 @@ export type QueryTestShippingMethodArgs = {
 };
 
 
+export type QueryTestEligibleShippingMethodsArgs = {
+  input: TestEligibleShippingMethodsInput
+};
+
+
 export type QueryTaxCategoryArgs = {
   id: Scalars['ID']
 };
@@ -3173,6 +3179,11 @@ export type TaxRateSortParameter = {
   value?: Maybe<SortOrder>,
 };
 
+export type TestEligibleShippingMethodsInput = {
+  shippingAddress: CreateAddressInput,
+  lines: Array<TestShippingMethodOrderLineInput>,
+};
+
 export type TestShippingMethodInput = {
   checker: ConfigurableOperationInput,
   calculator: ConfigurableOperationInput,

+ 49 - 0
packages/core/e2e/graphql/generated-e2e-admin-types.ts

@@ -2591,6 +2591,7 @@ export type Query = {
     shippingEligibilityCheckers: Array<ConfigurableOperationDefinition>;
     shippingCalculators: Array<ConfigurableOperationDefinition>;
     testShippingMethod: TestShippingMethodResult;
+    testEligibleShippingMethods: Array<ShippingMethodQuote>;
     taxCategories: Array<TaxCategory>;
     taxCategory?: Maybe<TaxCategory>;
     taxRates: TaxRateList;
@@ -2728,6 +2729,10 @@ export type QueryTestShippingMethodArgs = {
     input: TestShippingMethodInput;
 };
 
+export type QueryTestEligibleShippingMethodsArgs = {
+    input: TestEligibleShippingMethodsInput;
+};
+
 export type QueryTaxCategoryArgs = {
     id: Scalars['ID'];
 };
@@ -3073,6 +3078,11 @@ export type TaxRateSortParameter = {
     value?: Maybe<SortOrder>;
 };
 
+export type TestEligibleShippingMethodsInput = {
+    shippingAddress: CreateAddressInput;
+    lines: Array<TestShippingMethodOrderLineInput>;
+};
+
 export type TestShippingMethodInput = {
     checker: ConfigurableOperationInput;
     calculator: ConfigurableOperationInput;
@@ -4744,6 +4754,30 @@ export type GetCalculatorsQuery = { __typename?: 'Query' } & {
     >;
 };
 
+export type TestShippingMethodQueryVariables = {
+    input: TestShippingMethodInput;
+};
+
+export type TestShippingMethodQuery = { __typename?: 'Query' } & {
+    testShippingMethod: { __typename?: 'TestShippingMethodResult' } & Pick<
+        TestShippingMethodResult,
+        'eligible'
+    > & { price: Maybe<{ __typename?: 'ShippingPrice' } & Pick<ShippingPrice, 'price' | 'priceWithTax'>> };
+};
+
+export type TestEligibleMethodsQueryVariables = {
+    input: TestEligibleShippingMethodsInput;
+};
+
+export type TestEligibleMethodsQuery = { __typename?: 'Query' } & {
+    testEligibleShippingMethods: Array<
+        { __typename?: 'ShippingMethodQuote' } & Pick<
+            ShippingMethodQuote,
+            'id' | 'description' | 'price' | 'priceWithTax'
+        >
+    >;
+};
+
 export type GetMeQueryVariables = {};
 
 export type GetMeQuery = { __typename?: 'Query' } & {
@@ -5910,6 +5944,21 @@ export namespace GetCalculators {
     export type Args = NonNullable<(NonNullable<GetCalculatorsQuery['shippingCalculators'][0]>)['args'][0]>;
 }
 
+export namespace TestShippingMethod {
+    export type Variables = TestShippingMethodQueryVariables;
+    export type Query = TestShippingMethodQuery;
+    export type TestShippingMethod = TestShippingMethodQuery['testShippingMethod'];
+    export type Price = NonNullable<TestShippingMethodQuery['testShippingMethod']['price']>;
+}
+
+export namespace TestEligibleMethods {
+    export type Variables = TestEligibleMethodsQueryVariables;
+    export type Query = TestEligibleMethodsQuery;
+    export type TestEligibleShippingMethods = NonNullable<
+        TestEligibleMethodsQuery['testEligibleShippingMethods'][0]
+    >;
+}
+
 export namespace GetMe {
     export type Variables = GetMeQueryVariables;
     export type Query = GetMeQuery;

+ 103 - 0
packages/core/e2e/shipping-method.e2e-spec.ts

@@ -14,6 +14,8 @@ import {
     GetEligibilityCheckers,
     GetShippingMethod,
     GetShippingMethodList,
+    TestEligibleMethods,
+    TestShippingMethod,
     UpdateShippingMethod,
 } from './graphql/generated-e2e-admin-types';
 import { TestAdminClient, TestShopClient } from './test-client';
@@ -176,6 +178,84 @@ describe('ShippingMethod resolver', () => {
         const listResult2 = await adminClient.query<GetShippingMethodList.Query>(GET_SHIPPING_METHOD_LIST);
         expect(listResult2.shippingMethods.items.map(i => i.id)).toEqual(['T_1', 'T_2']);
     });
+
+    it('testShippingMethod', async () => {
+        const { testShippingMethod } = await adminClient.query<
+            TestShippingMethod.Query,
+            TestShippingMethod.Variables
+        >(TEST_SHIPPING_METHOD, {
+            input: {
+                calculator: {
+                    code: defaultShippingCalculator.code,
+                    arguments: [
+                        {
+                            name: 'rate',
+                            type: 'int',
+                            value: '1000',
+                        },
+                        {
+                            name: 'taxRate',
+                            type: 'int',
+                            value: '20',
+                        },
+                    ],
+                },
+                checker: {
+                    code: defaultShippingEligibilityChecker.code,
+                    arguments: [
+                        {
+                            name: 'orderMinimum',
+                            type: 'int',
+                            value: '0',
+                        },
+                    ],
+                },
+                lines: [{ productVariantId: 'T_1', quantity: 1 }],
+                shippingAddress: {
+                    streetLine1: '',
+                    countryCode: 'GB',
+                },
+            },
+        });
+
+        expect(testShippingMethod).toEqual({
+            eligible: true,
+            price: {
+                price: 1000,
+                priceWithTax: 1200,
+            },
+        });
+    });
+
+    it('testEligibleShippingMethods', async () => {
+        const { testEligibleShippingMethods } = await adminClient.query<
+            TestEligibleMethods.Query,
+            TestEligibleMethods.Variables
+        >(TEST_ELIGIBLE_SHIPPING_METHODS, {
+            input: {
+                lines: [{ productVariantId: 'T_1', quantity: 1 }],
+                shippingAddress: {
+                    streetLine1: '',
+                    countryCode: 'GB',
+                },
+            },
+        });
+
+        expect(testEligibleShippingMethods).toEqual([
+            {
+                id: 'T_1',
+                description: 'Standard Shipping',
+                price: 500,
+                priceWithTax: 500,
+            },
+            {
+                id: 'T_2',
+                description: 'Express Shipping',
+                price: 1000,
+                priceWithTax: 1000,
+            },
+        ]);
+    });
 });
 
 const SHIPPING_METHOD_FRAGMENT = gql`
@@ -271,3 +351,26 @@ const GET_CALCULATORS = gql`
         }
     }
 `;
+
+const TEST_SHIPPING_METHOD = gql`
+    query TestShippingMethod($input: TestShippingMethodInput!) {
+        testShippingMethod(input: $input) {
+            eligible
+            price {
+                price
+                priceWithTax
+            }
+        }
+    }
+`;
+
+export const TEST_ELIGIBLE_SHIPPING_METHODS = gql`
+    query TestEligibleMethods($input: TestEligibleShippingMethodsInput!) {
+        testEligibleShippingMethods(input: $input) {
+            id
+            description
+            price
+            priceWithTax
+        }
+    }
+`;

+ 11 - 0
packages/core/src/api/resolvers/admin/shipping-method.resolver.ts

@@ -8,6 +8,7 @@ import {
     Permission,
     QueryShippingMethodArgs,
     QueryShippingMethodsArgs,
+    QueryTestEligibleShippingMethodsArgs,
     QueryTestShippingMethodArgs,
 } from '@vendure/common/lib/generated-types';
 import { PaginatedList } from '@vendure/common/lib/shared-types';
@@ -77,4 +78,14 @@ export class ShippingMethodResolver {
         const { input } = args;
         return this.orderTestingService.testShippingMethod(ctx, input);
     }
+
+    @Query()
+    @Allow(Permission.ReadSettings)
+    testEligibleShippingMethods(
+        @Ctx() ctx: RequestContext,
+        @Args() args: QueryTestEligibleShippingMethodsArgs,
+    ) {
+        const { input } = args;
+        return this.orderTestingService.testEligibleShippingMethods(ctx, input);
+    }
 }

+ 6 - 0
packages/core/src/api/schema/admin-api/shipping-method.api.graphql

@@ -4,6 +4,7 @@ type Query {
     shippingEligibilityCheckers: [ConfigurableOperationDefinition!]!
     shippingCalculators: [ConfigurableOperationDefinition!]!
     testShippingMethod(input: TestShippingMethodInput!): TestShippingMethodResult!
+    testEligibleShippingMethods(input: TestEligibleShippingMethodsInput!): [ShippingMethodQuote!]!
 }
 
 type Mutation {
@@ -40,6 +41,11 @@ input TestShippingMethodInput {
     lines: [TestShippingMethodOrderLineInput!]!
 }
 
+input TestEligibleShippingMethodsInput {
+    shippingAddress: CreateAddressInput!
+    lines: [TestShippingMethodOrderLineInput!]!
+}
+
 input TestShippingMethodOrderLineInput {
     productVariantId: ID!
     quantity: Int!

+ 23 - 0
packages/core/src/service/services/order-testing.service.ts

@@ -2,6 +2,8 @@ import { Injectable } from '@nestjs/common';
 import { InjectConnection } from '@nestjs/typeorm';
 import {
     CreateAddressInput,
+    ShippingMethodQuote,
+    TestEligibleShippingMethodsInput,
     TestShippingMethodInput,
     TestShippingMethodResult,
 } from '@vendure/common/lib/generated-types';
@@ -15,6 +17,7 @@ import { Order } from '../../entity/order/order.entity';
 import { ProductVariant } from '../../entity/product-variant/product-variant.entity';
 import { ShippingMethod } from '../../entity/shipping-method/shipping-method.entity';
 import { OrderCalculator } from '../helpers/order-calculator/order-calculator';
+import { ShippingCalculator } from '../helpers/shipping-calculator/shipping-calculator';
 import { ShippingConfiguration } from '../helpers/shipping-configuration/shipping-configuration';
 import { getEntityOrThrow } from '../helpers/utils/get-entity-or-throw';
 
@@ -27,6 +30,7 @@ export class OrderTestingService {
     constructor(
         @InjectConnection() private connection: Connection,
         private orderCalculator: OrderCalculator,
+        private shippingCalculator: ShippingCalculator,
         private shippingConfiguration: ShippingConfiguration,
     ) {}
 
@@ -50,6 +54,25 @@ export class OrderTestingService {
             price,
         };
     }
+
+    /**
+     * Tests all available ShippingMethods against a mock Order and return those whic hare eligible. This
+     * is intended to simulate a call to the `eligibleShippingMethods` query of the Shop API.
+     */
+    async testEligibleShippingMethods(
+        ctx: RequestContext,
+        input: TestEligibleShippingMethodsInput,
+    ): Promise<ShippingMethodQuote[]> {
+        const mockOrder = await this.buildMockOrder(ctx, input.shippingAddress, input.lines);
+        const eligibleMethods = await this.shippingCalculator.getEligibleShippingMethods(ctx, mockOrder);
+        return eligibleMethods.map(result => ({
+            id: result.method.id as string,
+            price: result.price.price,
+            priceWithTax: result.price.priceWithTax,
+            description: result.method.description,
+        }));
+    }
+
     private async buildMockOrder(
         ctx: RequestContext,
         shippingAddress: CreateAddressInput,

File diff ditekan karena terlalu besar
+ 0 - 0
schema-admin.json


Beberapa file tidak ditampilkan karena terlalu banyak file yang berubah dalam diff ini