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

@@ -12,7 +12,7 @@ import {
MedusaContext,
MedusaError,
} from "@medusajs/utils"
import { DeepPartial, EntityManager, FindManyOptions } from "typeorm"
import { DeepPartial, EntityManager, FindManyOptions, In } from "typeorm"
import { InventoryItem } from "../models"
import { getListQuery } from "../utils/query"
import { buildQuery } from "../utils/build-query"
@@ -120,33 +120,35 @@ export default class InventoryItemService {
*/
@InjectEntityManager()
async create(
data: CreateInventoryItemInput,
data: CreateInventoryItemInput[],
@MedusaContext() context: SharedContext = {}
): Promise<InventoryItem> {
): Promise<InventoryItem[]> {
const manager = context.transactionManager!
const itemRepository = manager.getRepository(InventoryItem)
const inventoryItem = itemRepository.create({
sku: data.sku,
origin_country: data.origin_country,
metadata: data.metadata,
hs_code: data.hs_code,
mid_code: data.mid_code,
material: data.material,
weight: data.weight,
length: data.length,
height: data.height,
width: data.width,
requires_shipping: data.requires_shipping,
description: data.description,
thumbnail: data.thumbnail,
title: data.title,
})
const inventoryItem = itemRepository.create(
data.map((tc) => ({
sku: tc.sku,
origin_country: tc.origin_country,
metadata: tc.metadata,
hs_code: tc.hs_code,
mid_code: tc.mid_code,
material: tc.material,
weight: tc.weight,
length: tc.length,
height: tc.height,
width: tc.width,
requires_shipping: tc.requires_shipping,
description: tc.description,
thumbnail: tc.thumbnail,
title: tc.title,
}))
)
const result = await itemRepository.save(inventoryItem)
await this.eventBusService_?.emit?.(InventoryItemService.Events.CREATED, {
id: result.id,
ids: result.map((i) => i.id),
})
return result
@@ -195,16 +197,20 @@ export default class InventoryItemService {
*/
@InjectEntityManager()
async delete(
inventoryItemId: string,
inventoryItemId: string | string[],
@MedusaContext() context: SharedContext = {}
): Promise<void> {
const manager = context.transactionManager!
const itemRepository = manager.getRepository(InventoryItem)
await itemRepository.softRemove({ id: inventoryItemId })
const ids = Array.isArray(inventoryItemId)
? inventoryItemId
: [inventoryItemId]
await itemRepository.softDelete({ id: In(ids) })
await this.eventBusService_?.emit?.(InventoryItemService.Events.DELETED, {
id: inventoryItemId,
ids: inventoryItemId,
})
}
}
@@ -120,24 +120,28 @@ export default class InventoryLevelService {
*/
@InjectEntityManager()
async create(
data: CreateInventoryLevelInput,
data: CreateInventoryLevelInput[],
@MedusaContext() context: SharedContext = {}
): Promise<InventoryLevel> {
): Promise<InventoryLevel[]> {
const manager = context.transactionManager!
const toCreate = data.map((d) => {
return {
location_id: d.location_id,
inventory_item_id: d.inventory_item_id,
stocked_quantity: d.stocked_quantity,
reserved_quantity: d.reserved_quantity,
incoming_quantity: d.incoming_quantity,
}
})
const levelRepository = manager.getRepository(InventoryLevel)
const inventoryLevel = levelRepository.create({
location_id: data.location_id,
inventory_item_id: data.inventory_item_id,
stocked_quantity: data.stocked_quantity,
reserved_quantity: data.reserved_quantity,
incoming_quantity: data.incoming_quantity,
})
const inventoryLevels = levelRepository.create(toCreate)
const saved = await levelRepository.save(inventoryLevel)
const saved = await levelRepository.save(inventoryLevels)
await this.eventBusService_?.emit?.(InventoryLevelService.Events.CREATED, {
id: saved.id,
ids: saved.map((i) => i.id),
})
return saved
@@ -254,7 +258,7 @@ export default class InventoryLevelService {
await levelRepository.delete({ id: In(ids) })
await this.eventBusService_?.emit?.(InventoryLevelService.Events.DELETED, {
id: inventoryLevelId,
ids: inventoryLevelId,
})
}
@@ -265,16 +269,18 @@ export default class InventoryLevelService {
*/
@InjectEntityManager()
async deleteByLocationId(
locationId: string,
locationId: string | string[],
@MedusaContext() context: SharedContext = {}
): Promise<void> {
const manager = context.transactionManager!
const levelRepository = manager.getRepository(InventoryLevel)
await levelRepository.delete({ location_id: locationId })
const ids = Array.isArray(locationId) ? locationId : [locationId]
await levelRepository.delete({ location_id: In(ids) })
await this.eventBusService_?.emit?.(InventoryLevelService.Events.DELETED, {
location_id: locationId,
location_ids: ids,
})
}
+131 -49
View File
@@ -1,5 +1,6 @@
import { InternalModuleDeclaration } from "@medusajs/modules-sdk"
import {
BulkUpdateInventoryLevelInput,
CreateInventoryItemInput,
CreateInventoryLevelInput,
CreateReservationItemInput,
@@ -32,6 +33,7 @@ type InjectedDependencies = {
inventoryLevelService: InventoryLevelService
reservationItemService: ReservationItemService
}
export default class InventoryService implements IInventoryService {
protected readonly manager_: EntityManager
@@ -184,10 +186,63 @@ export default class InventoryService implements IInventoryService {
)
}
private async ensureInventoryLevels(
data: { location_id: string; inventory_item_id: string }[],
context: SharedContext = {}
): Promise<InventoryLevelDTO[]> {
const inventoryLevels = await this.inventoryLevelService_.list(
{
inventory_item_id: data.map((e) => e.inventory_item_id),
location_id: data.map((e) => e.location_id),
},
{},
context
)
const inventoryLevelMap: Map<
string,
Map<string, InventoryLevelDTO>
> = 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(
(i) => !inventoryLevelMap.get(i.inventory_item_id)?.get(i.location_id)
)
if (missing.length) {
const error = missing
.map((missing) => {
return `Item ${missing.inventory_item_id} is not stocked at location ${missing.location_id}`
})
.join(", ")
throw new MedusaError(MedusaError.Types.NOT_FOUND, error)
}
return inventoryLevels.map(
(i) => inventoryLevelMap.get(i.inventory_item_id)!.get(i.location_id)!
)
}
@InjectEntityManager(
(target) =>
target.moduleDeclaration?.resources === MODULE_RESOURCE_TYPE.ISOLATED
)
async createReservationItems(
input: CreateReservationItemInput[],
@MedusaContext() context: SharedContext = {}
): Promise<ReservationItemDTO[]> {
await this.ensureInventoryLevels(input, context)
return await this.reservationItemService_.create(input, context)
}
/**
* Creates a reservation item
* @param input - the input object
* @param context
* @return The created reservation item
*/
@InjectEntityManager(
@@ -198,29 +253,20 @@ export default class InventoryService implements IInventoryService {
input: CreateReservationItemInput,
@MedusaContext() context: SharedContext = {}
): Promise<ReservationItemDTO> {
// Verify that the item is stocked at the location
const [inventoryLevel] = await this.inventoryLevelService_.list(
{
inventory_item_id: input.inventory_item_id,
location_id: input.location_id,
},
{ take: 1 },
context
)
const [result] = await this.createReservationItems([input], context)
if (!inventoryLevel) {
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
`Item ${input.inventory_item_id} is not stocked at location ${input.location_id}`
)
}
return result
}
const reservationItem = await this.reservationItemService_.create(
input,
context
)
return { ...reservationItem }
@InjectEntityManager(
(target) =>
target.moduleDeclaration?.resources === MODULE_RESOURCE_TYPE.ISOLATED
)
async createInventoryItems(
input: CreateInventoryItemInput[],
@MedusaContext() context: SharedContext = {}
): Promise<InventoryItemDTO[]> {
return await this.inventoryItemService_.create(input, context)
}
/**
@@ -237,11 +283,20 @@ export default class InventoryService implements IInventoryService {
input: CreateInventoryItemInput,
@MedusaContext() context: SharedContext = {}
): Promise<InventoryItemDTO> {
const inventoryItem = await this.inventoryItemService_.create(
input,
context
)
return { ...inventoryItem }
const [result] = await this.createInventoryItems([input], context)
return result
}
@InjectEntityManager(
(target) =>
target.moduleDeclaration?.resources === MODULE_RESOURCE_TYPE.ISOLATED
)
async createInventoryLevels(
input: CreateInventoryLevelInput[],
@MedusaContext() context: SharedContext = {}
): Promise<InventoryLevelDTO[]> {
return await this.inventoryLevelService_.create(input, context)
}
/**
@@ -258,7 +313,9 @@ export default class InventoryService implements IInventoryService {
input: CreateInventoryLevelInput,
@MedusaContext() context: SharedContext = {}
): Promise<InventoryLevelDTO> {
return await this.inventoryLevelService_.create(input, context)
const [result] = await this.createInventoryLevels([input], context)
return result
}
/**
@@ -295,7 +352,7 @@ export default class InventoryService implements IInventoryService {
target.moduleDeclaration?.resources === MODULE_RESOURCE_TYPE.ISOLATED
)
async deleteInventoryItem(
inventoryItemId: string,
inventoryItemId: string | string[],
@MedusaContext() context: SharedContext = {}
): Promise<void> {
await this.inventoryLevelService_.deleteByInventoryItemId(
@@ -311,7 +368,7 @@ export default class InventoryService implements IInventoryService {
target.moduleDeclaration?.resources === MODULE_RESOURCE_TYPE.ISOLATED
)
async deleteInventoryItemLevelByLocationId(
locationId: string,
locationId: string | string[],
@MedusaContext() context: SharedContext = {}
): Promise<void> {
return await this.inventoryLevelService_.deleteByLocationId(
@@ -325,7 +382,7 @@ export default class InventoryService implements IInventoryService {
target.moduleDeclaration?.resources === MODULE_RESOURCE_TYPE.ISOLATED
)
async deleteReservationItemByLocationId(
locationId: string,
locationId: string | string[],
@MedusaContext() context: SharedContext = {}
): Promise<void> {
return await this.reservationItemService_.deleteByLocationId(
@@ -362,6 +419,38 @@ export default class InventoryService implements IInventoryService {
return await this.inventoryLevelService_.delete(inventoryLevel.id, context)
}
@InjectEntityManager(
(target) =>
target.moduleDeclaration?.resources === MODULE_RESOURCE_TYPE.ISOLATED
)
async updateInventoryLevels(
updates: ({
inventory_item_id: string
location_id: string
} & UpdateInventoryLevelInput)[],
context?: SharedContext
): Promise<InventoryLevelDTO[]> {
const inventoryLevels = await this.ensureInventoryLevels(updates)
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())
return await Promise.all(
updates.map(async (update) => {
const levelId = levelMap
.get(update.inventory_item_id)
.get(update.location_id)
// TODO make this bulk
return this.inventoryLevelService_.update(levelId, update, context)
})
)
}
/**
* Updates an inventory level
* @param inventoryItemId - the id of the inventory item associated with the level
@@ -376,28 +465,21 @@ export default class InventoryService implements IInventoryService {
)
async updateInventoryLevel(
inventoryItemId: string,
locationId: string,
input: UpdateInventoryLevelInput,
locationIdOrContext?: string,
input?: UpdateInventoryLevelInput,
@MedusaContext() context: SharedContext = {}
): Promise<InventoryLevelDTO> {
const [inventoryLevel] = await this.inventoryLevelService_.list(
{ inventory_item_id: inventoryItemId, location_id: locationId },
{ take: 1 },
context
)
const updates: BulkUpdateInventoryLevelInput[] = [
{
inventory_item_id: inventoryItemId,
location_id: locationIdOrContext as string,
...input,
},
]
if (!inventoryLevel) {
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
`Inventory level for item ${inventoryItemId} and location ${locationId} not found`
)
}
const [result] = await this.updateInventoryLevels(updates, context)
return await this.inventoryLevelService_.update(
inventoryLevel.id,
input,
context
)
return result
}
/**
@@ -131,38 +131,44 @@ export default class ReservationItemService {
*/
@InjectEntityManager()
async create(
data: CreateReservationItemInput,
data: CreateReservationItemInput[],
@MedusaContext() context: SharedContext = {}
): Promise<ReservationItem> {
): Promise<ReservationItem[]> {
const manager = context.transactionManager!
const reservationItemRepository = manager.getRepository(ReservationItem)
const reservationItem = reservationItemRepository.create({
inventory_item_id: data.inventory_item_id,
line_item_id: data.line_item_id,
location_id: data.location_id,
quantity: data.quantity,
metadata: data.metadata,
external_id: data.external_id,
description: data.description,
created_by: data.created_by,
})
const reservationItems = reservationItemRepository.create(
data.map((tc) => ({
inventory_item_id: tc.inventory_item_id,
line_item_id: tc.line_item_id,
location_id: tc.location_id,
quantity: tc.quantity,
metadata: tc.metadata,
external_id: tc.external_id,
description: tc.description,
created_by: tc.created_by,
}))
)
const [newReservationItem] = await Promise.all([
reservationItemRepository.save(reservationItem),
this.inventoryLevelService_.adjustReservedQuantity(
data.inventory_item_id,
data.location_id,
data.quantity,
context
const [newReservationItems] = await Promise.all([
reservationItemRepository.save(reservationItems),
...data.map(
async (data) =>
// TODO make bulk
await this.inventoryLevelService_.adjustReservedQuantity(
data.inventory_item_id,
data.location_id,
data.quantity,
context
)
),
])
await this.eventBusService_?.emit?.(ReservationItemService.Events.CREATED, {
id: newReservationItem.id,
ids: newReservationItems.map((i) => i.id),
})
return newReservationItem
return newReservationItems
}
/**
@@ -244,24 +250,24 @@ export default class ReservationItemService {
const manager = context.transactionManager!
const itemRepository = manager.getRepository(ReservationItem)
const itemsIds = Array.isArray(lineItemId) ? lineItemId : [lineItemId]
const lineItemIds = Array.isArray(lineItemId) ? lineItemId : [lineItemId]
const items = await this.list(
{ line_item_id: itemsIds },
const reservationItems = await this.list(
{ line_item_id: lineItemIds },
undefined,
context
)
const ops: Promise<unknown>[] = [
itemRepository.softDelete({ line_item_id: In(itemsIds) }),
itemRepository.softDelete({ line_item_id: In(lineItemIds) }),
]
for (const item of items) {
for (const reservation of reservationItems) {
ops.push(
this.inventoryLevelService_.adjustReservedQuantity(
item.inventory_item_id,
item.location_id,
item.quantity * -1,
reservation.inventory_item_id,
reservation.location_id,
reservation.quantity * -1,
context
)
)
@@ -281,18 +287,15 @@ export default class ReservationItemService {
*/
@InjectEntityManager()
async deleteByLocationId(
locationId: string,
locationId: string | string[],
@MedusaContext() context: SharedContext = {}
): Promise<void> {
const manager = context.transactionManager!
const itemRepository = manager.getRepository(ReservationItem)
await itemRepository
.createQueryBuilder("reservation_item")
.softDelete()
.where("location_id = :locationId", { locationId })
.andWhere("deleted_at IS NULL")
.execute()
const ids = Array.isArray(locationId) ? locationId : [locationId]
await itemRepository.softDelete({ location_id: In(ids) })
await this.eventBusService_?.emit?.(ReservationItemService.Events.DELETED, {
location_id: locationId,
@@ -330,7 +333,7 @@ export default class ReservationItemService {
await Promise.all(promises)
await this.eventBusService_?.emit?.(ReservationItemService.Events.DELETED, {
id: reservationItemId,
ids: reservationItemId,
})
}
}
@@ -1,8 +1,8 @@
import { humanizeAmount, MedusaError } from "medusa-core-utils"
import { updateInventoryAndReservations } from "@medusajs/medusa"
import { MedusaError, humanizeAmount } from "medusa-core-utils"
import { BaseService } from "medusa-interfaces"
import Brightpearl from "../utils/brightpearl"
import { updateInventoryAndReservations } from "@medusajs/medusa"
class BrightpearlService extends BaseService {
constructor(
@@ -117,7 +117,7 @@ class BrightpearlService extends BaseService {
httpMethod: "POST",
uriTemplate: `${this.options.backend_url}/brightpearl/inventory-update`,
bodyTemplate:
'{"account": "${account-code}", "lifecycle_event": "${lifecycle-event}", "resource_type": "${resource-type}", "id": "${resource-id}" }',
"{\"account\": \"${account-code}\", \"lifecycle_event\": \"${lifecycle-event}\", \"resource_type\": \"${resource-type}\", \"id\": \"${resource-id}\" }",
contentType: "application/json",
idSetAccepted: false,
},
@@ -760,8 +760,14 @@ class BrightpearlService extends BaseService {
)
}
/**
* create reservation based on reservation created event
* @param {{ ids: string[] }} eventData Event data from reservation created
*/
async createReservation(eventData) {
const { id } = eventData
const { ids } = eventData
const [id] = ids
if (!id) {
return
@@ -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
}
+5
View File
@@ -268,6 +268,11 @@ export type UpdateInventoryLevelInput = {
incoming_quantity?: number
}
export type BulkUpdateInventoryLevelInput = {
inventory_item_id: string
location_id: string
} & UpdateInventoryLevelInput
export type UpdateReservationItemInput = {
quantity?: number
location_id?: string
+27 -5
View File
@@ -56,16 +56,38 @@ export interface IInventoryService {
context?: SharedContext
): Promise<ReservationItemDTO>
// TODO make it bulk
createReservationItems(
input: CreateReservationItemInput[],
context?: SharedContext
): Promise<ReservationItemDTO[]>
createInventoryItem(
input: CreateInventoryItemInput,
context?: SharedContext
): Promise<InventoryItemDTO>
createInventoryItems(
input: CreateInventoryItemInput[],
context?: SharedContext
): Promise<InventoryItemDTO[]>
createInventoryLevel(
data: CreateInventoryLevelInput,
data: CreateInventoryLevelInput ,
context?: SharedContext
): Promise<InventoryLevelDTO>
createInventoryLevels(
data: CreateInventoryLevelInput[],
context?: SharedContext
): Promise<InventoryLevelDTO[]>
updateInventoryLevels(
updates: ({
inventory_item_id: string
location_id: string
} & UpdateInventoryLevelInput)[],
context?: SharedContext
): Promise<InventoryLevelDTO[]>
updateInventoryLevel(
inventoryItemId: string,
@@ -98,17 +120,17 @@ export interface IInventoryService {
// TODO make it bulk
deleteInventoryItem(
inventoryItemId: string,
inventoryItemId: string | string[],
context?: SharedContext
): Promise<void>
deleteInventoryItemLevelByLocationId(
locationId: string,
locationId: string | string[],
context?: SharedContext
): Promise<void>
deleteReservationItemByLocationId(
locationId: string,
locationId: string | string[],
context?: SharedContext
): Promise<void>