Feat(inventory-next, medusa): Add List inventory items endpoint (#6694)
* code for list inventory items * fix integration tests * move integration test * calculate reserved and stocked quantities in db * update lockfile
This commit is contained in:
1 parent
3dd55efd15
commit
c3f8c30ba6
14 files changed
+1247
-16
No files matched your search
@@ -0,0 +1,726 @@
|
|||||||
|
import { IInventoryServiceNext, IStockLocationService } from "@medusajs/types"
|
||||||
|
|
||||||
|
import { ContainerRegistrationKeys } from "@medusajs/utils"
|
||||||
|
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||||
|
import { createAdminUser } from "../../../helpers/create-admin-user"
|
||||||
|
import { remoteQueryObjectFromString } from "@medusajs/utils"
|
||||||
|
|
||||||
|
const { medusaIntegrationTestRunner } = require("medusa-test-utils")
|
||||||
|
|
||||||
|
jest.setTimeout(30000)
|
||||||
|
|
||||||
|
const { simpleProductFactory } = require("../../../factories")
|
||||||
|
const adminHeaders = { headers: { "x-medusa-access-token": "test_token" } }
|
||||||
|
|
||||||
|
medusaIntegrationTestRunner({
|
||||||
|
env: {
|
||||||
|
MEDUSA_FF_MEDUSA_V2: true,
|
||||||
|
},
|
||||||
|
testSuite: ({ dbConnection, getContainer, api }) => {
|
||||||
|
let appContainer
|
||||||
|
let shutdownServer
|
||||||
|
let service: IInventoryServiceNext
|
||||||
|
|
||||||
|
let variantId
|
||||||
|
let inventoryItems
|
||||||
|
let locationId
|
||||||
|
let location2Id
|
||||||
|
let location3Id
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
appContainer = getContainer()
|
||||||
|
|
||||||
|
await createAdminUser(dbConnection, adminHeaders, appContainer)
|
||||||
|
|
||||||
|
service = appContainer.resolve(ModuleRegistrationName.INVENTORY)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("Inventory Items", () => {
|
||||||
|
it.skip("should create, update and delete the inventory location levels", async () => {
|
||||||
|
const inventoryItemId = inventoryItems[0].id
|
||||||
|
|
||||||
|
await api.post(
|
||||||
|
`/admin/inventory-items/${inventoryItemId}/location-levels`,
|
||||||
|
{
|
||||||
|
location_id: locationId,
|
||||||
|
stocked_quantity: 17,
|
||||||
|
incoming_quantity: 2,
|
||||||
|
},
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
const inventoryService = appContainer.resolve("inventoryService")
|
||||||
|
const stockLevel = await inventoryService.retrieveInventoryLevel(
|
||||||
|
inventoryItemId,
|
||||||
|
locationId
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(stockLevel.location_id).toEqual(locationId)
|
||||||
|
expect(stockLevel.inventory_item_id).toEqual(inventoryItemId)
|
||||||
|
expect(stockLevel.stocked_quantity).toEqual(17)
|
||||||
|
expect(stockLevel.incoming_quantity).toEqual(2)
|
||||||
|
|
||||||
|
await api.post(
|
||||||
|
`/admin/inventory-items/${inventoryItemId}/location-levels/${locationId}`,
|
||||||
|
{
|
||||||
|
stocked_quantity: 21,
|
||||||
|
incoming_quantity: 0,
|
||||||
|
},
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
const newStockLevel = await inventoryService.retrieveInventoryLevel(
|
||||||
|
inventoryItemId,
|
||||||
|
locationId
|
||||||
|
)
|
||||||
|
expect(newStockLevel.stocked_quantity).toEqual(21)
|
||||||
|
expect(newStockLevel.incoming_quantity).toEqual(0)
|
||||||
|
|
||||||
|
await api.delete(
|
||||||
|
`/admin/inventory-items/${inventoryItemId}/location-levels/${locationId}`,
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
const invLevel = await inventoryService
|
||||||
|
.retrieveInventoryLevel(inventoryItemId, locationId)
|
||||||
|
.catch((e) => e)
|
||||||
|
|
||||||
|
expect(invLevel.message).toEqual(
|
||||||
|
`Inventory level for item ${inventoryItemId} and location ${locationId} not found`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it.skip("should update the inventory item", async () => {
|
||||||
|
const inventoryItemId = inventoryItems[0].id
|
||||||
|
|
||||||
|
const response = await api.post(
|
||||||
|
`/admin/inventory-items/${inventoryItemId}`,
|
||||||
|
{
|
||||||
|
mid_code: "updated mid_code",
|
||||||
|
weight: 120,
|
||||||
|
},
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.data.inventory_item).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
origin_country: "UK",
|
||||||
|
hs_code: "hs001",
|
||||||
|
mid_code: "updated mid_code",
|
||||||
|
weight: 120,
|
||||||
|
length: 100,
|
||||||
|
height: 200,
|
||||||
|
width: 150,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it.skip("should fail to update the location level to negative quantity", async () => {
|
||||||
|
const inventoryItemId = inventoryItems[0].id
|
||||||
|
|
||||||
|
await api.post(
|
||||||
|
`/admin/inventory-items/${inventoryItemId}/location-levels`,
|
||||||
|
{
|
||||||
|
location_id: locationId,
|
||||||
|
stocked_quantity: 17,
|
||||||
|
incoming_quantity: 2,
|
||||||
|
},
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
const res = await api
|
||||||
|
.post(
|
||||||
|
`/admin/inventory-items/${inventoryItemId}/location-levels/${locationId}`,
|
||||||
|
{
|
||||||
|
incoming_quantity: -1,
|
||||||
|
stocked_quantity: -1,
|
||||||
|
},
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
.catch((error) => error)
|
||||||
|
|
||||||
|
expect(res.response.status).toEqual(400)
|
||||||
|
expect(res.response.data).toEqual({
|
||||||
|
type: "invalid_data",
|
||||||
|
message:
|
||||||
|
"incoming_quantity must not be less than 0, stocked_quantity must not be less than 0",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it.skip("should retrieve the inventory item", async () => {
|
||||||
|
const inventoryItemId = inventoryItems[0].id
|
||||||
|
|
||||||
|
await api.post(
|
||||||
|
`/admin/inventory-items/${inventoryItemId}/location-levels`,
|
||||||
|
{
|
||||||
|
location_id: locationId,
|
||||||
|
stocked_quantity: 15,
|
||||||
|
incoming_quantity: 5,
|
||||||
|
},
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
await api.post(
|
||||||
|
`/admin/inventory-items/${inventoryItemId}/location-levels`,
|
||||||
|
{
|
||||||
|
location_id: location2Id,
|
||||||
|
stocked_quantity: 7,
|
||||||
|
incoming_quantity: 0,
|
||||||
|
},
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
const response = await api.get(
|
||||||
|
`/admin/inventory-items/${inventoryItemId}`,
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.data).toEqual({
|
||||||
|
inventory_item: expect.objectContaining({
|
||||||
|
height: 200,
|
||||||
|
hs_code: "hs001",
|
||||||
|
id: inventoryItemId,
|
||||||
|
length: 100,
|
||||||
|
location_levels: [
|
||||||
|
expect.objectContaining({
|
||||||
|
available_quantity: 15,
|
||||||
|
deleted_at: null,
|
||||||
|
id: expect.any(String),
|
||||||
|
incoming_quantity: 5,
|
||||||
|
inventory_item_id: inventoryItemId,
|
||||||
|
location_id: locationId,
|
||||||
|
metadata: null,
|
||||||
|
reserved_quantity: 0,
|
||||||
|
stocked_quantity: 15,
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
available_quantity: 7,
|
||||||
|
deleted_at: null,
|
||||||
|
id: expect.any(String),
|
||||||
|
incoming_quantity: 0,
|
||||||
|
inventory_item_id: inventoryItemId,
|
||||||
|
location_id: location2Id,
|
||||||
|
metadata: null,
|
||||||
|
reserved_quantity: 0,
|
||||||
|
stocked_quantity: 7,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
material: "material",
|
||||||
|
metadata: null,
|
||||||
|
mid_code: "mids",
|
||||||
|
origin_country: "UK",
|
||||||
|
requires_shipping: true,
|
||||||
|
sku: "MY_SKU",
|
||||||
|
weight: 300,
|
||||||
|
width: 150,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it.skip("should create the inventory item using the api", async () => {
|
||||||
|
const product = await simpleProductFactory(dbConnection, {})
|
||||||
|
|
||||||
|
const productRes = await api.get(
|
||||||
|
`/admin/products/${product.id}`,
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
const variantId = productRes.data.product.variants[0].id
|
||||||
|
|
||||||
|
let variantInventoryRes = await api.get(
|
||||||
|
`/admin/variants/${variantId}/inventory`,
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(variantInventoryRes.data).toEqual({
|
||||||
|
variant: {
|
||||||
|
id: variantId,
|
||||||
|
inventory: [],
|
||||||
|
sales_channel_availability: [],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
expect(variantInventoryRes.status).toEqual(200)
|
||||||
|
|
||||||
|
const inventoryItemCreateRes = await api.post(
|
||||||
|
`/admin/inventory-items`,
|
||||||
|
{ variant_id: variantId, sku: "attach_this_to_variant" },
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
variantInventoryRes = await api.get(
|
||||||
|
`/admin/variants/${variantId}/inventory`,
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(variantInventoryRes.data).toEqual({
|
||||||
|
variant: expect.objectContaining({
|
||||||
|
id: variantId,
|
||||||
|
inventory: [
|
||||||
|
expect.objectContaining({
|
||||||
|
...inventoryItemCreateRes.data.inventory_item,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
expect(variantInventoryRes.status).toEqual(200)
|
||||||
|
})
|
||||||
|
|
||||||
|
it.skip("should list the location levels based on id param constraint", async () => {
|
||||||
|
const inventoryItemId = inventoryItems[0].id
|
||||||
|
|
||||||
|
await api.post(
|
||||||
|
`/admin/inventory-items/${inventoryItemId}/location-levels`,
|
||||||
|
{
|
||||||
|
location_id: location2Id,
|
||||||
|
stocked_quantity: 10,
|
||||||
|
},
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
await api.post(
|
||||||
|
`/admin/inventory-items/${inventoryItemId}/location-levels`,
|
||||||
|
{
|
||||||
|
location_id: location3Id,
|
||||||
|
stocked_quantity: 5,
|
||||||
|
},
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
const result = await api.get(
|
||||||
|
`/admin/inventory-items/${inventoryItemId}/location-levels?location_id[]=${location2Id}`,
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(result.status).toEqual(200)
|
||||||
|
expect(result.data.inventory_item.location_levels).toHaveLength(1)
|
||||||
|
expect(result.data.inventory_item.location_levels[0]).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
stocked_quantity: 10,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe.skip("Create inventory item level", () => {
|
||||||
|
let location1
|
||||||
|
let location2
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await service.create([
|
||||||
|
{
|
||||||
|
sku: "MY_SKU",
|
||||||
|
origin_country: "UK",
|
||||||
|
hs_code: "hs001",
|
||||||
|
mid_code: "mids",
|
||||||
|
material: "material",
|
||||||
|
weight: 300,
|
||||||
|
length: 100,
|
||||||
|
height: 200,
|
||||||
|
width: 150,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
const stockLocationService: IStockLocationService =
|
||||||
|
appContainer.resolve(ModuleRegistrationName.STOCK_LOCATION)
|
||||||
|
|
||||||
|
location1 = await stockLocationService.create({
|
||||||
|
name: "location-1",
|
||||||
|
})
|
||||||
|
|
||||||
|
location2 = await stockLocationService.create({
|
||||||
|
name: "location-2",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should list the inventory items", async () => {
|
||||||
|
const [{ id: inventoryItemId }] = await service.list({})
|
||||||
|
|
||||||
|
await api.post(
|
||||||
|
`/admin/inventory-items/${inventoryItemId}/location-levels`,
|
||||||
|
{
|
||||||
|
location_id: location1.id,
|
||||||
|
stocked_quantity: 10,
|
||||||
|
},
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
await api.post(
|
||||||
|
`/admin/inventory-items/${inventoryItemId}/location-levels`,
|
||||||
|
{
|
||||||
|
location_id: location2.id,
|
||||||
|
stocked_quantity: 5,
|
||||||
|
},
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
const levels = await service.listInventoryLevels({
|
||||||
|
inventory_item_id: inventoryItemId,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(levels).toHaveLength(2)
|
||||||
|
expect(levels).toEqual([
|
||||||
|
expect.objectContaining({
|
||||||
|
location_id: location1.id,
|
||||||
|
stocked_quantity: 10,
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
location_id: location2.id,
|
||||||
|
stocked_quantity: 5,
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe.skip("Create inventory items", () => {
|
||||||
|
it("should create inventory items", async () => {
|
||||||
|
const createResult = await api.post(
|
||||||
|
`/admin/products`,
|
||||||
|
{
|
||||||
|
title: "Test Product",
|
||||||
|
variants: [
|
||||||
|
{
|
||||||
|
title: "Test Variant w. inventory 2",
|
||||||
|
sku: "MY_SKU1",
|
||||||
|
material: "material",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
const inventoryItems = await service.list({})
|
||||||
|
|
||||||
|
expect(inventoryItems).toHaveLength(0)
|
||||||
|
|
||||||
|
const response = await api.post(
|
||||||
|
`/admin/inventory-items`,
|
||||||
|
{
|
||||||
|
sku: "test-sku",
|
||||||
|
variant_id: createResult.data.product.variants[0].id,
|
||||||
|
},
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.status).toEqual(200)
|
||||||
|
expect(response.data.inventory_item).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
sku: "test-sku",
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should attach inventory items on creation", async () => {
|
||||||
|
const createResult = await api.post(
|
||||||
|
`/admin/products`,
|
||||||
|
{
|
||||||
|
title: "Test Product",
|
||||||
|
variants: [
|
||||||
|
{
|
||||||
|
title: "Test Variant w. inventory 2",
|
||||||
|
sku: "MY_SKU1",
|
||||||
|
material: "material",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
const inventoryItems = await service.list({})
|
||||||
|
|
||||||
|
expect(inventoryItems).toHaveLength(0)
|
||||||
|
|
||||||
|
await api.post(
|
||||||
|
`/admin/inventory-items`,
|
||||||
|
{
|
||||||
|
sku: "test-sku",
|
||||||
|
variant_id: createResult.data.product.variants[0].id,
|
||||||
|
},
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
const remoteQuery = appContainer.resolve(
|
||||||
|
ContainerRegistrationKeys.REMOTE_QUERY
|
||||||
|
)
|
||||||
|
|
||||||
|
const query = remoteQueryObjectFromString({
|
||||||
|
entryPoint: "product_variant_inventory_item",
|
||||||
|
variables: {
|
||||||
|
variant_id: createResult.data.product.variants[0].id,
|
||||||
|
},
|
||||||
|
fields: ["inventory_item_id", "variant_id"],
|
||||||
|
})
|
||||||
|
|
||||||
|
const existingItems = await remoteQuery(query)
|
||||||
|
|
||||||
|
expect(existingItems).toHaveLength(1)
|
||||||
|
expect(existingItems[0].variant_id).toEqual(
|
||||||
|
createResult.data.product.variants[0].id
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("List inventory items", () => {
|
||||||
|
let location1 = "loc_1"
|
||||||
|
let location2 = "loc_2"
|
||||||
|
beforeEach(async () => {
|
||||||
|
await service.create([
|
||||||
|
{
|
||||||
|
sku: "MY_SKU",
|
||||||
|
origin_country: "UK",
|
||||||
|
hs_code: "hs001",
|
||||||
|
mid_code: "mids",
|
||||||
|
material: "material",
|
||||||
|
weight: 300,
|
||||||
|
length: 100,
|
||||||
|
height: 200,
|
||||||
|
width: 150,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should list the inventory items", async () => {
|
||||||
|
const [{ id: inventoryItemId }] = await service.list({})
|
||||||
|
|
||||||
|
await service.createInventoryLevels([
|
||||||
|
{
|
||||||
|
inventory_item_id: inventoryItemId,
|
||||||
|
location_id: location1,
|
||||||
|
stocked_quantity: 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
inventory_item_id: inventoryItemId,
|
||||||
|
location_id: location2,
|
||||||
|
stocked_quantity: 5,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
const response = await api.get(`/admin/inventory-items`, adminHeaders)
|
||||||
|
|
||||||
|
expect(response.data.inventory_items).toHaveLength(1)
|
||||||
|
expect(response.data.inventory_items[0]).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: inventoryItemId,
|
||||||
|
sku: "MY_SKU",
|
||||||
|
origin_country: "UK",
|
||||||
|
hs_code: "hs001",
|
||||||
|
mid_code: "mids",
|
||||||
|
material: "material",
|
||||||
|
weight: 300,
|
||||||
|
length: 100,
|
||||||
|
height: 200,
|
||||||
|
width: 150,
|
||||||
|
requires_shipping: true,
|
||||||
|
metadata: null,
|
||||||
|
location_levels: expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
id: expect.any(String),
|
||||||
|
inventory_item_id: inventoryItemId,
|
||||||
|
location_id: location1,
|
||||||
|
stocked_quantity: 10,
|
||||||
|
reserved_quantity: 0,
|
||||||
|
incoming_quantity: 0,
|
||||||
|
metadata: null,
|
||||||
|
available_quantity: 10,
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
id: expect.any(String),
|
||||||
|
inventory_item_id: inventoryItemId,
|
||||||
|
location_id: location2,
|
||||||
|
stocked_quantity: 5,
|
||||||
|
reserved_quantity: 0,
|
||||||
|
incoming_quantity: 0,
|
||||||
|
metadata: null,
|
||||||
|
available_quantity: 5,
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
reserved_quantity: 0,
|
||||||
|
stocked_quantity: 15,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should list the inventory items searching by title, description and sku", async () => {
|
||||||
|
const inventoryService = appContainer.resolve("inventoryService")
|
||||||
|
|
||||||
|
await inventoryService.create([
|
||||||
|
{
|
||||||
|
title: "Test Item",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Test Desc",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sku: "Test Sku",
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
const response = await api.get(
|
||||||
|
`/admin/inventory-items?q=test`,
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.data.inventory_items).not.toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
sku: "MY_SKU",
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
)
|
||||||
|
expect(response.data.inventory_items).toHaveLength(3)
|
||||||
|
expect(response.data.inventory_items).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
sku: "Test Sku",
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
description: "Test Desc",
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
title: "Test Item",
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it.skip("should remove associated levels and reservations when deleting an inventory item", async () => {
|
||||||
|
const inventoryService = appContainer.resolve("inventoryService")
|
||||||
|
|
||||||
|
const invItem2 = await inventoryService.createInventoryItem({
|
||||||
|
sku: "1234567",
|
||||||
|
})
|
||||||
|
|
||||||
|
const stockRes = await api.post(
|
||||||
|
`/admin/stock-locations`,
|
||||||
|
{
|
||||||
|
name: "Fake Warehouse 1",
|
||||||
|
},
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
locationId = stockRes.data.stock_location.id
|
||||||
|
|
||||||
|
const level = await inventoryService.createInventoryLevel({
|
||||||
|
inventory_item_id: invItem2.id,
|
||||||
|
location_id: locationId,
|
||||||
|
stocked_quantity: 10,
|
||||||
|
})
|
||||||
|
|
||||||
|
const reservation = await inventoryService.createReservationItem({
|
||||||
|
inventory_item_id: invItem2.id,
|
||||||
|
location_id: locationId,
|
||||||
|
quantity: 5,
|
||||||
|
})
|
||||||
|
|
||||||
|
const [, reservationCount] =
|
||||||
|
await inventoryService.listReservationItems({
|
||||||
|
location_id: locationId,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(reservationCount).toEqual(1)
|
||||||
|
|
||||||
|
const [, inventoryLevelCount] =
|
||||||
|
await inventoryService.listInventoryLevels({
|
||||||
|
location_id: locationId,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(inventoryLevelCount).toEqual(1)
|
||||||
|
|
||||||
|
const res = await api.delete(
|
||||||
|
`/admin/stock-locations/${locationId}`,
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(res.status).toEqual(200)
|
||||||
|
|
||||||
|
const [, reservationCountPostDelete] =
|
||||||
|
await inventoryService.listReservationItems({
|
||||||
|
location_id: locationId,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(reservationCountPostDelete).toEqual(0)
|
||||||
|
|
||||||
|
const [, inventoryLevelCountPostDelete] =
|
||||||
|
await inventoryService.listInventoryLevels({
|
||||||
|
location_id: locationId,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(inventoryLevelCountPostDelete).toEqual(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
it.skip("should remove the product variant associations when deleting an inventory item", async () => {
|
||||||
|
await simpleProductFactory(
|
||||||
|
dbConnection,
|
||||||
|
{
|
||||||
|
id: "test-product-new",
|
||||||
|
variants: [],
|
||||||
|
},
|
||||||
|
5
|
||||||
|
)
|
||||||
|
|
||||||
|
const response = await api.post(
|
||||||
|
`/admin/products/test-product-new/variants`,
|
||||||
|
{
|
||||||
|
title: "Test2",
|
||||||
|
sku: "MY_SKU2",
|
||||||
|
manage_inventory: true,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
option_id: "test-product-new-option",
|
||||||
|
value: "Blue",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
prices: [{ currency_code: "usd", amount: 100 }],
|
||||||
|
},
|
||||||
|
{ headers: { "x-medusa-access-token": "test_token" } }
|
||||||
|
)
|
||||||
|
|
||||||
|
const secondVariantId = response.data.product.variants.find(
|
||||||
|
(v) => v.sku === "MY_SKU2"
|
||||||
|
).id
|
||||||
|
|
||||||
|
const inventoryService = appContainer.resolve("inventoryService")
|
||||||
|
const variantInventoryService = appContainer.resolve(
|
||||||
|
"productVariantInventoryService"
|
||||||
|
)
|
||||||
|
|
||||||
|
const invItem2 = await inventoryService.createInventoryItem({
|
||||||
|
sku: "123456",
|
||||||
|
})
|
||||||
|
|
||||||
|
await variantInventoryService.attachInventoryItem(
|
||||||
|
variantId,
|
||||||
|
invItem2.id,
|
||||||
|
2
|
||||||
|
)
|
||||||
|
await variantInventoryService.attachInventoryItem(
|
||||||
|
secondVariantId,
|
||||||
|
invItem2.id,
|
||||||
|
2
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(
|
||||||
|
await variantInventoryService.listInventoryItemsByVariant(variantId)
|
||||||
|
).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)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -4,7 +4,9 @@ const DB_USERNAME = process.env.DB_USERNAME
|
|||||||
const DB_PASSWORD = process.env.DB_PASSWORD
|
const DB_PASSWORD = process.env.DB_PASSWORD
|
||||||
const DB_NAME = process.env.DB_TEMP_NAME
|
const DB_NAME = process.env.DB_TEMP_NAME
|
||||||
const DB_URL = `postgres://${DB_USERNAME}:${DB_PASSWORD}@${DB_HOST}/${DB_NAME}`
|
const DB_URL = `postgres://${DB_USERNAME}:${DB_PASSWORD}@${DB_HOST}/${DB_NAME}`
|
||||||
|
|
||||||
process.env.POSTGRES_URL = DB_URL
|
process.env.POSTGRES_URL = DB_URL
|
||||||
|
process.env.LOG_LEVEL = "error"
|
||||||
|
|
||||||
const enableMedusaV2 = process.env.MEDUSA_FF_MEDUSA_V2 == "true"
|
const enableMedusaV2 = process.env.MEDUSA_FF_MEDUSA_V2 == "true"
|
||||||
|
|
||||||
@@ -38,6 +40,8 @@ module.exports = {
|
|||||||
medusa_v2: enableMedusaV2,
|
medusa_v2: enableMedusaV2,
|
||||||
},
|
},
|
||||||
modules: {
|
modules: {
|
||||||
|
workflows: true,
|
||||||
|
|
||||||
[Modules.AUTH]: {
|
[Modules.AUTH]: {
|
||||||
scope: "internal",
|
scope: "internal",
|
||||||
resources: "shared",
|
resources: "shared",
|
||||||
@@ -64,6 +68,11 @@ module.exports = {
|
|||||||
resources: "shared",
|
resources: "shared",
|
||||||
resolve: "@medusajs/inventory",
|
resolve: "@medusajs/inventory",
|
||||||
},
|
},
|
||||||
|
[Modules.PRICING]: {
|
||||||
|
scope: "internal",
|
||||||
|
resources: "shared",
|
||||||
|
resolve: "@medusajs/pricing",
|
||||||
|
},
|
||||||
[Modules.CACHE]: {
|
[Modules.CACHE]: {
|
||||||
resolve: "@medusajs/cache-inmemory",
|
resolve: "@medusajs/cache-inmemory",
|
||||||
options: { ttl: 0 }, // Cache disabled
|
options: { ttl: 0 }, // Cache disabled
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ import {
|
|||||||
Collection,
|
Collection,
|
||||||
Entity,
|
Entity,
|
||||||
Filter,
|
Filter,
|
||||||
|
Formula,
|
||||||
OnInit,
|
OnInit,
|
||||||
|
OnLoad,
|
||||||
OneToMany,
|
OneToMany,
|
||||||
OptionalProps,
|
OptionalProps,
|
||||||
PrimaryKey,
|
PrimaryKey,
|
||||||
@@ -106,7 +108,21 @@ export class InventoryItem {
|
|||||||
() => InventoryLevel,
|
() => InventoryLevel,
|
||||||
(inventoryLevel) => inventoryLevel.inventory_item
|
(inventoryLevel) => inventoryLevel.inventory_item
|
||||||
)
|
)
|
||||||
inventory_levels = new Collection<InventoryLevel>(this)
|
location_levels = new Collection<InventoryLevel>(this)
|
||||||
|
|
||||||
|
@Formula(
|
||||||
|
(item) =>
|
||||||
|
`(SELECT SUM(reserved_quantity) FROM inventory_level il WHERE il.inventory_item_id = ${item}.id)`,
|
||||||
|
{ lazy: true, serializer: Number, hidden: true }
|
||||||
|
)
|
||||||
|
reserved_quantity: number
|
||||||
|
|
||||||
|
@Formula(
|
||||||
|
(item) =>
|
||||||
|
`(SELECT SUM(stocked_quantity) FROM inventory_level il WHERE il.inventory_item_id = ${item}.id)`,
|
||||||
|
{ lazy: true, serializer: Number, hidden: true }
|
||||||
|
)
|
||||||
|
stocked_quantity: number
|
||||||
|
|
||||||
@BeforeCreate()
|
@BeforeCreate()
|
||||||
private beforeCreate(): void {
|
private beforeCreate(): void {
|
||||||
|
|||||||
@@ -4,11 +4,12 @@ import {
|
|||||||
Filter,
|
Filter,
|
||||||
ManyToOne,
|
ManyToOne,
|
||||||
OnInit,
|
OnInit,
|
||||||
|
OnLoad,
|
||||||
PrimaryKey,
|
PrimaryKey,
|
||||||
Property,
|
Property,
|
||||||
} from "@mikro-orm/core"
|
} from "@mikro-orm/core"
|
||||||
|
import { DALUtils, isDefined } from "@medusajs/utils"
|
||||||
|
|
||||||
import { DALUtils } from "@medusajs/utils"
|
|
||||||
import { InventoryItem } from "./inventory-item"
|
import { InventoryItem } from "./inventory-item"
|
||||||
import { createPsqlIndexStatementHelper } from "@medusajs/utils"
|
import { createPsqlIndexStatementHelper } from "@medusajs/utils"
|
||||||
import { generateEntityId } from "@medusajs/utils"
|
import { generateEntityId } from "@medusajs/utils"
|
||||||
@@ -91,6 +92,8 @@ export class InventoryLevel {
|
|||||||
})
|
})
|
||||||
inventory_item: InventoryItem
|
inventory_item: InventoryItem
|
||||||
|
|
||||||
|
available_quantity: number | null = null
|
||||||
|
|
||||||
@BeforeCreate()
|
@BeforeCreate()
|
||||||
private beforeCreate(): void {
|
private beforeCreate(): void {
|
||||||
this.id = generateEntityId(this.id, "ilev")
|
this.id = generateEntityId(this.id, "ilev")
|
||||||
@@ -101,4 +104,11 @@ export class InventoryLevel {
|
|||||||
private onInit(): void {
|
private onInit(): void {
|
||||||
this.id = generateEntityId(this.id, "ilev")
|
this.id = generateEntityId(this.id, "ilev")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OnLoad()
|
||||||
|
private onLoad(): void {
|
||||||
|
if (isDefined(this.stocked_quantity) && isDefined(this.reserved_quantity)) {
|
||||||
|
this.available_quantity = this.stocked_quantity - this.reserved_quantity
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,2 +1,3 @@
|
|||||||
export * from "./inventory-level"
|
export * from "./inventory-level"
|
||||||
|
export * from "./inventory-item"
|
||||||
export { MikroOrmBaseRepository as BaseRepository } from "@medusajs/utils"
|
export { MikroOrmBaseRepository as BaseRepository } from "@medusajs/utils"
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
import { Context, DAL } from "@medusajs/types"
|
||||||
|
import { InventoryItem, InventoryLevel } from "@models"
|
||||||
|
|
||||||
|
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||||
|
import { mikroOrmBaseRepositoryFactory } from "@medusajs/utils"
|
||||||
|
|
||||||
|
export class InventoryItemRepository extends mikroOrmBaseRepositoryFactory<InventoryItem>(
|
||||||
|
InventoryItem
|
||||||
|
) {
|
||||||
|
async find(
|
||||||
|
findOptions: DAL.FindOptions<InventoryItem & { q?: string }> = {
|
||||||
|
where: {},
|
||||||
|
},
|
||||||
|
context: Context = {}
|
||||||
|
): Promise<InventoryItem[]> {
|
||||||
|
const findOptions_ = { ...findOptions }
|
||||||
|
findOptions_.options ??= {}
|
||||||
|
|
||||||
|
this.applyFreeTextSearchFilters<InventoryItem>(
|
||||||
|
findOptions_,
|
||||||
|
this.getFreeTextSearchConstraints
|
||||||
|
)
|
||||||
|
|
||||||
|
return await super.find(findOptions_, context)
|
||||||
|
}
|
||||||
|
|
||||||
|
async findAndCount(
|
||||||
|
findOptions: DAL.FindOptions<InventoryItem & { q?: string }> = {
|
||||||
|
where: {},
|
||||||
|
},
|
||||||
|
context: Context = {}
|
||||||
|
): Promise<[InventoryItem[], number]> {
|
||||||
|
const findOptions_ = { ...findOptions }
|
||||||
|
findOptions_.options ??= {}
|
||||||
|
|
||||||
|
this.applyFreeTextSearchFilters<InventoryItem>(
|
||||||
|
findOptions_,
|
||||||
|
this.getFreeTextSearchConstraints
|
||||||
|
)
|
||||||
|
|
||||||
|
return await super.findAndCount(findOptions_, context)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getFreeTextSearchConstraints(q: string) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
description: {
|
||||||
|
$ilike: `%${q}%`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: {
|
||||||
|
$ilike: `%${q}%`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sku: {
|
||||||
|
$ilike: `%${q}%`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,10 +15,10 @@ import {
|
|||||||
InjectManager,
|
InjectManager,
|
||||||
InjectTransactionManager,
|
InjectTransactionManager,
|
||||||
InventoryEvents,
|
InventoryEvents,
|
||||||
|
isDefined,
|
||||||
MedusaContext,
|
MedusaContext,
|
||||||
MedusaError,
|
MedusaError,
|
||||||
ModulesSdkUtils,
|
ModulesSdkUtils,
|
||||||
isDefined,
|
|
||||||
partitionArray,
|
partitionArray,
|
||||||
} from "@medusajs/utils"
|
} from "@medusajs/utils"
|
||||||
import { InventoryItem, InventoryLevel, ReservationItem } from "@models"
|
import { InventoryItem, InventoryLevel, ReservationItem } from "@models"
|
||||||
@@ -100,7 +100,7 @@ export default class InventoryModuleService<
|
|||||||
{ location_id: string; inventory_item_id: string }[]
|
{ location_id: string; inventory_item_id: string }[]
|
||||||
]
|
]
|
||||||
|
|
||||||
const inventoryLevels = await this.inventoryLevelService_.list(
|
const inventoryLevels = await this.listInventoryLevels(
|
||||||
{
|
{
|
||||||
$or: [
|
$or: [
|
||||||
{ id: idData.filter(({ id }) => !!id).map((e) => e.id) },
|
{ id: idData.filter(({ id }) => !!id).map((e) => e.id) },
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import * as QueryConfig from "./query-config"
|
||||||
|
|
||||||
|
import {
|
||||||
|
AdminGetInventoryItemsItemParams,
|
||||||
|
AdminGetInventoryItemsParams,
|
||||||
|
AdminPostInventoryItemsItemLocationLevelsReq,
|
||||||
|
AdminPostInventoryItemsReq,
|
||||||
|
} from "./validators"
|
||||||
|
import { transformBody, transformQuery } from "../../../api/middlewares"
|
||||||
|
|
||||||
|
import { MiddlewareRoute } from "../../../types/middlewares"
|
||||||
|
import { authenticate } from "../../../utils/authenticate-middleware"
|
||||||
|
|
||||||
|
export const adminInventoryRoutesMiddlewares: MiddlewareRoute[] = [
|
||||||
|
{
|
||||||
|
method: "ALL",
|
||||||
|
matcher: "/admin/inventory-items*",
|
||||||
|
middlewares: [authenticate("admin", ["session", "bearer"])],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: ["GET"],
|
||||||
|
matcher: "/admin/inventory-items",
|
||||||
|
middlewares: [
|
||||||
|
transformQuery(
|
||||||
|
AdminGetInventoryItemsParams,
|
||||||
|
QueryConfig.listTransformQueryConfig
|
||||||
|
),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: ["POST"],
|
||||||
|
matcher: "/admin/inventory-items/:id/location-levels",
|
||||||
|
middlewares: [transformBody(AdminPostInventoryItemsItemLocationLevelsReq)],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: ["POST"],
|
||||||
|
matcher: "/admin/inventory-items",
|
||||||
|
middlewares: [transformBody(AdminPostInventoryItemsReq)],
|
||||||
|
},
|
||||||
|
]
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import { InventoryNext } from "@medusajs/types"
|
||||||
|
|
||||||
|
export const defaultAdminInventoryItemRelations = []
|
||||||
|
export const allowedAdminInventoryItemRelations = []
|
||||||
|
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
|
export const defaultAdminLocationLevelFields: (keyof InventoryNext.InventoryLevelDTO)[] =
|
||||||
|
[
|
||||||
|
"id",
|
||||||
|
"inventory_item_id",
|
||||||
|
"location_id",
|
||||||
|
"stocked_quantity",
|
||||||
|
"reserved_quantity",
|
||||||
|
"incoming_quantity",
|
||||||
|
"available_quantity",
|
||||||
|
"metadata",
|
||||||
|
"created_at",
|
||||||
|
"updated_at",
|
||||||
|
]
|
||||||
|
|
||||||
|
export const defaultAdminInventoryItemFields = [
|
||||||
|
"id",
|
||||||
|
"sku",
|
||||||
|
"title",
|
||||||
|
"description",
|
||||||
|
"thumbnail",
|
||||||
|
"origin_country",
|
||||||
|
"hs_code",
|
||||||
|
"requires_shipping",
|
||||||
|
"mid_code",
|
||||||
|
"material",
|
||||||
|
"weight",
|
||||||
|
"length",
|
||||||
|
"height",
|
||||||
|
"width",
|
||||||
|
"metadata",
|
||||||
|
"reserved_quantity",
|
||||||
|
"stocked_quantity",
|
||||||
|
"created_at",
|
||||||
|
"updated_at",
|
||||||
|
...defaultAdminLocationLevelFields.map(
|
||||||
|
(field) => `location_levels.${field.toString()}`
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
export const retrieveTransformQueryConfig = {
|
||||||
|
defaultFields: defaultAdminInventoryItemFields,
|
||||||
|
defaultRelations: defaultAdminInventoryItemRelations,
|
||||||
|
allowedRelations: allowedAdminInventoryItemRelations,
|
||||||
|
isList: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const listTransformQueryConfig = {
|
||||||
|
...retrieveTransformQueryConfig,
|
||||||
|
isList: true,
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import {
|
||||||
|
AuthenticatedMedusaRequest,
|
||||||
|
MedusaResponse,
|
||||||
|
} from "../../../types/routing"
|
||||||
|
import {
|
||||||
|
ContainerRegistrationKeys,
|
||||||
|
remoteQueryObjectFromString,
|
||||||
|
} from "@medusajs/utils"
|
||||||
|
|
||||||
|
// List inventory-items
|
||||||
|
export const GET = async (
|
||||||
|
req: AuthenticatedMedusaRequest,
|
||||||
|
res: MedusaResponse
|
||||||
|
) => {
|
||||||
|
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
|
||||||
|
|
||||||
|
const query = remoteQueryObjectFromString({
|
||||||
|
entryPoint: "inventory_items",
|
||||||
|
variables: {
|
||||||
|
filters: req.filterableFields,
|
||||||
|
order: req.listConfig.order,
|
||||||
|
skip: req.listConfig.skip,
|
||||||
|
take: req.listConfig.take,
|
||||||
|
},
|
||||||
|
fields: [...(req.listConfig.select as string[])],
|
||||||
|
})
|
||||||
|
|
||||||
|
const { rows: inventory_items, metadata } = await remoteQuery({
|
||||||
|
...query,
|
||||||
|
})
|
||||||
|
|
||||||
|
res.status(200).json({
|
||||||
|
inventory_items,
|
||||||
|
count: metadata.count,
|
||||||
|
offset: metadata.skip,
|
||||||
|
limit: metadata.take,
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,268 @@
|
|||||||
|
import {
|
||||||
|
DateComparisonOperator,
|
||||||
|
FindParams,
|
||||||
|
NumericalComparisonOperator,
|
||||||
|
StringComparisonOperator,
|
||||||
|
extendedFindParamsMixin,
|
||||||
|
} from "../../../types/common"
|
||||||
|
import {
|
||||||
|
IsBoolean,
|
||||||
|
IsEmail,
|
||||||
|
IsNotEmpty,
|
||||||
|
IsNumber,
|
||||||
|
IsObject,
|
||||||
|
IsOptional,
|
||||||
|
IsString,
|
||||||
|
ValidateNested,
|
||||||
|
} from "class-validator"
|
||||||
|
import { Transform, Type } from "class-transformer"
|
||||||
|
|
||||||
|
import { IsType } from "../../../utils"
|
||||||
|
|
||||||
|
export class AdminGetInventoryItemsItemParams extends FindParams {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parameters used to filter and configure the pagination of the retrieved inventory items.
|
||||||
|
*/
|
||||||
|
export class AdminGetInventoryItemsParams extends extendedFindParamsMixin({
|
||||||
|
limit: 20,
|
||||||
|
offset: 0,
|
||||||
|
}) {
|
||||||
|
/**
|
||||||
|
* IDs to filter inventory items by.
|
||||||
|
*/
|
||||||
|
@IsOptional()
|
||||||
|
@IsType([String, [String]])
|
||||||
|
id?: string | string[]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search terms to search inventory items' sku, title, and description.
|
||||||
|
*/
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
q?: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Location IDs to filter inventory items by.
|
||||||
|
*/
|
||||||
|
@IsOptional()
|
||||||
|
@IsType([String, [String]])
|
||||||
|
location_id?: string | string[]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SKUs to filter inventory items by.
|
||||||
|
*/
|
||||||
|
@IsOptional()
|
||||||
|
@IsType([String, [String]])
|
||||||
|
sku?: string | string[]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Origin countries to filter inventory items by.
|
||||||
|
*/
|
||||||
|
@IsOptional()
|
||||||
|
@IsType([String, [String]])
|
||||||
|
origin_country?: string | string[]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MID codes to filter inventory items by.
|
||||||
|
*/
|
||||||
|
@IsOptional()
|
||||||
|
@IsType([String, [String]])
|
||||||
|
mid_code?: string | string[]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Materials to filter inventory items by.
|
||||||
|
*/
|
||||||
|
@IsOptional()
|
||||||
|
@IsType([String, [String]])
|
||||||
|
material?: string | string[]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* String filters to apply to inventory items' `hs_code` field.
|
||||||
|
*/
|
||||||
|
@IsOptional()
|
||||||
|
@IsType([String, [String], StringComparisonOperator])
|
||||||
|
hs_code?: string | string[] | StringComparisonOperator
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number filters to apply to inventory items' `weight` field.
|
||||||
|
*/
|
||||||
|
@IsOptional()
|
||||||
|
@IsType([Number, NumericalComparisonOperator])
|
||||||
|
weight?: number | NumericalComparisonOperator
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number filters to apply to inventory items' `length` field.
|
||||||
|
*/
|
||||||
|
@IsOptional()
|
||||||
|
@IsType([Number, NumericalComparisonOperator])
|
||||||
|
length?: number | NumericalComparisonOperator
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number filters to apply to inventory items' `height` field.
|
||||||
|
*/
|
||||||
|
@IsOptional()
|
||||||
|
@IsType([Number, NumericalComparisonOperator])
|
||||||
|
height?: number | NumericalComparisonOperator
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number filters to apply to inventory items' `width` field.
|
||||||
|
*/
|
||||||
|
@IsOptional()
|
||||||
|
@IsType([Number, NumericalComparisonOperator])
|
||||||
|
width?: number | NumericalComparisonOperator
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter inventory items by whether they require shipping.
|
||||||
|
*/
|
||||||
|
@IsBoolean()
|
||||||
|
@IsOptional()
|
||||||
|
@Transform(({ value }) => value === "true")
|
||||||
|
requires_shipping?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AdminPostInventoryItemsItemLocationLevelsReq {
|
||||||
|
@IsString()
|
||||||
|
location_id: string
|
||||||
|
|
||||||
|
@IsNumber()
|
||||||
|
stocked_quantity: number
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsNumber()
|
||||||
|
incoming_quantity?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line
|
||||||
|
export class AdminPostInventoryItemsItemLocationLevelsParams extends FindParams {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @schema AdminPostInventoryItemsReq
|
||||||
|
* type: object
|
||||||
|
* description: "The details of the inventory item to create."
|
||||||
|
* required:
|
||||||
|
* - variant_id
|
||||||
|
* properties:
|
||||||
|
* variant_id:
|
||||||
|
* description: The ID of the variant to create the inventory item for.
|
||||||
|
* type: string
|
||||||
|
* sku:
|
||||||
|
* description: The unique SKU of the associated Product Variant.
|
||||||
|
* type: string
|
||||||
|
* ean:
|
||||||
|
* description: The EAN number of the item.
|
||||||
|
* type: string
|
||||||
|
* upc:
|
||||||
|
* description: The UPC number of the item.
|
||||||
|
* type: string
|
||||||
|
* barcode:
|
||||||
|
* description: A generic GTIN field for the Product Variant.
|
||||||
|
* type: string
|
||||||
|
* hs_code:
|
||||||
|
* description: The Harmonized System code of the Inventory Item. May be used by Fulfillment Providers to pass customs information to shipping carriers.
|
||||||
|
* type: string
|
||||||
|
* inventory_quantity:
|
||||||
|
* description: The amount of stock kept of the associated Product Variant.
|
||||||
|
* type: integer
|
||||||
|
* default: 0
|
||||||
|
* allow_backorder:
|
||||||
|
* description: Whether the associated Product Variant can be purchased when out of stock.
|
||||||
|
* type: boolean
|
||||||
|
* manage_inventory:
|
||||||
|
* description: Whether Medusa should keep track of the inventory for the associated Product Variant.
|
||||||
|
* type: boolean
|
||||||
|
* default: true
|
||||||
|
* weight:
|
||||||
|
* description: The weight of the Inventory Item. May be used in shipping rate calculations.
|
||||||
|
* type: number
|
||||||
|
* length:
|
||||||
|
* description: The length of the Inventory Item. May be used in shipping rate calculations.
|
||||||
|
* type: number
|
||||||
|
* height:
|
||||||
|
* description: The height of the Inventory Item. May be used in shipping rate calculations.
|
||||||
|
* type: number
|
||||||
|
* width:
|
||||||
|
* description: The width of the Inventory Item. May be used in shipping rate calculations.
|
||||||
|
* type: number
|
||||||
|
* origin_country:
|
||||||
|
* description: The country in which the Inventory Item was produced. May be used by Fulfillment Providers to pass customs information to shipping carriers.
|
||||||
|
* type: string
|
||||||
|
* mid_code:
|
||||||
|
* description: The Manufacturers Identification code that identifies the manufacturer of the Inventory Item. May be used by Fulfillment Providers to pass customs information to shipping carriers.
|
||||||
|
* type: string
|
||||||
|
* material:
|
||||||
|
* description: The material and composition that the Inventory Item is made of, May be used by Fulfillment Providers to pass customs information to shipping carriers.
|
||||||
|
* type: string
|
||||||
|
* title:
|
||||||
|
* description: The inventory item's title.
|
||||||
|
* type: string
|
||||||
|
* description:
|
||||||
|
* description: The inventory item's description.
|
||||||
|
* type: string
|
||||||
|
* thumbnail:
|
||||||
|
* description: The inventory item's thumbnail.
|
||||||
|
* type: string
|
||||||
|
* metadata:
|
||||||
|
* description: An optional set of key-value pairs with additional information.
|
||||||
|
* type: object
|
||||||
|
* externalDocs:
|
||||||
|
* description: "Learn about the metadata attribute, and how to delete and update it."
|
||||||
|
* url: "https://docs.medusajs.com/development/entities/overview#metadata-attribute"
|
||||||
|
*/
|
||||||
|
export class AdminPostInventoryItemsReq {
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
variant_id: string
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
sku?: string
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
hs_code?: string
|
||||||
|
|
||||||
|
@IsNumber()
|
||||||
|
@IsOptional()
|
||||||
|
weight?: number
|
||||||
|
|
||||||
|
@IsNumber()
|
||||||
|
@IsOptional()
|
||||||
|
length?: number
|
||||||
|
|
||||||
|
@IsNumber()
|
||||||
|
@IsOptional()
|
||||||
|
height?: number
|
||||||
|
|
||||||
|
@IsNumber()
|
||||||
|
@IsOptional()
|
||||||
|
width?: number
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
origin_country?: string
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
mid_code?: string
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
material?: string
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
title?: string
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
description?: string
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
thumbnail?: string
|
||||||
|
|
||||||
|
@IsObject()
|
||||||
|
@IsOptional()
|
||||||
|
metadata?: Record<string, unknown>
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import { adminCollectionRoutesMiddlewares } from "./admin/collections/middleware
|
|||||||
import { adminCurrencyRoutesMiddlewares } from "./admin/currencies/middlewares"
|
import { adminCurrencyRoutesMiddlewares } from "./admin/currencies/middlewares"
|
||||||
import { adminCustomerGroupRoutesMiddlewares } from "./admin/customer-groups/middlewares"
|
import { adminCustomerGroupRoutesMiddlewares } from "./admin/customer-groups/middlewares"
|
||||||
import { adminCustomerRoutesMiddlewares } from "./admin/customers/middlewares"
|
import { adminCustomerRoutesMiddlewares } from "./admin/customers/middlewares"
|
||||||
|
import { adminInventoryRoutesMiddlewares } from "./admin/inventory-items/middlewares"
|
||||||
import { adminInviteRoutesMiddlewares } from "./admin/invites/middlewares"
|
import { adminInviteRoutesMiddlewares } from "./admin/invites/middlewares"
|
||||||
import { adminPaymentRoutesMiddlewares } from "./admin/payments/middlewares"
|
import { adminPaymentRoutesMiddlewares } from "./admin/payments/middlewares"
|
||||||
import { adminPriceListsRoutesMiddlewares } from "./admin/price-lists/middlewares"
|
import { adminPriceListsRoutesMiddlewares } from "./admin/price-lists/middlewares"
|
||||||
@@ -49,6 +50,7 @@ export const config: MiddlewaresConfig = {
|
|||||||
...adminProductRoutesMiddlewares,
|
...adminProductRoutesMiddlewares,
|
||||||
...adminPaymentRoutesMiddlewares,
|
...adminPaymentRoutesMiddlewares,
|
||||||
...adminPriceListsRoutesMiddlewares,
|
...adminPriceListsRoutesMiddlewares,
|
||||||
|
...adminInventoryRoutesMiddlewares,
|
||||||
...adminCollectionRoutesMiddlewares,
|
...adminCollectionRoutesMiddlewares,
|
||||||
...adminPricingRoutesMiddlewares,
|
...adminPricingRoutesMiddlewares,
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,12 +1,3 @@
|
|||||||
import {
|
|
||||||
MedusaApp,
|
|
||||||
MedusaAppMigrateUp,
|
|
||||||
MedusaAppOutput,
|
|
||||||
MedusaModule,
|
|
||||||
MODULE_PACKAGE_NAMES,
|
|
||||||
Modules,
|
|
||||||
ModulesDefinition,
|
|
||||||
} from "@medusajs/modules-sdk"
|
|
||||||
import {
|
import {
|
||||||
CommonTypes,
|
CommonTypes,
|
||||||
InternalModuleDeclaration,
|
InternalModuleDeclaration,
|
||||||
@@ -17,12 +8,22 @@ import {
|
|||||||
import {
|
import {
|
||||||
ContainerRegistrationKeys,
|
ContainerRegistrationKeys,
|
||||||
FlagRouter,
|
FlagRouter,
|
||||||
isObject,
|
|
||||||
MedusaV2Flag,
|
MedusaV2Flag,
|
||||||
|
isObject,
|
||||||
} from "@medusajs/utils"
|
} from "@medusajs/utils"
|
||||||
|
import {
|
||||||
|
MODULE_PACKAGE_NAMES,
|
||||||
|
MedusaApp,
|
||||||
|
MedusaAppMigrateUp,
|
||||||
|
MedusaAppOutput,
|
||||||
|
MedusaModule,
|
||||||
|
Modules,
|
||||||
|
ModulesDefinition,
|
||||||
|
} from "@medusajs/modules-sdk"
|
||||||
|
|
||||||
import { asValue } from "awilix"
|
import { asValue } from "awilix"
|
||||||
import { remoteQueryFetchData } from "../utils/remote-query-fetch-data"
|
|
||||||
import { joinerConfig } from "../joiner-config"
|
import { joinerConfig } from "../joiner-config"
|
||||||
|
import { remoteQueryFetchData } from "../utils/remote-query-fetch-data"
|
||||||
|
|
||||||
export function mergeDefaultModules(
|
export function mergeDefaultModules(
|
||||||
modulesConfig: CommonTypes.ConfigModule["modules"]
|
modulesConfig: CommonTypes.ConfigModule["modules"]
|
||||||
@@ -74,7 +75,6 @@ export async function migrateMedusaApp(
|
|||||||
debug: !!(configModule.projectConfig.database_logging ?? false),
|
debug: !!(configModule.projectConfig.database_logging ?? false),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const configModules = mergeDefaultModules(configModule.modules)
|
const configModules = mergeDefaultModules(configModule.modules)
|
||||||
|
|
||||||
// Apply default options to legacy modules
|
// Apply default options to legacy modules
|
||||||
@@ -90,6 +90,7 @@ export async function migrateMedusaApp(
|
|||||||
database: {
|
database: {
|
||||||
type: "postgres",
|
type: "postgres",
|
||||||
url: sharedResourcesConfig.database.clientUrl,
|
url: sharedResourcesConfig.database.clientUrl,
|
||||||
|
clientUrl: sharedResourcesConfig.database.clientUrl,
|
||||||
extra: configModule.projectConfig.database_extra,
|
extra: configModule.projectConfig.database_extra,
|
||||||
schema: configModule.projectConfig.database_schema,
|
schema: configModule.projectConfig.database_schema,
|
||||||
logging: configModule.projectConfig.database_logging,
|
logging: configModule.projectConfig.database_logging,
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ export interface InventoryLevelDTO {
|
|||||||
stocked_quantity: number
|
stocked_quantity: number
|
||||||
reserved_quantity: number
|
reserved_quantity: number
|
||||||
incoming_quantity: number
|
incoming_quantity: number
|
||||||
|
available_quantity: number
|
||||||
metadata: Record<string, unknown> | null
|
metadata: Record<string, unknown> | null
|
||||||
created_at: string | Date
|
created_at: string | Date
|
||||||
updated_at: string | Date
|
updated_at: string | Date
|
||||||
|
|||||||
Reference in new issue
Block a user