Просмотр исходного кода

feat(core): Implement deleteShippingMethod mutation, add tests

Michael Bromley 6 лет назад
Родитель
Сommit
0b1dfd542f

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

@@ -1737,6 +1737,8 @@ export type Mutation = {
   createShippingMethod: ShippingMethod,
   /** Update an existing ShippingMethod */
   updateShippingMethod: ShippingMethod,
+  /** Delete a ShippingMethod */
+  deleteShippingMethod: DeletionResponse,
   /** Create a new TaxCategory */
   createTaxCategory: TaxCategory,
   /** Update an existing TaxCategory */
@@ -2059,6 +2061,11 @@ export type MutationUpdateShippingMethodArgs = {
 };
 
 
+export type MutationDeleteShippingMethodArgs = {
+  id: Scalars['ID']
+};
+
+
 export type MutationCreateTaxCategoryArgs = {
   input: CreateTaxCategoryInput
 };

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

@@ -1740,6 +1740,8 @@ export type Mutation = {
     createShippingMethod: ShippingMethod;
     /** Update an existing ShippingMethod */
     updateShippingMethod: ShippingMethod;
+    /** Delete a ShippingMethod */
+    deleteShippingMethod: DeletionResponse;
     /** Create a new TaxCategory */
     createTaxCategory: TaxCategory;
     /** Update an existing TaxCategory */
@@ -2003,6 +2005,10 @@ export type MutationUpdateShippingMethodArgs = {
     input: UpdateShippingMethodInput;
 };
 
+export type MutationDeleteShippingMethodArgs = {
+    id: Scalars['ID'];
+};
+
 export type MutationCreateTaxCategoryArgs = {
     input: CreateTaxCategoryInput;
 };
@@ -4654,6 +4660,90 @@ export type UpdateRoleMutation = { __typename?: 'Mutation' } & {
     updateRole: { __typename?: 'Role' } & RoleFragment;
 };
 
+export type ShippingMethodFragment = { __typename?: 'ShippingMethod' } & Pick<
+    ShippingMethod,
+    'id' | 'code' | 'description'
+> & {
+        calculator: { __typename?: 'ConfigurableOperation' } & Pick<ConfigurableOperation, 'code'>;
+        checker: { __typename?: 'ConfigurableOperation' } & Pick<ConfigurableOperation, 'code'>;
+    };
+
+export type GetShippingMethodListQueryVariables = {};
+
+export type GetShippingMethodListQuery = { __typename?: 'Query' } & {
+    shippingMethods: { __typename?: 'ShippingMethodList' } & Pick<ShippingMethodList, 'totalItems'> & {
+            items: Array<{ __typename?: 'ShippingMethod' } & ShippingMethodFragment>;
+        };
+};
+
+export type GetShippingMethodQueryVariables = {
+    id: Scalars['ID'];
+};
+
+export type GetShippingMethodQuery = { __typename?: 'Query' } & {
+    shippingMethod: Maybe<{ __typename?: 'ShippingMethod' } & ShippingMethodFragment>;
+};
+
+export type CreateShippingMethodMutationVariables = {
+    input: CreateShippingMethodInput;
+};
+
+export type CreateShippingMethodMutation = { __typename?: 'Mutation' } & {
+    createShippingMethod: { __typename?: 'ShippingMethod' } & ShippingMethodFragment;
+};
+
+export type UpdateShippingMethodMutationVariables = {
+    input: UpdateShippingMethodInput;
+};
+
+export type UpdateShippingMethodMutation = { __typename?: 'Mutation' } & {
+    updateShippingMethod: { __typename?: 'ShippingMethod' } & ShippingMethodFragment;
+};
+
+export type DeleteShippingMethodMutationVariables = {
+    id: Scalars['ID'];
+};
+
+export type DeleteShippingMethodMutation = { __typename?: 'Mutation' } & {
+    deleteShippingMethod: { __typename?: 'DeletionResponse' } & Pick<DeletionResponse, 'result' | 'message'>;
+};
+
+export type GetEligibilityCheckersQueryVariables = {};
+
+export type GetEligibilityCheckersQuery = { __typename?: 'Query' } & {
+    shippingEligibilityCheckers: Array<
+        { __typename?: 'ConfigurableOperationDefinition' } & Pick<
+            ConfigurableOperationDefinition,
+            'code' | 'description'
+        > & {
+                args: Array<
+                    { __typename?: 'ConfigArgDefinition' } & Pick<
+                        ConfigArgDefinition,
+                        'name' | 'type' | 'description' | 'label' | 'config'
+                    >
+                >;
+            }
+    >;
+};
+
+export type GetCalculatorsQueryVariables = {};
+
+export type GetCalculatorsQuery = { __typename?: 'Query' } & {
+    shippingCalculators: Array<
+        { __typename?: 'ConfigurableOperationDefinition' } & Pick<
+            ConfigurableOperationDefinition,
+            'code' | 'description'
+        > & {
+                args: Array<
+                    { __typename?: 'ConfigArgDefinition' } & Pick<
+                        ConfigArgDefinition,
+                        'name' | 'type' | 'description' | 'label' | 'config'
+                    >
+                >;
+            }
+    >;
+};
+
 export type GetMeQueryVariables = {};
 
 export type GetMeQuery = { __typename?: 'Query' } & {
@@ -5765,6 +5855,61 @@ export namespace UpdateRole {
     export type UpdateRole = RoleFragment;
 }
 
+export namespace ShippingMethod {
+    export type Fragment = ShippingMethodFragment;
+    export type Calculator = ShippingMethodFragment['calculator'];
+    export type Checker = ShippingMethodFragment['checker'];
+}
+
+export namespace GetShippingMethodList {
+    export type Variables = GetShippingMethodListQueryVariables;
+    export type Query = GetShippingMethodListQuery;
+    export type ShippingMethods = GetShippingMethodListQuery['shippingMethods'];
+    export type Items = ShippingMethodFragment;
+}
+
+export namespace GetShippingMethod {
+    export type Variables = GetShippingMethodQueryVariables;
+    export type Query = GetShippingMethodQuery;
+    export type ShippingMethod = ShippingMethodFragment;
+}
+
+export namespace CreateShippingMethod {
+    export type Variables = CreateShippingMethodMutationVariables;
+    export type Mutation = CreateShippingMethodMutation;
+    export type CreateShippingMethod = ShippingMethodFragment;
+}
+
+export namespace UpdateShippingMethod {
+    export type Variables = UpdateShippingMethodMutationVariables;
+    export type Mutation = UpdateShippingMethodMutation;
+    export type UpdateShippingMethod = ShippingMethodFragment;
+}
+
+export namespace DeleteShippingMethod {
+    export type Variables = DeleteShippingMethodMutationVariables;
+    export type Mutation = DeleteShippingMethodMutation;
+    export type DeleteShippingMethod = DeleteShippingMethodMutation['deleteShippingMethod'];
+}
+
+export namespace GetEligibilityCheckers {
+    export type Variables = GetEligibilityCheckersQueryVariables;
+    export type Query = GetEligibilityCheckersQuery;
+    export type ShippingEligibilityCheckers = NonNullable<
+        GetEligibilityCheckersQuery['shippingEligibilityCheckers'][0]
+    >;
+    export type Args = NonNullable<
+        (NonNullable<GetEligibilityCheckersQuery['shippingEligibilityCheckers'][0]>)['args'][0]
+    >;
+}
+
+export namespace GetCalculators {
+    export type Variables = GetCalculatorsQueryVariables;
+    export type Query = GetCalculatorsQuery;
+    export type ShippingCalculators = NonNullable<GetCalculatorsQuery['shippingCalculators'][0]>;
+    export type Args = NonNullable<(NonNullable<GetCalculatorsQuery['shippingCalculators'][0]>)['args'][0]>;
+}
+
 export namespace GetMe {
     export type Variables = GetMeQueryVariables;
     export type Query = GetMeQuery;

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

@@ -0,0 +1,273 @@
+/* tslint:disable:no-non-null-assertion */
+import gql from 'graphql-tag';
+import path from 'path';
+
+import { defaultShippingCalculator } from '../src/config/shipping-method/default-shipping-calculator';
+import { defaultShippingEligibilityChecker } from '../src/config/shipping-method/default-shipping-eligibility-checker';
+
+import { TEST_SETUP_TIMEOUT_MS } from './config/test-config';
+import {
+    CreateShippingMethod,
+    DeleteShippingMethod,
+    DeletionResult,
+    GetCalculators,
+    GetEligibilityCheckers,
+    GetShippingMethod,
+    GetShippingMethodList,
+    UpdateShippingMethod,
+} from './graphql/generated-e2e-admin-types';
+import { TestAdminClient, TestShopClient } from './test-client';
+import { TestServer } from './test-server';
+
+describe('ShippingMethod resolver', () => {
+    const adminClient = new TestAdminClient();
+    const shopClient = new TestShopClient();
+    const server = new TestServer();
+
+    beforeAll(async () => {
+        const token = await server.init({
+            productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-full.csv'),
+            customerCount: 1,
+        });
+        await adminClient.init();
+        await shopClient.init();
+    }, TEST_SETUP_TIMEOUT_MS);
+
+    afterAll(async () => {
+        await server.destroy();
+    });
+
+    it('shippingEligibilityCheckers', async () => {
+        const { shippingEligibilityCheckers } = await adminClient.query<GetEligibilityCheckers.Query>(
+            GET_ELIGIBILITY_CHECKERS,
+        );
+
+        expect(shippingEligibilityCheckers).toEqual([
+            {
+                args: [
+                    {
+                        config: {
+                            inputType: 'money',
+                        },
+                        description: 'Order is eligible only if its total is greater or equal to this value',
+                        label: 'Minimum order value',
+                        name: 'orderMinimum',
+                        type: 'int',
+                    },
+                ],
+                code: 'default-shipping-eligibility-checker',
+                description: 'Default Shipping Eligibility Checker',
+            },
+        ]);
+    });
+
+    it('shippingCalculators', async () => {
+        const { shippingCalculators } = await adminClient.query<GetCalculators.Query>(GET_CALCULATORS);
+
+        expect(shippingCalculators).toEqual([
+            {
+                args: [
+                    {
+                        config: {
+                            inputType: 'money',
+                        },
+                        description: null,
+                        label: 'Shipping price',
+                        name: 'rate',
+                        type: 'int',
+                    },
+                    {
+                        config: {
+                            inputType: 'percentage',
+                        },
+                        description: null,
+                        label: 'Tax rate',
+                        name: 'taxRate',
+                        type: 'int',
+                    },
+                ],
+                code: 'default-shipping-calculator',
+                description: 'Default Flat-Rate Shipping Calculator',
+            },
+        ]);
+    });
+
+    it('shippingMethods', async () => {
+        const { shippingMethods } = await adminClient.query<GetShippingMethodList.Query>(
+            GET_SHIPPING_METHOD_LIST,
+        );
+        expect(shippingMethods.totalItems).toEqual(2);
+        expect(shippingMethods.items[0].code).toBe('standard-shipping');
+        expect(shippingMethods.items[1].code).toBe('express-shipping');
+    });
+
+    it('shippingMethod', async () => {
+        const { shippingMethod } = await adminClient.query<
+            GetShippingMethod.Query,
+            GetShippingMethod.Variables
+        >(GET_SHIPPING_METHOD, {
+            id: 'T_1',
+        });
+        expect(shippingMethod!.code).toBe('standard-shipping');
+    });
+
+    it('createShippingMethod', async () => {
+        const { createShippingMethod } = await adminClient.query<
+            CreateShippingMethod.Mutation,
+            CreateShippingMethod.Variables
+        >(CREATE_SHIPPING_METHOD, {
+            input: {
+                code: 'new-method',
+                description: 'new method',
+                checker: {
+                    code: defaultShippingEligibilityChecker.code,
+                    arguments: [],
+                },
+                calculator: {
+                    code: defaultShippingCalculator.code,
+                    arguments: [],
+                },
+            },
+        });
+
+        expect(createShippingMethod).toEqual({
+            id: 'T_3',
+            code: 'new-method',
+            description: 'new method',
+            calculator: {
+                code: 'default-shipping-calculator',
+            },
+            checker: {
+                code: 'default-shipping-eligibility-checker',
+            },
+        });
+    });
+
+    it('updateShippingMethod', async () => {
+        const { updateShippingMethod } = await adminClient.query<
+            UpdateShippingMethod.Mutation,
+            UpdateShippingMethod.Variables
+        >(UPDATE_SHIPPING_METHOD, {
+            input: {
+                id: 'T_3',
+                description: 'changed method',
+            },
+        });
+
+        expect(updateShippingMethod.description).toBe('changed method');
+    });
+
+    it('deleteShippingMethod', async () => {
+        const listResult1 = await adminClient.query<GetShippingMethodList.Query>(GET_SHIPPING_METHOD_LIST);
+        expect(listResult1.shippingMethods.items.map(i => i.id)).toEqual(['T_1', 'T_2', 'T_3']);
+
+        const { deleteShippingMethod } = await adminClient.query<
+            DeleteShippingMethod.Mutation,
+            DeleteShippingMethod.Variables
+        >(DELETE_SHIPPING_METHOD, {
+            id: 'T_3',
+        });
+
+        expect(deleteShippingMethod).toEqual({
+            result: DeletionResult.DELETED,
+            message: null,
+        });
+
+        const listResult2 = await adminClient.query<GetShippingMethodList.Query>(GET_SHIPPING_METHOD_LIST);
+        expect(listResult2.shippingMethods.items.map(i => i.id)).toEqual(['T_1', 'T_2']);
+    });
+});
+
+const SHIPPING_METHOD_FRAGMENT = gql`
+    fragment ShippingMethod on ShippingMethod {
+        id
+        code
+        description
+        calculator {
+            code
+        }
+        checker {
+            code
+        }
+    }
+`;
+
+const GET_SHIPPING_METHOD_LIST = gql`
+    query GetShippingMethodList {
+        shippingMethods {
+            items {
+                ...ShippingMethod
+            }
+            totalItems
+        }
+    }
+    ${SHIPPING_METHOD_FRAGMENT}
+`;
+
+const GET_SHIPPING_METHOD = gql`
+    query GetShippingMethod($id: ID!) {
+        shippingMethod(id: $id) {
+            ...ShippingMethod
+        }
+    }
+    ${SHIPPING_METHOD_FRAGMENT}
+`;
+
+const CREATE_SHIPPING_METHOD = gql`
+    mutation CreateShippingMethod($input: CreateShippingMethodInput!) {
+        createShippingMethod(input: $input) {
+            ...ShippingMethod
+        }
+    }
+    ${SHIPPING_METHOD_FRAGMENT}
+`;
+
+const UPDATE_SHIPPING_METHOD = gql`
+    mutation UpdateShippingMethod($input: UpdateShippingMethodInput!) {
+        updateShippingMethod(input: $input) {
+            ...ShippingMethod
+        }
+    }
+    ${SHIPPING_METHOD_FRAGMENT}
+`;
+
+const DELETE_SHIPPING_METHOD = gql`
+    mutation DeleteShippingMethod($id: ID!) {
+        deleteShippingMethod(id: $id) {
+            result
+            message
+        }
+    }
+`;
+
+const GET_ELIGIBILITY_CHECKERS = gql`
+    query GetEligibilityCheckers {
+        shippingEligibilityCheckers {
+            code
+            description
+            args {
+                name
+                type
+                description
+                label
+                config
+            }
+        }
+    }
+`;
+
+const GET_CALCULATORS = gql`
+    query GetCalculators {
+        shippingCalculators {
+            code
+            description
+            args {
+                name
+                type
+                description
+                label
+                config
+            }
+        }
+    }
+`;

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

@@ -1,7 +1,9 @@
 import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
 import {
     ConfigurableOperationDefinition,
+    DeletionResponse,
     MutationCreateShippingMethodArgs,
+    MutationDeleteShippingMethodArgs,
     MutationUpdateShippingMethodArgs,
     Permission,
     QueryShippingMethodArgs,
@@ -62,6 +64,13 @@ export class ShippingMethodResolver {
         return this.shippingMethodService.update(input);
     }
 
+    @Mutation()
+    @Allow(Permission.DeleteSettings)
+    deleteShippingMethod(@Args() args: MutationDeleteShippingMethodArgs): Promise<DeletionResponse> {
+        const { id } = args;
+        return this.shippingMethodService.softDelete(id);
+    }
+
     @Query()
     @Allow(Permission.ReadSettings)
     testShippingMethod(@Ctx() ctx: RequestContext, @Args() args: QueryTestShippingMethodArgs) {

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

@@ -11,6 +11,8 @@ type Mutation {
     createShippingMethod(input: CreateShippingMethodInput!): ShippingMethod!
     "Update an existing ShippingMethod"
     updateShippingMethod(input: UpdateShippingMethodInput!): ShippingMethod!
+    "Delete a ShippingMethod"
+    deleteShippingMethod(id: ID!): DeletionResponse!
 }
 
 # generated by generateListOptions function

+ 10 - 2
packages/core/src/config/shipping-method/default-shipping-calculator.ts

@@ -6,8 +6,16 @@ export const defaultShippingCalculator = new ShippingCalculator({
     code: 'default-shipping-calculator',
     description: [{ languageCode: LanguageCode.en, value: 'Default Flat-Rate Shipping Calculator' }],
     args: {
-        rate: { type: 'int', config: { inputType: 'money' } },
-        taxRate: { type: 'int', config: { inputType: 'percentage' } },
+        rate: {
+            type: 'int',
+            config: { inputType: 'money' },
+            label: [{ languageCode: LanguageCode.en, value: 'Shipping price' }],
+        },
+        taxRate: {
+            type: 'int',
+            config: { inputType: 'percentage' },
+            label: [{ languageCode: LanguageCode.en, value: 'Tax rate' }],
+        },
     },
     calculate: (order, args) => {
         return { price: args.rate, priceWithTax: args.rate * ((100 + args.taxRate) / 100) };

+ 11 - 1
packages/core/src/config/shipping-method/default-shipping-eligibility-checker.ts

@@ -6,7 +6,17 @@ export const defaultShippingEligibilityChecker = new ShippingEligibilityChecker(
     code: 'default-shipping-eligibility-checker',
     description: [{ languageCode: LanguageCode.en, value: 'Default Shipping Eligibility Checker' }],
     args: {
-        orderMinimum: { type: 'int', config: { inputType: 'money' } },
+        orderMinimum: {
+            type: 'int',
+            config: { inputType: 'money' },
+            label: [{ languageCode: LanguageCode.en, value: 'Minimum order value' }],
+            description: [
+                {
+                    languageCode: LanguageCode.en,
+                    value: 'Order is eligible only if its total is greater or equal to this value',
+                },
+            ],
+        },
     },
     check: (order, args) => {
         return order.total >= args.orderMinimum;

+ 5 - 2
packages/core/src/entity/shipping-method/shipping-method.entity.ts

@@ -2,7 +2,7 @@ import { ConfigurableOperation } from '@vendure/common/lib/generated-types';
 import { DeepPartial } from '@vendure/common/lib/shared-types';
 import { Column, Entity, JoinTable, ManyToMany } from 'typeorm';
 
-import { ChannelAware } from '../../common/types/common-types';
+import { ChannelAware, SoftDeletable } from '../../common/types/common-types';
 import { getConfig } from '../../config/config-helpers';
 import { ShippingCalculator, ShippingPrice } from '../../config/shipping-method/shipping-calculator';
 import { ShippingEligibilityChecker } from '../../config/shipping-method/shipping-eligibility-checker';
@@ -21,7 +21,7 @@ import { Order } from '../order/order.entity';
  * @docsCategory entities
  */
 @Entity()
-export class ShippingMethod extends VendureEntity implements ChannelAware {
+export class ShippingMethod extends VendureEntity implements ChannelAware, SoftDeletable {
     private readonly allCheckers: { [code: string]: ShippingEligibilityChecker } = {};
     private readonly allCalculators: { [code: string]: ShippingCalculator } = {};
 
@@ -33,6 +33,9 @@ export class ShippingMethod extends VendureEntity implements ChannelAware {
         this.allCalculators = calculators.reduce((hash, o) => ({ ...hash, [o.code]: o }), {});
     }
 
+    @Column({ type: Date, nullable: true, default: null })
+    deletedAt: Date | null;
+
     @Column() code: string;
 
     @Column() description: string;

+ 21 - 1
packages/core/src/service/services/shipping-method.service.ts

@@ -3,6 +3,8 @@ import { InjectConnection } from '@nestjs/typeorm';
 import {
     ConfigurableOperationDefinition,
     CreateShippingMethodInput,
+    DeletionResponse,
+    DeletionResult,
     UpdateShippingMethodInput,
 } from '@vendure/common/lib/generated-types';
 import { omit } from '@vendure/common/lib/omit';
@@ -19,6 +21,7 @@ import { Channel } from '../../entity/channel/channel.entity';
 import { ShippingMethod } from '../../entity/shipping-method/shipping-method.entity';
 import { ListQueryBuilder } from '../helpers/list-query-builder/list-query-builder';
 import { ShippingConfiguration } from '../helpers/shipping-configuration/shipping-configuration';
+import { getEntityOrThrow } from '../helpers/utils/get-entity-or-throw';
 import { patchEntity } from '../helpers/utils/patch-entity';
 
 import { ChannelService } from './channel.service';
@@ -41,7 +44,10 @@ export class ShippingMethodService {
 
     findAll(options?: ListQueryOptions<ShippingMethod>): Promise<PaginatedList<ShippingMethod>> {
         return this.listQueryBuilder
-            .build(ShippingMethod, options, { relations: ['channels'] })
+            .build(ShippingMethod, options, {
+                relations: ['channels'],
+                where: { deletedAt: null },
+            })
             .getManyAndCount()
             .then(([items, totalItems]) => ({
                 items,
@@ -52,6 +58,7 @@ export class ShippingMethodService {
     findOne(shippingMethodId: ID): Promise<ShippingMethod | undefined> {
         return this.connection.manager.findOne(ShippingMethod, shippingMethodId, {
             relations: ['channels'],
+            where: { deletedAt: null },
         });
     }
 
@@ -87,6 +94,18 @@ export class ShippingMethodService {
         return assertFound(this.findOne(shippingMethod.id));
     }
 
+    async softDelete(id: ID): Promise<DeletionResponse> {
+        const shippingMethod = await getEntityOrThrow(this.connection, ShippingMethod, id, {
+            where: { deletedAt: null },
+        });
+        shippingMethod.deletedAt = new Date();
+        await this.connection.getRepository(ShippingMethod).save(shippingMethod);
+        await this.updateActiveShippingMethods();
+        return {
+            result: DeletionResult.DELETED,
+        };
+    }
+
     getShippingEligibilityCheckers(ctx: RequestContext): ConfigurableOperationDefinition[] {
         return this.shippingConfiguration.shippingEligibilityCheckers.map(x =>
             configurableDefToOperation(ctx, x),
@@ -104,6 +123,7 @@ export class ShippingMethodService {
     private async updateActiveShippingMethods() {
         this.activeShippingMethods = await this.connection.getRepository(ShippingMethod).find({
             relations: ['channels'],
+            where: { deletedAt: null },
         });
     }
 }

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
schema-admin.json


Некоторые файлы не были показаны из-за большого количества измененных файлов