Fix(medusa): stocked quantity counting when listing products (#3684)
* count stocked quantity correctly when listing products * add integration test * add changeset
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"@medusajs/medusa": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix(medusa): inventory stocked quantity regulation
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
const path = require("path")
|
||||||
|
|
||||||
|
const { bootstrapApp } = require("../../../../helpers/bootstrap-app")
|
||||||
|
const { initDb, useDb } = require("../../../../helpers/use-db")
|
||||||
|
const { setPort, useApi } = require("../../../../helpers/use-api")
|
||||||
|
|
||||||
|
const {
|
||||||
|
ProductVariantInventoryService,
|
||||||
|
ProductVariantService,
|
||||||
|
} = require("@medusajs/medusa")
|
||||||
|
|
||||||
|
const adminSeeder = require("../../../helpers/admin-seeder")
|
||||||
|
|
||||||
|
jest.setTimeout(30000)
|
||||||
|
|
||||||
|
const { simpleProductFactory } = require("../../../factories")
|
||||||
|
const { simpleSalesChannelFactory } = require("../../../../api/factories")
|
||||||
|
|
||||||
|
const adminHeaders = { headers: { Authorization: "Bearer test_token" } }
|
||||||
|
|
||||||
|
describe("Create Variant", () => {
|
||||||
|
let appContainer
|
||||||
|
let dbConnection
|
||||||
|
let express
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const cwd = path.resolve(path.join(__dirname, "..", "..", ".."))
|
||||||
|
dbConnection = await initDb({ cwd })
|
||||||
|
const { container, app, port } = await bootstrapApp({ cwd })
|
||||||
|
appContainer = container
|
||||||
|
|
||||||
|
setPort(port)
|
||||||
|
express = app.listen(port, (err) => {
|
||||||
|
process.send(port)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
const db = useDb()
|
||||||
|
await db.shutdown()
|
||||||
|
express.close()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
const db = useDb()
|
||||||
|
return await db.teardown()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("list-products", () => {
|
||||||
|
const productId = "test-product"
|
||||||
|
const variantId = "test-variant"
|
||||||
|
beforeEach(async () => {
|
||||||
|
await adminSeeder(dbConnection)
|
||||||
|
|
||||||
|
const stockLocationService = appContainer.resolve("stockLocationService")
|
||||||
|
const productVariantInventoryService = appContainer.resolve(
|
||||||
|
"productVariantInventoryService"
|
||||||
|
)
|
||||||
|
const inventoryService = appContainer.resolve("inventoryService")
|
||||||
|
const salesChannelLocationService = appContainer.resolve(
|
||||||
|
"salesChannelLocationService"
|
||||||
|
)
|
||||||
|
|
||||||
|
await simpleProductFactory(
|
||||||
|
dbConnection,
|
||||||
|
{
|
||||||
|
id: productId,
|
||||||
|
variants: [{ id: variantId }],
|
||||||
|
},
|
||||||
|
100
|
||||||
|
)
|
||||||
|
|
||||||
|
const sc1 = await simpleSalesChannelFactory(dbConnection, {})
|
||||||
|
const sc2 = await simpleSalesChannelFactory(dbConnection, {})
|
||||||
|
const sc3 = await simpleSalesChannelFactory(dbConnection, {})
|
||||||
|
|
||||||
|
const sl1 = await stockLocationService.create({ name: "sl1" })
|
||||||
|
const sl2 = await stockLocationService.create({ name: "sl2" })
|
||||||
|
|
||||||
|
await salesChannelLocationService.associateLocation(sc1.id, sl1.id)
|
||||||
|
await salesChannelLocationService.associateLocation(sc2.id, sl1.id)
|
||||||
|
await salesChannelLocationService.associateLocation(sc2.id, sl2.id)
|
||||||
|
|
||||||
|
const invItem = await inventoryService.createInventoryItem({
|
||||||
|
sku: "test-sku",
|
||||||
|
})
|
||||||
|
await productVariantInventoryService.attachInventoryItem(
|
||||||
|
variantId,
|
||||||
|
invItem.id
|
||||||
|
)
|
||||||
|
|
||||||
|
await inventoryService.createInventoryLevel({
|
||||||
|
inventory_item_id: invItem.id,
|
||||||
|
location_id: sl1.id,
|
||||||
|
stocked_quantity: 3,
|
||||||
|
})
|
||||||
|
|
||||||
|
await inventoryService.createInventoryLevel({
|
||||||
|
inventory_item_id: invItem.id,
|
||||||
|
location_id: sl2.id,
|
||||||
|
stocked_quantity: 1,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("lists location availability correctly", async () => {
|
||||||
|
const api = useApi()
|
||||||
|
|
||||||
|
const res = await api.get(`/admin/products`, adminHeaders)
|
||||||
|
|
||||||
|
expect(res.status).toEqual(200)
|
||||||
|
expect(res.data.products).toEqual([
|
||||||
|
expect.objectContaining({
|
||||||
|
id: productId,
|
||||||
|
variants: [
|
||||||
|
expect.objectContaining({
|
||||||
|
inventory_quantity: 4,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -658,21 +658,23 @@ class ProductVariantInventoryService extends TransactionBaseService {
|
|||||||
// first get all inventory items required for a variant
|
// first get all inventory items required for a variant
|
||||||
const variantInventory = await this.listByVariant(variant.id)
|
const variantInventory = await this.listByVariant(variant.id)
|
||||||
|
|
||||||
const salesChannelArray = Array.isArray(salesChannelId)
|
if (!variantInventory.length) {
|
||||||
? salesChannelId
|
variant.inventory_quantity = 0
|
||||||
: [salesChannelId]
|
return variant
|
||||||
|
}
|
||||||
|
|
||||||
const quantities = await Promise.all(
|
const locationIds =
|
||||||
salesChannelArray.map(async (salesChannel) => {
|
await this.salesChannelLocationService_.listLocationIds(
|
||||||
return await this.getVariantQuantityFromVariantInventoryItems(
|
salesChannelId
|
||||||
variantInventory,
|
|
||||||
salesChannel
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const [locations] = await this.inventoryService_.listInventoryLevels({
|
||||||
|
location_id: locationIds,
|
||||||
|
inventory_item_id: variantInventory[0].inventory_item_id,
|
||||||
})
|
})
|
||||||
)
|
|
||||||
|
|
||||||
variant.inventory_quantity = quantities.reduce(
|
variant.inventory_quantity = locations.reduce(
|
||||||
(acc, next) => acc + (next || 0),
|
(acc, next) => acc + (next.stocked_quantity || 0),
|
||||||
0
|
0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user