Files
medusa-store/packages/inventory/src/services/inventory.ts
Carlos R. L. Rodrigues aa54d902e5 chore: added missing withTransacton, create-variant using TO (#3047)
Create variant integrated with Inventory modules
2023-01-18 09:29:06 -03:00

478 lines
14 KiB
TypeScript

import {
ConfigurableModuleDeclaration,
CreateInventoryItemInput,
CreateInventoryLevelInput,
CreateReservationItemInput,
FilterableInventoryItemProps,
FilterableInventoryLevelProps,
FilterableReservationItemProps,
FindConfig,
IEventBusService,
IInventoryService,
InventoryItemDTO,
InventoryLevelDTO,
MODULE_RESOURCE_TYPE,
ReservationItemDTO,
TransactionBaseService,
UpdateInventoryLevelInput,
UpdateReservationItemInput,
} from "@medusajs/medusa"
import { MedusaError } from "medusa-core-utils"
import { EntityManager } from "typeorm"
import {
InventoryItemService,
InventoryLevelService,
ReservationItemService,
} from "./"
type InjectedDependencies = {
manager: EntityManager
eventBusService: IEventBusService
}
export default class InventoryService
extends TransactionBaseService
implements IInventoryService
{
protected readonly eventBusService_: IEventBusService
protected manager_: EntityManager
protected transactionManager_: EntityManager | undefined
protected readonly inventoryItemService_: InventoryItemService
protected readonly reservationItemService_: ReservationItemService
protected readonly inventoryLevelService_: InventoryLevelService
constructor(
{ eventBusService, manager }: InjectedDependencies,
options?: unknown,
moduleDeclaration?: ConfigurableModuleDeclaration
) {
// @ts-ignore
super(...arguments)
if (moduleDeclaration?.resources !== MODULE_RESOURCE_TYPE.SHARED) {
throw new MedusaError(
MedusaError.Types.INVALID_ARGUMENT,
"At the moment this module can only be used with shared resources"
)
}
this.eventBusService_ = eventBusService
this.manager_ = manager
this.inventoryItemService_ = new InventoryItemService({
eventBusService,
manager,
})
this.inventoryLevelService_ = new InventoryLevelService({
eventBusService,
manager,
})
this.reservationItemService_ = new ReservationItemService({
eventBusService,
manager,
inventoryLevelService: this.inventoryLevelService_,
})
}
private getManager(): EntityManager {
return this.transactionManager_ ?? this.manager_
}
/**
* Lists inventory items that match the given selector
* @param selector - the selector to filter inventory items by
* @param config - the find configuration to use
* @return A tuple of inventory items and their total count
*/
async listInventoryItems(
selector: FilterableInventoryItemProps,
config: FindConfig<InventoryItemDTO> = { relations: [], skip: 0, take: 10 }
): Promise<[InventoryItemDTO[], number]> {
return await this.inventoryItemService_
.withTransaction(this.getManager())
.listAndCount(selector, config)
}
/**
* Lists inventory levels that match the given selector
* @param selector - the selector to filter inventory levels by
* @param config - the find configuration to use
* @return A tuple of inventory levels and their total count
*/
async listInventoryLevels(
selector: FilterableInventoryLevelProps,
config: FindConfig<InventoryLevelDTO> = { relations: [], skip: 0, take: 10 }
): Promise<[InventoryLevelDTO[], number]> {
return await this.inventoryLevelService_
.withTransaction(this.getManager())
.listAndCount(selector, config)
}
/**
* Lists reservation items that match the given selector
* @param selector - the selector to filter reservation items by
* @param config - the find configuration to use
* @return A tuple of reservation items and their total count
*/
async listReservationItems(
selector: FilterableReservationItemProps,
config: FindConfig<ReservationItemDTO> = {
relations: [],
skip: 0,
take: 10,
}
): Promise<[ReservationItemDTO[], number]> {
return await this.reservationItemService_
.withTransaction(this.getManager())
.listAndCount(selector, config)
}
/**
* 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
* @return The retrieved inventory item
*/
async retrieveInventoryItem(
inventoryItemId: string,
config?: FindConfig<InventoryItemDTO>
): Promise<InventoryItemDTO> {
const inventoryItem = await this.inventoryItemService_
.withTransaction(this.getManager())
.retrieve(inventoryItemId, config)
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
* @return the retrieved inventory level
*/
async retrieveInventoryLevel(
inventoryItemId: string,
locationId: string
): Promise<InventoryLevelDTO> {
const [inventoryLevel] = await this.inventoryLevelService_
.withTransaction(this.getManager())
.list(
{ inventory_item_id: inventoryItemId, location_id: locationId },
{ take: 1 }
)
if (!inventoryLevel) {
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
`Inventory level for item ${inventoryItemId} and location ${locationId} not found`
)
}
return inventoryLevel
}
/**
* Creates a reservation item
* @param input - the input object
* @return The created reservation item
*/
async createReservationItem(
input: CreateReservationItemInput
): Promise<ReservationItemDTO> {
// Verify that the item is stocked at the location
const [inventoryLevel] = await this.inventoryLevelService_
.withTransaction(this.getManager())
.list(
{
inventory_item_id: input.inventory_item_id,
location_id: input.location_id,
},
{ take: 1 }
)
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_
.withTransaction(this.getManager())
.create(input)
return { ...reservationItem }
}
/**
* Creates an inventory item
* @param input - the input object
* @return The created inventory item
*/
async createInventoryItem(
input: CreateInventoryItemInput
): Promise<InventoryItemDTO> {
const inventoryItem = await this.inventoryItemService_
.withTransaction(this.getManager())
.create(input)
return { ...inventoryItem }
}
/**
* Creates an inventory item
* @param input - the input object
* @return The created inventory level
*/
async createInventoryLevel(
input: CreateInventoryLevelInput
): Promise<InventoryLevelDTO> {
return await this.inventoryLevelService_
.withTransaction(this.getManager())
.create(input)
}
/**
* Updates an inventory item
* @param inventoryItemId - the id of the inventory item to update
* @param input - the input object
* @return The updated inventory item
*/
async updateInventoryItem(
inventoryItemId: string,
input: Partial<CreateInventoryItemInput>
): Promise<InventoryItemDTO> {
const inventoryItem = await this.inventoryItemService_
.withTransaction(this.getManager())
.update(inventoryItemId, input)
return { ...inventoryItem }
}
/**
* Deletes an inventory item
* @param inventoryItemId - the id of the inventory item to delete
*/
async deleteInventoryItem(inventoryItemId: string): Promise<void> {
return await this.inventoryItemService_
.withTransaction(this.getManager())
.delete(inventoryItemId)
}
/**
* 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
*/
async deleteInventoryLevel(
inventoryItemId: string,
locationId: string
): Promise<void> {
const [inventoryLevel] = await this.inventoryLevelService_
.withTransaction(this.getManager())
.list(
{ inventory_item_id: inventoryItemId, location_id: locationId },
{ take: 1 }
)
if (!inventoryLevel) {
return
}
return await this.inventoryLevelService_
.withTransaction(this.getManager())
.delete(inventoryLevel.id)
}
/**
* 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
* @return The updated inventory level
*/
async updateInventoryLevel(
inventoryItemId: string,
locationId: string,
input: UpdateInventoryLevelInput
): Promise<InventoryLevelDTO> {
const [inventoryLevel] = await this.inventoryLevelService_
.withTransaction(this.getManager())
.list(
{ inventory_item_id: inventoryItemId, location_id: locationId },
{ take: 1 }
)
if (!inventoryLevel) {
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
`Inventory level for item ${inventoryItemId} and location ${locationId} not found`
)
}
return await this.inventoryLevelService_
.withTransaction(this.getManager())
.update(inventoryLevel.id, input)
}
/**
* Updates a reservation item
* @param inventoryItemId - the id of the inventory item associated with the level
* @param input - the input object
* @return The updated inventory level
*/
async updateReservationItem(
reservationItemId: string,
input: UpdateReservationItemInput
): Promise<ReservationItemDTO> {
return await this.reservationItemService_
.withTransaction(this.getManager())
.update(reservationItemId, input)
}
/**
* Deletes reservation items by line item
* @param lineItemId - the id of the line item associated with the reservation item
*/
async deleteReservationItemsByLineItem(lineItemId: string): Promise<void> {
return await this.reservationItemService_
.withTransaction(this.getManager())
.deleteByLineItem(lineItemId)
}
/**
* Deletes a reservation item
* @param reservationItemId - the id of the reservation item to delete
*/
async deleteReservationItem(reservationItemId: string): Promise<void> {
return await this.reservationItemService_
.withTransaction(this.getManager())
.delete(reservationItemId)
}
/**
* 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)
* @return The updated inventory level
* @throws when the inventory level is not found
*/
async adjustInventory(
inventoryItemId: string,
locationId: string,
adjustment: number
): Promise<InventoryLevelDTO> {
const [inventoryLevel] = await this.inventoryLevelService_
.withTransaction(this.getManager())
.list(
{ inventory_item_id: inventoryItemId, location_id: locationId },
{ take: 1 }
)
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_
.withTransaction(this.getManager())
.update(inventoryLevel.id, {
stocked_quantity: inventoryLevel.stocked_quantity + adjustment,
})
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
* @return The available quantity
* @throws when the inventory item is not found
*/
async retrieveAvailableQuantity(
inventoryItemId: string,
locationIds: string[]
): Promise<number> {
// Throws if item does not exist
await this.inventoryItemService_
.withTransaction(this.getManager())
.retrieve(inventoryItemId, {
select: ["id"],
})
const availableQuantity = await this.inventoryLevelService_
.withTransaction(this.getManager())
.getAvailableQuantity(inventoryItemId, locationIds)
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
* @return The stocked quantity
* @throws when the inventory item is not found
*/
async retrieveStockedQuantity(
inventoryItemId: string,
locationIds: string[]
): Promise<number> {
// Throws if item does not exist
await this.inventoryItemService_
.withTransaction(this.getManager())
.retrieve(inventoryItemId, {
select: ["id"],
})
const stockedQuantity = await this.inventoryLevelService_
.withTransaction(this.getManager())
.getStockedQuantity(inventoryItemId, locationIds)
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
* @return The reserved quantity
* @throws when the inventory item is not found
*/
async retrieveReservedQuantity(
inventoryItemId: string,
locationIds: string[]
): Promise<number> {
// Throws if item does not exist
await this.inventoryItemService_
.withTransaction(this.getManager())
.retrieve(inventoryItemId, {
select: ["id"],
})
const reservedQuantity = await this.inventoryLevelService_
.withTransaction(this.getManager())
.getReservedQuantity(inventoryItemId, locationIds)
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
* @return Whether there is sufficient inventory
*/
async confirmInventory(
inventoryItemId: string,
locationIds: string[],
quantity: number
): Promise<boolean> {
const availableQuantity = await this.retrieveAvailableQuantity(
inventoryItemId,
locationIds
)
return availableQuantity >= quantity
}
}