Skip to content

Commit

Permalink
feat(core): Implement promotion duplicator
Browse files Browse the repository at this point in the history
Relates to #627
  • Loading branch information
michaelbromley committed Feb 20, 2024
1 parent 8d20847 commit da58b0b
Show file tree
Hide file tree
Showing 7 changed files with 261 additions and 36 deletions.
145 changes: 144 additions & 1 deletion packages/core/e2e/duplicate-entity.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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';

Expand Down Expand Up @@ -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`
Expand Down
2 changes: 2 additions & 0 deletions packages/core/e2e/graphql/fragments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,8 @@ export const PROMOTION_FRAGMENT = gql`
name
description
enabled
perCustomerUsageLimit
usageLimit
conditions {
...ConfigurableOperation
}
Expand Down
48 changes: 24 additions & 24 deletions packages/core/e2e/graphql/generated-e2e-admin-types.ts

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions packages/core/e2e/graphql/shared-definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}
`;
12 changes: 2 additions & 10 deletions packages/core/e2e/promotion.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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';
Expand Down Expand Up @@ -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) {
Expand Down
8 changes: 7 additions & 1 deletion packages/core/src/config/entity/entity-duplicators/index.ts
Original file line number Diff line number Diff line change
@@ -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,
];
Original file line number Diff line number Diff line change
@@ -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;
},
});

0 comments on commit da58b0b

Please sign in to comment.