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:
Philip Korsholm authored and GitHub committed 2023-02-28 17:09:20 +01:00
1 parent 5eb61fa0ef
commit d61d6c7b7f
3 files changed
+68 -29

No files matched your search

+5
View File
@@ -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,10 +127,11 @@ 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 {
channel_name: channel.name as string, channel_name: channel.name as string,
@@ -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,30 +615,11 @@ 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