Skip to content

Commit

Permalink
Merge pull request #6652 from reactioncommerce/feat/promotions-add-du…
Browse files Browse the repository at this point in the history
…plicate-promotion-endpoint

feat/promotions add duplicate promotion endpoint
  • Loading branch information
brent-hoover authored Nov 15, 2022
2 parents 9c27f54 + c497802 commit 1f003d3
Show file tree
Hide file tree
Showing 11 changed files with 232 additions and 49 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,10 @@ export const defaultShopManagerRoles = [
"reaction:legacy:taxRates/create",
"reaction:legacy:taxRates/delete",
"reaction:legacy:taxRates/read",
"reaction:legacy:taxRates/update"
"reaction:legacy:taxRates/update",
"reaction:legacy:promotions/create",
"reaction:legacy:promotions/read",
"reaction:legacy:promotions/update"
];

export const defaultShopOwnerRoles = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import _ from "lodash";
import SimpleSchema from "simpl-schema";
import { Promotion as PromotionSchema, Promotion, Trigger } from "../simpleSchemas.js";
import createPromotion from "./createPromotion.js";
import { CreateOrderPromotion } from "./fixtures/orderPromotion.js";

const triggerKeys = ["offers"];
const promotionTypes = ["coupon"];
Expand All @@ -27,46 +28,7 @@ const insertResults = {
insertedId: "myId"
};
mockContext.collections.Promotions.insertOne = () => insertResults;
mockContext.mutations.incrementSequence = () => 1;

const now = new Date();

const OrderPromotion = {
_id: "orderPromotion",
shopId: "testShop",
promotionType: "coupon",
label: "5 percent off your entire order when you spend more then $200",
description: "5 percent off your entire order when you spend more then $200",
enabled: true,
triggers: [
{
triggerKey: "offers",
triggerParameters: {
name: "5 percent off your entire order when you spend more then $200",
conditions: {
any: [
{
fact: "cart",
path: "$.merchandiseTotal",
operator: "greaterThanInclusive",
value: 200
}
]
}
}
}
],
actions: [
{
actionKey: "noop",
actionParameters: {}
}
],
startDate: now,
endDate: new Date(now.getTime() + 1000 * 60 * 60 * 24 * 7),
stackAbility: "none"
};

mockContext.mutations.incrementSequence = () => 1000000;
mockContext.simpleSchemas = {
Promotion
};
Expand Down Expand Up @@ -103,7 +65,7 @@ test("will not insert a record if it fails simple-schema validation", async () =
});

test("will not insert a record with no triggers", async () => {
const promotion = _.cloneDeep(OrderPromotion);
const promotion = _.cloneDeep(CreateOrderPromotion);
promotion.triggers = [
{
triggerKey: "offers",
Expand All @@ -120,7 +82,7 @@ test("will not insert a record with no triggers", async () => {
});

test("will not insert a record if trigger parameters are incorrect", async () => {
const promotion = _.cloneDeep(OrderPromotion);
const promotion = _.cloneDeep(CreateOrderPromotion);
promotion.triggers = [];
try {
await createPromotion(mockContext, promotion);
Expand All @@ -131,7 +93,7 @@ test("will not insert a record if trigger parameters are incorrect", async () =>


test("will insert a record if it passes validation", async () => {
const promotionToInsert = OrderPromotion;
const promotionToInsert = CreateOrderPromotion;
try {
const { success } = await createPromotion(mockContext, promotionToInsert);
expect(success).toBeTruthy();
Expand Down
37 changes: 37 additions & 0 deletions packages/api-plugin-promotions/src/mutations/duplicatePromotion.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import _ from "lodash";
import Random from "@reactioncommerce/random";
import validateTriggerParams from "./validateTriggerParams.js";

/**
* @summary duplicate an existing promotion to a new one
* @param {Object} context - the per-request application context
* @param {String} promotionId - The ID of the promotion you want to duplicate
* @return {Promise<{success: boolean, promotion: *}|{success: boolean, errors: [{message: string}]}>} - return the newly created promotion or an array of errors
*/
export default async function duplicatePromotion(context, promotionId) {
const { collections: { Promotions }, simpleSchemas: { Promotion: PromotionSchema } } = context;
const now = new Date();
const existingPromotion = await Promotions.findOne({ _id: promotionId });
const newPromotion = _.cloneDeep(existingPromotion);
newPromotion._id = Random.id();
newPromotion.createdAt = now;
newPromotion.updatedAt = now;
newPromotion.name = `Copy of ${existingPromotion.name}`;
newPromotion.referenceId = await context.mutations.incrementSequence(context, newPromotion.shopId, "Promotions");
PromotionSchema.validate(newPromotion);
validateTriggerParams(context, newPromotion);
const results = await Promotions.insertOne(newPromotion);
const { insertedCount } = results;
if (!insertedCount) {
return {
success: false,
errors: [{
message: "The record could not be inserted but no error was thrown"
}]
};
}
return {
success: true,
promotion: newPromotion
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import mockCollection from "@reactioncommerce/api-utils/tests/mockCollection.js";
import mockContext from "@reactioncommerce/api-utils/tests/mockContext.js";
import SimpleSchema from "simpl-schema";
import { Promotion as PromotionSchema, Promotion, Trigger } from "../simpleSchemas.js";
import duplicatePromotion from "./duplicatePromotion.js";
import { ExistingOrderPromotion } from "./fixtures/orderPromotion.js";

const triggerKeys = ["offers"];
const promotionTypes = ["coupon"];

Trigger.extend({
triggerKey: {
allowedValues: [...Trigger.getAllowedValuesForKey("triggerKey"), ...triggerKeys]
}
});

PromotionSchema.extend({
promotionType: {
allowedValues: [...PromotionSchema.getAllowedValuesForKey("promotionType"), ...promotionTypes]
}
});

mockContext.collections.Promotions = mockCollection("Promotions");
const insertResults = {
insertedCount: 1,
insertedId: "myId"
};
mockContext.collections.Promotions.insertOne = () => insertResults;
mockContext.collections.Promotions.findOne = () => ExistingOrderPromotion;
mockContext.mutations.incrementSequence = () => 1000000;

mockContext.simpleSchemas = {
Promotion
};

export const OfferTriggerParameters = new SimpleSchema({
name: String,
conditions: {
type: Object,
blackbox: true
}
});

const offerTrigger = {
key: "offers",
handler: () => {},
paramSchema: OfferTriggerParameters,
type: "implicit"
};


mockContext.promotions = {
triggers: [
offerTrigger
]
};


test("duplicates existing promotions and creates new one", async () => {
try {
const { success, promotion } = await duplicatePromotion(mockContext, ExistingOrderPromotion._id);
expect(success).toBeTruthy();
expect(promotion.name).toEqual("Copy of Order Promotion");
expect(promotion.referenceId).toEqual(1000000);
expect(promotion._id).not.toEqual("orderPromotion");
} catch (error) {
expect(error).toBeUndefined();
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
const now = new Date();

export const CreateOrderPromotion = {
shopId: "testShop",
promotionType: "coupon",
name: "Order Promotion",
label: "5 percent off your entire order when you spend more then $200",
description: "5 percent off your entire order when you spend more then $200",
enabled: true,
triggers: [
{
triggerKey: "offers",
triggerParameters: {
name: "5 percent off your entire order when you spend more then $200",
conditions: {
any: [
{
fact: "cart",
path: "$.merchandiseTotal",
operator: "greaterThanInclusive",
value: 200
}
]
}
}
}
],
actions: [
{
actionKey: "noop",
actionParameters: {}
}
],
startDate: now,
endDate: new Date(now.getTime() + 1000 * 60 * 60 * 24 * 7),
stackAbility: "none"
};

export const ExistingOrderPromotion = {
_id: "orderPromotion",
referenceId: 1,
shopId: "testShop",
promotionType: "item-discount",
triggerType: "implicit",
name: "Order Promotion",
label: "5 percent off your entire order when you spend more then $200",
description: "5 percent off your entire order when you spend more then $200",
enabled: true,
triggers: [
{
triggerKey: "offers",
triggerParameters: {
name: "5 percent off your entire order when you spend more then $200",
conditions: {
any: [
{
fact: "cart",
path: "$.merchandiseTotal",
operator: "greaterThanInclusive",
value: 200
}
]
}
}
}
],
actions: [
{
actionKey: "noop",
actionParameters: {}
}
],
startDate: now,
endDate: new Date(now.getTime() + 1000 * 60 * 60 * 24 * 7),
stackAbility: "none"
};

4 changes: 3 additions & 1 deletion packages/api-plugin-promotions/src/mutations/index.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import applyExplicitPromotionToCart from "./applyExplicitPromotionToCart.js";
import createPromotion from "./createPromotion.js";
import updatePromotion from "./updatePromotion.js";
import duplicatePromotion from "./duplicatePromotion.js";

export default {
applyExplicitPromotionToCart,
createPromotion,
updatePromotion
updatePromotion,
duplicatePromotion
};
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ PromotionSchema.extend({
}
});


mockContext.collections.Promotions = mockCollection("Promotions");
const insertResults = {
insertedCount: 1,
Expand All @@ -37,6 +36,7 @@ const OrderPromotion = {
referenceId: 123,
shopId: "testShop",
promotionType: "coupon",
name: "Order Promotion",
triggerType: "explicit",
label: "5 percent off your entire order when you spend more then $200",
description: "5 percent off your entire order when you spend more then $200",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/**
* @summary duplicate an existing promotion
* @param {undefined} _ - unused
* @param {Object} args - The arguments passed to the mutation
* @param {Object} context - The application context
* @return {Promise<boolean>} - true if success
*/
export default async function duplicatePromotion(_, { input }, context) {
const { promotionId, shopId } = input;
await context.validatePermissions("reaction:legacy:promotions", "create", { shopId });
const duplicatePromotionResults = await context.mutations.duplicatePromotion(context, promotionId);
return duplicatePromotionResults;
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import updatePromotion from "./updatePromotion.js";
import createPromotion from "./createPromotion.js";
import duplicatePromotion from "./duplicatePromotion.js";

export default {
updatePromotion,
createPromotion
createPromotion,
duplicatePromotion
};
19 changes: 17 additions & 2 deletions packages/api-plugin-promotions/src/schemas/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -129,15 +129,18 @@ input PromotionFilter {

input PromotionCreateInput {

"The id of the shop that this promotion resides"
"The id of the shop that this promotion resides in"
shopId: String!

"What type of promotion this is for stackability purposes"
promotionType: String!

"The short description of the promotion"
"The short description of the promotion visible to the customer"
label: String!

"The short description of the promotion"
name: String!

"A longer detailed description of the promotion"
description: String!

Expand All @@ -160,6 +163,11 @@ input PromotionCreateInput {
stackAbility: Stackability
}

input PromotionDuplicateInput {
"The id of the promotion to duplicate"
promotionId: String!
}

"This is identical to the PromotionCreate except it includes the _id"
input PromotionUpdateInput {
"The unique ID of the promotion"
Expand All @@ -175,6 +183,9 @@ input PromotionUpdateInput {
promotionType: String!

"The short description of the promotion"
name: String!

"The short description of the promotion visible to the customer"
label: String!

"A longer detailed description of the promotion"
Expand Down Expand Up @@ -221,6 +232,10 @@ extend type Mutation {
input: PromotionCreateInput
): PromotionUpdateCreatePayload

duplicatePromotion(
input: PromotionDuplicateInput
): PromotionUpdateCreatePayload

updatePromotion(
input: PromotionUpdateInput
): PromotionUpdateCreatePayload
Expand Down
3 changes: 3 additions & 0 deletions packages/api-plugin-promotions/src/simpleSchemas.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ export const Promotion = new SimpleSchema({
"label": {
type: String
},
"name": {
type: String
},
"description": {
type: String
},
Expand Down

0 comments on commit 1f003d3

Please sign in to comment.