From 66413d094e916debbdb74b68800c96ca2c9302c9 Mon Sep 17 00:00:00 2001 From: Sebastian Rindom Date: Wed, 11 Oct 2023 11:01:56 -0700 Subject: [PATCH] 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? --- .changeset/tame-queens-cough.md | 8 + .../inventory-items/ff-many-to-many.js | 96 ++++++++++ .../inventory/inventory-items/index.js | 2 +- .../inventory/create-inventory-items.ts | 93 ++++++++++ integration-tests/plugins/package.json | 1 + packages/inventory/src/utils/build-query.ts | 2 +- packages/inventory/src/utils/query.ts | 4 + .../inventory-items/create-inventory-item.ts | 138 +++++++++----- .../transaction/create-inventory-item.ts | 173 ------------------ packages/types/src/workflow/index.ts | 1 + .../inventory/create-inventory-items.ts | 19 ++ .../types/src/workflow/inventory/index.ts | 1 + packages/utils/src/feature-flags/index.ts | 2 +- .../feature-flags/many-to-many-inventory.ts | 9 + packages/workflows/src/definition/index.ts | 1 + .../inventory/create-inventory-item.ts | 64 +++++++ .../src/definition/inventory/index.ts | 1 + .../src/definition/product/create-products.ts | 4 +- .../product/prepare-create-inventory-items.ts | 46 +++++ packages/workflows/src/definitions.ts | 4 + .../inventory/attach-inventory-items.ts | 15 +- .../inventory/create-inventory-items.ts | 61 ++---- .../inventory/detach-inventory-items.ts | 8 +- .../inventory/remove-inventory-items.ts | 5 +- yarn.lock | 3 +- 25 files changed, 480 insertions(+), 281 deletions(-) create mode 100644 .changeset/tame-queens-cough.md create mode 100644 integration-tests/plugins/__tests__/inventory/inventory-items/ff-many-to-many.js create mode 100644 integration-tests/plugins/__tests__/workflows/inventory/create-inventory-items.ts delete mode 100644 packages/medusa/src/api/routes/admin/inventory-items/transaction/create-inventory-item.ts create mode 100644 packages/types/src/workflow/inventory/create-inventory-items.ts create mode 100644 packages/types/src/workflow/inventory/index.ts create mode 100644 packages/utils/src/feature-flags/many-to-many-inventory.ts create mode 100644 packages/workflows/src/definition/inventory/create-inventory-item.ts create mode 100644 packages/workflows/src/definition/inventory/index.ts create mode 100644 packages/workflows/src/definition/product/prepare-create-inventory-items.ts diff --git a/.changeset/tame-queens-cough.md b/.changeset/tame-queens-cough.md new file mode 100644 index 0000000000..031e56f686 --- /dev/null +++ b/.changeset/tame-queens-cough.md @@ -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 diff --git a/integration-tests/plugins/__tests__/inventory/inventory-items/ff-many-to-many.js b/integration-tests/plugins/__tests__/inventory/inventory-items/ff-many-to-many.js new file mode 100644 index 0000000000..9e7c136b96 --- /dev/null +++ b/integration-tests/plugins/__tests__/inventory/inventory-items/ff-many-to-many.js @@ -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) + }) + }) +}) diff --git a/integration-tests/plugins/__tests__/inventory/inventory-items/index.js b/integration-tests/plugins/__tests__/inventory/inventory-items/index.js index 7a022ca431..e91ecfe267 100644 --- a/integration-tests/plugins/__tests__/inventory/inventory-items/index.js +++ b/integration-tests/plugins/__tests__/inventory/inventory-items/index.js @@ -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 ) diff --git a/integration-tests/plugins/__tests__/workflows/inventory/create-inventory-items.ts b/integration-tests/plugins/__tests__/workflows/inventory/create-inventory-items.ts new file mode 100644 index 0000000000..a6d1452599 --- /dev/null +++ b/integration-tests/plugins/__tests__/workflows/inventory/create-inventory-items.ts @@ -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), + }) + ) + }) +}) diff --git a/integration-tests/plugins/package.json b/integration-tests/plugins/package.json index f87092336e..33f4d43275 100644 --- a/integration-tests/plugins/package.json +++ b/integration-tests/plugins/package.json @@ -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", diff --git a/packages/inventory/src/utils/build-query.ts b/packages/inventory/src/utils/build-query.ts index f2d32c7ae2..ae4c86914f 100644 --- a/packages/inventory/src/utils/build-query.ts +++ b/packages/inventory/src/utils/build-query.ts @@ -41,7 +41,7 @@ export function buildQuery( where: buildWhere(selector), } - if ("deleted_at" in selector) { + if ("deleted_at" in selector || config.withDeleted) { query.withDeleted = true } diff --git a/packages/inventory/src/utils/query.ts b/packages/inventory/src/utils/query.ts index d750e5c7e9..386628d242 100644 --- a/packages/inventory/src/utils/query.ts +++ b/packages/inventory/src/utils/query.ts @@ -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]) => { diff --git a/packages/medusa/src/api/routes/admin/inventory-items/create-inventory-item.ts b/packages/medusa/src/api/routes/admin/inventory-items/create-inventory-item.ts index d6b53eef3c..4b64df33a1 100644 --- a/packages/medusa/src/api/routes/admin/inventory-items/create-inventory-item.ts +++ b/packages/medusa/src/api/routes/admin/inventory-items/create-inventory-item.ts @@ -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 diff --git a/packages/medusa/src/api/routes/admin/inventory-items/transaction/create-inventory-item.ts b/packages/medusa/src/api/routes/admin/inventory-items/transaction/create-inventory-item.ts deleted file mode 100644 index 7d21ec4cfd..0000000000 --- a/packages/medusa/src/api/routes/admin/inventory-items/transaction/create-inventory-item.ts +++ /dev/null @@ -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 -} - -export const createInventoryItemTransaction = async ( - dependencies: InjectedDependencies, - variantId: string, - input: CreateInventoryItemInput -): Promise => { - 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) -} diff --git a/packages/types/src/workflow/index.ts b/packages/types/src/workflow/index.ts index 8294d17488..ecce74d8af 100644 --- a/packages/types/src/workflow/index.ts +++ b/packages/types/src/workflow/index.ts @@ -1,3 +1,4 @@ export * as CartWorkflow from "./cart" export * as CommonWorkflow from "./common" export * as ProductWorkflow from "./product" +export * as InventoryWorkflow from "./inventory" diff --git a/packages/types/src/workflow/inventory/create-inventory-items.ts b/packages/types/src/workflow/inventory/create-inventory-items.ts new file mode 100644 index 0000000000..77f9780bbc --- /dev/null +++ b/packages/types/src/workflow/inventory/create-inventory-items.ts @@ -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 +} + +export interface CreateInventoryItemsWorkflowInputDTO { + inventoryItems: CreateInventoryItemInputDTO[] +} diff --git a/packages/types/src/workflow/inventory/index.ts b/packages/types/src/workflow/inventory/index.ts new file mode 100644 index 0000000000..dde4cda986 --- /dev/null +++ b/packages/types/src/workflow/inventory/index.ts @@ -0,0 +1 @@ +export * from "./create-inventory-items" diff --git a/packages/utils/src/feature-flags/index.ts b/packages/utils/src/feature-flags/index.ts index 2f795e693a..ffd68c21f6 100644 --- a/packages/utils/src/feature-flags/index.ts +++ b/packages/utils/src/feature-flags/index.ts @@ -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" diff --git a/packages/utils/src/feature-flags/many-to-many-inventory.ts b/packages/utils/src/feature-flags/many-to-many-inventory.ts new file mode 100644 index 0000000000..c2bd4fb6bd --- /dev/null +++ b/packages/utils/src/feature-flags/many-to-many-inventory.ts @@ -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", +} diff --git a/packages/workflows/src/definition/index.ts b/packages/workflows/src/definition/index.ts index b4ac306ea7..50b5440ede 100644 --- a/packages/workflows/src/definition/index.ts +++ b/packages/workflows/src/definition/index.ts @@ -1,2 +1,3 @@ export * from "./cart" export * from "./product" +export * from "./inventory" diff --git a/packages/workflows/src/definition/inventory/create-inventory-item.ts b/packages/workflows/src/definition/inventory/create-inventory-item.ts new file mode 100644 index 0000000000..1ca8aa3d37 --- /dev/null +++ b/packages/workflows/src/definition/inventory/create-inventory-item.ts @@ -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 +) diff --git a/packages/workflows/src/definition/inventory/index.ts b/packages/workflows/src/definition/inventory/index.ts new file mode 100644 index 0000000000..7c6ec04df0 --- /dev/null +++ b/packages/workflows/src/definition/inventory/index.ts @@ -0,0 +1 @@ +export * from "./create-inventory-item" diff --git a/packages/workflows/src/definition/product/create-products.ts b/packages/workflows/src/definition/product/create-products.ts index d4fc7a2956..be93a97cab 100644 --- a/packages/workflows/src/definition/product/create-products.ts +++ b/packages/workflows/src/definition/product/create-products.ts @@ -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( diff --git a/packages/workflows/src/definition/product/prepare-create-inventory-items.ts b/packages/workflows/src/definition/product/prepare-create-inventory-items.ts new file mode 100644 index 0000000000..36dbb908f8 --- /dev/null +++ b/packages/workflows/src/definition/product/prepare-create-inventory-items.ts @@ -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( + (acc, product: ObjectWithVariant) => { + const cleanVariants = product.variants.reduce( + (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", +} diff --git a/packages/workflows/src/definitions.ts b/packages/workflows/src/definitions.ts index c41de3610e..9b99ae77ff 100644 --- a/packages/workflows/src/definitions.ts +++ b/packages/workflows/src/definitions.ts @@ -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", } diff --git a/packages/workflows/src/handlers/inventory/attach-inventory-items.ts b/packages/workflows/src/handlers/inventory/attach-inventory-items.ts index 45894dbae3..f44453d6e0 100644 --- a/packages/workflows/src/handlers/inventory/attach-inventory-items.ts +++ b/packages/workflows/src/handlers/inventory/attach-inventory-items.ts @@ -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) } diff --git a/packages/workflows/src/handlers/inventory/create-inventory-items.ts b/packages/workflows/src/handlers/inventory/create-inventory-items.ts index dffe7d7d3d..11eb290a60 100644 --- a/packages/workflows/src/handlers/inventory/create-inventory-items.ts +++ b/packages/workflows/src/handlers/inventory/create-inventory-items.ts @@ -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 { 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", } diff --git a/packages/workflows/src/handlers/inventory/detach-inventory-items.ts b/packages/workflows/src/handlers/inventory/detach-inventory-items.ts index 6941630971..f38d65ed3f 100644 --- a/packages/workflows/src/handlers/inventory/detach-inventory-items.ts +++ b/packages/workflows/src/handlers/inventory/detach-inventory-items.ts @@ -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 { @@ -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 ) }) ) diff --git a/packages/workflows/src/handlers/inventory/remove-inventory-items.ts b/packages/workflows/src/handlers/inventory/remove-inventory-items.ts index 649f148e6c..480bc307f1 100644 --- a/packages/workflows/src/handlers/inventory/remove-inventory-items.ts +++ b/packages/workflows/src/handlers/inventory/remove-inventory-items.ts @@ -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) ) } diff --git a/yarn.lock b/yarn.lock index 534b257df3..b3b4bdd5a5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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: "*"