feat: Add support for batch method for products and product variants (#7038)
* feat(products): Add batch methods for product and variants * chore: Rename batch validator and minor changes based on PR review
This commit is contained in:
@@ -1,27 +1,48 @@
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
import { IPricingModuleService, PricingTypes } from "@medusajs/types"
|
||||
import {
|
||||
convertItemResponseToUpdateRequest,
|
||||
MedusaError,
|
||||
getSelectsAndRelationsFromObjectArray,
|
||||
} from "@medusajs/utils"
|
||||
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
|
||||
|
||||
type UpdatePriceSetsStepInput = {
|
||||
selector?: PricingTypes.FilterablePriceSetProps
|
||||
update?: PricingTypes.UpdatePriceSetDTO
|
||||
}
|
||||
type UpdatePriceSetsStepInput =
|
||||
| {
|
||||
selector?: PricingTypes.FilterablePriceSetProps
|
||||
update?: PricingTypes.UpdatePriceSetDTO
|
||||
}
|
||||
| {
|
||||
price_sets: PricingTypes.UpsertPriceSetDTO[]
|
||||
}
|
||||
|
||||
export const updatePriceSetsStepId = "update-price-sets"
|
||||
export const updatePriceSetsStep = createStep(
|
||||
updatePriceSetsStepId,
|
||||
async (data: UpdatePriceSetsStepInput, { container }) => {
|
||||
if (!data.selector || !data.update) {
|
||||
return new StepResponse([], null)
|
||||
}
|
||||
|
||||
const pricingModule = container.resolve<IPricingModuleService>(
|
||||
ModuleRegistrationName.PRICING
|
||||
)
|
||||
|
||||
if ("price_sets" in data) {
|
||||
if (data.price_sets.some((p) => !p.id)) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
"Price set id is required when doing a batch update"
|
||||
)
|
||||
}
|
||||
|
||||
const prevData = await pricingModule.list({
|
||||
id: data.price_sets.map((p) => p.id) as string[],
|
||||
})
|
||||
|
||||
const priceSets = await pricingModule.upsert(data.price_sets)
|
||||
return new StepResponse(priceSets, prevData)
|
||||
}
|
||||
|
||||
if (!data.selector || !data.update) {
|
||||
return new StepResponse([], null)
|
||||
}
|
||||
|
||||
const { selects, relations } = getSelectsAndRelationsFromObjectArray([
|
||||
data.update,
|
||||
])
|
||||
@@ -36,27 +57,17 @@ export const updatePriceSetsStep = createStep(
|
||||
data.update
|
||||
)
|
||||
|
||||
return new StepResponse(updatedPriceSets, {
|
||||
dataBeforeUpdate,
|
||||
selects,
|
||||
relations,
|
||||
})
|
||||
return new StepResponse(updatedPriceSets, dataBeforeUpdate)
|
||||
},
|
||||
async (revertInput, { container }) => {
|
||||
if (!revertInput || !revertInput.dataBeforeUpdate?.length) {
|
||||
return
|
||||
}
|
||||
|
||||
const { dataBeforeUpdate, selects, relations } = revertInput
|
||||
|
||||
const pricingModule = container.resolve<IPricingModuleService>(
|
||||
ModuleRegistrationName.PRICING
|
||||
)
|
||||
|
||||
await pricingModule.upsert(
|
||||
dataBeforeUpdate.map((data) =>
|
||||
convertItemResponseToUpdateRequest(data, selects, relations)
|
||||
)
|
||||
)
|
||||
if (!revertInput) {
|
||||
return
|
||||
}
|
||||
|
||||
await pricingModule.upsert(revertInput as PricingTypes.UpsertPriceSetDTO[])
|
||||
}
|
||||
)
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
|
||||
import { deleteProductVariantsWorkflow } from "../workflows/delete-product-variants"
|
||||
import { createProductVariantsWorkflow } from "../workflows/create-product-variants"
|
||||
import { updateProductVariantsWorkflow } from "../workflows/update-product-variants"
|
||||
import { PricingTypes, ProductTypes } from "@medusajs/types"
|
||||
|
||||
type BatchProductVariantsInput = {
|
||||
create: (ProductTypes.CreateProductVariantDTO & {
|
||||
prices?: PricingTypes.CreateMoneyAmountDTO[]
|
||||
})[]
|
||||
update: (ProductTypes.UpsertProductVariantDTO & {
|
||||
prices?: PricingTypes.CreateMoneyAmountDTO[]
|
||||
})[]
|
||||
delete: string[]
|
||||
}
|
||||
|
||||
export const batchProductVariantsStepId = "batch-product-variants"
|
||||
export const batchProductVariantsStep = createStep(
|
||||
batchProductVariantsStepId,
|
||||
async (data: BatchProductVariantsInput, { container }) => {
|
||||
const { transaction: createTransaction, result: created } =
|
||||
await createProductVariantsWorkflow(container).run({
|
||||
input: { product_variants: data.create },
|
||||
})
|
||||
const { transaction: updateTransaction, result: updated } =
|
||||
await updateProductVariantsWorkflow(container).run({
|
||||
input: { product_variants: data.update },
|
||||
})
|
||||
const { transaction: deleteTransaction } =
|
||||
await deleteProductVariantsWorkflow(container).run({
|
||||
input: { ids: data.delete },
|
||||
})
|
||||
|
||||
return new StepResponse(
|
||||
{
|
||||
created,
|
||||
updated,
|
||||
deleted: {
|
||||
ids: data.delete,
|
||||
object: "product_variant",
|
||||
deleted: true,
|
||||
},
|
||||
},
|
||||
{ createTransaction, updateTransaction, deleteTransaction }
|
||||
)
|
||||
},
|
||||
|
||||
async (flow, { container }) => {
|
||||
if (!flow) {
|
||||
return
|
||||
}
|
||||
|
||||
if (flow.createTransaction) {
|
||||
await createProductVariantsWorkflow(container).cancel({
|
||||
transaction: flow.createTransaction,
|
||||
})
|
||||
}
|
||||
|
||||
if (flow.updateTransaction) {
|
||||
await updateProductVariantsWorkflow(container).cancel({
|
||||
transaction: flow.updateTransaction,
|
||||
})
|
||||
}
|
||||
|
||||
if (flow.deleteTransaction) {
|
||||
await deleteProductVariantsWorkflow(container).cancel({
|
||||
transaction: flow.deleteTransaction,
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,75 @@
|
||||
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
|
||||
import { createProductsWorkflow } from "../workflows/create-products"
|
||||
import { updateProductsWorkflow } from "../workflows/update-products"
|
||||
import { deleteProductsWorkflow } from "../workflows/delete-products"
|
||||
import { PricingTypes, ProductTypes } from "@medusajs/types"
|
||||
|
||||
type WorkflowInput = {
|
||||
create: (Omit<ProductTypes.CreateProductDTO, "variants"> & {
|
||||
sales_channels?: { id: string }[]
|
||||
variants?: (ProductTypes.CreateProductVariantDTO & {
|
||||
prices?: PricingTypes.CreateMoneyAmountDTO[]
|
||||
})[]
|
||||
})[]
|
||||
update: (ProductTypes.UpsertProductDTO & {
|
||||
sales_channels?: { id: string }[]
|
||||
})[]
|
||||
delete: string[]
|
||||
}
|
||||
|
||||
export const batchProductsStepId = "batch-products"
|
||||
export const batchProductsStep = createStep(
|
||||
batchProductsStepId,
|
||||
async (data: WorkflowInput, { container }) => {
|
||||
const { transaction: createTransaction, result: created } =
|
||||
await createProductsWorkflow(container).run({
|
||||
input: { products: data.create },
|
||||
})
|
||||
const { transaction: updateTransaction, result: updated } =
|
||||
await updateProductsWorkflow(container).run({
|
||||
input: { products: data.update },
|
||||
})
|
||||
const { transaction: deleteTransaction } = await deleteProductsWorkflow(
|
||||
container
|
||||
).run({
|
||||
input: { ids: data.delete },
|
||||
})
|
||||
|
||||
return new StepResponse(
|
||||
{
|
||||
created,
|
||||
updated,
|
||||
deleted: {
|
||||
ids: data.delete,
|
||||
object: "product",
|
||||
deleted: true,
|
||||
},
|
||||
},
|
||||
{ createTransaction, updateTransaction, deleteTransaction }
|
||||
)
|
||||
},
|
||||
|
||||
async (flow, { container }) => {
|
||||
if (!flow) {
|
||||
return
|
||||
}
|
||||
|
||||
if (flow.createTransaction) {
|
||||
await createProductsWorkflow(container).cancel({
|
||||
transaction: flow.createTransaction,
|
||||
})
|
||||
}
|
||||
|
||||
if (flow.updateTransaction) {
|
||||
await updateProductsWorkflow(container).cancel({
|
||||
transaction: flow.updateTransaction,
|
||||
})
|
||||
}
|
||||
|
||||
if (flow.deleteTransaction) {
|
||||
await deleteProductsWorkflow(container).cancel({
|
||||
transaction: flow.deleteTransaction,
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -2,6 +2,7 @@ export * from "./create-products"
|
||||
export * from "./update-products"
|
||||
export * from "./delete-products"
|
||||
export * from "./get-products"
|
||||
export * from "./batch-products"
|
||||
export * from "./create-variant-pricing-link"
|
||||
export * from "./create-product-options"
|
||||
export * from "./update-product-options"
|
||||
@@ -9,6 +10,7 @@ export * from "./delete-product-options"
|
||||
export * from "./create-product-variants"
|
||||
export * from "./update-product-variants"
|
||||
export * from "./delete-product-variants"
|
||||
export * from "./batch-product-variants"
|
||||
export * from "./create-collections"
|
||||
export * from "./update-collections"
|
||||
export * from "./delete-collections"
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
import { IProductModuleService, ProductTypes } from "@medusajs/types"
|
||||
import { getSelectsAndRelationsFromObjectArray } from "@medusajs/utils"
|
||||
import {
|
||||
MedusaError,
|
||||
getSelectsAndRelationsFromObjectArray,
|
||||
} from "@medusajs/utils"
|
||||
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
|
||||
|
||||
type UpdateProductVariantsStepInput = {
|
||||
selector: ProductTypes.FilterableProductVariantProps
|
||||
update: ProductTypes.UpdateProductVariantDTO
|
||||
}
|
||||
type UpdateProductVariantsStepInput =
|
||||
| {
|
||||
selector: ProductTypes.FilterableProductVariantProps
|
||||
update: ProductTypes.UpdateProductVariantDTO
|
||||
}
|
||||
| {
|
||||
product_variants: ProductTypes.UpsertProductVariantDTO[]
|
||||
}
|
||||
|
||||
export const updateProductVariantsStepId = "update-product-variants"
|
||||
export const updateProductVariantsStep = createStep(
|
||||
@@ -16,6 +23,24 @@ export const updateProductVariantsStep = createStep(
|
||||
ModuleRegistrationName.PRODUCT
|
||||
)
|
||||
|
||||
if ("product_variants" in data) {
|
||||
if (data.product_variants.some((p) => !p.id)) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
"Product variant ID is required when doing a batch update of product variants"
|
||||
)
|
||||
}
|
||||
|
||||
const prevData = await service.listVariants({
|
||||
id: data.product_variants.map((p) => p.id) as string[],
|
||||
})
|
||||
|
||||
const productVariants = await service.upsertVariants(
|
||||
data.product_variants
|
||||
)
|
||||
return new StepResponse(productVariants, prevData)
|
||||
}
|
||||
|
||||
const { selects, relations } = getSelectsAndRelationsFromObjectArray([
|
||||
data.update,
|
||||
])
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
import { IProductModuleService, ProductTypes } from "@medusajs/types"
|
||||
import { getSelectsAndRelationsFromObjectArray } from "@medusajs/utils"
|
||||
import {
|
||||
MedusaError,
|
||||
getSelectsAndRelationsFromObjectArray,
|
||||
} from "@medusajs/utils"
|
||||
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
|
||||
|
||||
type UpdateProductsStepInput = {
|
||||
selector: ProductTypes.FilterableProductProps
|
||||
update: ProductTypes.UpdateProductDTO
|
||||
}
|
||||
type UpdateProductsStepInput =
|
||||
| {
|
||||
selector: ProductTypes.FilterableProductProps
|
||||
update: ProductTypes.UpdateProductDTO
|
||||
}
|
||||
| {
|
||||
products: ProductTypes.UpsertProductDTO[]
|
||||
}
|
||||
|
||||
export const updateProductsStepId = "update-products"
|
||||
export const updateProductsStep = createStep(
|
||||
@@ -16,6 +23,22 @@ export const updateProductsStep = createStep(
|
||||
ModuleRegistrationName.PRODUCT
|
||||
)
|
||||
|
||||
if ("products" in data) {
|
||||
if (data.products.some((p) => !p.id)) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
"Product ID is required when doing a batch update of products"
|
||||
)
|
||||
}
|
||||
|
||||
const prevData = await service.list({
|
||||
id: data.products.map((p) => p.id) as string[],
|
||||
})
|
||||
|
||||
const products = await service.upsert(data.products)
|
||||
return new StepResponse(products, prevData)
|
||||
}
|
||||
|
||||
const { selects, relations } = getSelectsAndRelationsFromObjectArray([
|
||||
data.update,
|
||||
])
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
|
||||
import { batchProductVariantsStep } from "../steps/batch-product-variants"
|
||||
import { PricingTypes, ProductTypes } from "@medusajs/types"
|
||||
|
||||
type BatchProductVariantsInput = {
|
||||
create: (ProductTypes.CreateProductVariantDTO & {
|
||||
prices?: PricingTypes.CreateMoneyAmountDTO[]
|
||||
})[]
|
||||
update: (ProductTypes.UpsertProductVariantDTO & {
|
||||
prices?: PricingTypes.CreateMoneyAmountDTO[]
|
||||
})[]
|
||||
delete: string[]
|
||||
}
|
||||
|
||||
type BatchProductVariantsOutput = {
|
||||
created: ProductTypes.ProductVariantDTO[]
|
||||
updated: ProductTypes.ProductVariantDTO[]
|
||||
deleted: {
|
||||
ids: string[]
|
||||
object: string
|
||||
deleted: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export const batchProductVariantsWorkflowId = "batch-product-variants"
|
||||
export const batchProductVariantsWorkflow = createWorkflow(
|
||||
batchProductVariantsWorkflowId,
|
||||
(
|
||||
input: WorkflowData<BatchProductVariantsInput>
|
||||
): WorkflowData<BatchProductVariantsOutput> => {
|
||||
return batchProductVariantsStep(input)
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,34 @@
|
||||
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
|
||||
import { batchProductsStep } from "../steps/batch-products"
|
||||
import { PricingTypes, ProductTypes } from "@medusajs/types"
|
||||
|
||||
type WorkflowInput = {
|
||||
create: (Omit<ProductTypes.CreateProductDTO, "variants"> & {
|
||||
sales_channels?: { id: string }[]
|
||||
variants?: (ProductTypes.CreateProductVariantDTO & {
|
||||
prices?: PricingTypes.CreateMoneyAmountDTO[]
|
||||
})[]
|
||||
})[]
|
||||
update: (ProductTypes.UpsertProductDTO & {
|
||||
sales_channels?: { id: string }[]
|
||||
})[]
|
||||
delete: string[]
|
||||
}
|
||||
|
||||
type BatchProductsOutput = {
|
||||
created: ProductTypes.ProductDTO[]
|
||||
updated: ProductTypes.ProductDTO[]
|
||||
deleted: {
|
||||
ids: string[]
|
||||
object: string
|
||||
deleted: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export const batchProductsWorkflowId = "batch-products"
|
||||
export const batchProductsWorkflow = createWorkflow(
|
||||
batchProductsWorkflowId,
|
||||
(input: WorkflowData<WorkflowInput>): WorkflowData<BatchProductsOutput> => {
|
||||
return batchProductsStep(input)
|
||||
}
|
||||
)
|
||||
@@ -4,7 +4,8 @@ import {
|
||||
createWorkflow,
|
||||
transform,
|
||||
} from "@medusajs/workflows-sdk"
|
||||
import { createProductsStep, createVariantPricingLinkStep } from "../steps"
|
||||
import { createProductsStep } from "../steps/create-products"
|
||||
import { createVariantPricingLinkStep } from "../steps/create-variant-pricing-link"
|
||||
import { createPriceSetsStep } from "../../pricing"
|
||||
import { associateProductsWithSalesChannelsStep } from "../../sales-channel"
|
||||
|
||||
|
||||
@@ -4,7 +4,8 @@ import {
|
||||
transform,
|
||||
} from "@medusajs/workflows-sdk"
|
||||
import { Modules } from "@medusajs/modules-sdk"
|
||||
import { deleteProductsStep, getProductsStep } from "../steps"
|
||||
import { deleteProductsStep } from "../steps/delete-products"
|
||||
import { getProductsStep } from "../steps/get-products"
|
||||
import { removeRemoteLinkStep } from "../../common"
|
||||
|
||||
type WorkflowInput = { ids: string[] }
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
export * from "./create-products"
|
||||
export * from "./delete-products"
|
||||
export * from "./update-products"
|
||||
export * from "./batch-products"
|
||||
export * from "./create-product-options"
|
||||
export * from "./delete-product-options"
|
||||
export * from "./update-product-options"
|
||||
export * from "./create-product-variants"
|
||||
export * from "./delete-product-variants"
|
||||
export * from "./update-product-variants"
|
||||
export * from "./batch-product-variants"
|
||||
export * from "./create-collections"
|
||||
export * from "./delete-collections"
|
||||
export * from "./update-collections"
|
||||
|
||||
@@ -8,12 +8,18 @@ import { updateProductVariantsStep } from "../steps"
|
||||
import { updatePriceSetsStep } from "../../pricing"
|
||||
import { getVariantPricingLinkStep } from "../steps/get-variant-pricing-link"
|
||||
|
||||
type UpdateProductVariantsStepInput = {
|
||||
selector: ProductTypes.FilterableProductVariantProps
|
||||
update: ProductTypes.UpdateProductVariantDTO & {
|
||||
prices?: Partial<PricingTypes.CreateMoneyAmountDTO>[]
|
||||
}
|
||||
}
|
||||
type UpdateProductVariantsStepInput =
|
||||
| {
|
||||
selector: ProductTypes.FilterableProductVariantProps
|
||||
update: ProductTypes.UpdateProductVariantDTO & {
|
||||
prices?: Partial<PricingTypes.CreateMoneyAmountDTO>[]
|
||||
}
|
||||
}
|
||||
| {
|
||||
product_variants: (ProductTypes.UpsertProductVariantDTO & {
|
||||
prices?: Partial<PricingTypes.CreateMoneyAmountDTO>[]
|
||||
})[]
|
||||
}
|
||||
|
||||
type WorkflowInput = UpdateProductVariantsStepInput
|
||||
|
||||
@@ -25,6 +31,17 @@ export const updateProductVariantsWorkflow = createWorkflow(
|
||||
): WorkflowData<ProductTypes.ProductVariantDTO[]> => {
|
||||
// Passing prices to the product module will fail, we want to keep them for after the variant is updated.
|
||||
const updateWithoutPrices = transform({ input }, (data) => {
|
||||
if ("product_variants" in data.input) {
|
||||
return {
|
||||
product_variants: data.input.product_variants.map((variant) => {
|
||||
return {
|
||||
...variant,
|
||||
prices: undefined,
|
||||
}
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
selector: data.input.selector,
|
||||
update: {
|
||||
@@ -38,6 +55,10 @@ export const updateProductVariantsWorkflow = createWorkflow(
|
||||
|
||||
// We don't want to do any pricing updates if the prices didn't change
|
||||
const variantIds = transform({ input, updatedVariants }, (data) => {
|
||||
if ("product_variants" in data.input) {
|
||||
return data.updatedVariants.map((v) => v.id)
|
||||
}
|
||||
|
||||
if (!data.input.update.prices) {
|
||||
return []
|
||||
}
|
||||
@@ -56,6 +77,19 @@ export const updateProductVariantsWorkflow = createWorkflow(
|
||||
return {}
|
||||
}
|
||||
|
||||
if ("product_variants" in data.input) {
|
||||
return data.variantPriceSetLinks.map((link) => {
|
||||
const variant = (data.input as any).product_variants.find(
|
||||
(v) => v.id === link.variant_id
|
||||
)
|
||||
|
||||
return {
|
||||
id: link.price_set_id,
|
||||
prices: variant.prices,
|
||||
} as PricingTypes.UpsertPriceSetDTO
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
selector: {
|
||||
id: data.variantPriceSetLinks.map((link) => link.price_set_id),
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import { ProductTypes } from "@medusajs/types"
|
||||
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
|
||||
import { updateProductsStep } from "../steps"
|
||||
import { updateProductsStep } from "../steps/update-products"
|
||||
|
||||
type UpdateProductsStepInput = {
|
||||
selector: ProductTypes.FilterableProductProps
|
||||
update: ProductTypes.UpdateProductDTO
|
||||
}
|
||||
type UpdateProductsStepInput =
|
||||
| {
|
||||
selector: ProductTypes.FilterableProductProps
|
||||
update: ProductTypes.UpdateProductDTO
|
||||
}
|
||||
| {
|
||||
products: ProductTypes.UpsertProductDTO[]
|
||||
}
|
||||
|
||||
type WorkflowInput = UpdateProductsStepInput
|
||||
|
||||
|
||||
Reference in New Issue
Block a user