chore(inventory, core-flows): big number support (#8204)
This commit is contained in:
@@ -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"));'
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import { Migration } from "@mikro-orm/migrations"
|
||||
|
||||
export class Migration20240719123015 extends Migration {
|
||||
async up(): Promise<void> {
|
||||
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<void> {
|
||||
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"
|
||||
`
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,7 @@ const InventoryItemSkuIndex = createPsqlIndexStatementHelper({
|
||||
tableName: "inventory_item",
|
||||
columns: "sku",
|
||||
unique: true,
|
||||
where: "deleted_at IS NULL",
|
||||
})
|
||||
|
||||
type InventoryItemOptionalProps = DAL.SoftDeletableModelDateColumns
|
||||
|
||||
@@ -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<string, unknown> | null
|
||||
@@ -95,7 +111,7 @@ export class InventoryLevel {
|
||||
})
|
||||
inventory_item: Rel<InventoryItem>
|
||||
|
||||
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)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<number> {
|
||||
): Promise<BigNumber> {
|
||||
const manager = super.getActiveManager<SqlEntityManager>(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<number> {
|
||||
): Promise<BigNumber> {
|
||||
const knex = super.getActiveManager<SqlEntityManager>(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<number> {
|
||||
): Promise<BigNumber> {
|
||||
const knex = super.getActiveManager<SqlEntityManager>(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))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<number> {
|
||||
): Promise<BigNumber> {
|
||||
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<number> {
|
||||
): Promise<BigNumber> {
|
||||
const locationIdArray = Array.isArray(locationIds)
|
||||
? locationIds
|
||||
: [locationIds]
|
||||
|
||||
@@ -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<any>
|
||||
}
|
||||
|
||||
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<InventoryTypes.InventoryLevelDTO[]> {
|
||||
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<TDTO = unknown>(
|
||||
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<ReservationItem[]> {
|
||||
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<string, Map<string, number>> = 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<InventoryTypes.InventoryLevelDTO>
|
||||
|
||||
@@ -940,7 +953,7 @@ export default class InventoryModuleService
|
||||
data: {
|
||||
inventoryItemId: string
|
||||
locationId: string
|
||||
adjustment: number
|
||||
adjustment: BigNumberInput
|
||||
}[],
|
||||
context: Context
|
||||
): Promise<InventoryTypes.InventoryLevelDTO[]>
|
||||
@@ -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<InventoryLevel> {
|
||||
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<InventoryTypes.InventoryLevelDTO> {
|
||||
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<number> {
|
||||
): Promise<BigNumber> {
|
||||
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<number> {
|
||||
): Promise<BigNumber> {
|
||||
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<number> {
|
||||
): Promise<BigNumber> {
|
||||
// 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<boolean> {
|
||||
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),
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user