fix(pricing, medusa): resolve minor pricing-module bugs (#5685)
* fix region price updates * pricing migration: include title/name field value migration * update migration scripts for pricing module * move file to script utils * rename file * rename file * add changeset * remove redundant maps * update migration script * nit * remove unnecessary variable * filter before map * array function naming cleanup * chore: address pr reviews --------- Co-authored-by: Philip Korsholm <philip.korsholm@hotmail.com> Co-authored-by: Philip Korsholm <88927411+pKorsholm@users.noreply.github.com>
This commit is contained in:
@@ -1,40 +1,10 @@
|
||||
import { AwilixContainer } from "awilix"
|
||||
import { IPricingModuleService } from "@medusajs/types"
|
||||
import { createDefaultRuleTypes } from "./utils/create-default-rule-types"
|
||||
import dotenv from "dotenv"
|
||||
import express from "express"
|
||||
import loaders from "../loaders"
|
||||
|
||||
dotenv.config()
|
||||
|
||||
export const createDefaultRuleTypes = async (container: AwilixContainer) => {
|
||||
const pricingModuleService: IPricingModuleService = container.resolve(
|
||||
"pricingModuleService"
|
||||
)
|
||||
const existing = await pricingModuleService.listRuleTypes(
|
||||
{ rule_attribute: ["region_id", "customer_group_id"] },
|
||||
{ take: 2 }
|
||||
)
|
||||
|
||||
if (existing.length === 2) {
|
||||
return
|
||||
}
|
||||
|
||||
if (existing.length === 0) {
|
||||
await pricingModuleService.createRuleTypes([
|
||||
{ name: "region_id", rule_attribute: "region_id" },
|
||||
{ name: "customer_group_id", rule_attribute: "customer_group_id" },
|
||||
])
|
||||
} else if (existing[0].rule_attribute === "region_id") {
|
||||
await pricingModuleService.createRuleTypes([
|
||||
{ name: "customer_group_id", rule_attribute: "customer_group_id" },
|
||||
])
|
||||
} else {
|
||||
await pricingModuleService.createRuleTypes([
|
||||
{ name: "region_id", rule_attribute: "region_id" },
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
const migrate = async function ({ directory }) {
|
||||
const app = express()
|
||||
const { container } = await loaders({
|
||||
|
||||
@@ -1,146 +0,0 @@
|
||||
import { IPricingModuleService, PricingTypes } from "@medusajs/types"
|
||||
import { AwilixContainer } from "awilix"
|
||||
import dotenv from "dotenv"
|
||||
import express from "express"
|
||||
import loaders from "../loaders"
|
||||
import Logger from "../loaders/logger"
|
||||
import { PriceListService } from "../services"
|
||||
|
||||
dotenv.config()
|
||||
|
||||
const BATCH_SIZE = 1000
|
||||
|
||||
export const migratePriceLists = async (container: AwilixContainer) => {
|
||||
const pricingModuleService: IPricingModuleService = container.resolve(
|
||||
"pricingModuleService"
|
||||
)
|
||||
|
||||
const priceListCoreService: PriceListService =
|
||||
container.resolve("priceListService")
|
||||
|
||||
const remoteQuery = container.resolve("remoteQuery")
|
||||
|
||||
const existingRuleTypes = await pricingModuleService.listRuleTypes(
|
||||
{ rule_attribute: ["customer_group_id"] },
|
||||
{ take: 2 }
|
||||
)
|
||||
|
||||
if (existingRuleTypes.length === 0) {
|
||||
Logger.info(
|
||||
`Run default rules migration before running this migration - node node_modules/@medusajs/medusa/dist/scripts/create-default-rule-types.js`
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
let offset = 0
|
||||
let arePriceListsAvailable = true
|
||||
|
||||
while (arePriceListsAvailable) {
|
||||
const priceLists = await priceListCoreService.list(
|
||||
{},
|
||||
{
|
||||
take: BATCH_SIZE,
|
||||
skip: offset,
|
||||
relations: ["customer_groups"],
|
||||
}
|
||||
)
|
||||
|
||||
if (priceLists.length === 0) {
|
||||
break
|
||||
}
|
||||
|
||||
offset += BATCH_SIZE
|
||||
|
||||
await pricingModuleService.update(
|
||||
priceLists.map((priceList) => {
|
||||
const updateData: PricingTypes.UpdatePriceListDTO = {
|
||||
id: priceList.id,
|
||||
title: priceList.name,
|
||||
}
|
||||
|
||||
if (priceList?.customer_groups?.length) {
|
||||
updateData.rules = {
|
||||
customer_group_id: priceList.customer_groups.map((cg) => cg.id),
|
||||
}
|
||||
}
|
||||
|
||||
return updateData
|
||||
})
|
||||
)
|
||||
|
||||
for (const priceList of priceLists) {
|
||||
let productsOffset = 0
|
||||
let areVariantsAvailable = true
|
||||
|
||||
while (areVariantsAvailable) {
|
||||
const [priceListVariants, variantsCount] =
|
||||
await priceListCoreService.listVariants(
|
||||
priceList.id,
|
||||
{},
|
||||
{
|
||||
skip: productsOffset,
|
||||
take: BATCH_SIZE,
|
||||
}
|
||||
)
|
||||
|
||||
if (variantsCount === 0) {
|
||||
break
|
||||
}
|
||||
|
||||
productsOffset += BATCH_SIZE
|
||||
|
||||
const query = {
|
||||
product_variant_price_set: {
|
||||
__args: {
|
||||
variant_id: priceListVariants.map((plv) => plv.id),
|
||||
},
|
||||
fields: ["variant_id", "price_set_id"],
|
||||
},
|
||||
}
|
||||
|
||||
const variantPriceSets = await remoteQuery(query)
|
||||
const variantPriceSetMap = new Map<string, string>(
|
||||
variantPriceSets.map((mps) => [mps.variant_id, mps.price_set_id])
|
||||
)
|
||||
|
||||
const variantPrices = priceListVariants
|
||||
.map((plv) => plv.prices || [])
|
||||
.flat()
|
||||
|
||||
await pricingModuleService.addPriceListPrices([
|
||||
{
|
||||
priceListId: priceList.id,
|
||||
prices: variantPrices.map((vp) => {
|
||||
return {
|
||||
id: vp.id,
|
||||
price_set_id: variantPriceSetMap.get(vp.variant_id)!,
|
||||
currency_code: vp.currency_code,
|
||||
amount: vp.amount,
|
||||
}
|
||||
}),
|
||||
},
|
||||
])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const migrate = async function ({ directory }) {
|
||||
const app = express()
|
||||
const { container } = await loaders({
|
||||
directory,
|
||||
expressApp: app,
|
||||
isTest: false,
|
||||
})
|
||||
|
||||
return await migratePriceLists(container)
|
||||
}
|
||||
|
||||
migrate({ directory: process.cwd() })
|
||||
.then(() => {
|
||||
console.log("Migrated price lists")
|
||||
})
|
||||
.catch(() => {
|
||||
console.log("Failed to migrate price lists")
|
||||
})
|
||||
@@ -0,0 +1,250 @@
|
||||
import { IPricingModuleService, PricingTypes } from "@medusajs/types"
|
||||
import { promiseAll } from "@medusajs/utils"
|
||||
import { AwilixContainer } from "awilix"
|
||||
import dotenv from "dotenv"
|
||||
import express from "express"
|
||||
import loaders from "../loaders"
|
||||
import Logger from "../loaders/logger"
|
||||
import { PriceList } from "../models"
|
||||
import { CurrencyService, PriceListService } from "../services"
|
||||
import { createDefaultRuleTypes } from "./utils/create-default-rule-types"
|
||||
import { migrateProductVariantPricing } from "./utils/migrate-money-amounts-to-pricing-module"
|
||||
|
||||
dotenv.config()
|
||||
|
||||
const BATCH_SIZE = 1000
|
||||
|
||||
const migratePriceLists = async (container: AwilixContainer) => {
|
||||
const pricingModuleService: IPricingModuleService = container.resolve(
|
||||
"pricingModuleService"
|
||||
)
|
||||
let offset = 0
|
||||
|
||||
const priceListCoreService: PriceListService =
|
||||
container.resolve("priceListService")
|
||||
|
||||
const remoteQuery = container.resolve("remoteQuery")
|
||||
|
||||
const [_, totalCount] = await priceListCoreService.listAndCount(
|
||||
{},
|
||||
{ select: ["id"] }
|
||||
)
|
||||
|
||||
while (offset < totalCount) {
|
||||
const corePriceLists = await priceListCoreService.list(
|
||||
{},
|
||||
{
|
||||
take: BATCH_SIZE,
|
||||
skip: offset,
|
||||
relations: ["customer_groups", "prices", "prices.variants"],
|
||||
}
|
||||
)
|
||||
|
||||
const pricingModulePriceLists = await pricingModuleService.listPriceLists(
|
||||
{ id: corePriceLists.map(({ id }) => id) },
|
||||
{
|
||||
take: BATCH_SIZE,
|
||||
skip: offset,
|
||||
select: ["id"],
|
||||
}
|
||||
)
|
||||
|
||||
const priceListIdsToUpdateSet = new Set<string>(
|
||||
pricingModulePriceLists.map(({ id }) => id)
|
||||
)
|
||||
|
||||
const priceListsToCreate: PriceList[] = []
|
||||
const priceListsToUpdate: PriceList[] = []
|
||||
const variantIds: string[] = []
|
||||
|
||||
for (const corePriceList of corePriceLists) {
|
||||
if (priceListIdsToUpdateSet.has(corePriceList.id)) {
|
||||
priceListsToCreate.push(corePriceList)
|
||||
} else {
|
||||
priceListsToUpdate.push(corePriceList)
|
||||
}
|
||||
|
||||
const corePrices = corePriceList.prices || []
|
||||
|
||||
variantIds.push(
|
||||
...corePrices.map((corePrice) => corePrice.variants?.[0]?.id)
|
||||
)
|
||||
}
|
||||
|
||||
const query = {
|
||||
product_variant_price_set: {
|
||||
__args: {
|
||||
variant_id: variantIds,
|
||||
},
|
||||
fields: ["variant_id", "price_set_id"],
|
||||
},
|
||||
}
|
||||
|
||||
const variantPriceSets = await remoteQuery(query)
|
||||
|
||||
const variantIdPriceSetIdMap = new Map<string, string>(
|
||||
variantPriceSets.map((vps) => [vps.variant_id, vps.price_set_id])
|
||||
)
|
||||
|
||||
const promises: Promise<any>[] = []
|
||||
|
||||
if (priceListsToUpdate.length) {
|
||||
await pricingModuleService.updatePriceLists(
|
||||
priceListsToUpdate.map((priceList) => {
|
||||
const updateData: PricingTypes.UpdatePriceListDTO = {
|
||||
id: priceList.id,
|
||||
title: priceList.name,
|
||||
}
|
||||
|
||||
if (priceList?.customer_groups?.length) {
|
||||
updateData.rules = {
|
||||
customer_group_id: priceList.customer_groups.map(({ id }) => id),
|
||||
}
|
||||
}
|
||||
|
||||
return updateData
|
||||
})
|
||||
)
|
||||
|
||||
promises.push(
|
||||
pricingModuleService.addPriceListPrices(
|
||||
priceListsToUpdate.map((priceList) => {
|
||||
return {
|
||||
priceListId: priceList.id,
|
||||
prices: priceList.prices
|
||||
.filter((price) =>
|
||||
variantIdPriceSetIdMap.has(price.variants?.[0]?.id)
|
||||
)
|
||||
.map((price) => {
|
||||
return {
|
||||
price_set_id: variantIdPriceSetIdMap.get(
|
||||
price.variants?.[0]?.id
|
||||
)!,
|
||||
currency_code: price.currency_code,
|
||||
amount: price.amount,
|
||||
min_quantity: price.min_quantity,
|
||||
max_quantity: price.max_quantity,
|
||||
}
|
||||
}),
|
||||
}
|
||||
})
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
if (priceListsToCreate.length) {
|
||||
promises.push(
|
||||
pricingModuleService.createPriceLists(
|
||||
priceListsToCreate.map(
|
||||
({ name: title, prices, customer_groups, ...priceList }) => {
|
||||
const createData: PricingTypes.CreatePriceListDTO = {
|
||||
...priceList,
|
||||
starts_at: priceList.starts_at?.toISOString(),
|
||||
ends_at: priceList.ends_at?.toISOString(),
|
||||
title,
|
||||
}
|
||||
|
||||
if (customer_groups?.length) {
|
||||
createData.rules = {
|
||||
customer_group_id: customer_groups.map(({ id }) => id),
|
||||
}
|
||||
}
|
||||
|
||||
if (prices?.length) {
|
||||
createData.prices = prices.map((price) => {
|
||||
return {
|
||||
price_set_id: variantIdPriceSetIdMap.get(
|
||||
price.variants?.[0]?.id
|
||||
)!,
|
||||
currency_code: price.currency_code,
|
||||
amount: price.amount,
|
||||
min_quantity: price.min_quantity,
|
||||
max_quantity: price.max_quantity,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return createData
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
await promiseAll(promises)
|
||||
|
||||
offset += corePriceLists.length
|
||||
|
||||
Logger.info(`Processed ${offset} of ${totalCount}`)
|
||||
}
|
||||
}
|
||||
|
||||
const ensureCurrencies = async (container: AwilixContainer) => {
|
||||
const currenciesService: CurrencyService =
|
||||
container.resolve("currencyService")
|
||||
|
||||
const pricingModuleService: IPricingModuleService = container.resolve(
|
||||
"pricingModuleService"
|
||||
)
|
||||
|
||||
const [coreCurrencies, totalCurrencies] =
|
||||
await currenciesService.listAndCount({}, {})
|
||||
|
||||
const moduleCurrencies = await pricingModuleService.listCurrencies(
|
||||
{},
|
||||
{ take: 100000 }
|
||||
)
|
||||
|
||||
const moduleCurrenciesSet = new Set(moduleCurrencies.map(({ code }) => code))
|
||||
|
||||
const currenciesToCreate = coreCurrencies
|
||||
.filter(({ code }) => {
|
||||
return !moduleCurrenciesSet.has(code)
|
||||
})
|
||||
.map(({ includes_tax, ...currency }) => currency)
|
||||
|
||||
await pricingModuleService.createCurrencies(currenciesToCreate)
|
||||
}
|
||||
|
||||
const migrate = async function ({ directory }) {
|
||||
const app = express()
|
||||
|
||||
const { container } = await loaders({
|
||||
directory,
|
||||
expressApp: app,
|
||||
isTest: false,
|
||||
})
|
||||
|
||||
Logger.info("-----------------------------------------------")
|
||||
Logger.info("------------- Creating currencies -------------")
|
||||
Logger.info("-----------------------------------------------")
|
||||
await ensureCurrencies(container)
|
||||
|
||||
Logger.info("-----------------------------------------------")
|
||||
Logger.info("--------- Creating default rule types ---------")
|
||||
Logger.info("-----------------------------------------------")
|
||||
await createDefaultRuleTypes(container)
|
||||
|
||||
Logger.info("-----------------------------------------------")
|
||||
Logger.info("---------- Migrating Variant Prices -----------")
|
||||
Logger.info("-----------------------------------------------")
|
||||
|
||||
await migrateProductVariantPricing(container)
|
||||
|
||||
Logger.info("-----------------------------------------------")
|
||||
Logger.info("----------- Migrating Price Lists -------------")
|
||||
Logger.info("-----------------------------------------------")
|
||||
|
||||
return await migratePriceLists(container)
|
||||
}
|
||||
|
||||
migrate({ directory: process.cwd() })
|
||||
.then(() => {
|
||||
Logger.info("Migrated price lists")
|
||||
process.exit(0)
|
||||
})
|
||||
.catch((error) => {
|
||||
console.warn(error)
|
||||
Logger.info("Failed to migrate price lists")
|
||||
process.exit(1)
|
||||
})
|
||||
@@ -1,140 +0,0 @@
|
||||
import { IPricingModuleService, MedusaContainer } from "@medusajs/types"
|
||||
import {
|
||||
FlagRouter,
|
||||
MedusaError,
|
||||
MedusaV2Flag,
|
||||
promiseAll,
|
||||
} from "@medusajs/utils"
|
||||
import dotenv from "dotenv"
|
||||
import express from "express"
|
||||
import { EntityManager } from "typeorm"
|
||||
import loaders from "../loaders"
|
||||
import loadMedusaApp from "../loaders/medusa-app"
|
||||
import { ProductVariant } from "../models"
|
||||
import { ProductVariantService } from "../services"
|
||||
import { createDefaultRuleTypes } from "./create-default-rule-types"
|
||||
|
||||
dotenv.config()
|
||||
|
||||
const BATCH_SIZE = 100
|
||||
|
||||
const migrateProductVariant = async (
|
||||
variant: ProductVariant,
|
||||
{
|
||||
container,
|
||||
}: { container: MedusaContainer; transactionManager: EntityManager }
|
||||
) => {
|
||||
const pricingService: IPricingModuleService = container.resolve(
|
||||
"pricingModuleService"
|
||||
)
|
||||
|
||||
const configModule = await container.resolve("configModule")
|
||||
const { link } = await loadMedusaApp(
|
||||
{ configModule, container },
|
||||
{ registerInContainer: false }
|
||||
)
|
||||
|
||||
if (!link) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
"Can't migrate money_amounts: Pricing module is not configured correctly"
|
||||
)
|
||||
}
|
||||
|
||||
const priceSet = await pricingService.create({
|
||||
rules: [{ rule_attribute: "region_id" }],
|
||||
prices: variant.prices.map((price) => ({
|
||||
rules: {
|
||||
...(price.region_id ? { region_id: price.region_id } : {}),
|
||||
},
|
||||
currency_code: price.currency_code,
|
||||
min_quantity: price.min_quantity,
|
||||
max_quantity: price.max_quantity,
|
||||
amount: price.amount,
|
||||
})),
|
||||
})
|
||||
|
||||
await link.create({
|
||||
productService: {
|
||||
variant_id: variant.id,
|
||||
},
|
||||
pricingService: {
|
||||
price_set_id: priceSet.id,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const processBatch = async (
|
||||
variants: ProductVariant[],
|
||||
container: MedusaContainer
|
||||
) => {
|
||||
const manager = container.resolve("manager")
|
||||
return await manager.transaction(async (transactionManager) => {
|
||||
await promiseAll(
|
||||
variants.map(async (variant) => {
|
||||
await migrateProductVariant(variant, {
|
||||
container,
|
||||
transactionManager,
|
||||
})
|
||||
})
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
const migrate = async function ({ directory }) {
|
||||
const app = express()
|
||||
const { container } = await loaders({
|
||||
directory,
|
||||
expressApp: app,
|
||||
isTest: false,
|
||||
})
|
||||
|
||||
const variantService: ProductVariantService = await container.resolve(
|
||||
"productVariantService"
|
||||
)
|
||||
const featureFlagRouter: FlagRouter = await container.resolve(
|
||||
"featureFlagRouter"
|
||||
)
|
||||
|
||||
if (!featureFlagRouter.isFeatureEnabled(MedusaV2Flag.key)) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
"Pricing module not enabled"
|
||||
)
|
||||
}
|
||||
|
||||
await createDefaultRuleTypes(container)
|
||||
|
||||
const [variants, totalCount] = await variantService.listAndCount(
|
||||
{},
|
||||
{ take: BATCH_SIZE, order: { id: "ASC" }, relations: ["prices"] }
|
||||
)
|
||||
|
||||
await processBatch(variants, container)
|
||||
|
||||
let processedCount = variants.length
|
||||
|
||||
console.log(`Processed ${processedCount} of ${totalCount}`)
|
||||
|
||||
while (processedCount < totalCount) {
|
||||
const nextBatch = await variantService.list(
|
||||
{},
|
||||
{
|
||||
skip: processedCount,
|
||||
take: BATCH_SIZE,
|
||||
order: { id: "ASC" },
|
||||
relations: ["prices"],
|
||||
}
|
||||
)
|
||||
|
||||
await processBatch(nextBatch, container)
|
||||
|
||||
processedCount += nextBatch.length
|
||||
console.log(`Processed ${processedCount} of ${totalCount}`)
|
||||
}
|
||||
|
||||
console.log("Done")
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
migrate({ directory: process.cwd() })
|
||||
@@ -0,0 +1,31 @@
|
||||
import { AwilixContainer } from "awilix"
|
||||
import { IPricingModuleService } from "@medusajs/types"
|
||||
|
||||
export const createDefaultRuleTypes = async (container: AwilixContainer) => {
|
||||
const pricingModuleService: IPricingModuleService = container.resolve(
|
||||
"pricingModuleService"
|
||||
)
|
||||
const existing = await pricingModuleService.listRuleTypes(
|
||||
{ rule_attribute: ["region_id", "customer_group_id"] },
|
||||
{ take: 2 }
|
||||
)
|
||||
|
||||
if (existing.length === 2) {
|
||||
return
|
||||
}
|
||||
|
||||
if (existing.length === 0) {
|
||||
await pricingModuleService.createRuleTypes([
|
||||
{ name: "region_id", rule_attribute: "region_id" },
|
||||
{ name: "customer_group_id", rule_attribute: "customer_group_id" },
|
||||
])
|
||||
} else if (existing[0].rule_attribute === "region_id") {
|
||||
await pricingModuleService.createRuleTypes([
|
||||
{ name: "customer_group_id", rule_attribute: "customer_group_id" },
|
||||
])
|
||||
} else {
|
||||
await pricingModuleService.createRuleTypes([
|
||||
{ name: "region_id", rule_attribute: "region_id" },
|
||||
])
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
import { IPricingModuleService, MedusaContainer } from "@medusajs/types"
|
||||
import { MedusaError, promiseAll } from "@medusajs/utils"
|
||||
|
||||
import { ProductVariantService } from "../../services"
|
||||
import dotenv from "dotenv"
|
||||
|
||||
dotenv.config()
|
||||
|
||||
const BATCH_SIZE = 100
|
||||
|
||||
export const migrateProductVariantPricing = async function (
|
||||
container: MedusaContainer
|
||||
) {
|
||||
const variantService: ProductVariantService = await container.resolve(
|
||||
"productVariantService"
|
||||
)
|
||||
|
||||
const pricingService: IPricingModuleService = container.resolve(
|
||||
"pricingModuleService"
|
||||
)
|
||||
|
||||
const link = await container.resolve("remoteLink")
|
||||
|
||||
if (!link) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
"Can't migrate money_amounts: Pricing module is not configured correctly"
|
||||
)
|
||||
}
|
||||
|
||||
const [_, totalCount] = await variantService.listAndCount(
|
||||
{},
|
||||
{ take: BATCH_SIZE, order: { id: "ASC" }, relations: ["prices"] }
|
||||
)
|
||||
|
||||
let processedCount = 0
|
||||
while (processedCount < totalCount) {
|
||||
const [variants] = await variantService.listAndCount(
|
||||
{},
|
||||
{
|
||||
skip: processedCount,
|
||||
take: BATCH_SIZE,
|
||||
order: { id: "ASC" },
|
||||
relations: ["prices"],
|
||||
}
|
||||
)
|
||||
|
||||
const links: any[] = []
|
||||
|
||||
await promiseAll(
|
||||
variants.map(async (variant) => {
|
||||
const priceSet = await pricingService.create({
|
||||
rules: [{ rule_attribute: "region_id" }],
|
||||
prices:
|
||||
variant?.prices
|
||||
?.filter(({ price_list_id }) => !price_list_id)
|
||||
.map((price) => ({
|
||||
rules: {
|
||||
...(price.region_id ? { region_id: price.region_id } : {}),
|
||||
},
|
||||
currency_code: price.currency_code,
|
||||
min_quantity: price.min_quantity,
|
||||
max_quantity: price.max_quantity,
|
||||
amount: price.amount,
|
||||
})) ?? [],
|
||||
})
|
||||
|
||||
links.push({
|
||||
productService: {
|
||||
variant_id: variant.id,
|
||||
},
|
||||
pricingService: {
|
||||
price_set_id: priceSet.id,
|
||||
},
|
||||
})
|
||||
})
|
||||
)
|
||||
await link.create(links)
|
||||
|
||||
processedCount += variants.length
|
||||
console.log(`Processed ${processedCount} of ${totalCount}`)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user