chore: added missing withTransacton, create-variant using TO (#3047)
Create variant integrated with Inventory modules
This commit is contained in:
committed by
GitHub
parent
150696de99
commit
aa54d902e5
@@ -1,53 +1,68 @@
|
||||
if (process.env.NODE_ENV !== "development") {
|
||||
return
|
||||
}
|
||||
|
||||
const path = require("path")
|
||||
|
||||
const Module = require("module")
|
||||
const originalRequire = Module.prototype.require
|
||||
const medusaCore = path.resolve(path.join(__dirname, "../../packages"))
|
||||
|
||||
function replacePath(requirePath, package, concatPackage = true) {
|
||||
const idx = requirePath.indexOf(package)
|
||||
const packPath = requirePath.substring(idx + package.length)
|
||||
function replacePath(requirePath, pack, concatPackage = true) {
|
||||
const idx = requirePath.indexOf(pack)
|
||||
const packPath = requirePath.substring(idx + pack.length).replace(/\\/g, "/")
|
||||
|
||||
let newPath =
|
||||
medusaCore +
|
||||
"/" +
|
||||
(concatPackage ? package + "/" : "") +
|
||||
(concatPackage ? pack + "/" : "") +
|
||||
packPath.replace("/dist", "/src").replace(".js", "")
|
||||
|
||||
if (!newPath.includes("/src")) {
|
||||
newPath += "/src"
|
||||
}
|
||||
|
||||
return path.resolve(newPath)
|
||||
}
|
||||
|
||||
Module.prototype.require = function (...args) {
|
||||
function checkAndReplacePaths(path) {
|
||||
const interfaces = "medusa-interfaces"
|
||||
const utils = "medusa-core-utils"
|
||||
const base = "@medusajs"
|
||||
|
||||
if (args[0].includes(base)) {
|
||||
args[0] = replacePath(args[0], base, false)
|
||||
} else if (args[0].includes(interfaces)) {
|
||||
args[0] = replacePath(args[0], interfaces)
|
||||
} else if (args[0].includes(utils)) {
|
||||
args[0] = replacePath(args[0], utils)
|
||||
if (path.includes(base)) {
|
||||
path = replacePath(path, base, false)
|
||||
} else if (path.includes(interfaces)) {
|
||||
path = replacePath(path, interfaces)
|
||||
} else if (path.includes(utils)) {
|
||||
path = replacePath(path, utils)
|
||||
}
|
||||
|
||||
return path
|
||||
}
|
||||
|
||||
Module.prototype.require = function (...args) {
|
||||
args[0] = checkAndReplacePaths(args[0])
|
||||
|
||||
if (args[0] === "glob") {
|
||||
const glob = originalRequire.apply(this, args)
|
||||
const originalGlobSync = glob.sync
|
||||
glob.GlobSync = glob.sync = (pattern, options) => {
|
||||
if (pattern.endsWith(".js") || pattern.endsWith(".ts")) {
|
||||
pattern = checkAndReplacePaths(pattern)
|
||||
pattern = pattern.replace(".js", ".{j,t}s").replace("/dist/", "/src/")
|
||||
}
|
||||
|
||||
return originalGlobSync.apply(this, [pattern, options])
|
||||
}
|
||||
return glob
|
||||
} else if (args[0] === "resolve-cwd") {
|
||||
const resolveCwd = originalRequire.apply(this, args)
|
||||
const newResolveCwd = (pattern) => {
|
||||
pattern = checkAndReplacePaths(pattern)
|
||||
|
||||
return resolveCwd.apply(this, [pattern])
|
||||
}
|
||||
return newResolveCwd
|
||||
}
|
||||
|
||||
return originalRequire.apply(this, args)
|
||||
|
||||
@@ -14,6 +14,22 @@ const medusaCore = path
|
||||
.replace(/\\/g, "/")
|
||||
|
||||
let WATCHING = false
|
||||
let IS_RELOADING = false
|
||||
|
||||
function getParentModulesIds(element) {
|
||||
if (!element) {
|
||||
return []
|
||||
}
|
||||
|
||||
const ids = [element.id]
|
||||
let parent = element.parent
|
||||
while (parent && parent.id.replace(/\\/g, "/").includes(medusaCore)) {
|
||||
ids.push(parent.id)
|
||||
parent = parent.parent
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
const watchFiles = () => {
|
||||
if (WATCHING) {
|
||||
return
|
||||
@@ -42,7 +58,12 @@ const watchFiles = () => {
|
||||
})
|
||||
|
||||
watcher.on("change", async function (rawFile) {
|
||||
if (IS_RELOADING) {
|
||||
return
|
||||
}
|
||||
|
||||
console.log("Reloading server...")
|
||||
IS_RELOADING = true
|
||||
const start = Date.now()
|
||||
|
||||
const file = rawFile.replace(/\\/g, "/")
|
||||
@@ -75,12 +96,18 @@ const watchFiles = () => {
|
||||
next.endsWith(".ts") ||
|
||||
name.startsWith(next)
|
||||
) {
|
||||
delete module.constructor._cache[rawName]
|
||||
const cacheToClean = getParentModulesIds(
|
||||
module.constructor._cache[rawName]
|
||||
)
|
||||
for (const id of cacheToClean) {
|
||||
delete module.constructor._cache[id]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await bootstrapApp()
|
||||
IS_RELOADING = false
|
||||
|
||||
console.log("Server reloaded in", Date.now() - start, "ms")
|
||||
})
|
||||
@@ -119,8 +146,6 @@ const bootstrapApp = async () => {
|
||||
watchFiles()
|
||||
console.log(`Server Running at localhost:${port}`)
|
||||
})
|
||||
|
||||
database = dbConnection
|
||||
}
|
||||
|
||||
bootstrapApp()
|
||||
void bootstrapApp()
|
||||
|
||||
@@ -66,7 +66,7 @@ export class inventorySetup1665748086258 implements MigrationInterface {
|
||||
CONSTRAINT "PK_inventory_item_id" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "IDX_inventory_item_sku" ON "inventory_item" ("sku");
|
||||
CREATE UNIQUE INDEX "IDX_inventory_item_sku" ON "inventory_item" ("sku") WHERE deleted_at IS NULL;
|
||||
|
||||
|
||||
CREATE TABLE "reservation_item" (
|
||||
|
||||
@@ -75,6 +75,10 @@ export default class InventoryService
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
@@ -85,7 +89,9 @@ export default class InventoryService
|
||||
selector: FilterableInventoryItemProps,
|
||||
config: FindConfig<InventoryItemDTO> = { relations: [], skip: 0, take: 10 }
|
||||
): Promise<[InventoryItemDTO[], number]> {
|
||||
return await this.inventoryItemService_.listAndCount(selector, config)
|
||||
return await this.inventoryItemService_
|
||||
.withTransaction(this.getManager())
|
||||
.listAndCount(selector, config)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -98,7 +104,9 @@ export default class InventoryService
|
||||
selector: FilterableInventoryLevelProps,
|
||||
config: FindConfig<InventoryLevelDTO> = { relations: [], skip: 0, take: 10 }
|
||||
): Promise<[InventoryLevelDTO[], number]> {
|
||||
return await this.inventoryLevelService_.listAndCount(selector, config)
|
||||
return await this.inventoryLevelService_
|
||||
.withTransaction(this.getManager())
|
||||
.listAndCount(selector, config)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -115,7 +123,9 @@ export default class InventoryService
|
||||
take: 10,
|
||||
}
|
||||
): Promise<[ReservationItemDTO[], number]> {
|
||||
return await this.reservationItemService_.listAndCount(selector, config)
|
||||
return await this.reservationItemService_
|
||||
.withTransaction(this.getManager())
|
||||
.listAndCount(selector, config)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -128,10 +138,9 @@ export default class InventoryService
|
||||
inventoryItemId: string,
|
||||
config?: FindConfig<InventoryItemDTO>
|
||||
): Promise<InventoryItemDTO> {
|
||||
const inventoryItem = await this.inventoryItemService_.retrieve(
|
||||
inventoryItemId,
|
||||
config
|
||||
)
|
||||
const inventoryItem = await this.inventoryItemService_
|
||||
.withTransaction(this.getManager())
|
||||
.retrieve(inventoryItemId, config)
|
||||
return { ...inventoryItem }
|
||||
}
|
||||
|
||||
@@ -145,10 +154,12 @@ export default class InventoryService
|
||||
inventoryItemId: string,
|
||||
locationId: string
|
||||
): Promise<InventoryLevelDTO> {
|
||||
const [inventoryLevel] = await this.inventoryLevelService_.list(
|
||||
{ inventory_item_id: inventoryItemId, location_id: locationId },
|
||||
{ take: 1 }
|
||||
)
|
||||
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,
|
||||
@@ -167,13 +178,15 @@ export default class InventoryService
|
||||
input: CreateReservationItemInput
|
||||
): 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 }
|
||||
)
|
||||
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(
|
||||
@@ -182,7 +195,9 @@ export default class InventoryService
|
||||
)
|
||||
}
|
||||
|
||||
const reservationItem = await this.reservationItemService_.create(input)
|
||||
const reservationItem = await this.reservationItemService_
|
||||
.withTransaction(this.getManager())
|
||||
.create(input)
|
||||
|
||||
return { ...reservationItem }
|
||||
}
|
||||
@@ -195,7 +210,9 @@ export default class InventoryService
|
||||
async createInventoryItem(
|
||||
input: CreateInventoryItemInput
|
||||
): Promise<InventoryItemDTO> {
|
||||
const inventoryItem = await this.inventoryItemService_.create(input)
|
||||
const inventoryItem = await this.inventoryItemService_
|
||||
.withTransaction(this.getManager())
|
||||
.create(input)
|
||||
return { ...inventoryItem }
|
||||
}
|
||||
|
||||
@@ -207,7 +224,9 @@ export default class InventoryService
|
||||
async createInventoryLevel(
|
||||
input: CreateInventoryLevelInput
|
||||
): Promise<InventoryLevelDTO> {
|
||||
return await this.inventoryLevelService_.create(input)
|
||||
return await this.inventoryLevelService_
|
||||
.withTransaction(this.getManager())
|
||||
.create(input)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -220,10 +239,9 @@ export default class InventoryService
|
||||
inventoryItemId: string,
|
||||
input: Partial<CreateInventoryItemInput>
|
||||
): Promise<InventoryItemDTO> {
|
||||
const inventoryItem = await this.inventoryItemService_.update(
|
||||
inventoryItemId,
|
||||
input
|
||||
)
|
||||
const inventoryItem = await this.inventoryItemService_
|
||||
.withTransaction(this.getManager())
|
||||
.update(inventoryItemId, input)
|
||||
return { ...inventoryItem }
|
||||
}
|
||||
|
||||
@@ -232,7 +250,9 @@ export default class InventoryService
|
||||
* @param inventoryItemId - the id of the inventory item to delete
|
||||
*/
|
||||
async deleteInventoryItem(inventoryItemId: string): Promise<void> {
|
||||
return await this.inventoryItemService_.delete(inventoryItemId)
|
||||
return await this.inventoryItemService_
|
||||
.withTransaction(this.getManager())
|
||||
.delete(inventoryItemId)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -244,16 +264,20 @@ export default class InventoryService
|
||||
inventoryItemId: string,
|
||||
locationId: string
|
||||
): Promise<void> {
|
||||
const [inventoryLevel] = await this.inventoryLevelService_.list(
|
||||
{ inventory_item_id: inventoryItemId, location_id: locationId },
|
||||
{ take: 1 }
|
||||
)
|
||||
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_.delete(inventoryLevel.id)
|
||||
return await this.inventoryLevelService_
|
||||
.withTransaction(this.getManager())
|
||||
.delete(inventoryLevel.id)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -268,10 +292,12 @@ export default class InventoryService
|
||||
locationId: string,
|
||||
input: UpdateInventoryLevelInput
|
||||
): Promise<InventoryLevelDTO> {
|
||||
const [inventoryLevel] = await this.inventoryLevelService_.list(
|
||||
{ inventory_item_id: inventoryItemId, location_id: locationId },
|
||||
{ take: 1 }
|
||||
)
|
||||
const [inventoryLevel] = await this.inventoryLevelService_
|
||||
.withTransaction(this.getManager())
|
||||
.list(
|
||||
{ inventory_item_id: inventoryItemId, location_id: locationId },
|
||||
{ take: 1 }
|
||||
)
|
||||
|
||||
if (!inventoryLevel) {
|
||||
throw new MedusaError(
|
||||
@@ -280,7 +306,9 @@ export default class InventoryService
|
||||
)
|
||||
}
|
||||
|
||||
return await this.inventoryLevelService_.update(inventoryLevel.id, input)
|
||||
return await this.inventoryLevelService_
|
||||
.withTransaction(this.getManager())
|
||||
.update(inventoryLevel.id, input)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -293,7 +321,9 @@ export default class InventoryService
|
||||
reservationItemId: string,
|
||||
input: UpdateReservationItemInput
|
||||
): Promise<ReservationItemDTO> {
|
||||
return await this.reservationItemService_.update(reservationItemId, input)
|
||||
return await this.reservationItemService_
|
||||
.withTransaction(this.getManager())
|
||||
.update(reservationItemId, input)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -301,7 +331,9 @@ export default class InventoryService
|
||||
* @param lineItemId - the id of the line item associated with the reservation item
|
||||
*/
|
||||
async deleteReservationItemsByLineItem(lineItemId: string): Promise<void> {
|
||||
return await this.reservationItemService_.deleteByLineItem(lineItemId)
|
||||
return await this.reservationItemService_
|
||||
.withTransaction(this.getManager())
|
||||
.deleteByLineItem(lineItemId)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -309,7 +341,9 @@ export default class InventoryService
|
||||
* @param reservationItemId - the id of the reservation item to delete
|
||||
*/
|
||||
async deleteReservationItem(reservationItemId: string): Promise<void> {
|
||||
return await this.reservationItemService_.delete(reservationItemId)
|
||||
return await this.reservationItemService_
|
||||
.withTransaction(this.getManager())
|
||||
.delete(reservationItemId)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -325,10 +359,12 @@ export default class InventoryService
|
||||
locationId: string,
|
||||
adjustment: number
|
||||
): Promise<InventoryLevelDTO> {
|
||||
const [inventoryLevel] = await this.inventoryLevelService_.list(
|
||||
{ inventory_item_id: inventoryItemId, location_id: locationId },
|
||||
{ take: 1 }
|
||||
)
|
||||
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,
|
||||
@@ -336,12 +372,11 @@ export default class InventoryService
|
||||
)
|
||||
}
|
||||
|
||||
const updatedInventoryLevel = await this.inventoryLevelService_.update(
|
||||
inventoryLevel.id,
|
||||
{
|
||||
const updatedInventoryLevel = await this.inventoryLevelService_
|
||||
.withTransaction(this.getManager())
|
||||
.update(inventoryLevel.id, {
|
||||
stocked_quantity: inventoryLevel.stocked_quantity + adjustment,
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
return { ...updatedInventoryLevel }
|
||||
}
|
||||
@@ -358,15 +393,15 @@ export default class InventoryService
|
||||
locationIds: string[]
|
||||
): Promise<number> {
|
||||
// Throws if item does not exist
|
||||
await this.inventoryItemService_.retrieve(inventoryItemId, {
|
||||
select: ["id"],
|
||||
})
|
||||
await this.inventoryItemService_
|
||||
.withTransaction(this.getManager())
|
||||
.retrieve(inventoryItemId, {
|
||||
select: ["id"],
|
||||
})
|
||||
|
||||
const availableQuantity =
|
||||
await this.inventoryLevelService_.getAvailableQuantity(
|
||||
inventoryItemId,
|
||||
locationIds
|
||||
)
|
||||
const availableQuantity = await this.inventoryLevelService_
|
||||
.withTransaction(this.getManager())
|
||||
.getAvailableQuantity(inventoryItemId, locationIds)
|
||||
|
||||
return availableQuantity
|
||||
}
|
||||
@@ -383,15 +418,15 @@ export default class InventoryService
|
||||
locationIds: string[]
|
||||
): Promise<number> {
|
||||
// Throws if item does not exist
|
||||
await this.inventoryItemService_.retrieve(inventoryItemId, {
|
||||
select: ["id"],
|
||||
})
|
||||
await this.inventoryItemService_
|
||||
.withTransaction(this.getManager())
|
||||
.retrieve(inventoryItemId, {
|
||||
select: ["id"],
|
||||
})
|
||||
|
||||
const stockedQuantity =
|
||||
await this.inventoryLevelService_.getStockedQuantity(
|
||||
inventoryItemId,
|
||||
locationIds
|
||||
)
|
||||
const stockedQuantity = await this.inventoryLevelService_
|
||||
.withTransaction(this.getManager())
|
||||
.getStockedQuantity(inventoryItemId, locationIds)
|
||||
|
||||
return stockedQuantity
|
||||
}
|
||||
@@ -408,15 +443,15 @@ export default class InventoryService
|
||||
locationIds: string[]
|
||||
): Promise<number> {
|
||||
// Throws if item does not exist
|
||||
await this.inventoryItemService_.retrieve(inventoryItemId, {
|
||||
select: ["id"],
|
||||
})
|
||||
await this.inventoryItemService_
|
||||
.withTransaction(this.getManager())
|
||||
.retrieve(inventoryItemId, {
|
||||
select: ["id"],
|
||||
})
|
||||
|
||||
const reservedQuantity =
|
||||
await this.inventoryLevelService_.getReservedQuantity(
|
||||
inventoryItemId,
|
||||
locationIds
|
||||
)
|
||||
const reservedQuantity = await this.inventoryLevelService_
|
||||
.withTransaction(this.getManager())
|
||||
.getReservedQuantity(inventoryItemId, locationIds)
|
||||
|
||||
return reservedQuantity
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ describe("POST /admin/products/:id/variants", () => {
|
||||
IdMap.getId("productWithOptions"),
|
||||
{
|
||||
inventory_quantity: 0,
|
||||
manage_inventory: true,
|
||||
title: "Test Product Variant",
|
||||
options: [],
|
||||
prices: [
|
||||
|
||||
@@ -7,17 +7,33 @@ import {
|
||||
IsString,
|
||||
ValidateNested,
|
||||
} from "class-validator"
|
||||
import { defaultAdminProductFields, defaultAdminProductRelations } from "."
|
||||
import { ProductService, ProductVariantService } from "../../../../services"
|
||||
|
||||
import { Type } from "class-transformer"
|
||||
import { EntityManager } from "typeorm"
|
||||
import {
|
||||
ProductService,
|
||||
ProductVariantService,
|
||||
ProductVariantInventoryService,
|
||||
} from "../../../../services"
|
||||
import { defaultAdminProductFields, defaultAdminProductRelations } from "."
|
||||
|
||||
import { IInventoryService } from "../../../../interfaces"
|
||||
import {
|
||||
CreateProductVariantInput,
|
||||
ProductVariantPricesCreateReq,
|
||||
} from "../../../../types/product-variant"
|
||||
import { validator } from "../../../../utils/validator"
|
||||
|
||||
import {
|
||||
TransactionHandlerType,
|
||||
TransactionOrchestrator,
|
||||
TransactionPayload,
|
||||
TransactionState,
|
||||
TransactionStepsDefinition,
|
||||
} from "../../../../utils/transaction"
|
||||
|
||||
import { ulid } from "ulid"
|
||||
import { MedusaError } from "medusa-core-utils"
|
||||
import { EntityManager } from "typeorm"
|
||||
|
||||
/**
|
||||
* @oas [post] /products/{id}/variants
|
||||
* operationId: "PostProductsProductVariants"
|
||||
@@ -103,6 +119,48 @@ import { validator } from "../../../../utils/validator"
|
||||
* "500":
|
||||
* $ref: "#/components/responses/500_error"
|
||||
*/
|
||||
|
||||
enum actions {
|
||||
createVariant = "createVariant",
|
||||
createInventoryItem = "createInventoryItem",
|
||||
attachInventoryItem = "attachInventoryItem",
|
||||
}
|
||||
|
||||
const simpleFlow: TransactionStepsDefinition = {
|
||||
next: {
|
||||
action: actions.createVariant,
|
||||
maxRetries: 0,
|
||||
},
|
||||
}
|
||||
|
||||
const flowWithInventory: TransactionStepsDefinition = {
|
||||
next: {
|
||||
action: actions.createVariant,
|
||||
forwardResponse: true,
|
||||
maxRetries: 0,
|
||||
next: {
|
||||
action: actions.createInventoryItem,
|
||||
forwardResponse: true,
|
||||
maxRetries: 0,
|
||||
next: {
|
||||
action: actions.attachInventoryItem,
|
||||
noCompensation: true,
|
||||
maxRetries: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const createSimpleVariantStrategy = new TransactionOrchestrator(
|
||||
"create-variant",
|
||||
simpleFlow
|
||||
)
|
||||
|
||||
const createVariantStrategyWithInventory = new TransactionOrchestrator(
|
||||
"create-variant-with-inventory",
|
||||
flowWithInventory
|
||||
)
|
||||
|
||||
export default async (req, res) => {
|
||||
const { id } = req.params
|
||||
|
||||
@@ -111,18 +169,140 @@ export default async (req, res) => {
|
||||
req.body
|
||||
)
|
||||
|
||||
const inventoryService: IInventoryService | undefined =
|
||||
req.scope.resolve("inventoryService")
|
||||
const productVariantInventoryService: ProductVariantInventoryService =
|
||||
req.scope.resolve("productVariantInventoryService")
|
||||
const productVariantService: ProductVariantService = req.scope.resolve(
|
||||
"productVariantService"
|
||||
)
|
||||
const productService: ProductService = req.scope.resolve("productService")
|
||||
|
||||
const createdId: Record<string, string | null> = {
|
||||
variant: null,
|
||||
inventoryItem: null,
|
||||
}
|
||||
|
||||
const manager: EntityManager = req.scope.resolve("manager")
|
||||
await manager.transaction(async (transactionManager) => {
|
||||
return await productVariantService
|
||||
.withTransaction(transactionManager)
|
||||
.create(id, validated as CreateProductVariantInput)
|
||||
const inventoryServiceTx =
|
||||
inventoryService?.withTransaction(transactionManager)
|
||||
|
||||
const productVariantInventoryServiceTx =
|
||||
productVariantInventoryService.withTransaction(transactionManager)
|
||||
|
||||
const productVariantServiceTx =
|
||||
productVariantService.withTransaction(transactionManager)
|
||||
|
||||
async function createVariant() {
|
||||
const variant = await productVariantServiceTx.create(
|
||||
id,
|
||||
validated as CreateProductVariantInput
|
||||
)
|
||||
|
||||
createdId.variant = variant.id
|
||||
|
||||
return { variant }
|
||||
}
|
||||
|
||||
async function removeVariant() {
|
||||
if (createdId.variant) {
|
||||
await productVariantServiceTx.delete(createdId.variant)
|
||||
}
|
||||
}
|
||||
|
||||
async function createInventoryItem(variant) {
|
||||
if (!validated.manage_inventory) {
|
||||
return
|
||||
}
|
||||
|
||||
const inventoryItem = await inventoryServiceTx!.createInventoryItem({
|
||||
sku: validated.sku,
|
||||
origin_country: validated.origin_country,
|
||||
hs_code: validated.hs_code,
|
||||
mid_code: validated.mid_code,
|
||||
material: validated.material,
|
||||
weight: validated.weight,
|
||||
length: validated.length,
|
||||
height: validated.height,
|
||||
width: validated.width,
|
||||
})
|
||||
|
||||
createdId.inventoryItem = inventoryItem.id
|
||||
|
||||
return { variant, inventoryItem }
|
||||
}
|
||||
|
||||
async function removeInventoryItem() {
|
||||
if (createdId.inventoryItem) {
|
||||
await inventoryServiceTx!.deleteInventoryItem(createdId.inventoryItem)
|
||||
}
|
||||
}
|
||||
|
||||
async function attachInventoryItem(variant, inventoryItem) {
|
||||
if (!validated.manage_inventory) {
|
||||
return
|
||||
}
|
||||
|
||||
await productVariantInventoryServiceTx.attachInventoryItem(
|
||||
variant.id,
|
||||
inventoryItem.id,
|
||||
validated.inventory_quantity
|
||||
)
|
||||
}
|
||||
|
||||
async function transactionHandler(
|
||||
actionId: string,
|
||||
type: TransactionHandlerType,
|
||||
payload: TransactionPayload
|
||||
) {
|
||||
const command = {
|
||||
[actions.createVariant]: {
|
||||
[TransactionHandlerType.INVOKE]: async () => {
|
||||
return await createVariant()
|
||||
},
|
||||
[TransactionHandlerType.COMPENSATE]: async () => {
|
||||
await removeVariant()
|
||||
},
|
||||
},
|
||||
[actions.createInventoryItem]: {
|
||||
[TransactionHandlerType.INVOKE]: async (data) => {
|
||||
const { variant } = data._response ?? {}
|
||||
return await createInventoryItem(variant)
|
||||
},
|
||||
[TransactionHandlerType.COMPENSATE]: async () => {
|
||||
await removeInventoryItem()
|
||||
},
|
||||
},
|
||||
[actions.attachInventoryItem]: {
|
||||
[TransactionHandlerType.INVOKE]: async (data) => {
|
||||
const { variant, inventoryItem } = data._response ?? {}
|
||||
return await attachInventoryItem(variant, inventoryItem)
|
||||
},
|
||||
},
|
||||
}
|
||||
return command[actionId][type](payload.data)
|
||||
}
|
||||
|
||||
const strategy = inventoryService
|
||||
? createVariantStrategyWithInventory
|
||||
: createSimpleVariantStrategy
|
||||
|
||||
const transaction = await strategy.beginTransaction(
|
||||
ulid(),
|
||||
transactionHandler,
|
||||
validated
|
||||
)
|
||||
await strategy.resume(transaction)
|
||||
|
||||
if (transaction.getState() !== TransactionState.DONE) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
transaction.errors.map((err) => err.error?.message).join("\n")
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
const productService: ProductService = req.scope.resolve("productService")
|
||||
const product = await productService.retrieve(id, {
|
||||
select: defaultAdminProductFields,
|
||||
relations: defaultAdminProductRelations,
|
||||
@@ -175,6 +355,7 @@ class ProductVariantOptionReq {
|
||||
* manage_inventory:
|
||||
* description: Whether Medusa should keep track of the inventory for this Product Variant.
|
||||
* type: boolean
|
||||
* default: true
|
||||
* weight:
|
||||
* description: The wieght of the Product Variant.
|
||||
* type: number
|
||||
@@ -274,7 +455,7 @@ export class AdminPostProductsProductVariantsReq {
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
manage_inventory?: boolean
|
||||
manage_inventory?: boolean = true
|
||||
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { EntityManager } from "typeorm"
|
||||
import { FindConfig } from "../../types/common"
|
||||
|
||||
import {
|
||||
@@ -15,6 +16,8 @@ import {
|
||||
} from "../../types/inventory"
|
||||
|
||||
export interface IInventoryService {
|
||||
withTransaction(transactionManager?: EntityManager): this
|
||||
|
||||
listInventoryItems(
|
||||
selector: FilterableInventoryItemProps,
|
||||
config?: FindConfig<InventoryItemDTO>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { EntityManager } from "typeorm"
|
||||
import { FindConfig } from "../../types/common"
|
||||
|
||||
import {
|
||||
@@ -8,6 +9,7 @@ import {
|
||||
} from "../../types/stock-location"
|
||||
|
||||
export interface IStockLocationService {
|
||||
withTransaction(transactionManager?: EntityManager): this
|
||||
list(
|
||||
selector: FilterableStockLocationProps,
|
||||
config?: FindConfig<StockLocationDTO>
|
||||
|
||||
@@ -81,7 +81,7 @@ describe("modules loader", () => {
|
||||
container = buildContainer()
|
||||
})
|
||||
|
||||
it("registers service as false in container when no resolution path is given", async () => {
|
||||
it("registers service as undefined in container when no resolution path is given", async () => {
|
||||
const moduleResolutions: Record<string, ModuleResolution> = {
|
||||
testService: {
|
||||
resolutionPath: false,
|
||||
@@ -110,7 +110,7 @@ describe("modules loader", () => {
|
||||
const testService = container.resolve(
|
||||
moduleResolutions.testService.definition.key
|
||||
)
|
||||
expect(testService).toBe(false)
|
||||
expect(testService).toBe(undefined)
|
||||
})
|
||||
|
||||
it("registers service ", async () => {
|
||||
|
||||
@@ -32,7 +32,7 @@ const registerModule = async (
|
||||
}
|
||||
|
||||
container.register({
|
||||
[constainerName]: asValue(false),
|
||||
[constainerName]: asValue(undefined),
|
||||
})
|
||||
|
||||
return {
|
||||
@@ -42,7 +42,7 @@ const registerModule = async (
|
||||
|
||||
if (!resolution.resolutionPath) {
|
||||
container.register({
|
||||
[constainerName]: asValue(false),
|
||||
[constainerName]: asValue(undefined),
|
||||
})
|
||||
|
||||
return
|
||||
|
||||
@@ -106,11 +106,13 @@ class ProductVariantInventoryService extends TransactionBaseService {
|
||||
const hasInventory = await Promise.all(
|
||||
variantInventory.map(async (inventoryPart) => {
|
||||
const itemQuantity = inventoryPart.required_quantity * quantity
|
||||
return await this.inventoryService_.confirmInventory(
|
||||
inventoryPart.inventory_item_id,
|
||||
locations,
|
||||
itemQuantity
|
||||
)
|
||||
return await this.inventoryService_
|
||||
.withTransaction(manager)
|
||||
.confirmInventory(
|
||||
inventoryPart.inventory_item_id,
|
||||
locations,
|
||||
itemQuantity
|
||||
)
|
||||
})
|
||||
)
|
||||
|
||||
@@ -250,9 +252,11 @@ class ProductVariantInventoryService extends TransactionBaseService {
|
||||
})
|
||||
|
||||
// Verify that item exists
|
||||
await this.inventoryService_.retrieveInventoryItem(inventoryItemId, {
|
||||
select: ["id"],
|
||||
})
|
||||
await this.inventoryService_
|
||||
.withTransaction(manager)
|
||||
.retrieveInventoryItem(inventoryItemId, {
|
||||
select: ["id"],
|
||||
})
|
||||
|
||||
const variantInventoryRepo = manager.getRepository(
|
||||
ProductVariantInventoryItem
|
||||
@@ -374,12 +378,14 @@ class ProductVariantInventoryService extends TransactionBaseService {
|
||||
await Promise.all(
|
||||
variantInventory.map(async (inventoryPart) => {
|
||||
const itemQuantity = inventoryPart.required_quantity * quantity
|
||||
return await this.inventoryService_.createReservationItem({
|
||||
...toReserve,
|
||||
location_id: locationId as string,
|
||||
inventory_item_id: inventoryPart.inventory_item_id,
|
||||
quantity: itemQuantity,
|
||||
})
|
||||
return await this.inventoryService_
|
||||
.withTransaction(manager)
|
||||
.createReservationItem({
|
||||
...toReserve,
|
||||
location_id: locationId as string,
|
||||
inventory_item_id: inventoryPart.inventory_item_id,
|
||||
quantity: itemQuantity,
|
||||
})
|
||||
})
|
||||
)
|
||||
}
|
||||
@@ -414,8 +420,11 @@ class ProductVariantInventoryService extends TransactionBaseService {
|
||||
})
|
||||
})
|
||||
}
|
||||
const [reservations, reservationCount] =
|
||||
await this.inventoryService_.listReservationItems(
|
||||
|
||||
const manager = this.transactionManager_ || this.manager_
|
||||
const [reservations, reservationCount] = await this.inventoryService_
|
||||
.withTransaction(manager)
|
||||
.listReservationItems(
|
||||
{
|
||||
line_item_id: lineItemId,
|
||||
},
|
||||
@@ -442,11 +451,15 @@ class ProductVariantInventoryService extends TransactionBaseService {
|
||||
quantity * productVariantInventory.required_quantity
|
||||
|
||||
if (reservationQtyUpdate === 0) {
|
||||
await this.inventoryService_.deleteReservationItem(reservation.id)
|
||||
await this.inventoryService_
|
||||
.withTransaction(manager)
|
||||
.deleteReservationItem(reservation.id)
|
||||
} else {
|
||||
await this.inventoryService_.updateReservationItem(reservation.id, {
|
||||
quantity: reservationQtyUpdate,
|
||||
})
|
||||
await this.inventoryService_
|
||||
.withTransaction(manager)
|
||||
.updateReservationItem(reservation.id, {
|
||||
quantity: reservationQtyUpdate,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -523,7 +536,10 @@ class ProductVariantInventoryService extends TransactionBaseService {
|
||||
})
|
||||
}
|
||||
|
||||
await this.inventoryService_.deleteReservationItemsByLineItem(lineItemId)
|
||||
const manager = this.transactionManager_ || this.manager_
|
||||
await this.inventoryService_
|
||||
.withTransaction(manager)
|
||||
.deleteReservationItemsByLineItem(lineItemId)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -554,6 +570,7 @@ class ProductVariantInventoryService extends TransactionBaseService {
|
||||
})
|
||||
})
|
||||
} else {
|
||||
const manager = this.transactionManager_ || this.manager_
|
||||
const variantInventory = await this.listByVariant(variantId)
|
||||
|
||||
if (variantInventory.length === 0) {
|
||||
@@ -563,11 +580,13 @@ class ProductVariantInventoryService extends TransactionBaseService {
|
||||
await Promise.all(
|
||||
variantInventory.map(async (inventoryPart) => {
|
||||
const itemQuantity = inventoryPart.required_quantity * quantity
|
||||
return await this.inventoryService_.adjustInventory(
|
||||
inventoryPart.inventory_item_id,
|
||||
locationId,
|
||||
itemQuantity
|
||||
)
|
||||
return await this.inventoryService_
|
||||
.withTransaction(manager)
|
||||
.adjustInventory(
|
||||
inventoryPart.inventory_item_id,
|
||||
locationId,
|
||||
itemQuantity
|
||||
)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user