feat(medusa, types, utils, workflow): Migrate medusa workflow to the workflow package (#4682)
This commit is contained in:
1 parent
433914ae01
commit
dc46927bc6
68 files changed
+1224
-1064
No files matched your search
@@ -36,13 +36,13 @@ import { DistributedTransaction } from "@medusajs/orchestration"
|
||||
import { EntityManager } from "typeorm"
|
||||
import { FeatureFlagDecorators } from "../../../../utils/feature-flag-decorators"
|
||||
import { FlagRouter } from "../../../../utils/flag-router"
|
||||
import { IInventoryService } from "@medusajs/types"
|
||||
import { IInventoryService, WorkflowTypes } from "@medusajs/types"
|
||||
import { Logger } from "../../../../types/global"
|
||||
import { ProductStatus } from "../../../../models"
|
||||
import SalesChannelFeatureFlag from "../../../../loaders/feature-flags/sales-channels"
|
||||
import { Type } from "class-transformer"
|
||||
import { createProductsWorkflow } from "../../../../workflows/admin/create-products"
|
||||
import { validator } from "../../../../utils/validator"
|
||||
import { validator } from "../../../../utils"
|
||||
import { createProducts } from "@medusajs/workflows"
|
||||
|
||||
/**
|
||||
* @oas [post] /admin/products
|
||||
@@ -133,13 +133,26 @@ export default async (req, res) => {
|
||||
const productModuleService = req.scope.resolve("productModuleService")
|
||||
|
||||
if (productModuleService) {
|
||||
const products = await createProductsWorkflow(
|
||||
{
|
||||
container: req.scope,
|
||||
const createProductWorkflow = createProducts(req.scope)
|
||||
|
||||
const input = {
|
||||
products: [
|
||||
validated,
|
||||
] as WorkflowTypes.ProductWorkflow.CreateProductInputDTO[],
|
||||
config: {
|
||||
listConfig: {
|
||||
select: defaultAdminProductFields,
|
||||
relations: defaultAdminProductRelations,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const { result: products } = await createProductWorkflow.run({
|
||||
input,
|
||||
context: {
|
||||
manager: entityManager,
|
||||
},
|
||||
[validated]
|
||||
)
|
||||
})
|
||||
|
||||
return res.json({ product: products[0] })
|
||||
}
|
||||
|
||||
@@ -1,421 +0,0 @@
|
||||
import {
|
||||
TransactionHandlerType,
|
||||
TransactionPayload,
|
||||
TransactionStepHandler,
|
||||
TransactionStepsDefinition,
|
||||
} from "@medusajs/orchestration"
|
||||
import {
|
||||
IInventoryService,
|
||||
MedusaContainer,
|
||||
ProductTypes,
|
||||
} from "@medusajs/types"
|
||||
import {
|
||||
defaultAdminProductFields,
|
||||
defaultAdminProductRelations,
|
||||
} from "../../../api"
|
||||
import {
|
||||
attachInventoryItems,
|
||||
attachSalesChannelToProducts,
|
||||
attachShippingProfileToProducts,
|
||||
createInventoryItems,
|
||||
createProducts,
|
||||
CreateProductsData,
|
||||
CreateProductsPreparedData,
|
||||
detachInventoryItems,
|
||||
detachSalesChannelFromProducts,
|
||||
detachShippingProfileFromProducts,
|
||||
prepareCreateProductsData,
|
||||
removeInventoryItems,
|
||||
removeProducts,
|
||||
updateProductsVariantsPrices,
|
||||
} from "../../functions"
|
||||
import { PricingService, ProductService } from "../../../services"
|
||||
import { CreateProductsWorkflowInputData, InjectedDependencies } from "./types"
|
||||
|
||||
export enum CreateProductsWorkflowActions {
|
||||
prepare = "prepare",
|
||||
createProducts = "createProducts",
|
||||
attachToSalesChannel = "attachToSalesChannel",
|
||||
attachShippingProfile = "attachShippingProfile",
|
||||
createPrices = "createPrices",
|
||||
createInventoryItems = "createInventoryItems",
|
||||
attachInventoryItems = "attachInventoryItems",
|
||||
result = "result",
|
||||
}
|
||||
|
||||
export const workflowSteps: TransactionStepsDefinition = {
|
||||
next: {
|
||||
action: CreateProductsWorkflowActions.prepare,
|
||||
noCompensation: true,
|
||||
next: {
|
||||
action: CreateProductsWorkflowActions.createProducts,
|
||||
next: [
|
||||
{
|
||||
action: CreateProductsWorkflowActions.attachShippingProfile,
|
||||
saveResponse: false,
|
||||
},
|
||||
{
|
||||
action: CreateProductsWorkflowActions.attachToSalesChannel,
|
||||
saveResponse: false,
|
||||
},
|
||||
{
|
||||
action: CreateProductsWorkflowActions.createPrices,
|
||||
next: {
|
||||
action: CreateProductsWorkflowActions.createInventoryItems,
|
||||
next: {
|
||||
action: CreateProductsWorkflowActions.attachInventoryItems,
|
||||
next: {
|
||||
action: CreateProductsWorkflowActions.result,
|
||||
noCompensation: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const shouldSkipInventoryStep = (
|
||||
container: MedusaContainer,
|
||||
stepName: string
|
||||
) => {
|
||||
const inventoryService = container.resolve(
|
||||
"inventoryService"
|
||||
) as IInventoryService
|
||||
if (!inventoryService) {
|
||||
const logger = container.resolve("logger")
|
||||
logger.warn(
|
||||
`Inventory service not found. You should install the @medusajs/inventory package to use inventory. The '${stepName}' will be skipped.`
|
||||
)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
export function transactionHandler(
|
||||
dependencies: InjectedDependencies
|
||||
): TransactionStepHandler {
|
||||
const { manager, container } = dependencies
|
||||
|
||||
const command = {
|
||||
[CreateProductsWorkflowActions.prepare]: {
|
||||
[TransactionHandlerType.INVOKE]: async (
|
||||
data: CreateProductsWorkflowInputData
|
||||
) => {
|
||||
return await prepareCreateProductsData({
|
||||
container,
|
||||
manager,
|
||||
data,
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
[CreateProductsWorkflowActions.createProducts]: {
|
||||
[TransactionHandlerType.INVOKE]: async (
|
||||
data: CreateProductsData
|
||||
): Promise<ProductTypes.ProductDTO[]> => {
|
||||
return await createProducts({
|
||||
container,
|
||||
data,
|
||||
})
|
||||
},
|
||||
[TransactionHandlerType.COMPENSATE]: async (
|
||||
data: CreateProductsWorkflowInputData,
|
||||
{ invoke }
|
||||
) => {
|
||||
const products = invoke[
|
||||
CreateProductsWorkflowActions.createProducts
|
||||
] as ProductTypes.ProductDTO[]
|
||||
|
||||
if (!products?.length) {
|
||||
return
|
||||
}
|
||||
|
||||
return await removeProducts({
|
||||
container,
|
||||
data: products,
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
[CreateProductsWorkflowActions.attachShippingProfile]: {
|
||||
[TransactionHandlerType.INVOKE]: async (
|
||||
data: CreateProductsWorkflowInputData,
|
||||
{ invoke }
|
||||
) => {
|
||||
const products = invoke[
|
||||
CreateProductsWorkflowActions.createProducts
|
||||
] as ProductTypes.ProductDTO[]
|
||||
const { productsHandleShippingProfileIdMap } = invoke[
|
||||
CreateProductsWorkflowActions.prepare
|
||||
] as CreateProductsPreparedData
|
||||
|
||||
return await attachShippingProfileToProducts({
|
||||
container,
|
||||
manager,
|
||||
data: {
|
||||
productsHandleShippingProfileIdMap,
|
||||
products,
|
||||
},
|
||||
})
|
||||
},
|
||||
[TransactionHandlerType.COMPENSATE]: async (
|
||||
data: CreateProductsWorkflowInputData,
|
||||
{ invoke }
|
||||
) => {
|
||||
const products = invoke[
|
||||
CreateProductsWorkflowActions.createProducts
|
||||
] as ProductTypes.ProductDTO[]
|
||||
const { productsHandleShippingProfileIdMap } = invoke[
|
||||
CreateProductsWorkflowActions.prepare
|
||||
] as CreateProductsPreparedData
|
||||
|
||||
return await detachShippingProfileFromProducts({
|
||||
container,
|
||||
manager,
|
||||
data: {
|
||||
productsHandleShippingProfileIdMap,
|
||||
products,
|
||||
},
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
[CreateProductsWorkflowActions.attachToSalesChannel]: {
|
||||
[TransactionHandlerType.INVOKE]: async (
|
||||
data: CreateProductsWorkflowInputData,
|
||||
{ invoke }
|
||||
) => {
|
||||
const products = invoke[
|
||||
CreateProductsWorkflowActions.createProducts
|
||||
] as ProductTypes.ProductDTO[]
|
||||
const { productsHandleSalesChannelsMap } = invoke[
|
||||
CreateProductsWorkflowActions.prepare
|
||||
] as CreateProductsPreparedData
|
||||
|
||||
return await attachSalesChannelToProducts({
|
||||
container,
|
||||
manager,
|
||||
data: {
|
||||
productsHandleSalesChannelsMap,
|
||||
products,
|
||||
},
|
||||
})
|
||||
},
|
||||
[TransactionHandlerType.COMPENSATE]: async (
|
||||
data: CreateProductsWorkflowInputData,
|
||||
{ invoke }
|
||||
) => {
|
||||
const products = invoke[
|
||||
CreateProductsWorkflowActions.createProducts
|
||||
] as ProductTypes.ProductDTO[]
|
||||
const { productsHandleSalesChannelsMap } = invoke[
|
||||
CreateProductsWorkflowActions.prepare
|
||||
] as CreateProductsPreparedData
|
||||
|
||||
return await detachSalesChannelFromProducts({
|
||||
container,
|
||||
manager,
|
||||
data: {
|
||||
productsHandleSalesChannelsMap,
|
||||
products,
|
||||
},
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
[CreateProductsWorkflowActions.createInventoryItems]: {
|
||||
[TransactionHandlerType.INVOKE]: async (
|
||||
data: CreateProductsWorkflowInputData,
|
||||
{ invoke }
|
||||
) => {
|
||||
const shouldSkipStep_ = shouldSkipInventoryStep(
|
||||
container,
|
||||
CreateProductsWorkflowActions.createInventoryItems
|
||||
)
|
||||
if (shouldSkipStep_) {
|
||||
return
|
||||
}
|
||||
|
||||
const { [CreateProductsWorkflowActions.createProducts]: products } =
|
||||
invoke
|
||||
|
||||
return await createInventoryItems({
|
||||
container,
|
||||
manager,
|
||||
data: products,
|
||||
})
|
||||
},
|
||||
[TransactionHandlerType.COMPENSATE]: async (_, { invoke }) => {
|
||||
const shouldSkipStep_ = shouldSkipInventoryStep(
|
||||
container,
|
||||
CreateProductsWorkflowActions.createInventoryItems
|
||||
)
|
||||
|
||||
const variantInventoryItemsData =
|
||||
invoke[CreateProductsWorkflowActions.createInventoryItems]
|
||||
|
||||
if (shouldSkipStep_ || !variantInventoryItemsData?.length) {
|
||||
return
|
||||
}
|
||||
|
||||
await removeInventoryItems({
|
||||
container,
|
||||
manager,
|
||||
data: variantInventoryItemsData,
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
[CreateProductsWorkflowActions.attachInventoryItems]: {
|
||||
[TransactionHandlerType.INVOKE]: async (
|
||||
data: CreateProductsWorkflowInputData,
|
||||
{ invoke }
|
||||
) => {
|
||||
const shouldSkipStep_ = shouldSkipInventoryStep(
|
||||
container,
|
||||
CreateProductsWorkflowActions.attachInventoryItems
|
||||
)
|
||||
if (shouldSkipStep_) {
|
||||
return
|
||||
}
|
||||
|
||||
const {
|
||||
[CreateProductsWorkflowActions.createInventoryItems]:
|
||||
inventoryItemsResult,
|
||||
} = invoke
|
||||
|
||||
return await attachInventoryItems({
|
||||
container,
|
||||
manager,
|
||||
data: inventoryItemsResult,
|
||||
})
|
||||
},
|
||||
[TransactionHandlerType.COMPENSATE]: async (
|
||||
data: CreateProductsWorkflowInputData,
|
||||
{ invoke }
|
||||
) => {
|
||||
const shouldSkipStep_ = shouldSkipInventoryStep(
|
||||
container,
|
||||
CreateProductsWorkflowActions.attachInventoryItems
|
||||
)
|
||||
|
||||
const {
|
||||
[CreateProductsWorkflowActions.createInventoryItems]:
|
||||
inventoryItemsResult,
|
||||
} = invoke
|
||||
|
||||
if (shouldSkipStep_ || !inventoryItemsResult?.length) {
|
||||
return
|
||||
}
|
||||
|
||||
return await detachInventoryItems({
|
||||
container,
|
||||
manager,
|
||||
data: inventoryItemsResult,
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
[CreateProductsWorkflowActions.createPrices]: {
|
||||
[TransactionHandlerType.INVOKE]: async (
|
||||
data: CreateProductsWorkflowInputData,
|
||||
{ invoke }
|
||||
) => {
|
||||
const { productsHandleVariantsIndexPricesMap } = invoke[
|
||||
CreateProductsWorkflowActions.prepare
|
||||
] as CreateProductsPreparedData
|
||||
const products = invoke[
|
||||
CreateProductsWorkflowActions.createProducts
|
||||
] as ProductTypes.ProductDTO[]
|
||||
|
||||
return await updateProductsVariantsPrices({
|
||||
container,
|
||||
manager,
|
||||
data: {
|
||||
products,
|
||||
productsHandleVariantsIndexPricesMap,
|
||||
},
|
||||
})
|
||||
},
|
||||
[TransactionHandlerType.COMPENSATE]: async (
|
||||
data: CreateProductsWorkflowInputData,
|
||||
{ invoke }
|
||||
) => {
|
||||
const { productsHandleVariantsIndexPricesMap } = invoke[
|
||||
CreateProductsWorkflowActions.prepare
|
||||
] as CreateProductsPreparedData
|
||||
const products = invoke[
|
||||
CreateProductsWorkflowActions.createProducts
|
||||
] as ProductTypes.ProductDTO[]
|
||||
|
||||
if (!productsHandleVariantsIndexPricesMap?.size) {
|
||||
return
|
||||
}
|
||||
|
||||
const updatedProductsHandleVariantsIndexPricesMap = new Map()
|
||||
productsHandleVariantsIndexPricesMap.forEach((items, productHandle) => {
|
||||
const existingItems =
|
||||
updatedProductsHandleVariantsIndexPricesMap.get(productHandle) ?? []
|
||||
|
||||
items.forEach(({ index }) => {
|
||||
existingItems.push({
|
||||
index,
|
||||
prices: [],
|
||||
})
|
||||
})
|
||||
|
||||
updatedProductsHandleVariantsIndexPricesMap.set(productHandle, items)
|
||||
})
|
||||
|
||||
return await updateProductsVariantsPrices({
|
||||
container,
|
||||
manager,
|
||||
data: {
|
||||
products,
|
||||
productsHandleVariantsIndexPricesMap:
|
||||
updatedProductsHandleVariantsIndexPricesMap,
|
||||
},
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
[CreateProductsWorkflowActions.result]: {
|
||||
[TransactionHandlerType.INVOKE]: async (
|
||||
data: CreateProductsWorkflowInputData,
|
||||
{ invoke }
|
||||
) => {
|
||||
const { [CreateProductsWorkflowActions.createProducts]: products } =
|
||||
invoke
|
||||
|
||||
const productService = container.resolve(
|
||||
"productService"
|
||||
) as ProductService
|
||||
const pricingService = container.resolve(
|
||||
"pricingService"
|
||||
) as PricingService
|
||||
|
||||
const rawProduct = await productService
|
||||
.withTransaction(manager)
|
||||
.retrieve(products[0].id, {
|
||||
select: defaultAdminProductFields,
|
||||
relations: defaultAdminProductRelations,
|
||||
})
|
||||
|
||||
const res = await pricingService
|
||||
.withTransaction(manager)
|
||||
.setProductPrices([rawProduct])
|
||||
|
||||
return res
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return (
|
||||
actionId: string,
|
||||
type: TransactionHandlerType,
|
||||
payload: TransactionPayload
|
||||
) => command[actionId][type](payload.data, payload.context)
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
import { ulid } from "ulid"
|
||||
import { MedusaError } from "@medusajs/utils"
|
||||
import {
|
||||
TransactionOrchestrator,
|
||||
TransactionState,
|
||||
} from "@medusajs/orchestration"
|
||||
import { AdminPostProductsReq } from "../../../api"
|
||||
import { Product } from "../../../models"
|
||||
import { PricedProduct } from "../../../types/pricing"
|
||||
import {
|
||||
CreateProductsWorkflowActions,
|
||||
transactionHandler,
|
||||
workflowSteps,
|
||||
} from "./definition"
|
||||
import { InjectedDependencies } from "./types"
|
||||
|
||||
const createProductsOrchestrator = new TransactionOrchestrator(
|
||||
"create-products",
|
||||
workflowSteps
|
||||
)
|
||||
|
||||
export async function createProductsWorkflow(
|
||||
dependencies: InjectedDependencies,
|
||||
input: AdminPostProductsReq[]
|
||||
): Promise<(Product | PricedProduct)[]> {
|
||||
const transaction = await createProductsOrchestrator.beginTransaction(
|
||||
ulid(),
|
||||
transactionHandler(dependencies),
|
||||
input
|
||||
)
|
||||
|
||||
await createProductsOrchestrator.resume(transaction)
|
||||
|
||||
if (transaction.getState() !== TransactionState.DONE) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
transaction
|
||||
.getErrors()
|
||||
.map((err) => err.error?.message)
|
||||
.join("\n")
|
||||
)
|
||||
}
|
||||
|
||||
return transaction.getContext().invoke[
|
||||
CreateProductsWorkflowActions.result
|
||||
] as (Product | PricedProduct)[]
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
import { EntityManager } from "typeorm"
|
||||
import { MedusaContainer } from "@medusajs/types"
|
||||
import { AdminPostProductsReq } from "../../../api"
|
||||
|
||||
export type InjectedDependencies = {
|
||||
manager: EntityManager
|
||||
container: MedusaContainer
|
||||
}
|
||||
|
||||
export type CreateProductsWorkflowInputData = AdminPostProductsReq[]
|
||||
@@ -1,30 +0,0 @@
|
||||
import {
|
||||
InventoryItemDTO,
|
||||
MedusaContainer,
|
||||
ProductTypes,
|
||||
} from "@medusajs/types"
|
||||
import { EntityManager } from "typeorm"
|
||||
import { ProductVariantInventoryService } from "../../services"
|
||||
|
||||
export async function attachInventoryItems({
|
||||
container,
|
||||
manager,
|
||||
data,
|
||||
}: {
|
||||
container: MedusaContainer
|
||||
manager: EntityManager
|
||||
data: {
|
||||
variant: ProductTypes.ProductVariantDTO
|
||||
inventoryItem: InventoryItemDTO
|
||||
}[]
|
||||
}) {
|
||||
const productVariantInventoryService: ProductVariantInventoryService =
|
||||
container.resolve("productVariantInventoryService").withTransaction(manager)
|
||||
|
||||
const inventoryData = data.map(({ variant, inventoryItem }) => ({
|
||||
variantId: variant.id,
|
||||
inventoryItemId: inventoryItem.id,
|
||||
}))
|
||||
|
||||
return await productVariantInventoryService.attachInventoryItem(inventoryData)
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
import { MedusaContainer, ProductTypes } from "@medusajs/types"
|
||||
import { EntityManager } from "typeorm"
|
||||
import { SalesChannelService } from "../../services"
|
||||
|
||||
type ProductHandle = string
|
||||
type SalesChannelId = string
|
||||
|
||||
export async function attachSalesChannelToProducts({
|
||||
container,
|
||||
manager,
|
||||
data,
|
||||
}: {
|
||||
container: MedusaContainer
|
||||
manager: EntityManager
|
||||
data: {
|
||||
productsHandleSalesChannelsMap: Map<ProductHandle, SalesChannelId[]>
|
||||
products: ProductTypes.ProductDTO[]
|
||||
}
|
||||
}): Promise<void> {
|
||||
const salesChannelService: SalesChannelService = container.resolve(
|
||||
"salesChannelService"
|
||||
)
|
||||
const salesChannelServiceTx = salesChannelService.withTransaction(manager)
|
||||
|
||||
const salesChannelIdProductIdsMap = new Map<ProductHandle, SalesChannelId[]>()
|
||||
data.products.forEach((product) => {
|
||||
const salesChannelIds = data.productsHandleSalesChannelsMap.get(
|
||||
product.handle!
|
||||
)
|
||||
if (salesChannelIds) {
|
||||
salesChannelIds.forEach((salesChannelId) => {
|
||||
const productIds = salesChannelIdProductIdsMap.get(salesChannelId) || []
|
||||
productIds.push(product.id)
|
||||
salesChannelIdProductIdsMap.set(salesChannelId, productIds)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
await Promise.all(
|
||||
Array.from(salesChannelIdProductIdsMap.entries()).map(
|
||||
async ([salesChannelId, productIds]) => {
|
||||
return await salesChannelServiceTx.addProducts(
|
||||
salesChannelId,
|
||||
productIds
|
||||
)
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
import { MedusaContainer, ProductTypes } from "@medusajs/types"
|
||||
import { EntityManager } from "typeorm"
|
||||
import { ShippingProfileService } from "../../services"
|
||||
|
||||
type ProductHandle = string
|
||||
type ShippingProfileId = string
|
||||
|
||||
export async function attachShippingProfileToProducts({
|
||||
container,
|
||||
manager,
|
||||
data,
|
||||
}: {
|
||||
container: MedusaContainer
|
||||
manager: EntityManager
|
||||
data: {
|
||||
productsHandleShippingProfileIdMap: Map<ProductHandle, ShippingProfileId>
|
||||
products: ProductTypes.ProductDTO[]
|
||||
}
|
||||
}): Promise<void> {
|
||||
const shippingProfileService: ShippingProfileService = container.resolve(
|
||||
"shippingProfileService"
|
||||
)
|
||||
const shippingProfileServiceTx =
|
||||
shippingProfileService.withTransaction(manager)
|
||||
|
||||
const profileIdProductIdsMap = new Map<ShippingProfileId, ProductHandle[]>()
|
||||
data.products.forEach((product) => {
|
||||
const profileId = data.productsHandleShippingProfileIdMap.get(
|
||||
product.handle!
|
||||
)
|
||||
if (profileId) {
|
||||
const productIds = profileIdProductIdsMap.get(profileId) || []
|
||||
productIds.push(product.id)
|
||||
profileIdProductIdsMap.set(profileId, productIds)
|
||||
}
|
||||
})
|
||||
|
||||
await Promise.all(
|
||||
Array.from(profileIdProductIdsMap.entries()).map(
|
||||
async ([profileId, productIds]) => {
|
||||
return await shippingProfileServiceTx.addProducts(profileId, productIds)
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
import {
|
||||
IInventoryService,
|
||||
MedusaContainer,
|
||||
ProductTypes,
|
||||
} from "@medusajs/types"
|
||||
import { EntityManager } from "typeorm"
|
||||
|
||||
export async function createInventoryItems({
|
||||
container,
|
||||
manager,
|
||||
data,
|
||||
}: {
|
||||
container: MedusaContainer
|
||||
manager: EntityManager
|
||||
data: ProductTypes.ProductDTO[]
|
||||
}) {
|
||||
const inventoryService: IInventoryService =
|
||||
container.resolve("inventoryService")
|
||||
const context = { transactionManager: manager }
|
||||
|
||||
const variants = data.reduce(
|
||||
(
|
||||
acc: ProductTypes.ProductVariantDTO[],
|
||||
product: ProductTypes.ProductDTO
|
||||
) => {
|
||||
return acc.concat(product.variants)
|
||||
},
|
||||
[]
|
||||
)
|
||||
|
||||
return await Promise.all(
|
||||
variants.map(async (variant) => {
|
||||
if (!variant.manage_inventory) {
|
||||
return
|
||||
}
|
||||
|
||||
const inventoryItem = await inventoryService!.createInventoryItem(
|
||||
{
|
||||
sku: variant.sku!,
|
||||
origin_country: variant.origin_country!,
|
||||
hs_code: variant.hs_code!,
|
||||
mid_code: variant.mid_code!,
|
||||
material: variant.material!,
|
||||
weight: variant.weight!,
|
||||
length: variant.length!,
|
||||
height: variant.height!,
|
||||
width: variant.width!,
|
||||
},
|
||||
context
|
||||
)
|
||||
|
||||
return { variant, inventoryItem }
|
||||
})
|
||||
)
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import { MedusaContainer, ProductTypes } from "@medusajs/types"
|
||||
|
||||
export type CreateProductsData = ProductTypes.CreateProductDTO[]
|
||||
|
||||
export async function createProducts({
|
||||
container,
|
||||
data,
|
||||
}: {
|
||||
container: MedusaContainer
|
||||
data: CreateProductsData
|
||||
}): Promise<ProductTypes.ProductDTO[]> {
|
||||
const productModuleService: ProductTypes.IProductModuleService =
|
||||
container.resolve("productModuleService")
|
||||
|
||||
return await productModuleService.create(data)
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
import {
|
||||
InventoryItemDTO,
|
||||
MedusaContainer,
|
||||
ProductTypes,
|
||||
} from "@medusajs/types"
|
||||
import { EntityManager } from "typeorm"
|
||||
import { ProductVariantInventoryService } from "../../services"
|
||||
|
||||
export async function detachInventoryItems({
|
||||
container,
|
||||
manager,
|
||||
data,
|
||||
}: {
|
||||
container: MedusaContainer
|
||||
manager: EntityManager
|
||||
data: {
|
||||
variant: ProductTypes.ProductVariantDTO
|
||||
inventoryItem: InventoryItemDTO
|
||||
}[]
|
||||
}) {
|
||||
const productVariantInventoryService: ProductVariantInventoryService =
|
||||
container.resolve("productVariantInventoryService").withTransaction(manager)
|
||||
|
||||
return await Promise.all(
|
||||
data.map(async ({ variant, inventoryItem }) => {
|
||||
return await productVariantInventoryService.detachInventoryItem(
|
||||
inventoryItem.id,
|
||||
variant.id
|
||||
)
|
||||
})
|
||||
)
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
import { MedusaContainer, ProductTypes } from "@medusajs/types"
|
||||
import { EntityManager } from "typeorm"
|
||||
import { SalesChannelService } from "../../services"
|
||||
|
||||
type ProductHandle = string
|
||||
type SalesChannelId = string
|
||||
|
||||
export async function detachSalesChannelFromProducts({
|
||||
container,
|
||||
manager,
|
||||
data,
|
||||
}: {
|
||||
container: MedusaContainer
|
||||
manager: EntityManager
|
||||
data: {
|
||||
productsHandleSalesChannelsMap: Map<ProductHandle, SalesChannelId[]>
|
||||
products: ProductTypes.ProductDTO[]
|
||||
}
|
||||
}): Promise<void> {
|
||||
const salesChannelService: SalesChannelService = container.resolve(
|
||||
"salesChannelService"
|
||||
)
|
||||
const salesChannelServiceTx = salesChannelService.withTransaction(manager)
|
||||
|
||||
const salesChannelIdProductIdsMap = new Map<ProductHandle, SalesChannelId[]>()
|
||||
data.products.forEach((product) => {
|
||||
const salesChannelIds = data.productsHandleSalesChannelsMap.get(
|
||||
product.handle!
|
||||
)
|
||||
if (salesChannelIds) {
|
||||
salesChannelIds.forEach((salesChannelId) => {
|
||||
const productIds = salesChannelIdProductIdsMap.get(salesChannelId) || []
|
||||
productIds.push(product.id)
|
||||
salesChannelIdProductIdsMap.set(salesChannelId, productIds)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
await Promise.all(
|
||||
Array.from(salesChannelIdProductIdsMap.entries()).map(
|
||||
async ([salesChannelId, productIds]) => {
|
||||
return await salesChannelServiceTx.removeProducts(
|
||||
salesChannelId,
|
||||
productIds
|
||||
)
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
import { MedusaContainer, ProductTypes } from "@medusajs/types"
|
||||
import { EntityManager } from "typeorm"
|
||||
import { ShippingProfileService } from "../../services"
|
||||
|
||||
type ProductHandle = string
|
||||
type ShippingProfileId = string
|
||||
|
||||
export async function detachShippingProfileFromProducts({
|
||||
container,
|
||||
manager,
|
||||
data,
|
||||
}: {
|
||||
container: MedusaContainer
|
||||
manager: EntityManager
|
||||
data: {
|
||||
productsHandleShippingProfileIdMap: Map<ProductHandle, ShippingProfileId>
|
||||
products: ProductTypes.ProductDTO[]
|
||||
}
|
||||
}): Promise<void> {
|
||||
const shippingProfileService: ShippingProfileService = container.resolve(
|
||||
"shippingProfileService"
|
||||
)
|
||||
const shippingProfileServiceTx =
|
||||
shippingProfileService.withTransaction(manager)
|
||||
|
||||
const profileIdProductIdsMap = new Map<ShippingProfileId, ProductHandle[]>()
|
||||
data.products.forEach((product) => {
|
||||
const profileId = data.productsHandleShippingProfileIdMap.get(
|
||||
product.handle!
|
||||
)
|
||||
if (profileId) {
|
||||
const productIds = profileIdProductIdsMap.get(profileId) || []
|
||||
productIds.push(product.id)
|
||||
profileIdProductIdsMap.set(profileId, productIds)
|
||||
}
|
||||
})
|
||||
|
||||
await Promise.all(
|
||||
Array.from(profileIdProductIdsMap.entries()).map(
|
||||
async ([profileId, productIds]) => {
|
||||
return await shippingProfileServiceTx.removeProducts(
|
||||
profileId,
|
||||
productIds
|
||||
)
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
export * from "./create-prducts"
|
||||
export * from "./remove-products"
|
||||
export * from "./create-inventory-items"
|
||||
export * from "./remove-inventory-items"
|
||||
export * from "./attach-inventory-items"
|
||||
export * from "./prepare-create-products-data"
|
||||
export * from "./attach-shipping-profile-to-products"
|
||||
export * from "./detach-shipping-profile-from-products"
|
||||
export * from "./attach-sales-channel-to-products"
|
||||
export * from "./detach-sales-channel-from-products"
|
||||
export * from "./update-products-variants-prices"
|
||||
export * from "./detach-inventory-items"
|
||||
@@ -1,142 +0,0 @@
|
||||
import { MedusaContainer, ProductTypes } from "@medusajs/types"
|
||||
import { EntityManager } from "typeorm"
|
||||
import { SalesChannelService, ShippingProfileService } from "../../services"
|
||||
import { kebabCase } from "@medusajs/utils"
|
||||
import { FlagRouter } from "../../utils/flag-router"
|
||||
import SalesChannelFeatureFlag from "../../loaders/feature-flags/sales-channels"
|
||||
import { SalesChannel, ShippingProfileType } from "../../models"
|
||||
import { ProductVariantPricesCreateReq } from "../../types/product-variant"
|
||||
import { AdminPostProductsReq } from "../../api"
|
||||
|
||||
type CreateProductsInputData = AdminPostProductsReq[]
|
||||
|
||||
type ShippingProfileId = string
|
||||
type SalesChannelId = string
|
||||
type ProductHandle = string
|
||||
type VariantIndexAndPrices = {
|
||||
index: number
|
||||
prices: ProductVariantPricesCreateReq[]
|
||||
}
|
||||
|
||||
export type CreateProductsPreparedData = {
|
||||
products: ProductTypes.CreateProductDTO[]
|
||||
productsHandleShippingProfileIdMap: Map<ProductHandle, ShippingProfileId>
|
||||
productsHandleSalesChannelsMap: Map<ProductHandle, SalesChannelId[]>
|
||||
productsHandleVariantsIndexPricesMap: Map<
|
||||
ProductHandle,
|
||||
VariantIndexAndPrices[]
|
||||
>
|
||||
}
|
||||
|
||||
export async function prepareCreateProductsData({
|
||||
container,
|
||||
manager,
|
||||
data,
|
||||
}: {
|
||||
container: MedusaContainer
|
||||
manager: EntityManager
|
||||
data: CreateProductsInputData
|
||||
}): Promise<CreateProductsPreparedData> {
|
||||
const shippingProfileService: ShippingProfileService = container
|
||||
.resolve("shippingProfileService")
|
||||
.withTransaction(manager)
|
||||
const featureFlagRouter: FlagRouter = container.resolve("featureFlagRouter")
|
||||
const salesChannelService: SalesChannelService = container
|
||||
.resolve("salesChannelService")
|
||||
.withTransaction(manager)
|
||||
|
||||
const shippingProfileServiceTx =
|
||||
shippingProfileService.withTransaction(manager)
|
||||
|
||||
const shippingProfiles = await shippingProfileServiceTx.list({
|
||||
type: [ShippingProfileType.DEFAULT, ShippingProfileType.GIFT_CARD],
|
||||
})
|
||||
const defaultShippingProfile = shippingProfiles.find(
|
||||
(sp) => sp.type === ShippingProfileType.DEFAULT
|
||||
)
|
||||
const gitCardShippingProfile = shippingProfiles.find(
|
||||
(sp) => sp.type === ShippingProfileType.GIFT_CARD
|
||||
)
|
||||
|
||||
let defaultSalesChannel: SalesChannel | undefined
|
||||
if (featureFlagRouter.isFeatureEnabled(SalesChannelFeatureFlag.key)) {
|
||||
defaultSalesChannel = await salesChannelService
|
||||
.withTransaction(manager)
|
||||
.retrieveDefault()
|
||||
}
|
||||
|
||||
const productsHandleShippingProfileIdMap = new Map<
|
||||
ProductHandle,
|
||||
ShippingProfileId
|
||||
>()
|
||||
const productsHandleSalesChannelsMap = new Map<
|
||||
ProductHandle,
|
||||
SalesChannelId[]
|
||||
>()
|
||||
const productsHandleVariantsIndexPricesMap = new Map<
|
||||
ProductHandle,
|
||||
VariantIndexAndPrices[]
|
||||
>()
|
||||
|
||||
for (const product of data) {
|
||||
product.handle ??= kebabCase(product.title)
|
||||
|
||||
if (product.is_giftcard) {
|
||||
productsHandleShippingProfileIdMap.set(
|
||||
product.handle!,
|
||||
gitCardShippingProfile!.id
|
||||
)
|
||||
} else {
|
||||
productsHandleShippingProfileIdMap.set(
|
||||
product.handle!,
|
||||
defaultShippingProfile!.id
|
||||
)
|
||||
}
|
||||
|
||||
if (
|
||||
featureFlagRouter.isFeatureEnabled(SalesChannelFeatureFlag.key) &&
|
||||
!product.sales_channels?.length
|
||||
) {
|
||||
productsHandleSalesChannelsMap.set(product.handle!, [
|
||||
defaultSalesChannel!.id,
|
||||
])
|
||||
} else {
|
||||
productsHandleSalesChannelsMap.set(
|
||||
product.handle!,
|
||||
product.sales_channels!.map((s) => s.id)
|
||||
)
|
||||
}
|
||||
|
||||
if (product.variants) {
|
||||
const hasPrices = product.variants.some((variant) => {
|
||||
return variant.prices.length > 0
|
||||
})
|
||||
|
||||
if (hasPrices) {
|
||||
const items =
|
||||
productsHandleVariantsIndexPricesMap.get(product.handle!) ?? []
|
||||
|
||||
product.variants.forEach((variant, index) => {
|
||||
items.push({
|
||||
index,
|
||||
prices: variant.prices,
|
||||
})
|
||||
})
|
||||
|
||||
productsHandleVariantsIndexPricesMap.set(product.handle!, items)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data = data.map((productData) => {
|
||||
delete productData.sales_channels
|
||||
return productData
|
||||
})
|
||||
|
||||
return {
|
||||
products: data as ProductTypes.CreateProductDTO[],
|
||||
productsHandleShippingProfileIdMap,
|
||||
productsHandleSalesChannelsMap,
|
||||
productsHandleVariantsIndexPricesMap,
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import { InventoryItemDTO, MedusaContainer } from "@medusajs/types"
|
||||
import { EntityManager } from "typeorm"
|
||||
|
||||
export async function removeInventoryItems({
|
||||
container,
|
||||
manager,
|
||||
data,
|
||||
}: {
|
||||
container: MedusaContainer
|
||||
manager: EntityManager
|
||||
data: {
|
||||
inventoryItem: InventoryItemDTO
|
||||
}[]
|
||||
}) {
|
||||
const inventoryService = container.resolve("inventoryService")
|
||||
const context = { transactionManager: manager }
|
||||
|
||||
return await inventoryService!.deleteInventoryItem(
|
||||
data.map(({ inventoryItem }) => inventoryItem.id),
|
||||
context
|
||||
)
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import { MedusaContainer, ProductTypes } from "@medusajs/types"
|
||||
|
||||
export async function removeProducts({
|
||||
container,
|
||||
data,
|
||||
}: {
|
||||
container: MedusaContainer
|
||||
data: ProductTypes.ProductDTO[]
|
||||
}) {
|
||||
const productModuleService: ProductTypes.IProductModuleService =
|
||||
container.resolve("productModuleService")
|
||||
return await productModuleService.softDelete(data.map((p) => p.id))
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
import { MedusaContainer, ProductTypes } from "@medusajs/types"
|
||||
import { EntityManager } from "typeorm"
|
||||
import { ProductVariantService } from "../../services"
|
||||
import {
|
||||
ProductVariantPricesCreateReq,
|
||||
UpdateVariantPricesData,
|
||||
} from "../../types/product-variant"
|
||||
import { MedusaError } from "@medusajs/utils"
|
||||
|
||||
type ProductHandle = string
|
||||
type VariantIndexAndPrices = {
|
||||
index: number
|
||||
prices: ProductVariantPricesCreateReq[]
|
||||
}
|
||||
|
||||
export async function updateProductsVariantsPrices({
|
||||
container,
|
||||
manager,
|
||||
data,
|
||||
}: {
|
||||
container: MedusaContainer
|
||||
manager: EntityManager
|
||||
data: {
|
||||
products: ProductTypes.ProductDTO[]
|
||||
productsHandleVariantsIndexPricesMap: Map<
|
||||
ProductHandle,
|
||||
VariantIndexAndPrices[]
|
||||
>
|
||||
}
|
||||
}) {
|
||||
const productVariantService: ProductVariantService = container.resolve(
|
||||
"productVariantService"
|
||||
)
|
||||
const productVariantServiceTx = productVariantService.withTransaction(manager)
|
||||
|
||||
const variantIdsPricesData: UpdateVariantPricesData[] = []
|
||||
const productsMap = new Map<string, ProductTypes.ProductDTO>(
|
||||
data.products.map((p) => [p.handle!, p])
|
||||
)
|
||||
|
||||
for (const mapData of data.productsHandleVariantsIndexPricesMap.entries()) {
|
||||
const [handle, variantData] = mapData
|
||||
|
||||
const product = productsMap.get(handle)
|
||||
if (!product) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`Product with handle ${handle} not found`
|
||||
)
|
||||
}
|
||||
|
||||
variantData.forEach((item, index) => {
|
||||
const variant = product.variants[index]
|
||||
variantIdsPricesData.push({
|
||||
variantId: variant.id,
|
||||
prices: item.prices,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
await productVariantServiceTx.updateVariantPrices(variantIdsPricesData)
|
||||
}
|
||||
Reference in new issue
Block a user