-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #6784 from reactioncommerce/feat/create-migration-…
…for-old-discount feat: create migration for old discount
- Loading branch information
Showing
11 changed files
with
298 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
import register from "./src/index.js"; | ||
|
||
export { default as migrations } from "./migrations/index.js"; | ||
|
||
export default register; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,213 @@ | ||
/* eslint-disable no-await-in-loop */ | ||
import Random from "@reactioncommerce/random"; | ||
import getCurrentShopTime from "./getCurrentShopTime.js"; | ||
|
||
/** | ||
* @summary returns an auto-incrementing integer id for a specific entity | ||
* @param {Object} db - The db instance | ||
* @param {String} shopId - The shop ID | ||
* @param {String} entity - The entity (normally a collection) that you are tracking the ID for | ||
* @return {Promise<Number>} - The auto-incrementing ID to use | ||
*/ | ||
async function incrementSequence(db, shopId, entity) { | ||
const { value: { value } } = await db.collection("Sequences").findOneAndUpdate( | ||
{ shopId, entity }, | ||
{ $inc: { value: 1 } }, | ||
{ returnDocument: "after" } | ||
); | ||
return value; | ||
} | ||
|
||
/** | ||
* @summary Migration current discounts v2 to version 2 | ||
* @param {Object} db MongoDB `Db` instance | ||
* @return {undefined} | ||
*/ | ||
async function migrationDiscounts(db) { | ||
const discounts = await db.collection("Discounts").find({}, { _id: 1 }).toArray(); | ||
|
||
// eslint-disable-next-line require-jsdoc | ||
function getDiscountCalculationType(discount) { | ||
if (discount.calculation.method === "discount") return "percentage"; | ||
if (discount.calculation.method === "shipping") return "shipping"; | ||
if (discount.calculation.method === "sale") return "flat"; | ||
return "fixed"; | ||
} | ||
|
||
for (const { _id } of discounts) { | ||
const discount = await db.collection("Discounts").findOne({ _id }); | ||
const promotionId = Random.id(); | ||
|
||
const now = new Date(); | ||
const shopTime = await getCurrentShopTime(db); | ||
|
||
// eslint-disable-next-line no-await-in-loop | ||
await db.collection("Promotions").insertOne({ | ||
_id: promotionId, | ||
shopId: discount.shopId, | ||
name: discount.code, | ||
label: discount.code, | ||
description: discount.code, | ||
promotionType: "order-discount", | ||
actions: [ | ||
{ | ||
actionKey: "discounts", | ||
actionParameters: { | ||
discountType: discount.discountType === "sale" ? "order" : "item", | ||
discountCalculationType: getDiscountCalculationType(discount), | ||
discountValue: Number(discount.discount) | ||
} | ||
} | ||
], | ||
triggers: [ | ||
{ | ||
triggerKey: "coupons", | ||
triggerParameters: { | ||
conditions: { | ||
all: [ | ||
{ | ||
fact: "totalItemAmount", | ||
operator: "greaterThanInclusive", | ||
value: 0 | ||
} | ||
] | ||
} | ||
} | ||
} | ||
], | ||
enabled: discount.conditions.enabled, | ||
stackability: { | ||
key: "all", | ||
parameters: {} | ||
}, | ||
triggerType: "explicit", | ||
state: "active", | ||
startDate: shopTime[discount.shopId], | ||
createdAt: now, | ||
updatedAt: now, | ||
referenceId: await incrementSequence(db, discount.shopId, "Promotions") | ||
}); | ||
|
||
const couponId = Random.id(); | ||
await db.collection("Coupons").insertOne({ | ||
_id: couponId, | ||
shopId: discount.shopId, | ||
promotionId, | ||
name: discount.code, | ||
code: discount.code, | ||
canUseInStore: false, | ||
usedCount: 0, | ||
expirationDate: null, | ||
createdAt: now, | ||
updatedAt: now, | ||
maxUsageTimesPerUser: discount.conditions.accountLimit, | ||
maxUsageTimes: discount.conditions.redemptionLimit, | ||
discountId: discount._id | ||
}); | ||
} | ||
} | ||
|
||
/** | ||
* @summary Migration current discount to promotion and coupon | ||
* @param {Object} db - The db instance | ||
* @param {String} discountId - The discount ID | ||
* @returns {Object} - The promotion | ||
*/ | ||
async function getPromotionByDiscountId(db, discountId) { | ||
const coupon = await db.collection("Coupons").findOne({ discountId }); | ||
if (!coupon) return null; | ||
const promotion = await db.collection("Promotions").findOne({ _id: coupon.promotionId }); | ||
if (!promotion) return null; | ||
|
||
promotion.relatedCoupon = { | ||
couponId: coupon._id, | ||
couponCode: coupon.code | ||
}; | ||
|
||
return promotion; | ||
} | ||
|
||
/** | ||
* @summary Migration current cart v1 to version 2 | ||
* @param {Object} db - The db instance | ||
* @returns {undefined} | ||
*/ | ||
async function migrateCart(db) { | ||
const carts = await db.collection("Cart").find({}, { _id: 1 }).toArray(); | ||
|
||
for (const { _id } of carts) { | ||
const cart = await db.collection("Cart").findOne({ _id }); | ||
if (cart.version && cart.version === 2) continue; | ||
|
||
if (!cart.billing) continue; | ||
|
||
if (!cart.appliedPromotions) cart.appliedPromotions = []; | ||
|
||
for (const billing of cart.billing) { | ||
if (!billing.data || !billing.data.discountId) continue; | ||
const promotion = await getPromotionByDiscountId(db, billing.data.discountId); | ||
cart.appliedPromotions.push(promotion); | ||
} | ||
|
||
cart.version = 2; | ||
await db.collection("Cart").updateOne({ _id: cart._id }, { $set: cart }); | ||
} | ||
} | ||
|
||
/** | ||
* @summary Performs migration up from previous data version | ||
* @param {Object} context Migration context | ||
* @param {Object} context.db MongoDB `Db` instance | ||
* @param {Function} context.progress A function to report progress, takes percent | ||
* number as argument. | ||
* @return {undefined} | ||
*/ | ||
async function up({ db, progress }) { | ||
try { | ||
await migrationDiscounts(db); | ||
} catch (err) { | ||
throw new Error(`Failed to migrate discounts: ${err.message}`); | ||
} | ||
|
||
progress(50); | ||
|
||
try { | ||
await migrateCart(db); | ||
} catch (err) { | ||
throw new Error(`Failed to migrate cart: ${err.message}`); | ||
} | ||
progress(100); | ||
} | ||
|
||
/** | ||
* @summary Performs migration down from previous data version | ||
* @param {Object} context Migration context | ||
* @param {Object} context.db MongoDB `Db` instance | ||
* @param {Function} context.progress A function to report progress, takes percent | ||
* number as argument. | ||
* @return {undefined} | ||
*/ | ||
async function down({ db, progress }) { | ||
const coupons = await db.collection("Coupons").find( | ||
{ discountId: { $exists: true } }, | ||
{ _id: 1, promotionId: 1 } | ||
).toArray(); | ||
|
||
const couponIds = coupons.map((coupon) => coupon._id); | ||
await db.collection("Coupons").remove({ _id: { $in: couponIds } }); | ||
|
||
const promotionIds = coupons.map((coupon) => coupon.promotionId); | ||
await db.collection("Promotions").remove({ _id: { $in: promotionIds } }); | ||
|
||
const carts = await db.collection("Cart").find({ version: 2 }, { _id: 1 }).toArray(); | ||
for (const { _id } of carts) { | ||
const cart = await db.collection("Cart").findOne({ _id }); | ||
cart.appliedPromotions.length = 0; | ||
cart.version = 1; | ||
await db.collection("Cart").updateOne({ _id: cart._id }, { $set: cart }); | ||
} | ||
|
||
progress(100); | ||
} | ||
|
||
export default { down, up }; |
31 changes: 31 additions & 0 deletions
31
packages/api-plugin-promotions-coupons/migrations/getCurrentShopTime.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
/** | ||
* @summary if no data in cache, repopulate | ||
* @param {Object} db - The db instance | ||
* @return {Promise<{Object}>} - The shop timezone object after pushing data to cache | ||
*/ | ||
async function populateCache(db) { | ||
const Shops = db.collection("Shops"); | ||
const shopTzObject = {}; | ||
const shops = await Shops.find({}).toArray(); | ||
for (const shop of shops) { | ||
const { _id: shopId } = shop; | ||
shopTzObject[shopId] = shop.timezone; | ||
} | ||
return shopTzObject; | ||
} | ||
|
||
/** | ||
* @summary get the current time in the shops timezone | ||
* @param {Object} db - The db instance | ||
* @return {Promise<{Object}>} - Object of shops and their current time in their timezone | ||
*/ | ||
export default async function getCurrentShopTime(db) { | ||
const shopTzData = await populateCache(db); | ||
const shopNow = {}; | ||
for (const shop of Object.keys(shopTzData)) { | ||
const now = new Date().toLocaleString("en-US", { timeZone: shopTzData[shop] }); | ||
const nowDate = new Date(now); | ||
shopNow[shop] = nowDate; | ||
} | ||
return shopNow; | ||
} |
13 changes: 13 additions & 0 deletions
13
packages/api-plugin-promotions-coupons/migrations/index.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { migrationsNamespace } from "./migrationsNamespace.js"; | ||
import migration2 from "./2.js"; | ||
|
||
export default { | ||
tracks: [ | ||
{ | ||
namespace: migrationsNamespace, | ||
migrations: { | ||
2: migration2 | ||
} | ||
} | ||
] | ||
}; |
1 change: 1 addition & 0 deletions
1
packages/api-plugin-promotions-coupons/migrations/migrationsNamespace.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export const migrationsNamespace = "promotion-coupons"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.