Feat(core-flows, medusa): delete inventory item (#6708)
* initial get-inventory-item * add exception throw test * remove console log * add changeset * remove inventory item * add changeset * fix pr feedback * use links * use modules instead of property names
This commit is contained in:
1 parent
c20eb15cd9
commit
62d5803b20
9 files changed
+226
-83
No files matched your search
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
"@medusajs/inventory-next": patch
|
||||||
|
"@medusajs/core-flows": patch
|
||||||
|
"@medusajs/medusa": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
feat(medusa, core-flows, inventory-next): add delete-inventory-item endpoint
|
||||||
@@ -615,145 +615,146 @@ medusaIntegrationTestRunner({
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it.skip("should remove associated levels and reservations when deleting an inventory item", async () => {
|
describe("delete inventory item", () => {
|
||||||
const inventoryService = appContainer.resolve("inventoryService")
|
let invItem
|
||||||
|
beforeEach(async () => {
|
||||||
const invItem2 = await inventoryService.createInventoryItem({
|
invItem = await service.create({
|
||||||
sku: "1234567",
|
sku: "MY_SKU",
|
||||||
|
origin_country: "UK",
|
||||||
|
hs_code: "hs001",
|
||||||
|
mid_code: "mids",
|
||||||
|
material: "material",
|
||||||
|
weight: 300,
|
||||||
|
length: 100,
|
||||||
|
height: 200,
|
||||||
|
width: 150,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const stockRes = await api.post(
|
it("should remove associated levels and reservations when deleting an inventory item", async () => {
|
||||||
`/admin/stock-locations`,
|
const inventoryService = appContainer.resolve(
|
||||||
{
|
ModuleRegistrationName.INVENTORY
|
||||||
name: "Fake Warehouse 1",
|
|
||||||
},
|
|
||||||
adminHeaders
|
|
||||||
)
|
)
|
||||||
|
|
||||||
locationId = stockRes.data.stock_location.id
|
locationId = "location1"
|
||||||
|
|
||||||
const level = await inventoryService.createInventoryLevel({
|
await inventoryService.createInventoryLevels({
|
||||||
inventory_item_id: invItem2.id,
|
inventory_item_id: invItem.id,
|
||||||
location_id: locationId,
|
location_id: locationId,
|
||||||
stocked_quantity: 10,
|
stocked_quantity: 10,
|
||||||
})
|
})
|
||||||
|
|
||||||
const reservation = await inventoryService.createReservationItem({
|
await inventoryService.createReservationItems({
|
||||||
inventory_item_id: invItem2.id,
|
inventory_item_id: invItem.id,
|
||||||
location_id: locationId,
|
location_id: locationId,
|
||||||
quantity: 5,
|
quantity: 5,
|
||||||
})
|
})
|
||||||
|
|
||||||
const [, reservationCount] =
|
const [, reservationCount] =
|
||||||
await inventoryService.listReservationItems({
|
await inventoryService.listAndCountReservationItems({
|
||||||
location_id: locationId,
|
location_id: locationId,
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(reservationCount).toEqual(1)
|
expect(reservationCount).toEqual(1)
|
||||||
|
|
||||||
const [, inventoryLevelCount] =
|
const [, inventoryLevelCount] =
|
||||||
await inventoryService.listInventoryLevels({
|
await inventoryService.listAndCountInventoryLevels({
|
||||||
location_id: locationId,
|
location_id: locationId,
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(inventoryLevelCount).toEqual(1)
|
expect(inventoryLevelCount).toEqual(1)
|
||||||
|
|
||||||
const res = await api.delete(
|
const res = await api.delete(
|
||||||
`/admin/stock-locations/${locationId}`,
|
`/admin/inventory-items/${invItem.id}`,
|
||||||
adminHeaders
|
adminHeaders
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(res.status).toEqual(200)
|
expect(res.status).toEqual(200)
|
||||||
|
|
||||||
const [, reservationCountPostDelete] =
|
const [, reservationCountPostDelete] =
|
||||||
await inventoryService.listReservationItems({
|
await inventoryService.listAndCountReservationItems({
|
||||||
location_id: locationId,
|
location_id: locationId,
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(reservationCountPostDelete).toEqual(0)
|
expect(reservationCountPostDelete).toEqual(0)
|
||||||
|
|
||||||
const [, inventoryLevelCountPostDelete] =
|
const [, inventoryLevelCountPostDelete] =
|
||||||
await inventoryService.listInventoryLevels({
|
await inventoryService.listAndCountInventoryLevels({
|
||||||
location_id: locationId,
|
location_id: locationId,
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(inventoryLevelCountPostDelete).toEqual(0)
|
expect(inventoryLevelCountPostDelete).toEqual(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
it.skip("should remove the product variant associations when deleting an inventory item", async () => {
|
it("should remove the product variant associations when deleting an inventory item", async () => {
|
||||||
await simpleProductFactory(
|
const secondVariantId = "test-2"
|
||||||
dbConnection,
|
const variantId = "test"
|
||||||
|
|
||||||
|
const remoteLinks = appContainer.resolve(
|
||||||
|
ContainerRegistrationKeys.REMOTE_LINK
|
||||||
|
)
|
||||||
|
const remoteQuery = appContainer.resolve(
|
||||||
|
ContainerRegistrationKeys.REMOTE_QUERY
|
||||||
|
)
|
||||||
|
|
||||||
|
await remoteLinks.create([
|
||||||
{
|
{
|
||||||
id: "test-product-new",
|
productService: {
|
||||||
variants: [],
|
variant_id: variantId,
|
||||||
|
},
|
||||||
|
inventoryService: {
|
||||||
|
inventory_item_id: invItem.id,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
5
|
|
||||||
)
|
|
||||||
|
|
||||||
const response = await api.post(
|
|
||||||
`/admin/products/test-product-new/variants`,
|
|
||||||
{
|
{
|
||||||
title: "Test2",
|
productService: {
|
||||||
sku: "MY_SKU2",
|
variant_id: secondVariantId,
|
||||||
manage_inventory: true,
|
},
|
||||||
options: [
|
inventoryService: {
|
||||||
|
inventory_item_id: invItem.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
let links = await remoteQuery(
|
||||||
|
remoteQueryObjectFromString({
|
||||||
|
entryPoint: "product_variant_inventory_item",
|
||||||
|
variables: {
|
||||||
|
filter: { variant_id: [variantId, secondVariantId] },
|
||||||
|
},
|
||||||
|
fields: ["variant_id", "inventory_item_id"],
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(links).toHaveLength(2)
|
||||||
|
expect(links).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
{
|
{
|
||||||
option_id: "test-product-new-option",
|
variant_id: "test",
|
||||||
value: "Blue",
|
inventory_item_id: invItem.id,
|
||||||
},
|
},
|
||||||
],
|
{
|
||||||
prices: [{ currency_code: "usd", amount: 100 }],
|
variant_id: "test-2",
|
||||||
|
inventory_item_id: invItem.id,
|
||||||
},
|
},
|
||||||
{ headers: { "x-medusa-access-token": "test_token" } }
|
])
|
||||||
)
|
)
|
||||||
|
|
||||||
const secondVariantId = response.data.product.variants.find(
|
await api.delete(`/admin/inventory-items/${invItem.id}`, adminHeaders)
|
||||||
(v) => v.sku === "MY_SKU2"
|
|
||||||
).id
|
|
||||||
|
|
||||||
const inventoryService = appContainer.resolve("inventoryService")
|
links = await remoteQuery(
|
||||||
const variantInventoryService = appContainer.resolve(
|
remoteQueryObjectFromString({
|
||||||
"productVariantInventoryService"
|
entryPoint: "product_variant_inventory_item",
|
||||||
)
|
variables: {
|
||||||
|
filter: { variant_id: [variantId, secondVariantId] },
|
||||||
const invItem2 = await inventoryService.createInventoryItem({
|
},
|
||||||
sku: "123456",
|
fields: ["variant_id", "inventory_item_id"],
|
||||||
})
|
})
|
||||||
|
|
||||||
await variantInventoryService.attachInventoryItem(
|
|
||||||
variantId,
|
|
||||||
invItem2.id,
|
|
||||||
2
|
|
||||||
)
|
|
||||||
await variantInventoryService.attachInventoryItem(
|
|
||||||
secondVariantId,
|
|
||||||
invItem2.id,
|
|
||||||
2
|
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(
|
expect(links).toHaveLength(0)
|
||||||
await variantInventoryService.listInventoryItemsByVariant(variantId)
|
expect(links).toEqual([])
|
||||||
).toHaveLength(2)
|
|
||||||
|
|
||||||
expect(
|
|
||||||
await variantInventoryService.listInventoryItemsByVariant(
|
|
||||||
secondVariantId
|
|
||||||
)
|
|
||||||
).toHaveLength(2)
|
|
||||||
|
|
||||||
await api.delete(`/admin/inventory-items/${invItem2.id}`, {
|
|
||||||
headers: { "x-medusa-access-token": "test_token" },
|
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(
|
|
||||||
await variantInventoryService.listInventoryItemsByVariant(variantId)
|
|
||||||
).toHaveLength(1)
|
|
||||||
|
|
||||||
expect(
|
|
||||||
await variantInventoryService.listInventoryItemsByVariant(
|
|
||||||
secondVariantId
|
|
||||||
)
|
|
||||||
).toHaveLength(1)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import {
|
||||||
|
ContainerRegistrationKeys,
|
||||||
|
remoteQueryObjectFromString,
|
||||||
|
} from "@medusajs/utils"
|
||||||
|
import { ModuleRegistrationName, Modules } from "@medusajs/modules-sdk"
|
||||||
|
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
|
||||||
|
|
||||||
|
import { ILinkModule } from "@medusajs/types"
|
||||||
|
|
||||||
|
export const deatachInventoryItemStepId = "deattach-inventory-items-step"
|
||||||
|
export const deatachInventoryItemStep = createStep(
|
||||||
|
deatachInventoryItemStepId,
|
||||||
|
async (ids: string[], { container }) => {
|
||||||
|
const remoteLink = container.resolve(ContainerRegistrationKeys.REMOTE_LINK)
|
||||||
|
|
||||||
|
const linkModule: ILinkModule = remoteLink.getLinkModule(
|
||||||
|
Modules.PRODUCT,
|
||||||
|
"variant_id",
|
||||||
|
Modules.INVENTORY,
|
||||||
|
"inventory_item_id"
|
||||||
|
)
|
||||||
|
|
||||||
|
const links = (await linkModule.list(
|
||||||
|
{ inventory_item_id: ids },
|
||||||
|
{ select: ["variant_id", "inventory_item_id"] }
|
||||||
|
)) as { inventory_item_id: string; variant_id: string }[]
|
||||||
|
|
||||||
|
await remoteLink.dismiss(
|
||||||
|
links.map(({ inventory_item_id, variant_id }) => ({
|
||||||
|
[Modules.PRODUCT]: {
|
||||||
|
variant_id,
|
||||||
|
},
|
||||||
|
[Modules.INVENTORY]: {
|
||||||
|
inventory_item_id,
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
|
||||||
|
return new StepResponse(void 0, links)
|
||||||
|
},
|
||||||
|
async (input, { container }) => {
|
||||||
|
if (!input?.length) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const remoteLink = container.resolve(ContainerRegistrationKeys.REMOTE_LINK)
|
||||||
|
|
||||||
|
const linkDefinitions = input.map(({ inventory_item_id, variant_id }) => ({
|
||||||
|
[Modules.PRODUCT]: {
|
||||||
|
variant_id,
|
||||||
|
},
|
||||||
|
[Modules.INVENTORY]: {
|
||||||
|
inventory_item_id,
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
const links = await remoteLink.create(linkDefinitions)
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
|
||||||
|
|
||||||
|
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||||
|
|
||||||
|
export const deleteInventoryItemStepId = "delete-inventory-item-step"
|
||||||
|
export const deleteInventoryItemStep = createStep(
|
||||||
|
deleteInventoryItemStepId,
|
||||||
|
async (ids: string[], { container }) => {
|
||||||
|
const inventoryService = container.resolve(ModuleRegistrationName.INVENTORY)
|
||||||
|
|
||||||
|
await inventoryService.softDelete(ids)
|
||||||
|
|
||||||
|
return new StepResponse(void 0, ids)
|
||||||
|
},
|
||||||
|
async (prevInventoryItemIds, { container }) => {
|
||||||
|
if (!prevInventoryItemIds?.length) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const inventoryService = container.resolve(ModuleRegistrationName.INVENTORY)
|
||||||
|
|
||||||
|
await inventoryService.restore(prevInventoryItemIds)
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
export * from "./delete-inventory-items"
|
||||||
|
export * from "./deatach-inventory-items"
|
||||||
export * from "./attach-inventory-items"
|
export * from "./attach-inventory-items"
|
||||||
export * from "./create-inventory-items"
|
export * from "./create-inventory-items"
|
||||||
export * from "./validate-singular-inventory-items-for-tags"
|
export * from "./validate-singular-inventory-items-for-tags"
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
|
||||||
|
import { deatachInventoryItemStep, deleteInventoryItemStep } from "../steps"
|
||||||
|
|
||||||
|
export const deleteInventoryItemWorkflowId = "delete-inventory-item-workflow"
|
||||||
|
export const deleteInventoryItemWorkflow = createWorkflow(
|
||||||
|
deleteInventoryItemWorkflowId,
|
||||||
|
(input: WorkflowData<string[]>): WorkflowData<string[]> => {
|
||||||
|
deleteInventoryItemStep(input)
|
||||||
|
|
||||||
|
deatachInventoryItemStep(input)
|
||||||
|
return input
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
export * from "./delete-inventory-items"
|
||||||
export * from "./create-inventory-items"
|
export * from "./create-inventory-items"
|
||||||
export * from "./create-inventory-levels"
|
export * from "./create-inventory-levels"
|
||||||
export * from "./delete-inventory-levels"
|
export * from "./delete-inventory-levels"
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
BeforeCreate,
|
BeforeCreate,
|
||||||
|
Cascade,
|
||||||
Collection,
|
Collection,
|
||||||
Entity,
|
Entity,
|
||||||
Filter,
|
Filter,
|
||||||
@@ -19,6 +20,7 @@ import {
|
|||||||
|
|
||||||
import { DAL } from "@medusajs/types"
|
import { DAL } from "@medusajs/types"
|
||||||
import { InventoryLevel } from "./inventory-level"
|
import { InventoryLevel } from "./inventory-level"
|
||||||
|
import { ReservationItem } from "./reservation-item"
|
||||||
|
|
||||||
const InventoryItemDeletedAtIndex = createPsqlIndexStatementHelper({
|
const InventoryItemDeletedAtIndex = createPsqlIndexStatementHelper({
|
||||||
tableName: "inventory_item",
|
tableName: "inventory_item",
|
||||||
@@ -106,10 +108,22 @@ export class InventoryItem {
|
|||||||
|
|
||||||
@OneToMany(
|
@OneToMany(
|
||||||
() => InventoryLevel,
|
() => InventoryLevel,
|
||||||
(inventoryLevel) => inventoryLevel.inventory_item
|
(inventoryLevel) => inventoryLevel.inventory_item,
|
||||||
|
{
|
||||||
|
cascade: ["soft-remove" as any],
|
||||||
|
}
|
||||||
)
|
)
|
||||||
location_levels = new Collection<InventoryLevel>(this)
|
location_levels = new Collection<InventoryLevel>(this)
|
||||||
|
|
||||||
|
@OneToMany(
|
||||||
|
() => ReservationItem,
|
||||||
|
(reservationItem) => reservationItem.inventory_item,
|
||||||
|
{
|
||||||
|
cascade: ["soft-remove" as any],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
reservation_items = new Collection<ReservationItem>(this)
|
||||||
|
|
||||||
@Formula(
|
@Formula(
|
||||||
(item) =>
|
(item) =>
|
||||||
`(SELECT SUM(reserved_quantity) FROM inventory_level il WHERE il.inventory_item_id = ${item}.id)`,
|
`(SELECT SUM(reserved_quantity) FROM inventory_level il WHERE il.inventory_item_id = ${item}.id)`,
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import {
|
|||||||
} from "@medusajs/utils"
|
} from "@medusajs/utils"
|
||||||
import { MedusaRequest, MedusaResponse } from "../../../../types/routing"
|
import { MedusaRequest, MedusaResponse } from "../../../../types/routing"
|
||||||
|
|
||||||
|
import { deleteInventoryItemWorkflow } from "@medusajs/core-flows"
|
||||||
|
|
||||||
export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
|
export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
|
||||||
const { id } = req.params
|
const { id } = req.params
|
||||||
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
|
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
|
||||||
@@ -35,3 +37,23 @@ export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
|
|||||||
inventory_item,
|
inventory_item,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const DELETE = async (req: MedusaRequest, res: MedusaResponse) => {
|
||||||
|
const id = req.params.id
|
||||||
|
const deleteInventoryItems = deleteInventoryItemWorkflow(req.scope)
|
||||||
|
|
||||||
|
const { errors } = await deleteInventoryItems.run({
|
||||||
|
input: [id],
|
||||||
|
throwOnError: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (Array.isArray(errors) && errors[0]) {
|
||||||
|
throw errors[0].error
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status(200).json({
|
||||||
|
id,
|
||||||
|
object: "inventory_item",
|
||||||
|
deleted: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
Reference in new issue
Block a user