diff --git a/integration-tests/http/__tests__/inventory/admin/inventory.spec.ts b/integration-tests/http/__tests__/inventory/admin/inventory.spec.ts index 0c9e09f31c..24cb8f69cc 100644 --- a/integration-tests/http/__tests__/inventory/admin/inventory.spec.ts +++ b/integration-tests/http/__tests__/inventory/admin/inventory.spec.ts @@ -292,16 +292,6 @@ medusaIntegrationTestRunner({ }) it("should fail to update the location level to negative quantity", async () => { - await api.post( - `/admin/inventory-items/${inventoryItem1.id}/location-levels`, - { - location_id: stockLocation1.id, - stocked_quantity: 17, - incoming_quantity: 2, - }, - adminHeaders - ) - const res = await api .post( `/admin/inventory-items/${inventoryItem1.id}/location-levels/${stockLocation1.id}`, @@ -581,7 +571,7 @@ medusaIntegrationTestRunner({ }) ) }) - + it("should retrieve the inventory item with correct stocked quantity given location levels have been deleted", async () => { await api.post( `/admin/inventory-items/${inventoryItem1.id}/location-levels`, @@ -751,12 +741,6 @@ medusaIntegrationTestRunner({ adminHeaders ) - await api.post( - `/admin/inventory-items/${inventoryItem1.id}/location-levels`, - { location_id: stockLocation1.id }, - adminHeaders - ) - await api.post( `/admin/reservations`, { @@ -784,7 +768,7 @@ medusaIntegrationTestRunner({ adminHeaders ) ).data - expect(levelsResponse.count).toEqual(2) + expect(levelsResponse.count).toEqual(1) const res = await api.delete( `/admin/inventory-items/${inventoryItem1.id}`, diff --git a/integration-tests/modules/__tests__/order/workflows/create-fulfillment.spec.ts b/integration-tests/modules/__tests__/order/workflows/create-fulfillment.spec.ts index 5aae561202..a19349d6a6 100644 --- a/integration-tests/modules/__tests__/order/workflows/create-fulfillment.spec.ts +++ b/integration-tests/modules/__tests__/order/workflows/create-fulfillment.spec.ts @@ -15,6 +15,7 @@ import { StockLocationDTO, } from "@medusajs/types" import { + BigNumber, ContainerRegistrationKeys, ModuleRegistrationName, Modules, @@ -345,6 +346,10 @@ medusaIntegrationTestRunner({ }) it("should create a order fulfillment and cancel it", async () => { + const inventoryModule = container.resolve( + ModuleRegistrationName.INVENTORY + ) + const order = await createOrderFixture({ container, product, location }) // Create a fulfillment @@ -389,9 +394,6 @@ medusaIntegrationTestRunner({ expect(orderFulfill.fulfillments).toHaveLength(1) expect(orderFulfill.items[0].detail.fulfilled_quantity).toEqual(1) - const inventoryModule = container.resolve( - ModuleRegistrationName.INVENTORY - ) const reservation = await inventoryModule.listReservationItems({ line_item_id: order.items![0].id, }) @@ -401,7 +403,7 @@ medusaIntegrationTestRunner({ inventoryItem.id, [location.id] ) - expect(stockAvailability).toEqual(1) + expect(stockAvailability).toEqual(new BigNumber(1)) // Cancel the fulfillment const cancelFulfillmentData: OrderWorkflow.CancelOrderFulfillmentWorkflowInput = @@ -443,7 +445,7 @@ medusaIntegrationTestRunner({ await inventoryModule.retrieveStockedQuantity(inventoryItem.id, [ location.id, ]) - expect(stockAvailabilityAfterCancelled).toEqual(2) + expect(stockAvailabilityAfterCancelled.valueOf()).toEqual(2) }) it("should revert an order fulfillment when it fails and recreate it when tried again", async () => { @@ -518,7 +520,7 @@ medusaIntegrationTestRunner({ inventoryItem.id, [location.id] ) - expect(stockAvailability).toEqual(1) + expect(stockAvailability.valueOf()).toEqual(1) }) }) }, diff --git a/integration-tests/modules/__tests__/order/workflows/create-shipment.spec.ts b/integration-tests/modules/__tests__/order/workflows/create-shipment.spec.ts index 0c648664cf..8647d713b3 100644 --- a/integration-tests/modules/__tests__/order/workflows/create-shipment.spec.ts +++ b/integration-tests/modules/__tests__/order/workflows/create-shipment.spec.ts @@ -418,7 +418,7 @@ medusaIntegrationTestRunner({ inventoryItem.id, [location.id] ) - expect(stockAvailability).toEqual(1) + expect(stockAvailability.valueOf()).toEqual(1) }) }) }, diff --git a/packages/core/core-flows/src/definition/cart/steps/fetch-line-items-to-upsert b/packages/core/core-flows/src/definition/cart/steps/fetch-line-items-to-upsert deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/core/core-flows/src/inventory/steps/adjust-inventory-levels.ts b/packages/core/core-flows/src/inventory/steps/adjust-inventory-levels.ts index e3db5ea3c8..3e5600e1d0 100644 --- a/packages/core/core-flows/src/inventory/steps/adjust-inventory-levels.ts +++ b/packages/core/core-flows/src/inventory/steps/adjust-inventory-levels.ts @@ -1,7 +1,7 @@ import { IInventoryService, InventoryTypes } from "@medusajs/types" import { StepResponse, createStep } from "@medusajs/workflows-sdk" -import { ModuleRegistrationName } from "@medusajs/utils" +import { MathBN, ModuleRegistrationName } from "@medusajs/utils" export const adjustInventoryLevelsStepId = "adjust-inventory-levels-step" export const adjustInventoryLevelsStep = createStep( @@ -30,7 +30,7 @@ export const adjustInventoryLevelsStep = createStep( input.map((item) => { return { ...item, - adjustment: item.adjustment * -1, + adjustment: MathBN.mult(item.adjustment, -1), } }) ) diff --git a/packages/core/core-flows/src/order/workflows/cancel-order-fulfillment.ts b/packages/core/core-flows/src/order/workflows/cancel-order-fulfillment.ts index 8178741166..16181c2a67 100644 --- a/packages/core/core-flows/src/order/workflows/cancel-order-fulfillment.ts +++ b/packages/core/core-flows/src/order/workflows/cancel-order-fulfillment.ts @@ -1,11 +1,16 @@ -import { FulfillmentDTO, OrderDTO, OrderWorkflow } from "@medusajs/types" +import { + BigNumberInput, + FulfillmentDTO, + OrderDTO, + OrderWorkflow, +} from "@medusajs/types" import { MedusaError, Modules } from "@medusajs/utils" import { + WorkflowData, createStep, createWorkflow, parallelize, transform, - WorkflowData, } from "@medusajs/workflows-sdk" import { useRemoteQueryStep } from "../../common" import { cancelFulfillmentWorkflow } from "../../fulfillment" @@ -82,7 +87,7 @@ function prepareInventoryUpdate({ const inventoryAdjustment: { inventory_item_id: string location_id: string - adjustment: number // TODO: BigNumberInput + adjustment: BigNumberInput }[] = [] for (const item of fulfillment.items) { diff --git a/packages/core/core-flows/src/order/workflows/create-fulfillment.ts b/packages/core/core-flows/src/order/workflows/create-fulfillment.ts index 50c45a72a7..2b85bd163d 100644 --- a/packages/core/core-flows/src/order/workflows/create-fulfillment.ts +++ b/packages/core/core-flows/src/order/workflows/create-fulfillment.ts @@ -1,11 +1,12 @@ import { + BigNumberInput, FulfillmentDTO, FulfillmentWorkflow, OrderDTO, OrderWorkflow, ReservationItemDTO, } from "@medusajs/types" -import { MedusaError, Modules } from "@medusajs/utils" +import { MathBN, MedusaError, Modules } from "@medusajs/utils" import { WorkflowData, createStep, @@ -68,6 +69,7 @@ function prepareFulfillmentData({ order, input, shippingOption, + shippingMethod, reservations, }: { order: OrderDTO @@ -77,6 +79,7 @@ function prepareFulfillmentData({ provider_id: string service_zone: { fulfillment_set: { location?: { id: string } } } } + shippingMethod: { data?: Record | null } reservations: ReservationItemDTO[] }) { const inputItems = input.items @@ -120,8 +123,9 @@ function prepareFulfillmentData({ location_id: locationId, provider_id: shippingOption.provider_id, shipping_option_id: shippingOption.id, + data: shippingMethod.data, items: fulfillmentItems, - labels: [] as FulfillmentWorkflow.CreateFulfillmentLabelWorkflowDTO[], // TODO: shipping labels + labels: input.labels ?? [], delivery_address: shippingAddress as any, }, } @@ -136,13 +140,13 @@ function prepareInventoryUpdate({ reservations, order, input, inputItemsMap }) { const toDelete: string[] = [] const toUpdate: { id: string - quantity: number // TODO: BigNumberInput + quantity: BigNumberInput location_id: string }[] = [] const inventoryAdjustment: { inventory_item_id: string location_id: string - adjustment: number // TODO: BigNumberInput + adjustment: BigNumberInput }[] = [] for (const item of order.items) { @@ -163,7 +167,7 @@ function prepareInventoryUpdate({ reservations, order, input, inputItemsMap }) { inventoryAdjustment.push({ inventory_item_id: reservation.inventory_item_id, location_id: input.location_id ?? reservation.location_id, - adjustment: -item.quantity, // TODO: MathBN.mul(-1, item.quantity) + adjustment: MathBN.mult(item.quantity, -1), }) if (quantity === 0) { @@ -201,6 +205,7 @@ export const createOrderFulfillmentWorkflow = createWorkflow( "items.variant.manage_inventory", "shipping_address.*", "shipping_methods.shipping_option_id", // TODO: which shipping method to use when multiple? + "shipping_methods.data", ], variables: { id: input.order_id }, list: false, @@ -216,6 +221,10 @@ export const createOrderFulfillmentWorkflow = createWorkflow( }, {}) }) + const shippingMethod = transform(order, (data) => { + return { data: data.shipping_methods?.[0].data } + }) + const shippingOptionId = transform(order, (data) => { return data.shipping_methods?.[0]?.shipping_option_id }) @@ -250,7 +259,7 @@ export const createOrderFulfillmentWorkflow = createWorkflow( }).config({ name: "get-reservations" }) const fulfillmentData = transform( - { order, input, shippingOption, reservations }, + { order, input, shippingOption, shippingMethod, reservations }, prepareFulfillmentData ) @@ -278,12 +287,12 @@ export const createOrderFulfillmentWorkflow = createWorkflow( prepareInventoryUpdate ) + adjustInventoryLevelsStep(inventoryAdjustment) parallelize( registerOrderFulfillmentStep(registerOrderFulfillmentData), createRemoteLinkStep(link), updateReservationsStep(toUpdate), - deleteReservationsStep(toDelete), - adjustInventoryLevelsStep(inventoryAdjustment) + deleteReservationsStep(toDelete) ) // trigger event OrderModuleService.Events.FULFILLMENT_CREATED diff --git a/packages/core/core-flows/src/order/workflows/create-shipment.ts b/packages/core/core-flows/src/order/workflows/create-shipment.ts index db470d68dd..42c8e34202 100644 --- a/packages/core/core-flows/src/order/workflows/create-shipment.ts +++ b/packages/core/core-flows/src/order/workflows/create-shipment.ts @@ -92,7 +92,7 @@ export const createOrderShipmentWorkflow = createWorkflow( const fulfillmentData = transform({ input }, ({ input }) => { return { id: input.fulfillment_id, - labels: input.labels, + labels: input.labels ?? [], } }) diff --git a/packages/core/core-flows/src/order/workflows/return/create-complete-return.ts b/packages/core/core-flows/src/order/workflows/return/create-complete-return.ts index 5bb319e65f..8012431d11 100644 --- a/packages/core/core-flows/src/order/workflows/return/create-complete-return.ts +++ b/packages/core/core-flows/src/order/workflows/return/create-complete-return.ts @@ -161,7 +161,7 @@ function prepareFulfillmentData({ provider_id: returnShippingOption.provider_id, shipping_option_id: input.return_shipping?.option_id, items: fulfillmentItems, - labels: [] as FulfillmentWorkflow.CreateFulfillmentLabelWorkflowDTO[], + labels: input.return_shipping?.labels ?? [], delivery_address: order.shipping_address ?? ({} as any), // TODO: should it be the stock location address? order: order, }, diff --git a/packages/core/types/src/inventory/common/reservation-item.ts b/packages/core/types/src/inventory/common/reservation-item.ts index 55200791e4..da12015413 100644 --- a/packages/core/types/src/inventory/common/reservation-item.ts +++ b/packages/core/types/src/inventory/common/reservation-item.ts @@ -2,6 +2,7 @@ import { NumericalComparisonOperator, StringComparisonOperator, } from "../../common" +import { BigNumberInput } from "../../totals/big-number" /** * The reservation item details. @@ -25,7 +26,7 @@ export interface ReservationItemDTO { /** * The quantity of the reservation item. */ - quantity: number + quantity: BigNumberInput /** * The associated line item's ID. diff --git a/packages/core/types/src/inventory/mutations/inventory-level.ts b/packages/core/types/src/inventory/mutations/inventory-level.ts index 088c494ed7..797d82ed46 100644 --- a/packages/core/types/src/inventory/mutations/inventory-level.ts +++ b/packages/core/types/src/inventory/mutations/inventory-level.ts @@ -1,3 +1,5 @@ +import { BigNumberInput } from "../../totals/big-number" + export interface CreateInventoryLevelInput { /** * The ID of the associated inventory item. @@ -66,5 +68,5 @@ export type BulkAdjustInventoryLevelInput = { /** * The quantity to adjust the inventory level by. */ - adjustment: number // TODO: BigNumberInput + adjustment: BigNumberInput } & UpdateInventoryLevelInput diff --git a/packages/core/types/src/inventory/mutations/reservation-item.ts b/packages/core/types/src/inventory/mutations/reservation-item.ts index d7eecb5be5..35ab01d3f5 100644 --- a/packages/core/types/src/inventory/mutations/reservation-item.ts +++ b/packages/core/types/src/inventory/mutations/reservation-item.ts @@ -1,3 +1,5 @@ +import { BigNumberInput } from "../../totals" + /** * @interface * @@ -8,7 +10,7 @@ export interface UpdateReservationItemInput { /** * The reserved quantity. */ - quantity?: number + quantity?: BigNumberInput /** * The ID of the associated location. */ @@ -48,7 +50,7 @@ export interface CreateReservationItemInput { /** * The reserved quantity. */ - quantity: number + quantity: BigNumberInput /** * Allow backorder of the item. If true, it won't check inventory levels before reserving it. */ diff --git a/packages/core/types/src/inventory/service.ts b/packages/core/types/src/inventory/service.ts index 1f0ac5a4e2..1aa3246fb0 100644 --- a/packages/core/types/src/inventory/service.ts +++ b/packages/core/types/src/inventory/service.ts @@ -3,6 +3,7 @@ import { RestoreReturn, SoftDeleteReturn } from "../dal" import { FindConfig } from "../common" import { IModuleService } from "../modules-sdk" import { Context } from "../shared-context" +import { BigNumberInput, IBigNumber } from "../totals" import { FilterableInventoryItemProps, FilterableInventoryLevelProps, @@ -1044,7 +1045,7 @@ export interface IInventoryService extends IModuleService { data: { inventoryItemId: string locationId: string - adjustment: number + adjustment: BigNumberInput }[], context?: Context ): Promise @@ -1052,7 +1053,7 @@ export interface IInventoryService extends IModuleService { adjustInventory( inventoryItemId: string, locationId: string, - adjustment: number, + adjustment: BigNumberInput, context?: Context ): Promise @@ -1076,7 +1077,7 @@ export interface IInventoryService extends IModuleService { confirmInventory( inventoryItemId: string, locationIds: string[], - quantity: number, + quantity: BigNumberInput, context?: Context ): Promise @@ -1086,7 +1087,7 @@ export interface IInventoryService extends IModuleService { * @param {string} inventoryItemId - The inventory item's ID. * @param {string[]} locationIds - The locations' IDs. * @param {Context} context - A context used to share resources, such as transaction manager, between the application and the module. - * @returns {Promise} The available quantity of the item. + * @returns {Promise} The available quantity of the item. * * @example * const availableQuantity = @@ -1099,7 +1100,7 @@ export interface IInventoryService extends IModuleService { inventoryItemId: string, locationIds: string[], context?: Context - ): Promise + ): Promise /** * This method retrieves the stocked quantity of an inventory item in the specified location. @@ -1107,7 +1108,7 @@ export interface IInventoryService extends IModuleService { * @param {string} inventoryItemId - The inventory item's ID. * @param {string[]} locationIds - The locations' IDs. * @param {Context} context - A context used to share resources, such as transaction manager, between the application and the module. - * @returns {Promise} The stocked quantity of the item. + * @returns {Promise} The stocked quantity of the item. * * @example * const stockedQuantity = @@ -1120,7 +1121,7 @@ export interface IInventoryService extends IModuleService { inventoryItemId: string, locationIds: string[], context?: Context - ): Promise + ): Promise /** * This method retrieves the reserved quantity of an inventory item in the specified location. @@ -1128,7 +1129,7 @@ export interface IInventoryService extends IModuleService { * @param {string} inventoryItemId - The inventory item's ID. * @param {string[]} locationIds - The locations' IDs. * @param {Context} context - A context used to share resources, such as transaction manager, between the application and the module. - * @returns {Promise} The reserved quantity of the item. + * @returns {Promise} The reserved quantity of the item. * * @example * const reservedQuantity = @@ -1141,5 +1142,5 @@ export interface IInventoryService extends IModuleService { inventoryItemId: string, locationIds: string[], context?: Context - ): Promise + ): Promise } diff --git a/packages/core/types/src/totals/big-number.ts b/packages/core/types/src/totals/big-number.ts index 972aac8c1f..06460512df 100644 --- a/packages/core/types/src/totals/big-number.ts +++ b/packages/core/types/src/totals/big-number.ts @@ -1,10 +1,24 @@ import BigNumberJS from "bignumber.js" +export interface IBigNumber { + numeric: number + raw?: BigNumberRawValue + bigNumber?: BigNumberJS + + toJSON(): number + valueOf(): number +} + export type BigNumberRawValue = { value: string | number [key: string]: unknown } -export type BigNumberInput = BigNumberRawValue | number | string | BigNumberJS +export type BigNumberInput = + | BigNumberRawValue + | number + | string + | BigNumberJS + | IBigNumber -export type BigNumberValue = BigNumberJS | number | string +export type BigNumberValue = BigNumberJS | number | string | IBigNumber diff --git a/packages/core/types/src/workflow/order/create-fulfillment.ts b/packages/core/types/src/workflow/order/create-fulfillment.ts index 767c5385fe..790573706c 100644 --- a/packages/core/types/src/workflow/order/create-fulfillment.ts +++ b/packages/core/types/src/workflow/order/create-fulfillment.ts @@ -1,4 +1,5 @@ import { BigNumberInput } from "../../totals" +import { CreateFulfillmentLabelWorkflowDTO } from "../fulfillment/create-fulfillment" interface CreateOrderFulfillmentItem { id: string @@ -9,6 +10,7 @@ export interface CreateOrderFulfillmentWorkflowInput { order_id: string created_by?: string // The id of the authenticated user items: CreateOrderFulfillmentItem[] + labels?: CreateFulfillmentLabelWorkflowDTO[] no_notification?: boolean location_id?: string | null metadata?: Record | null diff --git a/packages/core/types/src/workflow/order/create-return-order.ts b/packages/core/types/src/workflow/order/create-return-order.ts index 41fc98821d..6d374e710b 100644 --- a/packages/core/types/src/workflow/order/create-return-order.ts +++ b/packages/core/types/src/workflow/order/create-return-order.ts @@ -1,4 +1,5 @@ import { BigNumberInput } from "../../totals" +import { CreateFulfillmentLabelWorkflowDTO } from "../fulfillment/create-fulfillment" export interface CreateReturnItem { id: string @@ -16,6 +17,7 @@ export interface CreateOrderReturnWorkflowInput { return_shipping?: { option_id: string price?: number + labels?: CreateFulfillmentLabelWorkflowDTO[] } note?: string | null receive_now?: boolean diff --git a/packages/core/types/src/workflow/order/create-shipment.ts b/packages/core/types/src/workflow/order/create-shipment.ts index 0cd002e17b..024e107489 100644 --- a/packages/core/types/src/workflow/order/create-shipment.ts +++ b/packages/core/types/src/workflow/order/create-shipment.ts @@ -12,7 +12,7 @@ export interface CreateOrderShipmentWorkflowInput { fulfillment_id: string created_by?: string // The id of the authenticated user items: CreateOrderShipmentItem[] - labels: CreateFulfillmentLabelWorkflowDTO[] + labels?: CreateFulfillmentLabelWorkflowDTO[] no_notification?: boolean metadata?: MetadataType } diff --git a/packages/core/utils/src/totals/big-number.ts b/packages/core/utils/src/totals/big-number.ts index 87346c32b4..1defd1b2c8 100644 --- a/packages/core/utils/src/totals/big-number.ts +++ b/packages/core/utils/src/totals/big-number.ts @@ -1,8 +1,8 @@ -import { BigNumberInput, BigNumberRawValue } from "@medusajs/types" +import { BigNumberInput, BigNumberRawValue, IBigNumber } from "@medusajs/types" import { BigNumber as BigNumberJS } from "bignumber.js" import { isBigNumber, isString } from "../common" -export class BigNumber { +export class BigNumber implements IBigNumber { static DEFAULT_PRECISION = 20 private numeric_: number @@ -110,7 +110,7 @@ export class BigNumber { this.bignumber_ = newValue.bignumber_ } - toJSON() { + toJSON(): number { return this.bignumber_ ? this.bignumber_?.toNumber() : this.raw_ @@ -118,7 +118,7 @@ export class BigNumber { : this.numeric_ } - valueOf() { + valueOf(): number { return this.numeric_ } } diff --git a/packages/modules/inventory-next/integration-tests/__tests__/inventory-module-service.spec.ts b/packages/modules/inventory-next/integration-tests/__tests__/inventory-module-service.spec.ts index 7af965cfdf..d4ab993443 100644 --- a/packages/modules/inventory-next/integration-tests/__tests__/inventory-module-service.spec.ts +++ b/packages/modules/inventory-next/integration-tests/__tests__/inventory-module-service.spec.ts @@ -1,6 +1,6 @@ import { IInventoryService, InventoryItemDTO } from "@medusajs/types" +import { BigNumber, Module, Modules } from "@medusajs/utils" import { moduleIntegrationTestRunner } from "medusa-test-utils" -import { Module, Modules } from "@medusajs/utils" import { InventoryModuleService } from "../../src/services" jest.setTimeout(100000) @@ -882,7 +882,7 @@ moduleIntegrationTestRunner({ ["location-1", "location-2"] ) - expect(level).toEqual(6) + expect(level).toEqual(new BigNumber(6)) }) }) @@ -930,7 +930,7 @@ moduleIntegrationTestRunner({ ["location-1", "location-2"] ) - expect(stockedQuantity).toEqual(8) + expect(stockedQuantity.valueOf()).toEqual(8) }) }) @@ -991,12 +991,12 @@ moduleIntegrationTestRunner({ }) it("retrieves reserved quantity", async () => { - const reservedQuantity = await service.retrieveReservedQuantity( + const reservedQuantity = (await service.retrieveReservedQuantity( inventoryItem.id, ["location-1", "location-2"] - ) + )) as any - expect(reservedQuantity).toEqual(2) + expect(reservedQuantity + 0).toEqual(2) }) }) diff --git a/packages/modules/inventory-next/src/migrations/Migration20240307132720.ts b/packages/modules/inventory-next/src/migrations/Migration20240307132720.ts index 3779cdb793..7cafc79716 100644 --- a/packages/modules/inventory-next/src/migrations/Migration20240307132720.ts +++ b/packages/modules/inventory-next/src/migrations/Migration20240307132720.ts @@ -24,9 +24,6 @@ export class Migration20240307132720 extends Migration { this.addSql( 'CREATE INDEX IF NOT EXISTS "IDX_inventory_level_location_id" ON "inventory_level" (location_id);' ) - this.addSql( - 'CREATE INDEX IF NOT EXISTS "IDX_inventory_level_location_id" ON "inventory_level" (location_id);' - ) this.addSql( 'create table if not exists "reservation_item" ("id" text not null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, "line_item_id" text null, "location_id" text not null, "quantity" integer not null, "external_id" text null, "description" text null, "created_by" text null, "metadata" jsonb null, "inventory_item_id" text not null, constraint "reservation_item_pkey" primary key ("id"));' diff --git a/packages/modules/inventory-next/src/migrations/Migration20240719123015.ts b/packages/modules/inventory-next/src/migrations/Migration20240719123015.ts new file mode 100644 index 0000000000..01ceffa875 --- /dev/null +++ b/packages/modules/inventory-next/src/migrations/Migration20240719123015.ts @@ -0,0 +1,73 @@ +import { Migration } from "@mikro-orm/migrations" + +export class Migration20240719123015 extends Migration { + async up(): Promise { + this.addSql( + ` + ALTER TABLE "reservation_item" ALTER COLUMN "quantity" TYPE numeric; + ALTER TABLE "reservation_item" ADD COLUMN IF NOT EXISTS "raw_quantity" JSONB NULL; + + ALTER TABLE "inventory_level" ALTER COLUMN "stocked_quantity" TYPE numeric; + ALTER TABLE "inventory_level" ADD COLUMN IF NOT EXISTS "raw_stocked_quantity" JSONB NULL; + + ALTER TABLE "inventory_level" ALTER COLUMN "reserved_quantity" TYPE numeric; + ALTER TABLE "inventory_level" ADD COLUMN IF NOT EXISTS "raw_reserved_quantity" JSONB NULL; + + ALTER TABLE "inventory_level" ALTER COLUMN "incoming_quantity" TYPE numeric; + ALTER TABLE "inventory_level" ADD COLUMN IF NOT EXISTS "raw_incoming_quantity" JSONB NULL; + + + DROP INDEX IF EXISTS "IDX_inventory_item_sku_unique"; + DROP INDEX IF EXISTS "IDX_inventory_level_inventory_item_id"; + DROP INDEX IF EXISTS "IDX_inventory_level_location_id"; + DROP INDEX IF EXISTS "IDX_reservation_item_line_item_id"; + DROP INDEX IF EXISTS "IDX_reservation_item_location_id"; + DROP INDEX IF EXISTS "IDX_reservation_item_inventory_item_id"; + + CREATE UNIQUE INDEX IF NOT EXISTS "IDX_inventory_item_sku_unique" ON "inventory_item" (sku) WHERE deleted_at IS NULL; + CREATE INDEX IF NOT EXISTS "IDX_inventory_level_inventory_item_id" ON "inventory_level" (inventory_item_id) WHERE deleted_at IS NULL; + CREATE INDEX IF NOT EXISTS "IDX_inventory_level_location_id" ON "inventory_level" (location_id) WHERE deleted_at IS NULL; + CREATE INDEX IF NOT EXISTS "IDX_reservation_item_line_item_id" ON "reservation_item" (line_item_id) WHERE deleted_at IS NULL; + CREATE INDEX IF NOT EXISTS "IDX_reservation_item_location_id" ON "reservation_item" (location_id) WHERE deleted_at IS NULL; + CREATE INDEX IF NOT EXISTS "IDX_reservation_item_inventory_item_id" ON "reservation_item" (inventory_item_id) WHERE deleted_at IS NULL; + + CREATE UNIQUE INDEX "IDX_inventory_level_item_location" ON "inventory_level" (inventory_item_id, location_id) WHERE deleted_at IS NULL; + ` + ) + } + + async down(): Promise { + this.addSql( + ` + ALTER TABLE "reservation_item" ALTER COLUMN "quantity" TYPE integer; + ALTER TABLE "reservation_item" DROP COLUMN IF EXISTS "raw_quantity"; + + ALTER TABLE "inventory_level" ALTER COLUMN "stocked_quantity" TYPE integer; + ALTER TABLE "inventory_level" DROP COLUMN IF NOT EXISTS "raw_stocked_quantity"; + + ALTER TABLE "inventory_level" ALTER COLUMN "reserved_quantity" TYPE integer; + ALTER TABLE "inventory_level" DROP COLUMN IF NOT EXISTS "raw_reserved_quantity"; + + ALTER TABLE "inventory_level" ALTER COLUMN "incoming_quantity" TYPE integer; + ALTER TABLE "inventory_level" DROP COLUMN IF NOT EXISTS "raw_incoming_quantity"; + + + DROP INDEX IF EXISTS "IDX_inventory_item_sku_unique"; + DROP INDEX IF EXISTS "IDX_inventory_level_inventory_item_id"; + DROP INDEX IF EXISTS "IDX_inventory_level_location_id"; + DROP INDEX IF EXISTS "IDX_reservation_item_line_item_id"; + DROP INDEX IF EXISTS "IDX_reservation_item_location_id"; + DROP INDEX IF EXISTS "IDX_reservation_item_inventory_item_id"; + + CREATE UNIQUE INDEX IF NOT EXISTS "IDX_inventory_item_sku_unique" ON "inventory_item" (sku); + CREATE INDEX IF NOT EXISTS "IDX_inventory_level_inventory_item_id" ON "inventory_level" (inventory_item_id); + CREATE INDEX IF NOT EXISTS "IDX_inventory_level_location_id" ON "inventory_level" (location_id); + CREATE INDEX IF NOT EXISTS "IDX_reservation_item_line_item_id" ON "reservation_item" (line_item_id); + CREATE INDEX IF NOT EXISTS "IDX_reservation_item_location_id" ON "reservation_item" (location_id); + CREATE INDEX IF NOT EXISTS "IDX_reservation_item_inventory_item_id" ON "reservation_item" (inventory_item_id); + + DROP INDEX IF EXISTS "IDX_inventory_level_item_location" + ` + ) + } +} diff --git a/packages/modules/inventory-next/src/models/inventory-item.ts b/packages/modules/inventory-next/src/models/inventory-item.ts index 71bbccd433..1bb49d9103 100644 --- a/packages/modules/inventory-next/src/models/inventory-item.ts +++ b/packages/modules/inventory-next/src/models/inventory-item.ts @@ -32,6 +32,7 @@ const InventoryItemSkuIndex = createPsqlIndexStatementHelper({ tableName: "inventory_item", columns: "sku", unique: true, + where: "deleted_at IS NULL", }) type InventoryItemOptionalProps = DAL.SoftDeletableModelDateColumns diff --git a/packages/modules/inventory-next/src/models/inventory-level.ts b/packages/modules/inventory-next/src/models/inventory-level.ts index 5ac9a71ee9..5169151bc0 100644 --- a/packages/modules/inventory-next/src/models/inventory-level.ts +++ b/packages/modules/inventory-next/src/models/inventory-level.ts @@ -1,4 +1,4 @@ -import { DALUtils, isDefined } from "@medusajs/utils" +import { DALUtils, isDefined, MathBN } from "@medusajs/utils" import { BeforeCreate, Entity, @@ -11,9 +11,12 @@ import { Rel, } from "@mikro-orm/core" +import { BigNumberRawValue } from "@medusajs/types" import { + BigNumber, createPsqlIndexStatementHelper, generateEntityId, + MikroOrmBigNumberProperty, } from "@medusajs/utils" import { InventoryItem } from "./inventory-item" @@ -26,17 +29,21 @@ const InventoryLevelDeletedAtIndex = createPsqlIndexStatementHelper({ const InventoryLevelInventoryItemIdIndex = createPsqlIndexStatementHelper({ tableName: "inventory_level", columns: "inventory_item_id", + where: "deleted_at IS NULL", }) const InventoryLevelLocationIdIndex = createPsqlIndexStatementHelper({ tableName: "inventory_level", columns: "location_id", + where: "deleted_at IS NULL", }) const InventoryLevelLocationIdInventoryItemIdIndex = createPsqlIndexStatementHelper({ tableName: "inventory_level", - columns: "location_id", + columns: ["inventory_item_id", "location_id"], + unique: true, + where: "deleted_at IS NULL", }) @Entity() @@ -78,14 +85,23 @@ export class InventoryLevel { @Property({ type: "text" }) location_id: string - @Property({ type: "int" }) - stocked_quantity: number = 0 + @MikroOrmBigNumberProperty() + stocked_quantity: BigNumber | number = 0 - @Property({ type: "int" }) - reserved_quantity: number = 0 + @Property({ columnType: "jsonb" }) + raw_stocked_quantity: BigNumberRawValue - @Property({ type: "int" }) - incoming_quantity: number = 0 + @MikroOrmBigNumberProperty() + reserved_quantity: BigNumber | number = 0 + + @Property({ columnType: "jsonb" }) + raw_reserved_quantity: BigNumberRawValue + + @MikroOrmBigNumberProperty() + incoming_quantity: BigNumber | number = 0 + + @Property({ columnType: "jsonb" }) + raw_incoming_quantity: BigNumberRawValue @Property({ columnType: "jsonb", nullable: true }) metadata: Record | null @@ -95,7 +111,7 @@ export class InventoryLevel { }) inventory_item: Rel - available_quantity: number | null = null + available_quantity: BigNumber | number | null = null @BeforeCreate() private beforeCreate(): void { @@ -111,7 +127,9 @@ export class InventoryLevel { @OnLoad() private onLoad(): void { if (isDefined(this.stocked_quantity) && isDefined(this.reserved_quantity)) { - this.available_quantity = this.stocked_quantity - this.reserved_quantity + this.available_quantity = new BigNumber( + MathBN.sub(this.raw_stocked_quantity, this.raw_reserved_quantity) + ) } } } diff --git a/packages/modules/inventory-next/src/models/reservation-item.ts b/packages/modules/inventory-next/src/models/reservation-item.ts index fa352e1e24..e2961ec572 100644 --- a/packages/modules/inventory-next/src/models/reservation-item.ts +++ b/packages/modules/inventory-next/src/models/reservation-item.ts @@ -9,8 +9,11 @@ import { Rel, } from "@mikro-orm/core" +import { BigNumberRawValue } from "@medusajs/types" import { + BigNumber, DALUtils, + MikroOrmBigNumberProperty, createPsqlIndexStatementHelper, generateEntityId, } from "@medusajs/utils" @@ -24,16 +27,19 @@ const ReservationItemDeletedAtIndex = createPsqlIndexStatementHelper({ const ReservationItemLineItemIdIndex = createPsqlIndexStatementHelper({ tableName: "reservation_item", columns: "line_item_id", + where: "deleted_at IS NULL", }) const ReservationItemInventoryItemIdIndex = createPsqlIndexStatementHelper({ tableName: "reservation_item", columns: "inventory_item_id", + where: "deleted_at IS NULL", }) const ReservationItemLocationIdIndex = createPsqlIndexStatementHelper({ tableName: "reservation_item", columns: "location_id", + where: "deleted_at IS NULL", }) @Entity() @@ -72,8 +78,11 @@ export class ReservationItem { @Property({ type: "text" }) location_id: string - @Property({ columnType: "integer" }) - quantity: number + @MikroOrmBigNumberProperty() + quantity: BigNumber | number + + @Property({ columnType: "jsonb" }) + raw_quantity: BigNumberRawValue @Property({ type: "text", nullable: true }) external_id: string | null = null diff --git a/packages/modules/inventory-next/src/repositories/inventory-level.ts b/packages/modules/inventory-next/src/repositories/inventory-level.ts index e92437b899..eb9fbc7ade 100644 --- a/packages/modules/inventory-next/src/repositories/inventory-level.ts +++ b/packages/modules/inventory-next/src/repositories/inventory-level.ts @@ -1,7 +1,11 @@ import { Context } from "@medusajs/types" -import { InventoryLevel } from "@models" +import { + BigNumber, + MathBN, + mikroOrmBaseRepositoryFactory, +} from "@medusajs/utils" import { SqlEntityManager } from "@mikro-orm/postgresql" -import { mikroOrmBaseRepositoryFactory } from "@medusajs/utils" +import { InventoryLevel } from "@models" export class InventoryLevelRepository extends mikroOrmBaseRepositoryFactory( InventoryLevel @@ -10,42 +14,42 @@ export class InventoryLevelRepository extends mikroOrmBaseRepositoryFactory( inventoryItemId: string, locationIds: string[], context: Context = {} - ): Promise { + ): Promise { const manager = super.getActiveManager(context) - const [result] = (await manager + const result = await manager .getKnex()({ il: "inventory_level" }) - .sum("reserved_quantity") + .select("raw_reserved_quantity") .whereIn("location_id", locationIds) - .andWhere("inventory_item_id", inventoryItemId)) as { - sum: string - }[] + .andWhere("inventory_item_id", inventoryItemId) + .andWhereRaw("deleted_at IS NULL") - return parseInt(result.sum) + return new BigNumber( + MathBN.sum(...result.map((r) => r.raw_reserved_quantity)) + ) } async getAvailableQuantity( inventoryItemId: string, locationIds: string[], context: Context = {} - ): Promise { + ): Promise { const knex = super.getActiveManager(context).getKnex() - const [result] = (await knex({ + const result = await knex({ il: "inventory_level", }) - .sum({ - stocked_quantity: "stocked_quantity", - reserved_quantity: "reserved_quantity", - }) + .select("raw_stocked_quantity", "raw_reserved_quantity") .whereIn("location_id", locationIds) - .andWhere("inventory_item_id", inventoryItemId)) as { - reserved_quantity: string - stocked_quantity: string - }[] + .andWhere("inventory_item_id", inventoryItemId) + .andWhereRaw("deleted_at IS NULL") - return ( - parseInt(result.stocked_quantity) - parseInt(result.reserved_quantity) + return new BigNumber( + MathBN.sum( + ...result.map((r) => { + return MathBN.sub(r.raw_stocked_quantity, r.raw_reserved_quantity) + }) + ) ) } @@ -53,20 +57,19 @@ export class InventoryLevelRepository extends mikroOrmBaseRepositoryFactory( inventoryItemId: string, locationIds: string[], context: Context = {} - ): Promise { + ): Promise { const knex = super.getActiveManager(context).getKnex() - const [result] = (await knex({ + const result = await knex({ il: "inventory_level", }) - .sum({ - stocked_quantity: "stocked_quantity", - }) + .select("raw_stocked_quantity") .whereIn("location_id", locationIds) - .andWhere("inventory_item_id", inventoryItemId)) as { - stocked_quantity: string - }[] + .andWhere("inventory_item_id", inventoryItemId) + .andWhereRaw("deleted_at IS NULL") - return parseInt(result.stocked_quantity) + return new BigNumber( + MathBN.sum(...result.map((r) => r.raw_stocked_quantity)) + ) } } diff --git a/packages/modules/inventory-next/src/services/inventory-level.ts b/packages/modules/inventory-next/src/services/inventory-level.ts index c39ced5fc2..225f8bc7a2 100644 --- a/packages/modules/inventory-next/src/services/inventory-level.ts +++ b/packages/modules/inventory-next/src/services/inventory-level.ts @@ -1,5 +1,5 @@ import { Context } from "@medusajs/types" -import { ModulesSdkUtils } from "@medusajs/utils" +import { BigNumber, ModulesSdkUtils } from "@medusajs/utils" import { InventoryLevelRepository } from "@repositories" import { InventoryLevel } from "../models/inventory-level" @@ -23,7 +23,7 @@ export default class InventoryLevelService extends ModulesSdkUtils.MedusaInterna inventoryItemId: string, locationIds: string[] | string, context: Context = {} - ): Promise { + ): Promise { const locationIdArray = Array.isArray(locationIds) ? locationIds : [locationIds] @@ -39,7 +39,7 @@ export default class InventoryLevelService extends ModulesSdkUtils.MedusaInterna inventoryItemId: string, locationIds: string[] | string, context: Context = {} - ): Promise { + ): Promise { const locationIdArray = Array.isArray(locationIds) ? locationIds : [locationIds] diff --git a/packages/modules/inventory-next/src/services/inventory-module.ts b/packages/modules/inventory-next/src/services/inventory-module.ts index 65452e760c..1f5261c1c0 100644 --- a/packages/modules/inventory-next/src/services/inventory-module.ts +++ b/packages/modules/inventory-next/src/services/inventory-module.ts @@ -1,5 +1,6 @@ import { InternalModuleDeclaration } from "@medusajs/modules-sdk" import { + BigNumberInput, Context, DAL, InventoryTypes, @@ -11,19 +12,20 @@ import { } from "@medusajs/types" import { IInventoryService } from "@medusajs/types/dist/inventory" import { - arrayDifference, + BigNumber, CommonEvents, EmitEvents, InjectManager, InjectTransactionManager, InventoryEvents, - isDefined, - isString, + MathBN, MedusaContext, MedusaError, MedusaService, + arrayDifference, + isDefined, + isString, partitionArray, - promiseAll, } from "@medusajs/utils" import { InventoryItem, InventoryLevel, ReservationItem } from "@models" import { joinerConfig } from "../joiner-config" @@ -36,6 +38,14 @@ type InjectedDependencies = { reservationItemService: ModulesSdkTypes.IMedusaInternalService } +type InventoryItemCheckLevel = { + id?: string + location_id: string + inventory_item_id: string + quantity?: BigNumberInput + allow_backorder?: boolean +} + export default class InventoryModuleService extends MedusaService<{ InventoryItem: { @@ -84,14 +94,23 @@ export default class InventoryModuleService } private async ensureInventoryLevels( - data: ( - | { location_id: string; inventory_item_id: string } - | { id: string } - )[], - context: Context + 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, + data_, ({ id }) => !!id ) as [ { id: string }[], @@ -122,13 +141,14 @@ export default class InventoryModuleService return acc }, new Map()) - const missing = data.filter((i) => { - if ("id" in i) { - return !inventoryLevelIdMap.has(i.id) + const missing = data.filter((item) => { + if (item.id) { + return !inventoryLevelIdMap.has(item.id!) } + return !inventoryLevelItemLocationMap - .get(i.inventory_item_id) - ?.has(i.location_id) + .get(item.inventory_item_id) + ?.has(item.location_id) }) if (missing.length) { @@ -144,6 +164,27 @@ export default class InventoryModuleService 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 } @@ -151,7 +192,7 @@ export default class InventoryModuleService // We sanitize the inputs here to prevent that from being used to update it private sanitizeInventoryLevelInput( input: (TDTO & { - reserved_quantity?: number + reserved_quantity?: BigNumberInput })[] ): TDTO[] { return input.map((input) => { @@ -173,37 +214,6 @@ export default class InventoryModuleService }) } - private async ensureInventoryAvailability( - data: { - allow_backorder: boolean - inventory_item_id: string - location_id: string - quantity: number - }[], - context: Context - ) { - const checkLevels = data.map(async (reservation) => { - if (!!reservation.allow_backorder) { - return - } - - const available = await this.retrieveAvailableQuantity( - reservation.inventory_item_id, - [reservation.location_id], - context - ) - - if (available < reservation.quantity) { - throw new MedusaError( - MedusaError.Types.NOT_ALLOWED, - `Not enough stock available for item ${reservation.inventory_item_id} at location ${reservation.location_id}` - ) - } - }) - - await promiseAll(checkLevels) - } - // @ts-ignore async createReservationItems( input: InventoryTypes.CreateReservationItemInput[], @@ -225,13 +235,6 @@ export default class InventoryModuleService InventoryTypes.ReservationItemDTO[] | InventoryTypes.ReservationItemDTO > { const toCreate = Array.isArray(input) ? input : [input] - const sanitized = toCreate.map((d) => ({ - ...d, - allow_backorder: d.allow_backorder || false, - })) - - await this.ensureInventoryAvailability(sanitized, context) - const created = await this.createReservationItems_(toCreate, context) context.messageAggregator?.saveRawMessageData( @@ -262,10 +265,17 @@ export default class InventoryModuleService @MedusaContext() context: Context = {} ): Promise { const inventoryLevels = await this.ensureInventoryLevels( - input.map(({ location_id, inventory_item_id }) => ({ - location_id, - inventory_item_id, - })), + 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) @@ -275,7 +285,7 @@ export default class InventoryModuleService const locationMap = acc.get(curr.inventory_item_id) ?? new Map() const adjustment = locationMap.get(curr.location_id) ?? 0 - locationMap.set(curr.location_id, adjustment + curr.quantity) + locationMap.set(curr.location_id, MathBN.add(adjustment, curr.quantity)) acc.set(curr.inventory_item_id, locationMap) return acc @@ -294,7 +304,7 @@ export default class InventoryModuleService return { id: level.id, - reserved_quantity: level.reserved_quantity + adjustment, + reserved_quantity: MathBN.add(level.reserved_quantity, adjustment), } }) @@ -581,6 +591,7 @@ export default class InventoryModuleService location_id, inventory_item_id, })), + undefined, context ) @@ -682,21 +693,6 @@ export default class InventoryModuleService reservationItems.map((r) => [r.id, r]) ) - const availabilityData = input.map((data) => { - const reservation = reservationMap.get(data.id)! - - return { - ...data, - quantity: data.quantity ?? reservation.quantity, - allow_backorder: - data.allow_backorder || reservation.allow_backorder || false, - inventory_item_id: reservation.inventory_item_id, - location_id: data.location_id ?? reservation.location_id, - } - }) - - await this.ensureInventoryAvailability(availabilityData, context) - const adjustments: Map> = input.reduce( (acc, update) => { const reservation = reservationMap.get(update.id)! @@ -711,7 +707,7 @@ export default class InventoryModuleService locationMap.set( reservation.location_id, - reservationLocationAdjustment - reservation.quantity + MathBN.sub(reservationLocationAdjustment, reservation.quantity) ) const updateLocationAdjustment = @@ -719,18 +715,24 @@ export default class InventoryModuleService locationMap.set( update.location_id, - updateLocationAdjustment + (update.quantity || reservation.quantity) + MathBN.add( + updateLocationAdjustment, + update.quantity || reservation.quantity + ) ) } else if ( isDefined(update.quantity) && - update.quantity !== reservation.quantity + !MathBN.eq(update.quantity, reservation.quantity) ) { const locationAdjustment = locationMap.get(reservation.location_id) ?? 0 locationMap.set( reservation.location_id, - locationAdjustment + (update.quantity! - reservation.quantity) + MathBN.add( + locationAdjustment, + MathBN.sub(update.quantity!, reservation.quantity) + ) ) } @@ -740,17 +742,28 @@ export default class InventoryModuleService }, new Map() ) + const availabilityData = input.map((data) => { + const reservation = reservationMap.get(data.id)! - const result = await this.reservationItemService_.update(input, context) + return { + inventory_item_id: reservation.inventory_item_id, + location_id: data.location_id ?? reservation.location_id, + quantity: data.quantity ?? reservation.quantity, + allow_backorder: + data.allow_backorder || reservation.allow_backorder || false, + } + }) const inventoryLevels = await this.ensureInventoryLevels( - reservationItems.map((r) => ({ - inventory_item_id: r.inventory_item_id, - location_id: r.location_id, - })), + availabilityData, + { + validateQuantityAtLocation: true, + }, context ) + const result = await this.reservationItemService_.update(input, context) + const levelAdjustmentUpdates = inventoryLevels .map((level) => { const adjustment = adjustments @@ -763,7 +776,7 @@ export default class InventoryModuleService return { id: level.id, - reserved_quantity: level.reserved_quantity + adjustment, + reserved_quantity: MathBN.add(level.reserved_quantity, adjustment), } }) .filter(Boolean) @@ -932,7 +945,7 @@ export default class InventoryModuleService adjustInventory( inventoryItemId: string, locationId: string, - adjustment: number, + adjustment: BigNumberInput, context: Context ): Promise @@ -940,7 +953,7 @@ export default class InventoryModuleService data: { inventoryItemId: string locationId: string - adjustment: number + adjustment: BigNumberInput }[], context: Context ): Promise @@ -950,7 +963,7 @@ export default class InventoryModuleService async adjustInventory( inventoryItemIdOrData: string | any, locationId?: string | Context, - adjustment?: number, + adjustment?: BigNumberInput, @MedusaContext() context: Context = {} ): Promise< InventoryTypes.InventoryLevelDTO | InventoryTypes.InventoryLevelDTO[] @@ -1000,7 +1013,7 @@ export default class InventoryModuleService async adjustInventory_( inventoryItemId: string, locationId: string, - adjustment: number, + adjustment: BigNumberInput, @MedusaContext() context: Context = {} ): Promise { const inventoryLevel = await this.retrieveInventoryLevelByItemAndLocation( @@ -1012,7 +1025,10 @@ export default class InventoryModuleService const result = await this.inventoryLevelService_.update( { id: inventoryLevel.id, - stocked_quantity: inventoryLevel.stocked_quantity + adjustment, + stocked_quantity: MathBN.add( + inventoryLevel.stocked_quantity, + adjustment + ), }, context ) @@ -1026,9 +1042,9 @@ export default class InventoryModuleService locationId: string, @MedusaContext() context: Context = {} ): Promise { - const [inventoryLevel] = await this.listInventoryLevels( + const inventoryLevel = await this.listInventoryLevels( { inventory_item_id: inventoryItemId, location_id: locationId }, - { take: 1 }, + { take: null }, context ) @@ -1039,7 +1055,7 @@ export default class InventoryModuleService ) } - return inventoryLevel + return inventoryLevel[0] } /** @@ -1055,9 +1071,9 @@ export default class InventoryModuleService inventoryItemId: string, locationIds: string[], @MedusaContext() context: Context = {} - ): Promise { + ): Promise { if (locationIds.length === 0) { - return 0 + return new BigNumber(0) } await this.inventoryItemService_.retrieve( @@ -1091,9 +1107,9 @@ export default class InventoryModuleService inventoryItemId: string, locationIds: string[], @MedusaContext() context: Context = {} - ): Promise { + ): Promise { if (locationIds.length === 0) { - return 0 + return new BigNumber(0) } // Throws if item does not exist @@ -1128,7 +1144,7 @@ export default class InventoryModuleService inventoryItemId: string, locationIds: string[], @MedusaContext() context: Context = {} - ): Promise { + ): Promise { // Throws if item does not exist await this.inventoryItemService_.retrieve( inventoryItemId, @@ -1139,7 +1155,7 @@ export default class InventoryModuleService ) if (locationIds.length === 0) { - return 0 + return new BigNumber(0) } const reservedQuantity = @@ -1164,7 +1180,7 @@ export default class InventoryModuleService async confirmInventory( inventoryItemId: string, locationIds: string[], - quantity: number, + quantity: BigNumberInput, @MedusaContext() context: Context = {} ): Promise { const availableQuantity = await this.retrieveAvailableQuantity( @@ -1172,7 +1188,7 @@ export default class InventoryModuleService locationIds, context ) - return availableQuantity >= quantity + return MathBN.gte(availableQuantity, quantity) } private async adjustInventoryLevelsForReservationsDeletion( @@ -1208,6 +1224,7 @@ export default class InventoryModuleService inventory_item_id: r.inventory_item_id, location_id: r.location_id, })), + undefined, context ) @@ -1218,8 +1235,11 @@ export default class InventoryModuleService const inventoryLevelMap = acc.get(curr.inventory_item_id) ?? new Map() const adjustment = inventoryLevelMap.has(curr.location_id) - ? inventoryLevelMap.get(curr.location_id) + curr.quantity * multiplier - : curr.quantity * multiplier + ? 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) @@ -1237,7 +1257,7 @@ export default class InventoryModuleService return { id: level.id, - reserved_quantity: level.reserved_quantity + adjustment, + reserved_quantity: MathBN.add(level.reserved_quantity, adjustment), } })