Преглед на файлове

feat(core): Implement promotion duplicator

Relates to #627
Michael Bromley преди 1 година
родител
ревизия
da58b0bd8a

+ 144 - 1
packages/core/e2e/duplicate-entity.e2e-spec.ts

@@ -5,11 +5,13 @@ import {
     CollectionService,
     defaultEntityDuplicators,
     EntityDuplicator,
-    variantIdCollectionFilter,
+    freeShipping,
     LanguageCode,
     mergeConfig,
+    minimumOrderAmount,
     PermissionDefinition,
     TransactionalConnection,
+    variantIdCollectionFilter,
 } from '@vendure/core';
 import { createErrorResultGuard, createTestEnvironment, ErrorResultGuard } from '@vendure/testing';
 import gql from 'graphql-tag';
@@ -32,11 +34,13 @@ import {
 import {
     CREATE_ADMINISTRATOR,
     CREATE_COLLECTION,
+    CREATE_PROMOTION,
     CREATE_ROLE,
     GET_COLLECTION,
     GET_COLLECTIONS,
     GET_FACET_WITH_VALUES,
     GET_PRODUCT_WITH_VARIANTS,
+    GET_PROMOTION,
     UPDATE_PRODUCT_VARIANTS,
 } from './graphql/shared-definitions';
 
@@ -713,6 +717,145 @@ describe('Duplicating entities', () => {
             });
         });
     });
+
+    describe('Promotion duplicator', () => {
+        let testPromotion: Codegen.PromotionFragment;
+        let duplicatedPromotionId: string;
+        const promotionGuard: ErrorResultGuard<{ id: string }> = createErrorResultGuard(
+            result => !!result.id,
+        );
+
+        beforeAll(async () => {
+            await adminClient.asSuperAdmin();
+
+            const { createPromotion } = await adminClient.query<
+                Codegen.CreatePromotionMutation,
+                Codegen.CreatePromotionMutationVariables
+            >(CREATE_PROMOTION, {
+                input: {
+                    enabled: true,
+                    couponCode: 'TEST',
+                    perCustomerUsageLimit: 1,
+                    usageLimit: 100,
+                    startsAt: new Date().toISOString(),
+                    endsAt: new Date(Date.now() + 1000 * 60 * 60 * 24 * 30).toISOString(),
+                    translations: [
+                        {
+                            name: 'Test Promotion',
+                            description: 'Test Promotion description',
+                            languageCode: LanguageCode.en,
+                        },
+                    ],
+                    conditions: [
+                        {
+                            code: minimumOrderAmount.code,
+                            arguments: [
+                                {
+                                    name: 'amount',
+                                    value: '1000',
+                                },
+                                {
+                                    name: 'taxInclusive',
+                                    value: 'true',
+                                },
+                            ],
+                        },
+                    ],
+                    actions: [
+                        {
+                            code: freeShipping.code,
+                            arguments: [],
+                        },
+                    ],
+                },
+            });
+
+            promotionGuard.assertSuccess(createPromotion);
+            testPromotion = createPromotion;
+        });
+
+        it('duplicate promotion', async () => {
+            const { duplicateEntity } = await adminClient.query<
+                Codegen.DuplicateEntityMutation,
+                Codegen.DuplicateEntityMutationVariables
+            >(DUPLICATE_ENTITY, {
+                input: {
+                    entityName: 'Promotion',
+                    entityId: testPromotion.id,
+                    duplicatorInput: {
+                        code: 'promotion-duplicator',
+                        arguments: [],
+                    },
+                },
+            });
+
+            duplicateEntityGuard.assertSuccess(duplicateEntity);
+
+            expect(testPromotion.id).toBe('T_1');
+            expect(duplicateEntity.newEntityId).toBe('T_2');
+
+            duplicatedPromotionId = duplicateEntity.newEntityId;
+        });
+
+        it('promotion name is suffixed', async () => {
+            const { promotion } = await adminClient.query<
+                Codegen.GetPromotionQuery,
+                Codegen.GetPromotionQueryVariables
+            >(GET_PROMOTION, {
+                id: duplicatedPromotionId,
+            });
+
+            expect(promotion?.name).toBe('Test Promotion (copy)');
+        });
+
+        it('is initially disabled', async () => {
+            const { promotion } = await adminClient.query<
+                Codegen.GetPromotionQuery,
+                Codegen.GetPromotionQueryVariables
+            >(GET_PROMOTION, {
+                id: duplicatedPromotionId,
+            });
+
+            expect(promotion?.enabled).toBe(false);
+        });
+
+        it('properties are duplicated', async () => {
+            const { promotion } = await adminClient.query<
+                Codegen.GetPromotionQuery,
+                Codegen.GetPromotionQueryVariables
+            >(GET_PROMOTION, {
+                id: duplicatedPromotionId,
+            });
+
+            expect(promotion?.startsAt).toBe(testPromotion.startsAt);
+            expect(promotion?.endsAt).toBe(testPromotion.endsAt);
+            expect(promotion?.couponCode).toBe(testPromotion.couponCode);
+            expect(promotion?.perCustomerUsageLimit).toBe(testPromotion.perCustomerUsageLimit);
+            expect(promotion?.usageLimit).toBe(testPromotion.usageLimit);
+        });
+
+        it('conditions are duplicated', async () => {
+            const { promotion } = await adminClient.query<
+                Codegen.GetPromotionQuery,
+                Codegen.GetPromotionQueryVariables
+            >(GET_PROMOTION, {
+                id: duplicatedPromotionId,
+            });
+
+            expect(promotion?.conditions).toEqual(testPromotion.conditions);
+        });
+
+        it('actions are duplicated', async () => {
+            const { promotion } = await adminClient.query<
+                Codegen.GetPromotionQuery,
+                Codegen.GetPromotionQueryVariables
+            >(GET_PROMOTION, {
+                id: duplicatedPromotionId,
+            });
+
+            expect(promotion?.actions).toEqual(testPromotion.actions);
+        });
+    });
 });
 
 const GET_ENTITY_DUPLICATORS = gql`

+ 2 - 0
packages/core/e2e/graphql/fragments.ts

@@ -448,6 +448,8 @@ export const PROMOTION_FRAGMENT = gql`
         name
         description
         enabled
+        perCustomerUsageLimit
+        usageLimit
         conditions {
             ...ConfigurableOperation
         }

Файловите разлики са ограничени, защото са твърде много
+ 18 - 18
packages/core/e2e/graphql/generated-e2e-admin-types.ts


+ 9 - 0
packages/core/e2e/graphql/shared-definitions.ts

@@ -1063,3 +1063,12 @@ export const GET_FACET_WITH_VALUES = gql`
     }
     ${FACET_WITH_VALUES_FRAGMENT}
 `;
+
+export const GET_PROMOTION = gql`
+    query GetPromotion($id: ID!) {
+        promotion(id: $id) {
+            ...Promotion
+        }
+    }
+    ${PROMOTION_FRAGMENT}
+`;

+ 2 - 10
packages/core/e2e/promotion.e2e-spec.ts

@@ -11,7 +11,7 @@ import path from 'path';
 import { afterAll, beforeAll, describe, expect, it } from 'vitest';
 
 import { initialData } from '../../../e2e-common/e2e-initial-data';
-import { testConfig, TEST_SETUP_TIMEOUT_MS } from '../../../e2e-common/test-config';
+import { TEST_SETUP_TIMEOUT_MS, testConfig } from '../../../e2e-common/test-config';
 
 import { PROMOTION_FRAGMENT } from './graphql/fragments';
 import * as Codegen from './graphql/generated-e2e-admin-types';
@@ -21,6 +21,7 @@ import {
     CREATE_CHANNEL,
     CREATE_PROMOTION,
     DELETE_PROMOTION,
+    GET_PROMOTION,
     REMOVE_PROMOTIONS_FROM_CHANNEL,
 } from './graphql/shared-definitions';
 import { assertThrowsWithMessage } from './utils/assert-throws-with-message';
@@ -443,15 +444,6 @@ export const GET_PROMOTION_LIST = gql`
     ${PROMOTION_FRAGMENT}
 `;
 
-export const GET_PROMOTION = gql`
-    query GetPromotion($id: ID!) {
-        promotion(id: $id) {
-            ...Promotion
-        }
-    }
-    ${PROMOTION_FRAGMENT}
-`;
-
 export const UPDATE_PROMOTION = gql`
     mutation UpdatePromotion($input: UpdatePromotionInput!) {
         updatePromotion(input: $input) {

+ 7 - 1
packages/core/src/config/entity/entity-duplicators/index.ts

@@ -1,5 +1,11 @@
 import { collectionDuplicator } from './collection-duplicator';
 import { facetDuplicator } from './facet-duplicator';
 import { productDuplicator } from './product-duplicator';
+import { promotionDuplicator } from './promotion-duplicator';
 
-export const defaultEntityDuplicators = [productDuplicator, collectionDuplicator, facetDuplicator];
+export const defaultEntityDuplicators = [
+    productDuplicator,
+    collectionDuplicator,
+    facetDuplicator,
+    promotionDuplicator,
+];

+ 73 - 0
packages/core/src/config/entity/entity-duplicators/promotion-duplicator.ts

@@ -0,0 +1,73 @@
+import {
+    CreateFacetInput,
+    CreatePromotionInput,
+    FacetTranslationInput,
+    LanguageCode,
+    Permission,
+    PromotionTranslationInput,
+} from '@vendure/common/lib/generated-types';
+
+import { Injector, isGraphQlErrorResult } from '../../../common/index';
+import { TransactionalConnection } from '../../../connection/index';
+import { Facet, Promotion } from '../../../entity/index';
+import { FacetService, FacetValueService, PromotionService } from '../../../service/index';
+import { EntityDuplicator } from '../entity-duplicator';
+
+let connection: TransactionalConnection;
+let promotionService: PromotionService;
+
+/**
+ * @description
+ * Duplicates a Promotion
+ */
+export const promotionDuplicator = new EntityDuplicator({
+    code: 'promotion-duplicator',
+    description: [
+        {
+            languageCode: LanguageCode.en,
+            value: 'Default duplicator for Promotions',
+        },
+    ],
+    requiresPermission: [Permission.CreatePromotion],
+    forEntities: ['Promotion'],
+    args: {},
+    init(injector: Injector) {
+        connection = injector.get(TransactionalConnection);
+        promotionService = injector.get(PromotionService);
+    },
+    async duplicate({ ctx, id }) {
+        const promotion = await connection.getEntityOrThrow(ctx, Promotion, id);
+        const translations: PromotionTranslationInput[] = promotion.translations.map(translation => {
+            return {
+                name: translation.name + ' (copy)',
+                description: translation.description,
+                languageCode: translation.languageCode,
+                customFields: translation.customFields,
+            };
+        });
+        const promotionInput: CreatePromotionInput = {
+            couponCode: promotion.couponCode,
+            startsAt: promotion.startsAt,
+            endsAt: promotion.endsAt,
+            perCustomerUsageLimit: promotion.perCustomerUsageLimit,
+            usageLimit: promotion.usageLimit,
+            conditions: promotion.conditions.map(condition => ({
+                code: condition.code,
+                arguments: condition.args,
+            })),
+            actions: promotion.actions.map(action => ({
+                code: action.code,
+                arguments: action.args,
+            })),
+            enabled: false,
+            translations,
+            customFields: promotion.customFields,
+        };
+
+        const duplicatedPromotion = await promotionService.createPromotion(ctx, promotionInput);
+        if (isGraphQlErrorResult(duplicatedPromotion)) {
+            throw new Error(duplicatedPromotion.message);
+        }
+        return duplicatedPromotion;
+    },
+});

Някои файлове не бяха показани, защото твърде много файлове са промени