Feat/bulk operations for inventory service (#4503)

* initial push

* bulk delete reservations by location ids

* add method to interface (not implemented yet)

* bulk update

* delete reservations by location id bulk

* add create bulk for inventory item

* refactor attach inventory item method

* add changeset

* verbose false

* method override instead of multiple methods

* change up method signature

* redo changes when updating interface

* update createInventoryLevel method

* rename variables

* fix feedback

* return correct string array when emitting event

* refactor inventory service

* redo order changes

* snapshot

* move prep methods
This commit is contained in:
Philip Korsholm authored and GitHub committed 2023-07-18 11:17:57 +02:00
1 parent d440c834d3
commit d184d23c63
20 files changed
+1194 -228

No files matched your search

@@ -1,13 +1,3 @@
import { IInventoryService, InventoryItemDTO } from "@medusajs/types"
import { MedusaError } from "@medusajs/utils"
import { EntityManager } from "typeorm"
import { ulid } from "ulid"
import { ProductVariant } from "../../../../../models"
import {
ProductVariantInventoryService,
ProductVariantService,
} from "../../../../../services"
import { CreateProductVariantInput } from "../../../../../types/product-variant"
import {
DistributedTransaction,
TransactionHandlerType,
@@ -16,6 +6,17 @@ import {
TransactionState,
TransactionStepsDefinition,
} from "../../../../../utils/transaction"
import { IInventoryService, InventoryItemDTO } from "@medusajs/types"
import {
ProductVariantInventoryService,
ProductVariantService,
} from "../../../../../services"
import { CreateProductVariantInput } from "../../../../../types/product-variant"
import { EntityManager } from "typeorm"
import { MedusaError } from "@medusajs/utils"
import { ProductVariant } from "../../../../../models"
import { ulid } from "ulid"
enum actions {
createVariants = "createVariants",
@@ -3,21 +3,22 @@ import {
ICacheService,
IEventBusService,
IInventoryService,
IStockLocationService,
InventoryItemDTO,
InventoryLevelDTO,
IStockLocationService,
ReservationItemDTO,
ReserveQuantityContext,
} from "@medusajs/types"
import { LineItem, Product, ProductVariant } from "../models"
import { isDefined, MedusaError } from "@medusajs/utils"
import { MedusaError, isDefined } from "@medusajs/utils"
import { PricedProduct, PricedVariant } from "../types/pricing"
import { TransactionBaseService } from "../interfaces"
import { ProductVariantInventoryItem } from "../models/product-variant-inventory-item"
import ProductVariantService from "./product-variant"
import SalesChannelInventoryService from "./sales-channel-inventory"
import SalesChannelLocationService from "./sales-channel-location"
import { TransactionBaseService } from "../interfaces"
import { getSetDifference } from "../utils/diff-set"
type InjectedDependencies = {
manager: EntityManager
@@ -253,61 +254,146 @@ class ProductVariantInventoryService extends TransactionBaseService {
* @param requiredQuantity quantity of variant to attach
* @returns the variant inventory item
*/
async attachInventoryItem(
attachments: {
variantId: string
inventoryItemId: string
requiredQuantity?: number
}[]
): Promise<ProductVariantInventoryItem[]>
async attachInventoryItem(
variantId: string,
inventoryItemId: string,
requiredQuantity?: number
): Promise<ProductVariantInventoryItem> {
): Promise<ProductVariantInventoryItem[]>
async attachInventoryItem(
variantIdOrAttachments:
| string
| {
variantId: string
inventoryItemId: string
requiredQuantity?: number
}[],
inventoryItemId?: string,
requiredQuantity?: number
): Promise<ProductVariantInventoryItem[]> {
const data = Array.isArray(variantIdOrAttachments)
? variantIdOrAttachments
: [
{
variantId: variantIdOrAttachments,
inventoryItemId: inventoryItemId!,
requiredQuantity,
},
]
const invalidDataEntries = data.filter(
(d) => typeof d.requiredQuantity === "number" && d.requiredQuantity < 1
)
if (invalidDataEntries.length) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`"requiredQuantity" must be greater than 0, the following entries are invalid: ${invalidDataEntries
.map((d) => JSON.stringify(d))
.join(", ")}`
)
}
// Verify that variant exists
await this.productVariantService_
const variants = await this.productVariantService_
.withTransaction(this.activeManager_)
.retrieve(variantId, {
select: ["id"],
.list({
id: data.map((d) => d.variantId),
})
const foundVariantIds = new Set(variants.map((v) => v.id))
const requestedVariantIds = new Set(data.map((v) => v.variantId))
if (foundVariantIds.size !== requestedVariantIds.size) {
const difference = getSetDifference(requestedVariantIds, foundVariantIds)
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
`Variants not found for the following ids: ${[...difference].join(
", "
)}`
)
}
// Verify that item exists
await this.inventoryService_.retrieveInventoryItem(
inventoryItemId,
const [inventoryItems] = await this.inventoryService_.listInventoryItems(
{
id: data.map((d) => d.inventoryItemId),
},
{
select: ["id"],
},
{ transactionManager: this.activeManager_ }
{
transactionManager: this.activeManager_,
}
)
const foundInventoryItemIds = new Set(inventoryItems.map((v) => v.id))
const requestedInventoryItemIds = new Set(
data.map((v) => v.inventoryItemId)
)
if (foundInventoryItemIds.size !== requestedInventoryItemIds.size) {
const difference = getSetDifference(
requestedInventoryItemIds,
foundInventoryItemIds
)
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
`Inventory items not found for the following ids: ${[
...difference,
].join(", ")}`
)
}
const variantInventoryRepo = this.activeManager_.getRepository(
ProductVariantInventoryItem
)
const existing = await variantInventoryRepo.findOne({
where: {
variant_id: variantId,
inventory_item_id: inventoryItemId,
const existingAttachments = await variantInventoryRepo.find({
where: data.map((d) => ({
variant_id: d.variantId,
inventory_item_id: d.inventoryItemId,
})),
})
const existingMap: Map<string, Set<string>> = existingAttachments.reduce(
(acc, curr) => {
const existingSet = acc.get(curr.variant_id) || new Set()
existingSet.add(curr.inventory_item_id)
acc.set(curr.variant_id, existingSet)
return acc
},
})
new Map()
)
if (existing) {
return existing
}
const toCreate = (
await Promise.all(
data.map(async (d) => {
if (existingMap.get(d.variantId)?.has(d.inventoryItemId)) {
return null
}
let quantityToStore = 1
if (typeof requiredQuantity !== "undefined") {
if (requiredQuantity < 1) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
"Quantity must be greater than 0"
)
} else {
quantityToStore = requiredQuantity
}
}
return variantInventoryRepo.create({
variant_id: d.variantId,
inventory_item_id: d.inventoryItemId,
required_quantity: d.requiredQuantity ?? 1,
})
})
)
).filter(
(
tc: ProductVariantInventoryItem | null
): tc is ProductVariantInventoryItem => !!tc
)
const variantInventory = variantInventoryRepo.create({
variant_id: variantId,
inventory_item_id: inventoryItemId,
required_quantity: quantityToStore,
})
return await variantInventoryRepo.save(variantInventory)
return await variantInventoryRepo.save(toCreate)
}
/**
@@ -411,22 +497,22 @@ class ProductVariantInventoryService extends TransactionBaseService {
locationId = locations[0].location_id
}
const reservationItems = await Promise.all(
variantInventory.map(async (inventoryPart) => {
const itemQuantity = inventoryPart.required_quantity * quantity
return await this.inventoryService_.createReservationItem(
{
const reservationItems =
await this.inventoryService_.createReservationItems(
variantInventory.map((inventoryPart) => {
const itemQuantity = inventoryPart.required_quantity * quantity
return {
...toReserve,
location_id: locationId as string,
inventory_item_id: inventoryPart.inventory_item_id,
quantity: itemQuantity,
},
moduleContext
)
})
)
}
}),
moduleContext
)
return reservationItems
return reservationItems.flat()
}
/**
+14
View File
@@ -0,0 +1,14 @@
export function getSetDifference<T>(
orignalSet: Set<T>,
compareSet: Set<T>
): Set<T> {
const difference = new Set<T>()
orignalSet.forEach((element) => {
if (!compareSet.has(element)) {
difference.add(element)
}
})
return difference
}