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:
Sebastian Rindom
2023-10-11 11:01:56 -07:00
committed by GitHub
parent bbd9dd408f
commit 66413d094e
25 changed files with 480 additions and 281 deletions

View File

@@ -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)
})
})
})

View File

@@ -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
)

View File

@@ -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),
})
)
})
})

View File

@@ -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",