From d61d6c7b7f8549996090f5315597d22d2af968f9 Mon Sep 17 00:00:00 2001 From: Philip Korsholm <88927411+pKorsholm@users.noreply.github.com> Date: Tue, 28 Feb 2023 17:09:20 +0100 Subject: [PATCH] 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 --- .changeset/violet-pans-greet.md | 5 ++ .../routes/admin/variants/get-inventory.ts | 16 ++-- .../src/services/product-variant-inventory.ts | 76 +++++++++++++------ 3 files changed, 68 insertions(+), 29 deletions(-) create mode 100644 .changeset/violet-pans-greet.md diff --git a/.changeset/violet-pans-greet.md b/.changeset/violet-pans-greet.md new file mode 100644 index 0000000000000..0fd73833d5680 --- /dev/null +++ b/.changeset/violet-pans-greet.md @@ -0,0 +1,5 @@ +--- +"@medusajs/medusa": patch +--- + +fix(medusa): Account for multiple inventory items when getting inventory diff --git a/packages/medusa/src/api/routes/admin/variants/get-inventory.ts b/packages/medusa/src/api/routes/admin/variants/get-inventory.ts index a6a01bac621ab..c70027df77095 100644 --- a/packages/medusa/src/api/routes/admin/variants/get-inventory.ts +++ b/packages/medusa/src/api/routes/admin/variants/get-inventory.ts @@ -4,6 +4,7 @@ import { } from "../../../../types/inventory" import ProductVariantInventoryService from "../../../../services/product-variant-inventory" import { + SalesChannelInventoryService, SalesChannelLocationService, SalesChannelService, } from "../../../../services" @@ -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" ) @@ -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) => { @@ -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, diff --git a/packages/medusa/src/services/product-variant-inventory.ts b/packages/medusa/src/services/product-variant-inventory.ts index 240da4c337516..aba455e66f313 100644 --- a/packages/medusa/src/services/product-variant-inventory.ts +++ b/packages/medusa/src/services/product-variant-inventory.ts @@ -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 { const variantInventoryRepo = this.activeManager_.getRepository( @@ -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 }) @@ -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 { + 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