feat: move create inventory to @medusajs/workflows (#5301)
**Why** - We have some workflow-like flows in @medusajs/medusa. These should be moved over to the workflows package. - Inventory Items <> Variant currently assume a 1-1 mapping. There should be support for a many-to-many mapping. **What** - PR introduces a feature flag for supporting many-to-many mappings for inventory and variants. - Deletes legacy transaction handler in @medusajs/medusa. - Adjusts existing createInventoryItems handler to remove dependency on variant data. **Unkowns** ~~1. Couldn't find an existing test for the CreateProduct workflow. It should be tested that this still works as expected.~~ 2. Have removed transaction managers as we should move to handling consistency through orchestration tooling. Are we ready for that?
This commit is contained in:
8
.changeset/tame-queens-cough.md
Normal file
8
.changeset/tame-queens-cough.md
Normal file
@@ -0,0 +1,8 @@
|
||||
---
|
||||
"@medusajs/medusa": patch
|
||||
"@medusajs/inventory": patch
|
||||
"@medusajs/types": patch
|
||||
"@medusajs/utils": patch
|
||||
"@medusajs/workflows": patch
|
||||
---
|
||||
fix: move create inventory workflow to @medusajs/workflows
|
||||
@@ -0,0 +1,96 @@
|
||||
const path = require("path")
|
||||
const { ProductVariantInventoryService } = require("@medusajs/medusa")
|
||||
|
||||
const {
|
||||
bootstrapApp,
|
||||
} = require("../../../../environment-helpers/bootstrap-app")
|
||||
const { initDb, useDb } = require("../../../../environment-helpers/use-db")
|
||||
const { setPort, useApi } = require("../../../../environment-helpers/use-api")
|
||||
|
||||
const adminSeeder = require("../../../../helpers/admin-seeder")
|
||||
|
||||
jest.setTimeout(30000)
|
||||
|
||||
const {
|
||||
simpleProductFactory,
|
||||
simpleOrderFactory,
|
||||
} = require("../../../../factories")
|
||||
const adminHeaders = { headers: { "x-medusa-access-token": "test_token" } }
|
||||
|
||||
describe("Inventory Items endpoints", () => {
|
||||
let appContainer
|
||||
let dbConnection
|
||||
let express
|
||||
|
||||
let variantId
|
||||
let inventoryItems
|
||||
let locationId
|
||||
let location2Id
|
||||
let location3Id
|
||||
|
||||
beforeAll(async () => {
|
||||
const cwd = path.resolve(path.join(__dirname, "..", "..", ".."))
|
||||
dbConnection = await initDb({ cwd })
|
||||
|
||||
const { container, app, port } = await bootstrapApp({ cwd, verbose: true })
|
||||
appContainer = container
|
||||
|
||||
// Set feature flag
|
||||
const flagRouter = appContainer.resolve("featureFlagRouter")
|
||||
flagRouter.setFlag("many_to_many_inventory", true)
|
||||
|
||||
setPort(port)
|
||||
express = app.listen(port, (err) => {
|
||||
process.send(port)
|
||||
})
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
await adminSeeder(dbConnection)
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
const flagRouter = appContainer.resolve("featureFlagRouter")
|
||||
flagRouter.setFlag("many_to_many_inventory", false)
|
||||
|
||||
const db = useDb()
|
||||
await db.shutdown()
|
||||
express.close()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
const db = useDb()
|
||||
return await db.teardown()
|
||||
})
|
||||
|
||||
describe("Inventory Items", () => {
|
||||
it("should create inventory item without variant id", async () => {
|
||||
const api = useApi()
|
||||
|
||||
await api.post(
|
||||
`/admin/inventory-items`,
|
||||
{
|
||||
sku: "TABLE_LEG",
|
||||
description: "Table Leg",
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
/** @type {ProductVariantInventoryService} */
|
||||
const productVariantInventoryService = appContainer.resolve(
|
||||
"productVariantInventoryService"
|
||||
)
|
||||
|
||||
const inventoryService = appContainer.resolve("inventoryService")
|
||||
const inventoryItems = await inventoryService.list()
|
||||
|
||||
expect(inventoryItems.length).toEqual(1)
|
||||
|
||||
const variants = await productVariantInventoryService.listByItem([
|
||||
inventoryItems[0].id,
|
||||
])
|
||||
expect(variants.length).toEqual(0)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -345,7 +345,7 @@ describe("Inventory Items endpoints", () => {
|
||||
|
||||
const inventoryItemCreateRes = await api.post(
|
||||
`/admin/inventory-items`,
|
||||
{ variant_id: variantId },
|
||||
{ variant_id: variantId, sku: "attach_this_to_variant" },
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
import path from "path"
|
||||
import { initDb, useDb } from "../../../../environment-helpers/use-db"
|
||||
import { bootstrapApp } from "../../../../environment-helpers/bootstrap-app"
|
||||
import {
|
||||
createInventoryItems,
|
||||
CreateInventoryItemActions,
|
||||
pipe,
|
||||
} from "@medusajs/workflows"
|
||||
import { IInventoryService, WorkflowTypes } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
describe("CreateInventoryItem workflow", function () {
|
||||
let medusaProcess
|
||||
let medusaContainer
|
||||
|
||||
beforeAll(async () => {
|
||||
const cwd = path.resolve(path.join(__dirname, "..", "..", ".."))
|
||||
await initDb({ cwd } as any)
|
||||
const { container } = await bootstrapApp({ cwd })
|
||||
medusaContainer = container
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
const db = useDb()
|
||||
await db.shutdown()
|
||||
|
||||
medusaProcess.kill()
|
||||
})
|
||||
|
||||
it("should compensate all the invoke if something fails", async () => {
|
||||
const workflow = createInventoryItems(medusaContainer)
|
||||
|
||||
workflow.appendAction(
|
||||
"fail_step",
|
||||
CreateInventoryItemActions.createInventoryItems,
|
||||
{
|
||||
invoke: pipe({}, async function failStep() {
|
||||
throw new Error(`Failed`)
|
||||
}),
|
||||
},
|
||||
{
|
||||
noCompensation: true,
|
||||
}
|
||||
)
|
||||
|
||||
const input: WorkflowTypes.InventoryWorkflow.CreateInventoryItemsWorkflowInputDTO =
|
||||
{
|
||||
inventoryItems: [
|
||||
{
|
||||
sku: "TABLE_LEG",
|
||||
description: "Table Leg",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const { result, errors, transaction } = await workflow.run({
|
||||
input,
|
||||
context: {},
|
||||
throwOnError: false,
|
||||
})
|
||||
|
||||
expect(errors).toEqual([
|
||||
{
|
||||
action: "fail_step",
|
||||
handlerType: "invoke",
|
||||
error: new Error(`Failed`),
|
||||
},
|
||||
])
|
||||
|
||||
expect(transaction.getState()).toEqual("reverted")
|
||||
|
||||
expect(result).toHaveLength(1)
|
||||
expect(result[0].inventoryItem).toEqual(
|
||||
expect.objectContaining({ id: expect.any(String) })
|
||||
)
|
||||
|
||||
const inventoryService: IInventoryService = medusaContainer.resolve(
|
||||
ModuleRegistrationName.INVENTORY
|
||||
)
|
||||
|
||||
const [inventoryItems] = await inventoryService.listInventoryItems(
|
||||
{ id: result[0].inventoryItem.id },
|
||||
{ withDeleted: true }
|
||||
)
|
||||
|
||||
expect(inventoryItems[0]).toEqual(
|
||||
expect.objectContaining({
|
||||
id: result[0].inventoryItem.id,
|
||||
deleted_at: expect.any(Date),
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -11,6 +11,7 @@
|
||||
"dependencies": {
|
||||
"@medusajs/cache-inmemory": "workspace:*",
|
||||
"@medusajs/event-bus-local": "workspace:*",
|
||||
"@medusajs/inventory": "workspace:^",
|
||||
"@medusajs/medusa": "workspace:*",
|
||||
"@medusajs/product": "workspace:^",
|
||||
"faker": "^5.5.3",
|
||||
|
||||
@@ -41,7 +41,7 @@ export function buildQuery<TWhereKeys extends object, TEntity = unknown>(
|
||||
where: buildWhere<TWhereKeys, TEntity>(selector),
|
||||
}
|
||||
|
||||
if ("deleted_at" in selector) {
|
||||
if ("deleted_at" in selector || config.withDeleted) {
|
||||
query.withDeleted = true
|
||||
}
|
||||
|
||||
|
||||
@@ -70,6 +70,10 @@ export function getListQuery(
|
||||
queryBuilder.select(legacySelect.map((s) => "inv_item." + s))
|
||||
}
|
||||
|
||||
if (query.withDeleted) {
|
||||
queryBuilder.withDeleted()
|
||||
}
|
||||
|
||||
if (query.order) {
|
||||
const toSelect: string[] = []
|
||||
const parsed = Object.entries(query.order).reduce((acc, [k, v]) => {
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import { FlagRouter, ManyToManyInventoryFeatureFlag } from "@medusajs/utils"
|
||||
import { IsNumber, IsObject, IsOptional, IsString } from "class-validator"
|
||||
import {
|
||||
ProductVariantInventoryService,
|
||||
ProductVariantService,
|
||||
} from "../../../../services"
|
||||
createInventoryItems,
|
||||
CreateInventoryItemActions,
|
||||
pipe,
|
||||
} from "@medusajs/workflows"
|
||||
import { ProductVariantInventoryService } from "../../../../services"
|
||||
|
||||
import { EntityManager } from "typeorm"
|
||||
import { FindParams } from "../../../../types/common"
|
||||
import { IInventoryService } from "@medusajs/types"
|
||||
import { MedusaError } from "@medusajs/utils"
|
||||
import { createInventoryItemTransaction } from "./transaction/create-inventory-item"
|
||||
import { validator } from "../../../../utils/validator"
|
||||
|
||||
/**
|
||||
* @oas [post] /admin/inventory-items
|
||||
@@ -78,54 +77,106 @@ import { validator } from "../../../../utils/validator"
|
||||
*/
|
||||
|
||||
export default async (req, res) => {
|
||||
const validated = await validator(AdminPostInventoryItemsReq, req.body)
|
||||
const { variant_id, ...input } = validated
|
||||
const { variant_id, ...inventoryItemInput } = req.validatedBody
|
||||
|
||||
const inventoryService: IInventoryService =
|
||||
req.scope.resolve("inventoryService")
|
||||
const featureFlagRouter: FlagRouter = req.scope.resolve("featureFlagRouter")
|
||||
const productVariantInventoryService: ProductVariantInventoryService =
|
||||
req.scope.resolve("productVariantInventoryService")
|
||||
const productVariantService: ProductVariantService = req.scope.resolve(
|
||||
"productVariantService"
|
||||
)
|
||||
|
||||
let inventoryItems = await productVariantInventoryService.listByVariant(
|
||||
variant_id
|
||||
)
|
||||
const createInventoryItemWorkflow = createInventoryItems(req.scope)
|
||||
|
||||
// TODO: this is a temporary fix to prevent duplicate inventory items since we don't support this functionality yet
|
||||
if (inventoryItems.length) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
"Inventory Item already exists for this variant"
|
||||
if (!featureFlagRouter.isFeatureEnabled(ManyToManyInventoryFeatureFlag.key)) {
|
||||
if (!variant_id) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
"variant_id is required"
|
||||
)
|
||||
}
|
||||
|
||||
createInventoryItemWorkflow.appendAction(
|
||||
"attachInventoryItems",
|
||||
CreateInventoryItemActions.createInventoryItems,
|
||||
{
|
||||
invoke: pipe(
|
||||
{
|
||||
invoke: {
|
||||
from: CreateInventoryItemActions.createInventoryItems,
|
||||
alias: "createdItems",
|
||||
},
|
||||
},
|
||||
generateAttachInventoryToVariantHandler(
|
||||
variant_id,
|
||||
productVariantInventoryService
|
||||
)
|
||||
),
|
||||
compensate: pipe(
|
||||
{
|
||||
invoke: {
|
||||
from: "attachInventoryItems",
|
||||
alias: "attachedItems",
|
||||
},
|
||||
},
|
||||
generateDetachInventoryItemFromVariantHandler(
|
||||
productVariantInventoryService
|
||||
)
|
||||
),
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
const manager: EntityManager = req.scope.resolve("manager")
|
||||
|
||||
await manager.transaction(async (transactionManager) => {
|
||||
await createInventoryItemTransaction(
|
||||
{
|
||||
manager: transactionManager,
|
||||
inventoryService,
|
||||
productVariantInventoryService,
|
||||
productVariantService,
|
||||
},
|
||||
variant_id,
|
||||
input
|
||||
)
|
||||
const { result } = await createInventoryItemWorkflow.run({
|
||||
input: {
|
||||
inventoryItems: [inventoryItemInput],
|
||||
},
|
||||
})
|
||||
|
||||
inventoryItems = await productVariantInventoryService.listByVariant(
|
||||
variant_id
|
||||
)
|
||||
res.status(200).json({ inventory_item: result[0].inventoryItem })
|
||||
}
|
||||
|
||||
const inventoryItem = await inventoryService.retrieveInventoryItem(
|
||||
inventoryItems[0].inventory_item_id,
|
||||
req.retrieveConfig
|
||||
)
|
||||
function generateDetachInventoryItemFromVariantHandler(
|
||||
productVariantInventoryService: ProductVariantInventoryService
|
||||
) {
|
||||
return async ({ data }) => {
|
||||
if (!data.attachedItems || !data.attachedItems.length) {
|
||||
return
|
||||
}
|
||||
|
||||
res.status(200).json({ inventory_item: inventoryItem })
|
||||
const [variantId, inventoryItemId] = data.attachedItems
|
||||
if (!variantId || !inventoryItemId) {
|
||||
return
|
||||
}
|
||||
|
||||
return await productVariantInventoryService.detachInventoryItem(
|
||||
inventoryItemId,
|
||||
variantId
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function generateAttachInventoryToVariantHandler(
|
||||
variantId: string,
|
||||
productVariantInventoryService: ProductVariantInventoryService
|
||||
) {
|
||||
return async ({ data }) => {
|
||||
let inventoryItems = await productVariantInventoryService.listByVariant(
|
||||
variantId
|
||||
)
|
||||
|
||||
// TODO: this is a temporary fix to prevent duplicate inventory
|
||||
// items since we don't support this functionality yet
|
||||
if (inventoryItems.length) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
"Inventory Item already exists for this variant"
|
||||
)
|
||||
}
|
||||
const inventoryItemId = data.createdItems[0].inventoryItem.id
|
||||
await productVariantInventoryService.attachInventoryItem(
|
||||
variantId,
|
||||
inventoryItemId
|
||||
)
|
||||
return [variantId, inventoryItemId]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -192,6 +243,7 @@ export default async (req, res) => {
|
||||
* url: "https://docs.medusajs.com/development/entities/overview#metadata-attribute"
|
||||
*/
|
||||
export class AdminPostInventoryItemsReq {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
variant_id: string
|
||||
|
||||
|
||||
@@ -1,173 +0,0 @@
|
||||
import {
|
||||
DistributedTransaction,
|
||||
TransactionHandlerType,
|
||||
TransactionOrchestrator,
|
||||
TransactionPayload,
|
||||
TransactionState,
|
||||
TransactionStepsDefinition,
|
||||
} from "@medusajs/orchestration"
|
||||
import { IInventoryService, InventoryItemDTO } from "@medusajs/types"
|
||||
import {
|
||||
ProductVariantInventoryService,
|
||||
ProductVariantService,
|
||||
} from "../../../../../services"
|
||||
|
||||
import { EntityManager } from "typeorm"
|
||||
import { MedusaError } from "@medusajs/utils"
|
||||
import { ulid } from "ulid"
|
||||
|
||||
enum actions {
|
||||
createInventoryItem = "createInventoryItem",
|
||||
attachInventoryItem = "attachInventoryItem",
|
||||
}
|
||||
|
||||
const flow: TransactionStepsDefinition = {
|
||||
next: {
|
||||
action: actions.createInventoryItem,
|
||||
saveResponse: true,
|
||||
next: {
|
||||
action: actions.attachInventoryItem,
|
||||
noCompensation: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const createInventoryItemStrategy = new TransactionOrchestrator(
|
||||
"create-inventory-item",
|
||||
flow
|
||||
)
|
||||
|
||||
type InjectedDependencies = {
|
||||
manager: EntityManager
|
||||
productVariantService: ProductVariantService
|
||||
productVariantInventoryService: ProductVariantInventoryService
|
||||
inventoryService: IInventoryService
|
||||
}
|
||||
|
||||
type CreateInventoryItemInput = {
|
||||
sku?: string
|
||||
hs_code?: string
|
||||
weight?: number
|
||||
length?: number
|
||||
height?: number
|
||||
width?: number
|
||||
origin_country?: string
|
||||
mid_code?: string
|
||||
material?: string
|
||||
title?: string
|
||||
description?: string
|
||||
thumbnail?: string
|
||||
metadata?: Record<string, unknown>
|
||||
}
|
||||
|
||||
export const createInventoryItemTransaction = async (
|
||||
dependencies: InjectedDependencies,
|
||||
variantId: string,
|
||||
input: CreateInventoryItemInput
|
||||
): Promise<DistributedTransaction> => {
|
||||
const {
|
||||
manager,
|
||||
productVariantService,
|
||||
inventoryService,
|
||||
productVariantInventoryService,
|
||||
} = dependencies
|
||||
|
||||
const productVariantInventoryServiceTx =
|
||||
productVariantInventoryService.withTransaction(manager)
|
||||
|
||||
const productVariantServiceTx = productVariantService.withTransaction(manager)
|
||||
|
||||
async function createInventoryItem(input: CreateInventoryItemInput) {
|
||||
return await inventoryService!.createInventoryItem({
|
||||
sku: input.sku,
|
||||
origin_country: input.origin_country,
|
||||
hs_code: input.hs_code,
|
||||
mid_code: input.mid_code,
|
||||
material: input.material,
|
||||
weight: input.weight,
|
||||
length: input.length,
|
||||
height: input.height,
|
||||
width: input.width,
|
||||
title: input.title,
|
||||
description: input.description,
|
||||
thumbnail: input.thumbnail,
|
||||
})
|
||||
}
|
||||
|
||||
async function removeInventoryItem(inventoryItem: InventoryItemDTO) {
|
||||
if (inventoryItem) {
|
||||
await inventoryService!.deleteInventoryItem(inventoryItem.id)
|
||||
}
|
||||
}
|
||||
|
||||
async function attachInventoryItem(inventoryItem: InventoryItemDTO) {
|
||||
const variant = await productVariantServiceTx.retrieve(variantId)
|
||||
|
||||
if (!variant.manage_inventory) {
|
||||
return
|
||||
}
|
||||
|
||||
await productVariantInventoryServiceTx.attachInventoryItem(
|
||||
variant.id,
|
||||
inventoryItem.id
|
||||
)
|
||||
}
|
||||
|
||||
async function transactionHandler(
|
||||
actionId: string,
|
||||
type: TransactionHandlerType,
|
||||
payload: TransactionPayload
|
||||
) {
|
||||
const command = {
|
||||
[actions.createInventoryItem]: {
|
||||
[TransactionHandlerType.INVOKE]: async (
|
||||
data: CreateInventoryItemInput
|
||||
) => {
|
||||
return await createInventoryItem(data)
|
||||
},
|
||||
[TransactionHandlerType.COMPENSATE]: async (
|
||||
data: CreateInventoryItemInput,
|
||||
{ invoke }
|
||||
) => {
|
||||
await removeInventoryItem(invoke[actions.createInventoryItem])
|
||||
},
|
||||
},
|
||||
[actions.attachInventoryItem]: {
|
||||
[TransactionHandlerType.INVOKE]: async (
|
||||
data: CreateInventoryItemInput,
|
||||
{ invoke }
|
||||
) => {
|
||||
const { [actions.createInventoryItem]: inventoryItem } = invoke
|
||||
|
||||
return await attachInventoryItem(inventoryItem)
|
||||
},
|
||||
},
|
||||
}
|
||||
return command[actionId][type](payload.data, payload.context)
|
||||
}
|
||||
|
||||
const transaction = await createInventoryItemStrategy.beginTransaction(
|
||||
ulid(),
|
||||
transactionHandler,
|
||||
input
|
||||
)
|
||||
await createInventoryItemStrategy.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
|
||||
}
|
||||
|
||||
export const revertVariantTransaction = async (
|
||||
transaction: DistributedTransaction
|
||||
) => {
|
||||
await createInventoryItemStrategy.cancelTransaction(transaction)
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
export * as CartWorkflow from "./cart"
|
||||
export * as CommonWorkflow from "./common"
|
||||
export * as ProductWorkflow from "./product"
|
||||
export * as InventoryWorkflow from "./inventory"
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
export interface CreateInventoryItemInputDTO {
|
||||
sku?: string
|
||||
hs_code?: string
|
||||
weight?: number
|
||||
length?: number
|
||||
height?: number
|
||||
width?: number
|
||||
origin_country?: string
|
||||
mid_code?: string
|
||||
material?: string
|
||||
title?: string
|
||||
description?: string
|
||||
thumbnail?: string
|
||||
metadata?: Record<string, unknown>
|
||||
}
|
||||
|
||||
export interface CreateInventoryItemsWorkflowInputDTO {
|
||||
inventoryItems: CreateInventoryItemInputDTO[]
|
||||
}
|
||||
1
packages/types/src/workflow/inventory/index.ts
Normal file
1
packages/types/src/workflow/inventory/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./create-inventory-items"
|
||||
@@ -6,4 +6,4 @@ export * from "./sales-channels"
|
||||
export * from "./tax-inclusive-pricing"
|
||||
export * from "./utils"
|
||||
export * from "./workflows"
|
||||
|
||||
export * from "./many-to-many-inventory"
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { FeatureFlagTypes } from "@medusajs/types"
|
||||
|
||||
export const ManyToManyInventoryFeatureFlag: FeatureFlagTypes.FlagSettings = {
|
||||
key: "many_to_many_inventory",
|
||||
default_val: false,
|
||||
env_key: "MEDUSA_FF_MANY_TO_MANY_INVENTORY",
|
||||
description:
|
||||
"Enable capability to have many to many relationship between inventory items and variants",
|
||||
}
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from "./cart"
|
||||
export * from "./product"
|
||||
export * from "./inventory"
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
import { Workflows } from "../../definitions"
|
||||
import {
|
||||
TransactionStepsDefinition,
|
||||
WorkflowManager,
|
||||
} from "@medusajs/orchestration"
|
||||
import { exportWorkflow, pipe } from "../../helper"
|
||||
|
||||
import { InventoryTypes, WorkflowTypes } from "@medusajs/types"
|
||||
import { InventoryHandlers } from "../../handlers"
|
||||
|
||||
export enum CreateInventoryItemActions {
|
||||
prepare = "prepare",
|
||||
createInventoryItems = "createInventoryItems",
|
||||
}
|
||||
|
||||
const workflowSteps: TransactionStepsDefinition = {
|
||||
next: {
|
||||
action: CreateInventoryItemActions.createInventoryItems,
|
||||
},
|
||||
}
|
||||
|
||||
const handlers = new Map([
|
||||
[
|
||||
CreateInventoryItemActions.createInventoryItems,
|
||||
{
|
||||
invoke: pipe(
|
||||
{
|
||||
inputAlias: CreateInventoryItemActions.prepare,
|
||||
merge: true,
|
||||
invoke: {
|
||||
from: CreateInventoryItemActions.prepare,
|
||||
},
|
||||
},
|
||||
InventoryHandlers.createInventoryItems
|
||||
),
|
||||
compensate: pipe(
|
||||
{
|
||||
merge: true,
|
||||
invoke: {
|
||||
from: CreateInventoryItemActions.createInventoryItems,
|
||||
alias:
|
||||
InventoryHandlers.removeInventoryItems.aliases.inventoryItems,
|
||||
},
|
||||
},
|
||||
InventoryHandlers.removeInventoryItems
|
||||
),
|
||||
},
|
||||
],
|
||||
])
|
||||
|
||||
WorkflowManager.register(
|
||||
Workflows.CreateInventoryItems,
|
||||
workflowSteps,
|
||||
handlers
|
||||
)
|
||||
|
||||
export const createInventoryItems = exportWorkflow<
|
||||
WorkflowTypes.InventoryWorkflow.CreateInventoryItemsWorkflowInputDTO,
|
||||
{ tag: string; inventoryItem: InventoryTypes.InventoryItemDTO }[]
|
||||
>(
|
||||
Workflows.CreateInventoryItems,
|
||||
CreateInventoryItemActions.createInventoryItems,
|
||||
async (data) => data
|
||||
)
|
||||
1
packages/workflows/src/definition/inventory/index.ts
Normal file
1
packages/workflows/src/definition/inventory/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./create-inventory-item"
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
MiddlewaresHandlers,
|
||||
ProductHandlers,
|
||||
} from "../../handlers"
|
||||
import { prepareCreateInventoryItems } from "./prepare-create-inventory-items"
|
||||
|
||||
export enum CreateProductsActions {
|
||||
prepare = "prepare",
|
||||
@@ -175,9 +176,10 @@ const handlers = new Map([
|
||||
merge: true,
|
||||
invoke: {
|
||||
from: CreateProductsActions.createProducts,
|
||||
alias: InventoryHandlers.createInventoryItems.aliases.products,
|
||||
alias: prepareCreateInventoryItems.aliases.products,
|
||||
},
|
||||
},
|
||||
prepareCreateInventoryItems,
|
||||
InventoryHandlers.createInventoryItems
|
||||
),
|
||||
compensate: pipe(
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import { ProductTypes } from "@medusajs/types"
|
||||
import { WorkflowArguments } from "../../helper"
|
||||
|
||||
type AssociationTaggedVariant = ProductTypes.ProductVariantDTO & {
|
||||
_associationTag?: string
|
||||
}
|
||||
|
||||
type ObjectWithVariant = { variants: ProductTypes.ProductVariantDTO[] }
|
||||
|
||||
export async function prepareCreateInventoryItems({
|
||||
data,
|
||||
}: WorkflowArguments<{
|
||||
products: ObjectWithVariant[]
|
||||
}>) {
|
||||
const taggedVariants = data.products.reduce<AssociationTaggedVariant[]>(
|
||||
(acc, product: ObjectWithVariant) => {
|
||||
const cleanVariants = product.variants.reduce<AssociationTaggedVariant[]>(
|
||||
(acc, variant: AssociationTaggedVariant) => {
|
||||
if (!variant.manage_inventory) {
|
||||
return acc
|
||||
}
|
||||
|
||||
variant._associationTag = variant.id
|
||||
|
||||
acc.push(variant)
|
||||
return acc
|
||||
},
|
||||
[]
|
||||
)
|
||||
return acc.concat(cleanVariants)
|
||||
},
|
||||
[]
|
||||
)
|
||||
|
||||
return {
|
||||
alias: prepareCreateInventoryItems.aliases.output,
|
||||
value: {
|
||||
inventoryItems: taggedVariants,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
prepareCreateInventoryItems.aliases = {
|
||||
products: "products",
|
||||
output: "prepareCreateInventoryItemsOutput",
|
||||
}
|
||||
@@ -4,6 +4,8 @@ export enum Workflows {
|
||||
|
||||
// Cart workflows
|
||||
CreateCart = "create-cart",
|
||||
|
||||
CreateInventoryItems = "create-inventory-items",
|
||||
}
|
||||
|
||||
export enum InputAlias {
|
||||
@@ -16,4 +18,6 @@ export enum InputAlias {
|
||||
|
||||
AttachedInventoryItems = "attachedInventoryItems",
|
||||
DetachedInventoryItems = "detachedInventoryItems",
|
||||
|
||||
InventoryItemsInputData = "inventoryItemsInputData",
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { InventoryItemDTO, ProductTypes } from "@medusajs/types"
|
||||
import { InventoryItemDTO } from "@medusajs/types"
|
||||
import { WorkflowArguments } from "../../helper"
|
||||
|
||||
export async function attachInventoryItems({
|
||||
@@ -7,12 +7,11 @@ export async function attachInventoryItems({
|
||||
data,
|
||||
}: WorkflowArguments<{
|
||||
inventoryItems: {
|
||||
variant: ProductTypes.ProductVariantDTO
|
||||
tag: string
|
||||
inventoryItem: InventoryItemDTO
|
||||
}[]
|
||||
}>) {
|
||||
const { manager } = context
|
||||
|
||||
const productVariantInventoryService = container
|
||||
.resolve("productVariantInventoryService")
|
||||
.withTransaction(manager)
|
||||
@@ -21,12 +20,10 @@ export async function attachInventoryItems({
|
||||
return
|
||||
}
|
||||
|
||||
const inventoryData = data.inventoryItems.map(
|
||||
({ variant, inventoryItem }) => ({
|
||||
variantId: variant.id,
|
||||
inventoryItemId: inventoryItem.id,
|
||||
})
|
||||
)
|
||||
const inventoryData = data.inventoryItems.map(({ tag, inventoryItem }) => ({
|
||||
variantId: tag,
|
||||
inventoryItemId: inventoryItem.id,
|
||||
}))
|
||||
|
||||
return await productVariantInventoryService.attachInventoryItem(inventoryData)
|
||||
}
|
||||
|
||||
@@ -1,21 +1,16 @@
|
||||
import {
|
||||
IInventoryService,
|
||||
InventoryItemDTO,
|
||||
ProductTypes,
|
||||
} from "@medusajs/types"
|
||||
import { IInventoryService, InventoryItemDTO } from "@medusajs/types"
|
||||
import { WorkflowArguments } from "../../helper"
|
||||
|
||||
type Result = {
|
||||
variant: ProductTypes.ProductVariantDTO
|
||||
tag: string
|
||||
inventoryItem: InventoryItemDTO
|
||||
}[]
|
||||
|
||||
export async function createInventoryItems({
|
||||
container,
|
||||
context,
|
||||
data,
|
||||
}: WorkflowArguments<{
|
||||
products: ProductTypes.ProductDTO[]
|
||||
inventoryItems: (InventoryItemDTO & { _associationTag?: string })[]
|
||||
}>): Promise<Result | void> {
|
||||
const inventoryService: IInventoryService =
|
||||
container.resolve("inventoryService")
|
||||
@@ -28,47 +23,27 @@ export async function createInventoryItems({
|
||||
return void 0
|
||||
}
|
||||
|
||||
const variants = data.products.reduce(
|
||||
(
|
||||
acc: ProductTypes.ProductVariantDTO[],
|
||||
product: ProductTypes.ProductDTO
|
||||
) => {
|
||||
return acc.concat(product.variants)
|
||||
},
|
||||
[]
|
||||
)
|
||||
|
||||
const result = await Promise.all(
|
||||
variants.map(async (variant) => {
|
||||
if (!variant.manage_inventory) {
|
||||
return
|
||||
}
|
||||
data.inventoryItems.map(async (item) => {
|
||||
const inventoryItem = await inventoryService!.createInventoryItem({
|
||||
sku: item.sku!,
|
||||
origin_country: item.origin_country!,
|
||||
hs_code: item.hs_code!,
|
||||
mid_code: item.mid_code!,
|
||||
material: item.material!,
|
||||
weight: item.weight!,
|
||||
length: item.length!,
|
||||
height: item.height!,
|
||||
width: item.width!,
|
||||
})
|
||||
|
||||
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!,
|
||||
},
|
||||
{
|
||||
transactionManager: (context.transactionManager ??
|
||||
context.manager) as any,
|
||||
}
|
||||
)
|
||||
|
||||
return { variant, inventoryItem }
|
||||
return { tag: item._associationTag ?? inventoryItem.id, inventoryItem }
|
||||
})
|
||||
)
|
||||
|
||||
return result.filter(Boolean) as Result
|
||||
return result
|
||||
}
|
||||
|
||||
createInventoryItems.aliases = {
|
||||
products: "products",
|
||||
payload: "payload",
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { InventoryItemDTO, ProductTypes } from "@medusajs/types"
|
||||
import { InventoryItemDTO } from "@medusajs/types"
|
||||
import { WorkflowArguments } from "../../helper"
|
||||
|
||||
export async function detachInventoryItems({
|
||||
@@ -7,7 +7,7 @@ export async function detachInventoryItems({
|
||||
data,
|
||||
}: WorkflowArguments<{
|
||||
inventoryItems: {
|
||||
variant: ProductTypes.ProductVariantDTO
|
||||
tag: string
|
||||
inventoryItem: InventoryItemDTO
|
||||
}[]
|
||||
}>): Promise<void> {
|
||||
@@ -22,10 +22,10 @@ export async function detachInventoryItems({
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
data.inventoryItems.map(async ({ variant, inventoryItem }) => {
|
||||
data.inventoryItems.map(async ({ tag, inventoryItem }) => {
|
||||
return await productVariantInventoryService.detachInventoryItem(
|
||||
inventoryItem.id,
|
||||
variant.id
|
||||
tag
|
||||
)
|
||||
})
|
||||
)
|
||||
|
||||
@@ -3,12 +3,10 @@ import { WorkflowArguments } from "../../helper"
|
||||
|
||||
export async function removeInventoryItems({
|
||||
container,
|
||||
context,
|
||||
data,
|
||||
}: WorkflowArguments<{
|
||||
inventoryItems: { inventoryItem: InventoryItemDTO }[]
|
||||
}>) {
|
||||
const { manager } = context
|
||||
const inventoryService = container.resolve("inventoryService")
|
||||
|
||||
if (!inventoryService) {
|
||||
@@ -20,8 +18,7 @@ export async function removeInventoryItems({
|
||||
}
|
||||
|
||||
return await inventoryService!.deleteInventoryItem(
|
||||
data.inventoryItems.map(({ inventoryItem }) => inventoryItem.id),
|
||||
{ transactionManager: manager }
|
||||
data.inventoryItems.map(({ inventoryItem }) => inventoryItem.id)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -6499,7 +6499,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@medusajs/inventory@workspace:packages/inventory":
|
||||
"@medusajs/inventory@workspace:^, @medusajs/inventory@workspace:packages/inventory":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@medusajs/inventory@workspace:packages/inventory"
|
||||
dependencies:
|
||||
@@ -26516,6 +26516,7 @@ __metadata:
|
||||
"@babel/node": ^7.12.10
|
||||
"@medusajs/cache-inmemory": "workspace:*"
|
||||
"@medusajs/event-bus-local": "workspace:*"
|
||||
"@medusajs/inventory": "workspace:^"
|
||||
"@medusajs/medusa": "workspace:*"
|
||||
"@medusajs/product": "workspace:^"
|
||||
babel-preset-medusa-package: "*"
|
||||
|
||||
Reference in New Issue
Block a user