fix(medusa): Account for multiple inventory items in get-inventory (#3094)
* use pvi service method instead of inventory service method to get quantity for sales channel * save a few invocations * rename quantity * omit || 0 * add changeset * extract method for retrieving variant inventory quantity * update pr with feedback
This commit is contained in:
1 parent
5eb61fa0ef
commit
d61d6c7b7f
3 files changed
+67
-28
No files matched your search
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"@medusajs/medusa": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix(medusa): Account for multiple inventory items when getting inventory
|
||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
} from "../../../../types/inventory"
|
} from "../../../../types/inventory"
|
||||||
import ProductVariantInventoryService from "../../../../services/product-variant-inventory"
|
import ProductVariantInventoryService from "../../../../services/product-variant-inventory"
|
||||||
import {
|
import {
|
||||||
|
SalesChannelInventoryService,
|
||||||
SalesChannelLocationService,
|
SalesChannelLocationService,
|
||||||
SalesChannelService,
|
SalesChannelService,
|
||||||
} from "../../../../services"
|
} from "../../../../services"
|
||||||
@@ -75,6 +76,8 @@ export default async (req, res) => {
|
|||||||
const channelLocationService: SalesChannelLocationService = req.scope.resolve(
|
const channelLocationService: SalesChannelLocationService = req.scope.resolve(
|
||||||
"salesChannelLocationService"
|
"salesChannelLocationService"
|
||||||
)
|
)
|
||||||
|
const salesChannelInventoryService: SalesChannelInventoryService =
|
||||||
|
req.scope.resolve("salesChannelInventoryService")
|
||||||
const channelService: SalesChannelService = req.scope.resolve(
|
const channelService: SalesChannelService = req.scope.resolve(
|
||||||
"salesChannelService"
|
"salesChannelService"
|
||||||
)
|
)
|
||||||
@@ -106,11 +109,13 @@ export default async (req, res) => {
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const variantInventoryItems =
|
||||||
|
await productVariantInventoryService.listByVariant(variant.id)
|
||||||
|
|
||||||
const inventory =
|
const inventory =
|
||||||
await productVariantInventoryService.listInventoryItemsByVariant(variant.id)
|
await productVariantInventoryService.listInventoryItemsByVariant(variant.id)
|
||||||
responseVariant.inventory = await joinLevels(inventory, [], inventoryService)
|
responseVariant.inventory = await joinLevels(inventory, [], inventoryService)
|
||||||
|
|
||||||
// TODO: adjust for required quantity
|
|
||||||
if (inventory.length) {
|
if (inventory.length) {
|
||||||
responseVariant.sales_channel_availability = await Promise.all(
|
responseVariant.sales_channel_availability = await Promise.all(
|
||||||
channels.map(async (channel) => {
|
channels.map(async (channel) => {
|
||||||
@@ -122,9 +127,10 @@ export default async (req, res) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const quantity = await inventoryService.retrieveAvailableQuantity(
|
const quantity =
|
||||||
inventory[0].id,
|
await productVariantInventoryService.getVariantQuantityFromVariantInventoryItems(
|
||||||
channel.locations
|
variantInventoryItems,
|
||||||
|
channel.id
|
||||||
)
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -179,7 +179,7 @@ class ProductVariantInventoryService extends TransactionBaseService {
|
|||||||
* @param variantId variant id
|
* @param variantId variant id
|
||||||
* @returns variant inventory items for the variant id
|
* @returns variant inventory items for the variant id
|
||||||
*/
|
*/
|
||||||
private async listByVariant(
|
public async listByVariant(
|
||||||
variantId: string | string[]
|
variantId: string | string[]
|
||||||
): Promise<ProductVariantInventoryItem[]> {
|
): Promise<ProductVariantInventoryItem[]> {
|
||||||
const variantInventoryRepo = this.activeManager_.getRepository(
|
const variantInventoryRepo = this.activeManager_.getRepository(
|
||||||
@@ -615,29 +615,10 @@ 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 salesChannelInventoryServiceTx =
|
variant.inventory_quantity =
|
||||||
this.salesChannelInventoryService_.withTransaction(
|
await this.getVariantQuantityFromVariantInventoryItems(
|
||||||
this.activeManager_
|
variantInventory,
|
||||||
)
|
salesChannelId
|
||||||
// the inventory quantity of the variant should be equal to the inventory
|
|
||||||
// item with the smallest stock, adjusted for quantity required to fulfill
|
|
||||||
// the given variant
|
|
||||||
variant.inventory_quantity = Math.min(
|
|
||||||
...(await Promise.all(
|
|
||||||
variantInventory.map(async (variantInventory) => {
|
|
||||||
// get the total available quantity for the given sales channel
|
|
||||||
// divided by the required quantity to account for how many of the
|
|
||||||
// variant we can fulfill at the current time. Take the minimum we
|
|
||||||
// can fulfill and set that as quantity
|
|
||||||
return (
|
|
||||||
// eslint-disable-next-line max-len
|
|
||||||
(await salesChannelInventoryServiceTx.retrieveAvailableItemQuantity(
|
|
||||||
salesChannelId,
|
|
||||||
variantInventory.inventory_item_id
|
|
||||||
)) / variantInventory.required_quantity
|
|
||||||
)
|
|
||||||
})
|
|
||||||
))
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return variant
|
return variant
|
||||||
@@ -664,6 +645,53 @@ class ProductVariantInventoryService extends TransactionBaseService {
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the quantity of a variant from a list of variantInventoryItems
|
||||||
|
* The inventory quantity of the variant should be equal to the inventory
|
||||||
|
* item with the smallest stock, adjusted for quantity required to fulfill
|
||||||
|
* the given variant.
|
||||||
|
*
|
||||||
|
* @param variantInventoryItems List of inventoryItems for a given variant, These must all be for the same variant
|
||||||
|
* @param channelId Sales channel id to fetch availability for
|
||||||
|
* @returns The available quantity of the variant from the inventoryItems
|
||||||
|
*/
|
||||||
|
async getVariantQuantityFromVariantInventoryItems(
|
||||||
|
variantInventoryItems: ProductVariantInventoryItem[],
|
||||||
|
channelId: string
|
||||||
|
): Promise<number> {
|
||||||
|
const variantItemsAreMixed = variantInventoryItems.some(
|
||||||
|
(inventoryItem) =>
|
||||||
|
inventoryItem.variant_id !== variantInventoryItems[0].variant_id
|
||||||
|
)
|
||||||
|
|
||||||
|
if (variantInventoryItems.length && variantItemsAreMixed) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.INVALID_DATA,
|
||||||
|
"All variant inventory items must belong to the same variant"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.min(
|
||||||
|
...(await Promise.all(
|
||||||
|
variantInventoryItems.map(async (variantInventory) => {
|
||||||
|
// get the total available quantity for the given sales channel
|
||||||
|
// divided by the required quantity to account for how many of the
|
||||||
|
// variant we can fulfill at the current time. Take the minimum we
|
||||||
|
// can fulfill and set that as quantity
|
||||||
|
return (
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
|
(await this.salesChannelInventoryService_
|
||||||
|
.withTransaction(this.activeManager_)
|
||||||
|
.retrieveAvailableItemQuantity(
|
||||||
|
channelId,
|
||||||
|
variantInventory.inventory_item_id
|
||||||
|
)) / variantInventory.required_quantity
|
||||||
|
)
|
||||||
|
})
|
||||||
|
))
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ProductVariantInventoryService
|
export default ProductVariantInventoryService
|
||||||
Reference in new issue
Block a user