Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(medusa): Account for multiple inventory items in get-inventory #3094

Merged
5 changes: 5 additions & 0 deletions .changeset/violet-pans-greet.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@medusajs/medusa": patch
---

fix(medusa): Account for multiple inventory items when getting inventory
16 changes: 11 additions & 5 deletions packages/medusa/src/api/routes/admin/variants/get-inventory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
} from "../../../../types/inventory"
import ProductVariantInventoryService from "../../../../services/product-variant-inventory"
import {
SalesChannelInventoryService,
SalesChannelLocationService,
SalesChannelService,
} from "../../../../services"
Expand Down Expand Up @@ -75,6 +76,8 @@ export default async (req, res) => {
const channelLocationService: SalesChannelLocationService = req.scope.resolve(
"salesChannelLocationService"
)
const salesChannelInventoryService: SalesChannelInventoryService =
req.scope.resolve("salesChannelInventoryService")
const channelService: SalesChannelService = req.scope.resolve(
"salesChannelService"
)
Expand Down Expand Up @@ -106,11 +109,13 @@ export default async (req, res) => {
})
)

const variantInventoryItems =
await productVariantInventoryService.listByVariant(variant.id)

const inventory =
await productVariantInventoryService.listInventoryItemsByVariant(variant.id)
responseVariant.inventory = await joinLevels(inventory, [], inventoryService)

// TODO: adjust for required quantity
if (inventory.length) {
responseVariant.sales_channel_availability = await Promise.all(
channels.map(async (channel) => {
Expand All @@ -122,10 +127,11 @@ export default async (req, res) => {
}
}

const quantity = await inventoryService.retrieveAvailableQuantity(
inventory[0].id,
channel.locations
)
const quantity =
await productVariantInventoryService.getVariantQuantityFromVariantInventoryItems(
variantInventoryItems,
channel.id
)

return {
channel_name: channel.name as string,
Expand Down
76 changes: 52 additions & 24 deletions packages/medusa/src/services/product-variant-inventory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ class ProductVariantInventoryService extends TransactionBaseService {
* @param variantId variant id
* @returns variant inventory items for the variant id
*/
private async listByVariant(
public async listByVariant(
variantId: string | string[]
): Promise<ProductVariantInventoryItem[]> {
const variantInventoryRepo = this.activeManager_.getRepository(
Expand Down Expand Up @@ -615,30 +615,11 @@ class ProductVariantInventoryService extends TransactionBaseService {
// first get all inventory items required for a variant
const variantInventory = await this.listByVariant(variant.id)

const salesChannelInventoryServiceTx =
this.salesChannelInventoryService_.withTransaction(
this.activeManager_
variant.inventory_quantity =
await this.getVariantQuantityFromVariantInventoryItems(
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
})
Expand All @@ -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(
olivermrbl marked this conversation as resolved.
Show resolved Hide resolved
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