import { BigNumberInput, Context, DAL, IInventoryService, InferEntityType, InternalModuleDeclaration, InventoryTypes, ModuleJoinerConfig, ModulesSdkTypes, ReservationItemDTO, RestoreReturn, SoftDeleteReturn, } from "@medusajs/framework/types" import { arrayDifference, BigNumber, EmitEvents, InjectManager, InjectTransactionManager, isDefined, isString, MathBN, MedusaContext, MedusaError, MedusaService, partitionArray, } from "@medusajs/framework/utils" import { InventoryItem, InventoryLevel, ReservationItem } from "@models" import { joinerConfig } from "../joiner-config" import { applyEntityHooks } from "../utils/apply-decorators" import InventoryLevelService from "./inventory-level" type InjectedDependencies = { baseRepository: DAL.RepositoryService inventoryItemService: ModulesSdkTypes.IMedusaInternalService inventoryLevelService: InventoryLevelService reservationItemService: ModulesSdkTypes.IMedusaInternalService } type InventoryItemCheckLevel = { id?: string location_id: string inventory_item_id: string quantity?: BigNumberInput allow_backorder?: boolean } applyEntityHooks() export default class InventoryModuleService extends MedusaService<{ InventoryItem: { dto: InventoryTypes.InventoryItemDTO } InventoryLevel: { dto: InventoryTypes.InventoryLevelDTO } ReservationItem: { dto: InventoryTypes.ReservationItemDTO } }>({ InventoryItem, InventoryLevel, ReservationItem, }) implements IInventoryService { protected baseRepository_: DAL.RepositoryService protected readonly inventoryItemService_: ModulesSdkTypes.IMedusaInternalService< typeof InventoryItem > protected readonly reservationItemService_: ModulesSdkTypes.IMedusaInternalService< typeof ReservationItem > protected readonly inventoryLevelService_: InventoryLevelService constructor( { baseRepository, inventoryItemService, inventoryLevelService, reservationItemService, }: InjectedDependencies, protected readonly moduleDeclaration?: InternalModuleDeclaration ) { // @ts-ignore // eslint-disable-next-line prefer-rest-params super(...arguments) this.baseRepository_ = baseRepository this.inventoryItemService_ = inventoryItemService this.inventoryLevelService_ = inventoryLevelService this.reservationItemService_ = reservationItemService } __joinerConfig(): ModuleJoinerConfig { return joinerConfig } private async ensureInventoryLevels( data: InventoryItemCheckLevel[], options?: { validateQuantityAtLocation?: boolean }, context?: Context ): Promise { options ??= {} const validateQuantityAtLocation = options.validateQuantityAtLocation ?? false const data_ = data.map((dt: any) => ({ location_id: dt.location_id, inventory_item_id: dt.inventory_item_id, })) as InventoryItemCheckLevel[] const [idData, itemLocationData] = partitionArray( data_, ({ id }) => !!id ) as [ { id: string }[], { location_id: string; inventory_item_id: string }[] ] const inventoryLevels = await this.inventoryLevelService_.list( { $or: [ { id: idData.filter(({ id }) => !!id).map((e) => e.id) }, ...itemLocationData, ], }, {}, context ) const inventoryLevelIdMap: Map = new Map(inventoryLevels.map((level) => [level.id, level])) const inventoryLevelItemLocationMap: Map< string, Map > = inventoryLevels.reduce((acc, curr) => { const inventoryLevelMap = acc.get(curr.inventory_item_id) ?? new Map() inventoryLevelMap.set(curr.location_id, curr) acc.set(curr.inventory_item_id, inventoryLevelMap) return acc }, new Map()) const missing = data.filter((item) => { if (item.id) { return !inventoryLevelIdMap.has(item.id!) } return !inventoryLevelItemLocationMap .get(item.inventory_item_id) ?.has(item.location_id) }) if (missing.length) { const error = missing .map((missing) => { if ("id" in missing) { return `Inventory level with id ${missing.id} does not exist` } return `Item ${missing.inventory_item_id} is not stocked at location ${missing.location_id}` }) .join(", ") throw new MedusaError(MedusaError.Types.NOT_FOUND, error) } if (validateQuantityAtLocation) { for (const item of data) { if (!!item.allow_backorder) { continue } const locations = inventoryLevelItemLocationMap.get( item.inventory_item_id )! const level = locations?.get(item.location_id)! if (MathBN.lt(level.available_quantity, item.quantity!)) { throw new MedusaError( MedusaError.Types.NOT_ALLOWED, `Not enough stock available for item ${item.inventory_item_id} at location ${item.location_id}` ) } } } return inventoryLevels } // reserved_quantity should solely be handled through creating & updating reservation items // We sanitize the inputs here to prevent that from being used to update it private sanitizeInventoryLevelInput( input: (TDTO & { reserved_quantity?: BigNumberInput })[] ): TDTO[] { return input.map((input) => { const { reserved_quantity, ...validInput } = input return validInput as TDTO }) } private sanitizeInventoryItemInput( input: (TDTO & { location_levels?: object[] })[] ): TDTO[] { return input.map((input) => { const { location_levels, ...validInput } = input return validInput as TDTO }) } // @ts-expect-error async createReservationItems( input: InventoryTypes.CreateReservationItemInput[], context?: Context ): Promise // @ts-expect-error async createReservationItems( input: InventoryTypes.CreateReservationItemInput, context?: Context ): Promise @InjectManager() @EmitEvents() // @ts-expect-error async createReservationItems( input: | InventoryTypes.CreateReservationItemInput[] | InventoryTypes.CreateReservationItemInput, @MedusaContext() context: Context = {} ): Promise< InventoryTypes.ReservationItemDTO[] | InventoryTypes.ReservationItemDTO > { const toCreate = Array.isArray(input) ? input : [input] const created = await this.createReservationItems_(toCreate, context) const serializedReservations = await this.baseRepository_.serialize< InventoryTypes.ReservationItemDTO[] | InventoryTypes.ReservationItemDTO >(created) return Array.isArray(input) ? serializedReservations : serializedReservations[0] } @InjectTransactionManager() async createReservationItems_( input: InventoryTypes.CreateReservationItemInput[], @MedusaContext() context: Context = {} ): Promise[]> { const inventoryLevels = await this.ensureInventoryLevels( input.map( ({ location_id, inventory_item_id, quantity, allow_backorder }) => ({ location_id, inventory_item_id, quantity, allow_backorder, }) ), { validateQuantityAtLocation: true, }, context ) const created = await this.reservationItemService_.create(input, context) const adjustments: Map> = input.reduce( (acc, curr) => { const locationMap = acc.get(curr.inventory_item_id) ?? new Map() const adjustment = locationMap.get(curr.location_id) ?? 0 locationMap.set(curr.location_id, MathBN.add(adjustment, curr.quantity)) acc.set(curr.inventory_item_id, locationMap) return acc }, new Map() ) const levelAdjustmentUpdates = inventoryLevels.map((level) => { const adjustment = adjustments .get(level.inventory_item_id) ?.get(level.location_id) if (!adjustment) { return } return { id: level.id, reserved_quantity: MathBN.add(level.reserved_quantity, adjustment), } }) await this.inventoryLevelService_.update(levelAdjustmentUpdates, context) return created } // @ts-expect-error createInventoryItems( input: InventoryTypes.CreateInventoryItemInput, context?: Context ): Promise createInventoryItems( input: InventoryTypes.CreateInventoryItemInput[], context?: Context ): Promise @InjectManager() @EmitEvents() async createInventoryItems( input: | InventoryTypes.CreateInventoryItemInput | InventoryTypes.CreateInventoryItemInput[], @MedusaContext() context: Context = {} ): Promise< InventoryTypes.InventoryItemDTO | InventoryTypes.InventoryItemDTO[] > { const toCreate = this.sanitizeInventoryItemInput( Array.isArray(input) ? input : [input] ) const result = await this.createInventoryItems_(toCreate, context) const serializedItems = await this.baseRepository_.serialize< InventoryTypes.InventoryItemDTO | InventoryTypes.InventoryItemDTO[] >(result) return Array.isArray(input) ? serializedItems : serializedItems[0] } @InjectTransactionManager() async createInventoryItems_( input: InventoryTypes.CreateInventoryItemInput[], @MedusaContext() context: Context = {} ): Promise { return await this.inventoryItemService_.create(input, context) } // @ts-ignore createInventoryLevels( input: InventoryTypes.CreateInventoryLevelInput, context?: Context ): Promise // @ts-expect-error createInventoryLevels( input: InventoryTypes.CreateInventoryLevelInput[], context?: Context ): Promise @InjectManager() @EmitEvents() // @ts-expect-error async createInventoryLevels( input: | InventoryTypes.CreateInventoryLevelInput[] | InventoryTypes.CreateInventoryLevelInput, @MedusaContext() context: Context = {} ): Promise< InventoryTypes.InventoryLevelDTO[] | InventoryTypes.InventoryLevelDTO > { const toCreate = this.sanitizeInventoryLevelInput( Array.isArray(input) ? input : [input] ) const created = await this.createInventoryLevels_(toCreate, context) const serialized = await this.baseRepository_.serialize< InventoryTypes.InventoryLevelDTO[] | InventoryTypes.InventoryLevelDTO >(created) return Array.isArray(input) ? serialized : serialized[0] } @InjectTransactionManager() async createInventoryLevels_( input: InventoryTypes.CreateInventoryLevelInput[], @MedusaContext() context: Context = {} ): Promise[]> { return await this.inventoryLevelService_.create(input, context) } // @ts-expect-error updateInventoryItems( input: InventoryTypes.UpdateInventoryItemInput[], context?: Context ): Promise // @ts-expect-error updateInventoryItems( input: InventoryTypes.UpdateInventoryItemInput, context?: Context ): Promise @InjectManager() @EmitEvents() // @ts-expect-error async updateInventoryItems( input: | InventoryTypes.UpdateInventoryItemInput | InventoryTypes.UpdateInventoryItemInput[], @MedusaContext() context: Context = {} ): Promise< InventoryTypes.InventoryItemDTO | InventoryTypes.InventoryItemDTO[] > { const updates = this.sanitizeInventoryItemInput( Array.isArray(input) ? input : [input] ) const result = await this.updateInventoryItems_(updates, context) const serializedItems = await this.baseRepository_.serialize< InventoryTypes.InventoryItemDTO | InventoryTypes.InventoryItemDTO[] >(result) return Array.isArray(input) ? serializedItems : serializedItems[0] } @InjectTransactionManager() async updateInventoryItems_( input: (Partial & { id: string })[], @MedusaContext() context: Context = {} ): Promise[]> { return await this.inventoryItemService_.update(input, context) } @InjectManager() @EmitEvents() async deleteInventoryItemLevelByLocationId( locationId: string | string[], @MedusaContext() context: Context = {} ): Promise<[object[], Record]> { const result = await this.inventoryLevelService_.softDelete( { location_id: locationId }, context ) return result } /** * Deletes an inventory level * @param inventoryItemId - the id of the inventory item associated with the level * @param locationId - the id of the location associated with the level * @param context */ @InjectTransactionManager() @EmitEvents() async deleteInventoryLevel( inventoryItemId: string, locationId: string, @MedusaContext() context: Context = {} ): Promise { const [inventoryLevel] = await this.inventoryLevelService_.list( { inventory_item_id: inventoryItemId, location_id: locationId }, { take: 1 }, context ) if (!inventoryLevel) { return } await this.inventoryLevelService_.delete(inventoryLevel.id, context) } // @ts-ignore async updateInventoryLevels( updates: InventoryTypes.UpdateInventoryLevelInput[], context?: Context ): Promise // @ts-expect-error async updateInventoryLevels( updates: InventoryTypes.UpdateInventoryLevelInput, context?: Context ): Promise @InjectManager() @EmitEvents() // @ts-expect-error async updateInventoryLevels( updates: | InventoryTypes.UpdateInventoryLevelInput[] | InventoryTypes.UpdateInventoryLevelInput, @MedusaContext() context: Context = {} ): Promise< InventoryTypes.InventoryLevelDTO | InventoryTypes.InventoryLevelDTO[] > { const input = this.sanitizeInventoryLevelInput( Array.isArray(updates) ? updates : [updates] ) const levels = await this.updateInventoryLevels_(input, context) const updatedLevels = await this.baseRepository_.serialize< InventoryTypes.InventoryLevelDTO | InventoryTypes.InventoryLevelDTO[] >(levels) return Array.isArray(updates) ? updatedLevels : updatedLevels[0] } @InjectTransactionManager() async updateInventoryLevels_( updates: InventoryTypes.UpdateInventoryLevelInput[], @MedusaContext() context: Context = {} ) { const inventoryLevels = await this.ensureInventoryLevels( updates.map(({ location_id, inventory_item_id }) => ({ location_id, inventory_item_id, })), undefined, context ) const levelMap = inventoryLevels.reduce((acc, curr) => { const inventoryLevelMap = acc.get(curr.inventory_item_id) ?? new Map() inventoryLevelMap.set(curr.location_id, curr.id) acc.set(curr.inventory_item_id, inventoryLevelMap) return acc }, new Map()) const updatesWithIds = updates.map((update) => { const id = levelMap.get(update.inventory_item_id).get(update.location_id) return { id, ...update } }) return await this.inventoryLevelService_.update(updatesWithIds, context) } /** * Updates a reservation item * @param reservationItemId * @param input - the input object * @param context * @param context * @return The updated inventory level */ // @ts-expect-error async updateReservationItems( input: InventoryTypes.UpdateReservationItemInput[], context?: Context ): Promise // @ts-expect-error async updateReservationItems( input: InventoryTypes.UpdateReservationItemInput, context?: Context ): Promise @InjectManager() @EmitEvents() // @ts-expect-error async updateReservationItems( input: | InventoryTypes.UpdateReservationItemInput | InventoryTypes.UpdateReservationItemInput[], @MedusaContext() context: Context = {} ): Promise< InventoryTypes.ReservationItemDTO | InventoryTypes.ReservationItemDTO[] > { const update = Array.isArray(input) ? input : [input] const result = await this.updateReservationItems_(update, context) const serialized = await this.baseRepository_.serialize< InventoryTypes.ReservationItemDTO | InventoryTypes.ReservationItemDTO[] >(result) return Array.isArray(input) ? serialized : serialized[0] } @InjectTransactionManager() async updateReservationItems_( input: (InventoryTypes.UpdateReservationItemInput & { id: string })[], @MedusaContext() context: Context = {} ): Promise[]> { const ids = input.map((u) => u.id) const reservationItems = await this.reservationItemService_.list( { id: ids }, {}, context ) const diff = arrayDifference( ids, reservationItems.map((i) => i.id) ) if (diff.length) { throw new MedusaError( MedusaError.Types.INVALID_DATA, `Reservation item with id ${diff.join(", ")} not found` ) } const reservationMap: Map = new Map( reservationItems.map((r) => [r.id, r]) ) const adjustments: Map> = input.reduce( (acc, update) => { const reservation = reservationMap.get(update.id)! const locationMap = acc.get(reservation.inventory_item_id) ?? new Map() if ( isDefined(update.location_id) && update.location_id !== reservation.location_id ) { const reservationLocationAdjustment = locationMap.get(reservation.location_id) ?? 0 locationMap.set( reservation.location_id, MathBN.sub(reservationLocationAdjustment, reservation.quantity) ) const updateLocationAdjustment = locationMap.get(update.location_id) ?? 0 locationMap.set( update.location_id, MathBN.add( updateLocationAdjustment, update.quantity || reservation.quantity ) ) } else if ( isDefined(update.quantity) && !MathBN.eq(update.quantity, reservation.quantity) ) { const locationAdjustment = locationMap.get(reservation.location_id) ?? 0 locationMap.set( reservation.location_id, MathBN.add( locationAdjustment, MathBN.sub(update.quantity!, reservation.quantity) ) ) } acc.set(reservation.inventory_item_id, locationMap) return acc }, new Map() ) const availabilityData = input.map((data) => { const reservation = reservationMap.get(data.id)! let adjustment = data.quantity ? MathBN.sub(data.quantity, reservation.quantity) : 0 if (MathBN.lt(adjustment, 0)) { adjustment = 0 } return { inventory_item_id: reservation.inventory_item_id, location_id: data.location_id ?? reservation.location_id, quantity: adjustment, allow_backorder: data.allow_backorder || reservation.allow_backorder || false, } }) const inventoryLevels = await this.ensureInventoryLevels( availabilityData, { validateQuantityAtLocation: true, }, context ) const result = await this.reservationItemService_.update(input, context) const levelAdjustmentUpdates = inventoryLevels .map((level) => { const adjustment = adjustments .get(level.inventory_item_id) ?.get(level.location_id) if (!adjustment) { return } return { id: level.id, reserved_quantity: MathBN.add(level.reserved_quantity, adjustment), } }) .filter(Boolean) await this.inventoryLevelService_.update(levelAdjustmentUpdates, context) return result } @InjectManager() @EmitEvents() // @ts-expect-error async softDeleteReservationItems( ids: string | string[], config?: SoftDeleteReturn, @MedusaContext() context: Context = {} ): Promise { return await this.softDeleteReservationItems_(ids, config, context) } @InjectTransactionManager() protected async softDeleteReservationItems_( ids: string | string[], config?: SoftDeleteReturn, @MedusaContext() context: Context = {} ): Promise { const reservations: InventoryTypes.ReservationItemDTO[] = await this.reservationItemService_.list({ id: ids }, {}, context) await super.softDeleteReservationItems(ids, config, context) await this.adjustInventoryLevelsForReservationsDeletion( reservations, context ) } @InjectManager() @EmitEvents() // @ts-expect-error async restoreReservationItems( ids: string | string[], config?: RestoreReturn, @MedusaContext() context: Context = {} ): Promise { return await this.restoreReservationItems_(ids, config, context) } @InjectTransactionManager() protected async restoreReservationItems_( ids: string | string[], config?: RestoreReturn, @MedusaContext() context: Context = {} ): Promise { const reservations: InventoryTypes.ReservationItemDTO[] = await super.listReservationItems({ id: ids }, {}, context) await super.restoreReservationItems({ id: ids }, config, context) await this.adjustInventoryLevelsForReservationsRestore( reservations, context ) } @InjectManager() @EmitEvents() async deleteReservationItemByLocationId( locationId: string | string[], @MedusaContext() context: Context = {} ): Promise { return await this.deleteReservationItemByLocationId_(locationId, context) } @InjectTransactionManager() protected async deleteReservationItemByLocationId_( locationId: string | string[], @MedusaContext() context: Context = {} ): Promise { const reservations: InventoryTypes.ReservationItemDTO[] = await this.reservationItemService_.list( { location_id: locationId }, {}, context ) await this.reservationItemService_.softDelete( { location_id: locationId }, context ) await this.adjustInventoryLevelsForReservationsDeletion( reservations, context ) } /** * Deletes reservation items by line item * @param lineItemId - the id of the line item associated with the reservation item * @param context */ @InjectManager() @EmitEvents() async deleteReservationItemsByLineItem( lineItemId: string | string[], @MedusaContext() context: Context = {} ): Promise { return await this.deleteReservationItemsByLineItem_(lineItemId, context) } @InjectTransactionManager() protected async deleteReservationItemsByLineItem_( lineItemId: string | string[], @MedusaContext() context: Context = {} ): Promise { const reservations: InventoryTypes.ReservationItemDTO[] = await this.reservationItemService_.list( { line_item_id: lineItemId }, {}, context ) await this.reservationItemService_.softDelete( { line_item_id: lineItemId }, context ) await this.adjustInventoryLevelsForReservationsDeletion( reservations, context ) } /** * Deletes reservation items by line item * @param lineItemId - the id of the line item associated with the reservation item * @param context */ @InjectManager() @EmitEvents() async restoreReservationItemsByLineItem( lineItemId: string | string[], @MedusaContext() context: Context = {} ): Promise { return await this.restoreReservationItemsByLineItem_(lineItemId, context) } @InjectTransactionManager() protected async restoreReservationItemsByLineItem_( lineItemId: string | string[], @MedusaContext() context: Context = {} ): Promise { const reservations: InventoryTypes.ReservationItemDTO[] = await this.reservationItemService_.list( { line_item_id: lineItemId }, {}, context ) await this.reservationItemService_.restore( { line_item_id: lineItemId }, context ) await this.adjustInventoryLevelsForReservationsRestore( reservations, context ) } /** * Adjusts the inventory level for a given inventory item and location. * @param inventoryItemId - the id of the inventory item * @param locationId - the id of the location * @param adjustment - the number to adjust the inventory by (can be positive or negative) * @param context * @return The updated inventory level * @throws when the inventory level is not found */ adjustInventory( inventoryItemId: string, locationId: string, adjustment: BigNumberInput, context: Context ): Promise adjustInventory( data: { inventoryItemId: string locationId: string adjustment: BigNumberInput }[], context: Context ): Promise @InjectManager() @EmitEvents() async adjustInventory( inventoryItemIdOrData: string | any, locationId?: string | Context, adjustment?: BigNumberInput, @MedusaContext() context: Context = {} ): Promise< InventoryTypes.InventoryLevelDTO | InventoryTypes.InventoryLevelDTO[] > { let all: any = inventoryItemIdOrData if (isString(inventoryItemIdOrData)) { all = [ { inventoryItemId: inventoryItemIdOrData, locationId, adjustment, }, ] } const results: InferEntityType[] = [] for (const data of all) { const result = await this.adjustInventory_( data.inventoryItemId, data.locationId, data.adjustment, context ) results.push(result) } return await this.baseRepository_.serialize( Array.isArray(inventoryItemIdOrData) ? results : results[0] ) } @InjectTransactionManager() async adjustInventory_( inventoryItemId: string, locationId: string, adjustment: BigNumberInput, @MedusaContext() context: Context = {} ): Promise> { const [inventoryLevel] = await this.inventoryLevelService_.list( { inventory_item_id: inventoryItemId, location_id: locationId }, {}, context ) if (!inventoryLevel) { throw new MedusaError( MedusaError.Types.NOT_FOUND, `Inventory level for item ${inventoryItemId} and location ${locationId} not found` ) } const result = await this.inventoryLevelService_.update( { id: inventoryLevel.id, stocked_quantity: MathBN.add( inventoryLevel.stocked_quantity, adjustment ), }, context ) return result } @InjectManager() async retrieveInventoryLevelByItemAndLocation( inventoryItemId: string, locationId: string, @MedusaContext() context: Context = {} ): Promise { const [inventoryLevel] = await this.listInventoryLevels( { inventory_item_id: inventoryItemId, location_id: locationId }, {}, context ) if (!inventoryLevel) { throw new MedusaError( MedusaError.Types.NOT_FOUND, `Inventory level for item ${inventoryItemId} and location ${locationId} not found` ) } return inventoryLevel } /** * Retrieves the available quantity of a given inventory item in a given location. * @param inventoryItemId - the id of the inventory item * @param locationIds - the ids of the locations to check * @param context * @return The available quantity * @throws when the inventory item is not found */ @InjectManager() async retrieveAvailableQuantity( inventoryItemId: string, locationIds: string[], @MedusaContext() context: Context = {} ): Promise { if (locationIds.length === 0) { return new BigNumber(0) } await this.inventoryItemService_.retrieve( inventoryItemId, { select: ["id"], }, context ) const availableQuantity = await this.inventoryLevelService_.getAvailableQuantity( inventoryItemId, locationIds, context ) return availableQuantity } /** * Retrieves the stocked quantity of a given inventory item in a given location. * @param inventoryItemId - the id of the inventory item * @param locationIds - the ids of the locations to check * @param context * @return The stocked quantity * @throws when the inventory item is not found */ @InjectManager() async retrieveStockedQuantity( inventoryItemId: string, locationIds: string[], @MedusaContext() context: Context = {} ): Promise { if (locationIds.length === 0) { return new BigNumber(0) } // Throws if item does not exist await this.inventoryItemService_.retrieve( inventoryItemId, { select: ["id"], }, context ) const stockedQuantity = await this.inventoryLevelService_.retrieveStockedQuantity( inventoryItemId, locationIds, context ) return stockedQuantity } /** * Retrieves the reserved quantity of a given inventory item in a given location. * @param inventoryItemId - the id of the inventory item * @param locationIds - the ids of the locations to check * @param context * @return The reserved quantity * @throws when the inventory item is not found */ @InjectManager() async retrieveReservedQuantity( inventoryItemId: string, locationIds: string[], @MedusaContext() context: Context = {} ): Promise { // Throws if item does not exist await this.inventoryItemService_.retrieve( inventoryItemId, { select: ["id"], }, context ) if (locationIds.length === 0) { return new BigNumber(0) } const reservedQuantity = await this.inventoryLevelService_.getReservedQuantity( inventoryItemId, locationIds, context ) return reservedQuantity } /** * Confirms whether there is sufficient inventory for a given quantity of a given inventory item in a given location. * @param inventoryItemId - the id of the inventory item * @param locationIds - the ids of the locations to check * @param quantity - the quantity to check * @param context * @return Whether there is sufficient inventory */ @InjectManager() async confirmInventory( inventoryItemId: string, locationIds: string[], quantity: BigNumberInput, @MedusaContext() context: Context = {} ): Promise { const availableQuantity = await this.retrieveAvailableQuantity( inventoryItemId, locationIds, context ) return MathBN.gte(availableQuantity, quantity) } private async adjustInventoryLevelsForReservationsDeletion( reservations: ReservationItemDTO[], context: Context ): Promise { await this.adjustInventoryLevelsForReservations_( reservations, true, context ) } private async adjustInventoryLevelsForReservationsRestore( reservations: ReservationItemDTO[], context: Context ): Promise { await this.adjustInventoryLevelsForReservations_( reservations, false, context ) } private async adjustInventoryLevelsForReservations_( reservations: ReservationItemDTO[], isDelete: boolean, context: Context ): Promise { const multiplier = isDelete ? -1 : 1 const inventoryLevels = await this.ensureInventoryLevels( reservations.map((r) => ({ inventory_item_id: r.inventory_item_id, location_id: r.location_id, })), undefined, context ) const inventoryLevelAdjustments: Map< string, Map > = reservations.reduce((acc, curr) => { const inventoryLevelMap = acc.get(curr.inventory_item_id) ?? new Map() const adjustment = inventoryLevelMap.has(curr.location_id) ? MathBN.add( inventoryLevelMap.get(curr.location_id), MathBN.mult(curr.quantity, multiplier) ) : MathBN.mult(curr.quantity, multiplier) inventoryLevelMap.set(curr.location_id, adjustment) acc.set(curr.inventory_item_id, inventoryLevelMap) return acc }, new Map()) const levelAdjustmentUpdates = inventoryLevels.map((level) => { const adjustment = inventoryLevelAdjustments .get(level.inventory_item_id) ?.get(level.location_id) if (!adjustment) { return } return { id: level.id, reserved_quantity: MathBN.add(level.reserved_quantity, adjustment), } }) await this.inventoryLevelService_.update(levelAdjustmentUpdates, context) } }