638 lines
18 KiB
TypeScript
638 lines
18 KiB
TypeScript
import { InternalModuleDeclaration } from "@medusajs/modules-sdk"
|
|
import {
|
|
CreateInventoryItemInput,
|
|
CreateInventoryLevelInput,
|
|
CreateReservationItemInput,
|
|
FilterableInventoryItemProps,
|
|
FilterableInventoryLevelProps,
|
|
FilterableReservationItemProps,
|
|
FindConfig,
|
|
IInventoryService,
|
|
InventoryItemDTO,
|
|
InventoryLevelDTO,
|
|
MODULE_RESOURCE_TYPE,
|
|
ReservationItemDTO,
|
|
SharedContext,
|
|
UpdateInventoryLevelInput,
|
|
UpdateReservationItemInput,
|
|
} from "@medusajs/types"
|
|
import {
|
|
InjectEntityManager,
|
|
MedusaContext,
|
|
MedusaError,
|
|
} from "@medusajs/utils"
|
|
import { EntityManager } from "typeorm"
|
|
import InventoryItemService from "./inventory-item"
|
|
import InventoryLevelService from "./inventory-level"
|
|
import ReservationItemService from "./reservation-item"
|
|
|
|
type InjectedDependencies = {
|
|
manager: EntityManager
|
|
inventoryItemService: InventoryItemService
|
|
inventoryLevelService: InventoryLevelService
|
|
reservationItemService: ReservationItemService
|
|
}
|
|
export default class InventoryService implements IInventoryService {
|
|
protected readonly manager_: EntityManager
|
|
|
|
protected readonly inventoryItemService_: InventoryItemService
|
|
protected readonly reservationItemService_: ReservationItemService
|
|
protected readonly inventoryLevelService_: InventoryLevelService
|
|
|
|
constructor(
|
|
{
|
|
manager,
|
|
inventoryItemService,
|
|
inventoryLevelService,
|
|
reservationItemService,
|
|
}: InjectedDependencies,
|
|
options?: unknown,
|
|
protected readonly moduleDeclaration?: InternalModuleDeclaration
|
|
) {
|
|
this.manager_ = manager
|
|
this.inventoryItemService_ = inventoryItemService
|
|
this.inventoryLevelService_ = inventoryLevelService
|
|
this.reservationItemService_ = reservationItemService
|
|
}
|
|
|
|
/**
|
|
* Lists inventory items that match the given selector
|
|
* @param selector - the selector to filter inventory items by
|
|
* @param config - the find configuration to use
|
|
* @param context
|
|
* @return A tuple of inventory items and their total count
|
|
*/
|
|
async listInventoryItems(
|
|
selector: FilterableInventoryItemProps,
|
|
config: FindConfig<InventoryItemDTO> = { relations: [], skip: 0, take: 10 },
|
|
context: SharedContext = {}
|
|
): Promise<[InventoryItemDTO[], number]> {
|
|
return await this.inventoryItemService_.listAndCount(
|
|
selector,
|
|
config,
|
|
context
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Lists inventory levels that match the given selector
|
|
* @param selector - the selector to filter inventory levels by
|
|
* @param config - the find configuration to use
|
|
* @param context
|
|
* @return A tuple of inventory levels and their total count
|
|
*/
|
|
async listInventoryLevels(
|
|
selector: FilterableInventoryLevelProps,
|
|
config: FindConfig<InventoryLevelDTO> = {
|
|
relations: [],
|
|
skip: 0,
|
|
take: 10,
|
|
},
|
|
context: SharedContext = {}
|
|
): Promise<[InventoryLevelDTO[], number]> {
|
|
return await this.inventoryLevelService_.listAndCount(
|
|
selector,
|
|
config,
|
|
context
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Lists reservation items that match the given selector
|
|
* @param selector - the selector to filter reservation items by
|
|
* @param config - the find configuration to use
|
|
* @param context
|
|
* @return A tuple of reservation items and their total count
|
|
*/
|
|
async listReservationItems(
|
|
selector: FilterableReservationItemProps,
|
|
config: FindConfig<ReservationItemDTO> = {
|
|
relations: [],
|
|
skip: 0,
|
|
take: 10,
|
|
},
|
|
context: SharedContext = {}
|
|
): Promise<[ReservationItemDTO[], number]> {
|
|
return await this.reservationItemService_.listAndCount(
|
|
selector,
|
|
config,
|
|
context
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Retrieves an inventory item with the given id
|
|
* @param inventoryItemId - the id of the inventory item to retrieve
|
|
* @param config - the find configuration to use
|
|
* @param context
|
|
* @return The retrieved inventory item
|
|
*/
|
|
async retrieveInventoryItem(
|
|
inventoryItemId: string,
|
|
config?: FindConfig<InventoryItemDTO>,
|
|
context: SharedContext = {}
|
|
): Promise<InventoryItemDTO> {
|
|
const inventoryItem = await this.inventoryItemService_.retrieve(
|
|
inventoryItemId,
|
|
config,
|
|
context
|
|
)
|
|
return { ...inventoryItem }
|
|
}
|
|
|
|
/**
|
|
* Retrieves an inventory level for a given inventory item and location
|
|
* @param inventoryItemId - the id of the inventory item
|
|
* @param locationId - the id of the location
|
|
* @param context
|
|
* @return the retrieved inventory level
|
|
*/
|
|
async retrieveInventoryLevel(
|
|
inventoryItemId: string,
|
|
locationId: string,
|
|
context: SharedContext = {}
|
|
): Promise<InventoryLevelDTO> {
|
|
const [inventoryLevel] = await this.inventoryLevelService_.list(
|
|
{ inventory_item_id: inventoryItemId, location_id: locationId },
|
|
{ take: 1 },
|
|
context
|
|
)
|
|
if (!inventoryLevel) {
|
|
throw new MedusaError(
|
|
MedusaError.Types.NOT_FOUND,
|
|
`Inventory level for item ${inventoryItemId} and location ${locationId} not found`
|
|
)
|
|
}
|
|
return inventoryLevel
|
|
}
|
|
|
|
/**
|
|
* Retrieves a reservation item
|
|
* @param reservationId
|
|
* @param context
|
|
* @param reservationId
|
|
* @param context
|
|
*/
|
|
async retrieveReservationItem(
|
|
reservationId: string,
|
|
context: SharedContext = {}
|
|
): Promise<ReservationItemDTO> {
|
|
return await this.reservationItemService_.retrieve(
|
|
reservationId,
|
|
undefined,
|
|
context
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Creates a reservation item
|
|
* @param input - the input object
|
|
* @param context
|
|
* @return The created reservation item
|
|
*/
|
|
@InjectEntityManager(
|
|
(target) =>
|
|
target.moduleDeclaration?.resources === MODULE_RESOURCE_TYPE.ISOLATED
|
|
)
|
|
async createReservationItem(
|
|
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
|
|
)
|
|
|
|
if (!inventoryLevel) {
|
|
throw new MedusaError(
|
|
MedusaError.Types.NOT_FOUND,
|
|
`Item ${input.inventory_item_id} is not stocked at location ${input.location_id}`
|
|
)
|
|
}
|
|
|
|
const reservationItem = await this.reservationItemService_.create(
|
|
input,
|
|
context
|
|
)
|
|
|
|
return { ...reservationItem }
|
|
}
|
|
|
|
/**
|
|
* Creates an inventory item
|
|
* @param input - the input object
|
|
* @param context
|
|
* @return The created inventory item
|
|
*/
|
|
@InjectEntityManager(
|
|
(target) =>
|
|
target.moduleDeclaration?.resources === MODULE_RESOURCE_TYPE.ISOLATED
|
|
)
|
|
async createInventoryItem(
|
|
input: CreateInventoryItemInput,
|
|
@MedusaContext() context: SharedContext = {}
|
|
): Promise<InventoryItemDTO> {
|
|
const inventoryItem = await this.inventoryItemService_.create(
|
|
input,
|
|
context
|
|
)
|
|
return { ...inventoryItem }
|
|
}
|
|
|
|
/**
|
|
* Creates an inventory item
|
|
* @param input - the input object
|
|
* @param context
|
|
* @return The created inventory level
|
|
*/
|
|
@InjectEntityManager(
|
|
(target) =>
|
|
target.moduleDeclaration?.resources === MODULE_RESOURCE_TYPE.ISOLATED
|
|
)
|
|
async createInventoryLevel(
|
|
input: CreateInventoryLevelInput,
|
|
@MedusaContext() context: SharedContext = {}
|
|
): Promise<InventoryLevelDTO> {
|
|
return await this.inventoryLevelService_.create(input, context)
|
|
}
|
|
|
|
/**
|
|
* Updates an inventory item
|
|
* @param inventoryItemId - the id of the inventory item to update
|
|
* @param input - the input object
|
|
* @param context
|
|
* @return The updated inventory item
|
|
*/
|
|
@InjectEntityManager(
|
|
(target) =>
|
|
target.moduleDeclaration?.resources === MODULE_RESOURCE_TYPE.ISOLATED
|
|
)
|
|
async updateInventoryItem(
|
|
inventoryItemId: string,
|
|
input: Partial<CreateInventoryItemInput>,
|
|
@MedusaContext() context: SharedContext = {}
|
|
): Promise<InventoryItemDTO> {
|
|
const inventoryItem = await this.inventoryItemService_.update(
|
|
inventoryItemId,
|
|
input,
|
|
context
|
|
)
|
|
return { ...inventoryItem }
|
|
}
|
|
|
|
/**
|
|
* Deletes an inventory item
|
|
* @param inventoryItemId - the id of the inventory item to delete
|
|
* @param context
|
|
*/
|
|
@InjectEntityManager(
|
|
(target) =>
|
|
target.moduleDeclaration?.resources === MODULE_RESOURCE_TYPE.ISOLATED
|
|
)
|
|
async deleteInventoryItem(
|
|
inventoryItemId: string,
|
|
@MedusaContext() context: SharedContext = {}
|
|
): Promise<void> {
|
|
await this.inventoryLevelService_.deleteByInventoryItemId(
|
|
inventoryItemId,
|
|
context
|
|
)
|
|
|
|
return await this.inventoryItemService_.delete(inventoryItemId, context)
|
|
}
|
|
|
|
@InjectEntityManager(
|
|
(target) =>
|
|
target.moduleDeclaration?.resources === MODULE_RESOURCE_TYPE.ISOLATED
|
|
)
|
|
async deleteInventoryItemLevelByLocationId(
|
|
locationId: string,
|
|
@MedusaContext() context: SharedContext = {}
|
|
): Promise<void> {
|
|
return await this.inventoryLevelService_.deleteByLocationId(
|
|
locationId,
|
|
context
|
|
)
|
|
}
|
|
|
|
@InjectEntityManager(
|
|
(target) =>
|
|
target.moduleDeclaration?.resources === MODULE_RESOURCE_TYPE.ISOLATED
|
|
)
|
|
async deleteReservationItemByLocationId(
|
|
locationId: string,
|
|
@MedusaContext() context: SharedContext = {}
|
|
): Promise<void> {
|
|
return await this.reservationItemService_.deleteByLocationId(
|
|
locationId,
|
|
context
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Deletes an inventory level
|
|
* @param inventoryItemId - the id of the inventory item associated with the level
|
|
* @param locationId - the id of the location associated with the level
|
|
* @param context
|
|
*/
|
|
@InjectEntityManager(
|
|
(target) =>
|
|
target.moduleDeclaration?.resources === MODULE_RESOURCE_TYPE.ISOLATED
|
|
)
|
|
async deleteInventoryLevel(
|
|
inventoryItemId: string,
|
|
locationId: string,
|
|
@MedusaContext() context: SharedContext = {}
|
|
): Promise<void> {
|
|
const [inventoryLevel] = await this.inventoryLevelService_.list(
|
|
{ inventory_item_id: inventoryItemId, location_id: locationId },
|
|
{ take: 1 },
|
|
context
|
|
)
|
|
|
|
if (!inventoryLevel) {
|
|
return
|
|
}
|
|
|
|
return await this.inventoryLevelService_.delete(inventoryLevel.id, context)
|
|
}
|
|
|
|
/**
|
|
* Updates an inventory level
|
|
* @param inventoryItemId - the id of the inventory item associated with the level
|
|
* @param locationId - the id of the location associated with the level
|
|
* @param input - the input object
|
|
* @param context
|
|
* @return The updated inventory level
|
|
*/
|
|
@InjectEntityManager(
|
|
(target) =>
|
|
target.moduleDeclaration?.resources === MODULE_RESOURCE_TYPE.ISOLATED
|
|
)
|
|
async updateInventoryLevel(
|
|
inventoryItemId: string,
|
|
locationId: string,
|
|
input: UpdateInventoryLevelInput,
|
|
@MedusaContext() context: SharedContext = {}
|
|
): Promise<InventoryLevelDTO> {
|
|
const [inventoryLevel] = await this.inventoryLevelService_.list(
|
|
{ inventory_item_id: inventoryItemId, location_id: locationId },
|
|
{ take: 1 },
|
|
context
|
|
)
|
|
|
|
if (!inventoryLevel) {
|
|
throw new MedusaError(
|
|
MedusaError.Types.NOT_FOUND,
|
|
`Inventory level for item ${inventoryItemId} and location ${locationId} not found`
|
|
)
|
|
}
|
|
|
|
return await this.inventoryLevelService_.update(
|
|
inventoryLevel.id,
|
|
input,
|
|
context
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Updates a reservation item
|
|
* @param reservationItemId
|
|
* @param input - the input object
|
|
* @param context
|
|
* @param context
|
|
* @return The updated inventory level
|
|
*/
|
|
@InjectEntityManager(
|
|
(target) =>
|
|
target.moduleDeclaration?.resources === MODULE_RESOURCE_TYPE.ISOLATED
|
|
)
|
|
async updateReservationItem(
|
|
reservationItemId: string,
|
|
input: UpdateReservationItemInput,
|
|
@MedusaContext() context: SharedContext = {}
|
|
): Promise<ReservationItemDTO> {
|
|
return await this.reservationItemService_.update(
|
|
reservationItemId,
|
|
input,
|
|
context
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Deletes reservation items by line item
|
|
* @param lineItemId - the id of the line item associated with the reservation item
|
|
* @param context
|
|
*/
|
|
@InjectEntityManager(
|
|
(target) =>
|
|
target.moduleDeclaration?.resources === MODULE_RESOURCE_TYPE.ISOLATED
|
|
)
|
|
async deleteReservationItemsByLineItem(
|
|
lineItemId: string | string[],
|
|
@MedusaContext() context: SharedContext = {}
|
|
): Promise<void> {
|
|
return await this.reservationItemService_.deleteByLineItem(
|
|
lineItemId,
|
|
context
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Deletes a reservation item
|
|
* @param reservationItemId - the id of the reservation item to delete
|
|
* @param context
|
|
*/
|
|
@InjectEntityManager(
|
|
(target) =>
|
|
target.moduleDeclaration?.resources === MODULE_RESOURCE_TYPE.ISOLATED
|
|
)
|
|
async deleteReservationItem(
|
|
reservationItemId: string | string[],
|
|
@MedusaContext() context: SharedContext = {}
|
|
): Promise<void> {
|
|
return await this.reservationItemService_.delete(reservationItemId, context)
|
|
}
|
|
|
|
/**
|
|
* Adjusts the inventory level for a given inventory item and location.
|
|
* @param inventoryItemId - the id of the inventory item
|
|
* @param locationId - the id of the location
|
|
* @param adjustment - the number to adjust the inventory by (can be positive or negative)
|
|
* @param context
|
|
* @return The updated inventory level
|
|
* @throws when the inventory level is not found
|
|
*/
|
|
@InjectEntityManager(
|
|
(target) =>
|
|
target.moduleDeclaration?.resources === MODULE_RESOURCE_TYPE.ISOLATED
|
|
)
|
|
async adjustInventory(
|
|
inventoryItemId: string,
|
|
locationId: string,
|
|
adjustment: number,
|
|
@MedusaContext() context: SharedContext = {}
|
|
): Promise<InventoryLevelDTO> {
|
|
const [inventoryLevel] = await this.inventoryLevelService_.list(
|
|
{ inventory_item_id: inventoryItemId, location_id: locationId },
|
|
{ take: 1 },
|
|
context
|
|
)
|
|
if (!inventoryLevel) {
|
|
throw new MedusaError(
|
|
MedusaError.Types.NOT_FOUND,
|
|
`Inventory level for inventory item ${inventoryItemId} and location ${locationId} not found`
|
|
)
|
|
}
|
|
|
|
const updatedInventoryLevel = await this.inventoryLevelService_.update(
|
|
inventoryLevel.id,
|
|
{
|
|
stocked_quantity: inventoryLevel.stocked_quantity + adjustment,
|
|
},
|
|
context
|
|
)
|
|
|
|
return { ...updatedInventoryLevel }
|
|
}
|
|
|
|
/**
|
|
* Retrieves the available quantity of a given inventory item in a given location.
|
|
* @param inventoryItemId - the id of the inventory item
|
|
* @param locationIds - the ids of the locations to check
|
|
* @param context
|
|
* @return The available quantity
|
|
* @throws when the inventory item is not found
|
|
*/
|
|
async retrieveAvailableQuantity(
|
|
inventoryItemId: string,
|
|
locationIds: string[],
|
|
context: SharedContext = {}
|
|
): Promise<number> {
|
|
// Throws if item does not exist
|
|
await this.inventoryItemService_.retrieve(
|
|
inventoryItemId,
|
|
{
|
|
select: ["id"],
|
|
},
|
|
context
|
|
)
|
|
|
|
if (locationIds.length === 0) {
|
|
return 0
|
|
}
|
|
|
|
const availableQuantity =
|
|
await this.inventoryLevelService_.getAvailableQuantity(
|
|
inventoryItemId,
|
|
locationIds,
|
|
context
|
|
)
|
|
|
|
return availableQuantity
|
|
}
|
|
|
|
/**
|
|
* Retrieves the stocked quantity of a given inventory item in a given location.
|
|
* @param inventoryItemId - the id of the inventory item
|
|
* @param locationIds - the ids of the locations to check
|
|
* @param context
|
|
* @return The stocked quantity
|
|
* @throws when the inventory item is not found
|
|
*/
|
|
async retrieveStockedQuantity(
|
|
inventoryItemId: string,
|
|
locationIds: string[],
|
|
context: SharedContext = {}
|
|
): Promise<number> {
|
|
// Throws if item does not exist
|
|
await this.inventoryItemService_.retrieve(
|
|
inventoryItemId,
|
|
{
|
|
select: ["id"],
|
|
},
|
|
context
|
|
)
|
|
|
|
if (locationIds.length === 0) {
|
|
return 0
|
|
}
|
|
|
|
const stockedQuantity =
|
|
await this.inventoryLevelService_.getStockedQuantity(
|
|
inventoryItemId,
|
|
locationIds,
|
|
context
|
|
)
|
|
|
|
return stockedQuantity
|
|
}
|
|
|
|
/**
|
|
* Retrieves the reserved quantity of a given inventory item in a given location.
|
|
* @param inventoryItemId - the id of the inventory item
|
|
* @param locationIds - the ids of the locations to check
|
|
* @param context
|
|
* @return The reserved quantity
|
|
* @throws when the inventory item is not found
|
|
*/
|
|
async retrieveReservedQuantity(
|
|
inventoryItemId: string,
|
|
locationIds: string[],
|
|
context: SharedContext = {}
|
|
): Promise<number> {
|
|
// Throws if item does not exist
|
|
await this.inventoryItemService_.retrieve(
|
|
inventoryItemId,
|
|
{
|
|
select: ["id"],
|
|
},
|
|
context
|
|
)
|
|
|
|
if (locationIds.length === 0) {
|
|
return 0
|
|
}
|
|
|
|
const reservedQuantity =
|
|
await this.inventoryLevelService_.getReservedQuantity(
|
|
inventoryItemId,
|
|
locationIds,
|
|
context
|
|
)
|
|
|
|
return reservedQuantity
|
|
}
|
|
|
|
/**
|
|
* Confirms whether there is sufficient inventory for a given quantity of a given inventory item in a given location.
|
|
* @param inventoryItemId - the id of the inventory item
|
|
* @param locationIds - the ids of the locations to check
|
|
* @param quantity - the quantity to check
|
|
* @param context
|
|
* @return Whether there is sufficient inventory
|
|
*/
|
|
@InjectEntityManager(
|
|
(target) =>
|
|
target.moduleDeclaration?.resources === MODULE_RESOURCE_TYPE.ISOLATED
|
|
)
|
|
async confirmInventory(
|
|
inventoryItemId: string,
|
|
locationIds: string[],
|
|
quantity: number,
|
|
@MedusaContext() context: SharedContext = {}
|
|
): Promise<boolean> {
|
|
const availableQuantity = await this.retrieveAvailableQuantity(
|
|
inventoryItemId,
|
|
locationIds,
|
|
context
|
|
)
|
|
return availableQuantity >= quantity
|
|
}
|
|
}
|