chore: added missing withTransacton, create-variant using TO (#3047)

Create variant integrated with Inventory modules
This commit is contained in:
Carlos R. L. Rodrigues
2023-01-18 09:29:06 -03:00
committed by GitHub
parent 150696de99
commit aa54d902e5
11 changed files with 409 additions and 128 deletions
+27 -12
View File
@@ -1,53 +1,68 @@
if (process.env.NODE_ENV !== "development") { if (process.env.NODE_ENV !== "development") {
return return
} }
const path = require("path") const path = require("path")
const Module = require("module") const Module = require("module")
const originalRequire = Module.prototype.require const originalRequire = Module.prototype.require
const medusaCore = path.resolve(path.join(__dirname, "../../packages")) const medusaCore = path.resolve(path.join(__dirname, "../../packages"))
function replacePath(requirePath, package, concatPackage = true) { function replacePath(requirePath, pack, concatPackage = true) {
const idx = requirePath.indexOf(package) const idx = requirePath.indexOf(pack)
const packPath = requirePath.substring(idx + package.length) const packPath = requirePath.substring(idx + pack.length).replace(/\\/g, "/")
let newPath = let newPath =
medusaCore + medusaCore +
"/" + "/" +
(concatPackage ? package + "/" : "") + (concatPackage ? pack + "/" : "") +
packPath.replace("/dist", "/src").replace(".js", "") packPath.replace("/dist", "/src").replace(".js", "")
if (!newPath.includes("/src")) { if (!newPath.includes("/src")) {
newPath += "/src" newPath += "/src"
} }
return path.resolve(newPath) return path.resolve(newPath)
} }
Module.prototype.require = function (...args) { function checkAndReplacePaths(path) {
const interfaces = "medusa-interfaces" const interfaces = "medusa-interfaces"
const utils = "medusa-core-utils" const utils = "medusa-core-utils"
const base = "@medusajs" const base = "@medusajs"
if (args[0].includes(base)) { if (path.includes(base)) {
args[0] = replacePath(args[0], base, false) path = replacePath(path, base, false)
} else if (args[0].includes(interfaces)) { } else if (path.includes(interfaces)) {
args[0] = replacePath(args[0], interfaces) path = replacePath(path, interfaces)
} else if (args[0].includes(utils)) { } else if (path.includes(utils)) {
args[0] = replacePath(args[0], utils) path = replacePath(path, utils)
} }
return path
}
Module.prototype.require = function (...args) {
args[0] = checkAndReplacePaths(args[0])
if (args[0] === "glob") { if (args[0] === "glob") {
const glob = originalRequire.apply(this, args) const glob = originalRequire.apply(this, args)
const originalGlobSync = glob.sync const originalGlobSync = glob.sync
glob.GlobSync = glob.sync = (pattern, options) => { glob.GlobSync = glob.sync = (pattern, options) => {
if (pattern.endsWith(".js") || pattern.endsWith(".ts")) { if (pattern.endsWith(".js") || pattern.endsWith(".ts")) {
pattern = checkAndReplacePaths(pattern)
pattern = pattern.replace(".js", ".{j,t}s").replace("/dist/", "/src/") pattern = pattern.replace(".js", ".{j,t}s").replace("/dist/", "/src/")
} }
return originalGlobSync.apply(this, [pattern, options]) return originalGlobSync.apply(this, [pattern, options])
} }
return glob 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) return originalRequire.apply(this, args)
+29 -4
View File
@@ -14,6 +14,22 @@ const medusaCore = path
.replace(/\\/g, "/") .replace(/\\/g, "/")
let WATCHING = false 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 = () => { const watchFiles = () => {
if (WATCHING) { if (WATCHING) {
return return
@@ -42,7 +58,12 @@ const watchFiles = () => {
}) })
watcher.on("change", async function (rawFile) { watcher.on("change", async function (rawFile) {
if (IS_RELOADING) {
return
}
console.log("Reloading server...") console.log("Reloading server...")
IS_RELOADING = true
const start = Date.now() const start = Date.now()
const file = rawFile.replace(/\\/g, "/") const file = rawFile.replace(/\\/g, "/")
@@ -75,12 +96,18 @@ const watchFiles = () => {
next.endsWith(".ts") || next.endsWith(".ts") ||
name.startsWith(next) 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() await bootstrapApp()
IS_RELOADING = false
console.log("Server reloaded in", Date.now() - start, "ms") console.log("Server reloaded in", Date.now() - start, "ms")
}) })
@@ -119,8 +146,6 @@ const bootstrapApp = async () => {
watchFiles() watchFiles()
console.log(`Server Running at localhost:${port}`) 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") 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" ( CREATE TABLE "reservation_item" (
+107 -72
View File
@@ -75,6 +75,10 @@ export default class InventoryService
}) })
} }
private getManager(): EntityManager {
return this.transactionManager_ ?? this.manager_
}
/** /**
* Lists inventory items that match the given selector * Lists inventory items that match the given selector
* @param selector - the selector to filter inventory items by * @param selector - the selector to filter inventory items by
@@ -85,7 +89,9 @@ export default class InventoryService
selector: FilterableInventoryItemProps, selector: FilterableInventoryItemProps,
config: FindConfig<InventoryItemDTO> = { relations: [], skip: 0, take: 10 } config: FindConfig<InventoryItemDTO> = { relations: [], skip: 0, take: 10 }
): Promise<[InventoryItemDTO[], number]> { ): 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, selector: FilterableInventoryLevelProps,
config: FindConfig<InventoryLevelDTO> = { relations: [], skip: 0, take: 10 } config: FindConfig<InventoryLevelDTO> = { relations: [], skip: 0, take: 10 }
): Promise<[InventoryLevelDTO[], number]> { ): 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, take: 10,
} }
): Promise<[ReservationItemDTO[], number]> { ): 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, inventoryItemId: string,
config?: FindConfig<InventoryItemDTO> config?: FindConfig<InventoryItemDTO>
): Promise<InventoryItemDTO> { ): Promise<InventoryItemDTO> {
const inventoryItem = await this.inventoryItemService_.retrieve( const inventoryItem = await this.inventoryItemService_
inventoryItemId, .withTransaction(this.getManager())
config .retrieve(inventoryItemId, config)
)
return { ...inventoryItem } return { ...inventoryItem }
} }
@@ -145,10 +154,12 @@ export default class InventoryService
inventoryItemId: string, inventoryItemId: string,
locationId: string locationId: string
): Promise<InventoryLevelDTO> { ): Promise<InventoryLevelDTO> {
const [inventoryLevel] = await this.inventoryLevelService_.list( const [inventoryLevel] = await this.inventoryLevelService_
{ inventory_item_id: inventoryItemId, location_id: locationId }, .withTransaction(this.getManager())
{ take: 1 } .list(
) { inventory_item_id: inventoryItemId, location_id: locationId },
{ take: 1 }
)
if (!inventoryLevel) { if (!inventoryLevel) {
throw new MedusaError( throw new MedusaError(
MedusaError.Types.NOT_FOUND, MedusaError.Types.NOT_FOUND,
@@ -167,13 +178,15 @@ export default class InventoryService
input: CreateReservationItemInput input: CreateReservationItemInput
): Promise<ReservationItemDTO> { ): Promise<ReservationItemDTO> {
// Verify that the item is stocked at the location // Verify that the item is stocked at the location
const [inventoryLevel] = await this.inventoryLevelService_.list( const [inventoryLevel] = await this.inventoryLevelService_
{ .withTransaction(this.getManager())
inventory_item_id: input.inventory_item_id, .list(
location_id: input.location_id, {
}, inventory_item_id: input.inventory_item_id,
{ take: 1 } location_id: input.location_id,
) },
{ take: 1 }
)
if (!inventoryLevel) { if (!inventoryLevel) {
throw new MedusaError( 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 } return { ...reservationItem }
} }
@@ -195,7 +210,9 @@ export default class InventoryService
async createInventoryItem( async createInventoryItem(
input: CreateInventoryItemInput input: CreateInventoryItemInput
): Promise<InventoryItemDTO> { ): Promise<InventoryItemDTO> {
const inventoryItem = await this.inventoryItemService_.create(input) const inventoryItem = await this.inventoryItemService_
.withTransaction(this.getManager())
.create(input)
return { ...inventoryItem } return { ...inventoryItem }
} }
@@ -207,7 +224,9 @@ export default class InventoryService
async createInventoryLevel( async createInventoryLevel(
input: CreateInventoryLevelInput input: CreateInventoryLevelInput
): Promise<InventoryLevelDTO> { ): 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, inventoryItemId: string,
input: Partial<CreateInventoryItemInput> input: Partial<CreateInventoryItemInput>
): Promise<InventoryItemDTO> { ): Promise<InventoryItemDTO> {
const inventoryItem = await this.inventoryItemService_.update( const inventoryItem = await this.inventoryItemService_
inventoryItemId, .withTransaction(this.getManager())
input .update(inventoryItemId, input)
)
return { ...inventoryItem } return { ...inventoryItem }
} }
@@ -232,7 +250,9 @@ export default class InventoryService
* @param inventoryItemId - the id of the inventory item to delete * @param inventoryItemId - the id of the inventory item to delete
*/ */
async deleteInventoryItem(inventoryItemId: string): Promise<void> { 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, inventoryItemId: string,
locationId: string locationId: string
): Promise<void> { ): Promise<void> {
const [inventoryLevel] = await this.inventoryLevelService_.list( const [inventoryLevel] = await this.inventoryLevelService_
{ inventory_item_id: inventoryItemId, location_id: locationId }, .withTransaction(this.getManager())
{ take: 1 } .list(
) { inventory_item_id: inventoryItemId, location_id: locationId },
{ take: 1 }
)
if (!inventoryLevel) { if (!inventoryLevel) {
return 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, locationId: string,
input: UpdateInventoryLevelInput input: UpdateInventoryLevelInput
): Promise<InventoryLevelDTO> { ): Promise<InventoryLevelDTO> {
const [inventoryLevel] = await this.inventoryLevelService_.list( const [inventoryLevel] = await this.inventoryLevelService_
{ inventory_item_id: inventoryItemId, location_id: locationId }, .withTransaction(this.getManager())
{ take: 1 } .list(
) { inventory_item_id: inventoryItemId, location_id: locationId },
{ take: 1 }
)
if (!inventoryLevel) { if (!inventoryLevel) {
throw new MedusaError( 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, reservationItemId: string,
input: UpdateReservationItemInput input: UpdateReservationItemInput
): Promise<ReservationItemDTO> { ): 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 * @param lineItemId - the id of the line item associated with the reservation item
*/ */
async deleteReservationItemsByLineItem(lineItemId: string): Promise<void> { 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 * @param reservationItemId - the id of the reservation item to delete
*/ */
async deleteReservationItem(reservationItemId: string): Promise<void> { 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, locationId: string,
adjustment: number adjustment: number
): Promise<InventoryLevelDTO> { ): Promise<InventoryLevelDTO> {
const [inventoryLevel] = await this.inventoryLevelService_.list( const [inventoryLevel] = await this.inventoryLevelService_
{ inventory_item_id: inventoryItemId, location_id: locationId }, .withTransaction(this.getManager())
{ take: 1 } .list(
) { inventory_item_id: inventoryItemId, location_id: locationId },
{ take: 1 }
)
if (!inventoryLevel) { if (!inventoryLevel) {
throw new MedusaError( throw new MedusaError(
MedusaError.Types.NOT_FOUND, MedusaError.Types.NOT_FOUND,
@@ -336,12 +372,11 @@ export default class InventoryService
) )
} }
const updatedInventoryLevel = await this.inventoryLevelService_.update( const updatedInventoryLevel = await this.inventoryLevelService_
inventoryLevel.id, .withTransaction(this.getManager())
{ .update(inventoryLevel.id, {
stocked_quantity: inventoryLevel.stocked_quantity + adjustment, stocked_quantity: inventoryLevel.stocked_quantity + adjustment,
} })
)
return { ...updatedInventoryLevel } return { ...updatedInventoryLevel }
} }
@@ -358,15 +393,15 @@ export default class InventoryService
locationIds: string[] locationIds: string[]
): Promise<number> { ): Promise<number> {
// Throws if item does not exist // Throws if item does not exist
await this.inventoryItemService_.retrieve(inventoryItemId, { await this.inventoryItemService_
select: ["id"], .withTransaction(this.getManager())
}) .retrieve(inventoryItemId, {
select: ["id"],
})
const availableQuantity = const availableQuantity = await this.inventoryLevelService_
await this.inventoryLevelService_.getAvailableQuantity( .withTransaction(this.getManager())
inventoryItemId, .getAvailableQuantity(inventoryItemId, locationIds)
locationIds
)
return availableQuantity return availableQuantity
} }
@@ -383,15 +418,15 @@ export default class InventoryService
locationIds: string[] locationIds: string[]
): Promise<number> { ): Promise<number> {
// Throws if item does not exist // Throws if item does not exist
await this.inventoryItemService_.retrieve(inventoryItemId, { await this.inventoryItemService_
select: ["id"], .withTransaction(this.getManager())
}) .retrieve(inventoryItemId, {
select: ["id"],
})
const stockedQuantity = const stockedQuantity = await this.inventoryLevelService_
await this.inventoryLevelService_.getStockedQuantity( .withTransaction(this.getManager())
inventoryItemId, .getStockedQuantity(inventoryItemId, locationIds)
locationIds
)
return stockedQuantity return stockedQuantity
} }
@@ -408,15 +443,15 @@ export default class InventoryService
locationIds: string[] locationIds: string[]
): Promise<number> { ): Promise<number> {
// Throws if item does not exist // Throws if item does not exist
await this.inventoryItemService_.retrieve(inventoryItemId, { await this.inventoryItemService_
select: ["id"], .withTransaction(this.getManager())
}) .retrieve(inventoryItemId, {
select: ["id"],
})
const reservedQuantity = const reservedQuantity = await this.inventoryLevelService_
await this.inventoryLevelService_.getReservedQuantity( .withTransaction(this.getManager())
inventoryItemId, .getReservedQuantity(inventoryItemId, locationIds)
locationIds
)
return reservedQuantity return reservedQuantity
} }
@@ -39,6 +39,7 @@ describe("POST /admin/products/:id/variants", () => {
IdMap.getId("productWithOptions"), IdMap.getId("productWithOptions"),
{ {
inventory_quantity: 0, inventory_quantity: 0,
manage_inventory: true,
title: "Test Product Variant", title: "Test Product Variant",
options: [], options: [],
prices: [ prices: [
@@ -7,17 +7,33 @@ import {
IsString, IsString,
ValidateNested, ValidateNested,
} from "class-validator" } from "class-validator"
import { defaultAdminProductFields, defaultAdminProductRelations } from "."
import { ProductService, ProductVariantService } from "../../../../services"
import { Type } from "class-transformer" import { Type } from "class-transformer"
import { EntityManager } from "typeorm" import {
ProductService,
ProductVariantService,
ProductVariantInventoryService,
} from "../../../../services"
import { defaultAdminProductFields, defaultAdminProductRelations } from "."
import { IInventoryService } from "../../../../interfaces"
import { import {
CreateProductVariantInput, CreateProductVariantInput,
ProductVariantPricesCreateReq, ProductVariantPricesCreateReq,
} from "../../../../types/product-variant" } from "../../../../types/product-variant"
import { validator } from "../../../../utils/validator" 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 * @oas [post] /products/{id}/variants
* operationId: "PostProductsProductVariants" * operationId: "PostProductsProductVariants"
@@ -103,6 +119,48 @@ import { validator } from "../../../../utils/validator"
* "500": * "500":
* $ref: "#/components/responses/500_error" * $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) => { export default async (req, res) => {
const { id } = req.params const { id } = req.params
@@ -111,18 +169,140 @@ export default async (req, res) => {
req.body req.body
) )
const inventoryService: IInventoryService | undefined =
req.scope.resolve("inventoryService")
const productVariantInventoryService: ProductVariantInventoryService =
req.scope.resolve("productVariantInventoryService")
const productVariantService: ProductVariantService = req.scope.resolve( const productVariantService: ProductVariantService = req.scope.resolve(
"productVariantService" "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") const manager: EntityManager = req.scope.resolve("manager")
await manager.transaction(async (transactionManager) => { await manager.transaction(async (transactionManager) => {
return await productVariantService const inventoryServiceTx =
.withTransaction(transactionManager) inventoryService?.withTransaction(transactionManager)
.create(id, validated as CreateProductVariantInput)
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, { const product = await productService.retrieve(id, {
select: defaultAdminProductFields, select: defaultAdminProductFields,
relations: defaultAdminProductRelations, relations: defaultAdminProductRelations,
@@ -175,6 +355,7 @@ class ProductVariantOptionReq {
* manage_inventory: * manage_inventory:
* description: Whether Medusa should keep track of the inventory for this Product Variant. * description: Whether Medusa should keep track of the inventory for this Product Variant.
* type: boolean * type: boolean
* default: true
* weight: * weight:
* description: The wieght of the Product Variant. * description: The wieght of the Product Variant.
* type: number * type: number
@@ -274,7 +455,7 @@ export class AdminPostProductsProductVariantsReq {
@IsBoolean() @IsBoolean()
@IsOptional() @IsOptional()
manage_inventory?: boolean manage_inventory?: boolean = true
@IsNumber() @IsNumber()
@IsOptional() @IsOptional()
@@ -1,3 +1,4 @@
import { EntityManager } from "typeorm"
import { FindConfig } from "../../types/common" import { FindConfig } from "../../types/common"
import { import {
@@ -15,6 +16,8 @@ import {
} from "../../types/inventory" } from "../../types/inventory"
export interface IInventoryService { export interface IInventoryService {
withTransaction(transactionManager?: EntityManager): this
listInventoryItems( listInventoryItems(
selector: FilterableInventoryItemProps, selector: FilterableInventoryItemProps,
config?: FindConfig<InventoryItemDTO> config?: FindConfig<InventoryItemDTO>
@@ -1,3 +1,4 @@
import { EntityManager } from "typeorm"
import { FindConfig } from "../../types/common" import { FindConfig } from "../../types/common"
import { import {
@@ -8,6 +9,7 @@ import {
} from "../../types/stock-location" } from "../../types/stock-location"
export interface IStockLocationService { export interface IStockLocationService {
withTransaction(transactionManager?: EntityManager): this
list( list(
selector: FilterableStockLocationProps, selector: FilterableStockLocationProps,
config?: FindConfig<StockLocationDTO> config?: FindConfig<StockLocationDTO>
@@ -81,7 +81,7 @@ describe("modules loader", () => {
container = buildContainer() 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> = { const moduleResolutions: Record<string, ModuleResolution> = {
testService: { testService: {
resolutionPath: false, resolutionPath: false,
@@ -110,7 +110,7 @@ describe("modules loader", () => {
const testService = container.resolve( const testService = container.resolve(
moduleResolutions.testService.definition.key moduleResolutions.testService.definition.key
) )
expect(testService).toBe(false) expect(testService).toBe(undefined)
}) })
it("registers service ", async () => { it("registers service ", async () => {
+2 -2
View File
@@ -32,7 +32,7 @@ const registerModule = async (
} }
container.register({ container.register({
[constainerName]: asValue(false), [constainerName]: asValue(undefined),
}) })
return { return {
@@ -42,7 +42,7 @@ const registerModule = async (
if (!resolution.resolutionPath) { if (!resolution.resolutionPath) {
container.register({ container.register({
[constainerName]: asValue(false), [constainerName]: asValue(undefined),
}) })
return return
@@ -106,11 +106,13 @@ class ProductVariantInventoryService extends TransactionBaseService {
const hasInventory = await Promise.all( const hasInventory = await Promise.all(
variantInventory.map(async (inventoryPart) => { variantInventory.map(async (inventoryPart) => {
const itemQuantity = inventoryPart.required_quantity * quantity const itemQuantity = inventoryPart.required_quantity * quantity
return await this.inventoryService_.confirmInventory( return await this.inventoryService_
inventoryPart.inventory_item_id, .withTransaction(manager)
locations, .confirmInventory(
itemQuantity inventoryPart.inventory_item_id,
) locations,
itemQuantity
)
}) })
) )
@@ -250,9 +252,11 @@ class ProductVariantInventoryService extends TransactionBaseService {
}) })
// Verify that item exists // Verify that item exists
await this.inventoryService_.retrieveInventoryItem(inventoryItemId, { await this.inventoryService_
select: ["id"], .withTransaction(manager)
}) .retrieveInventoryItem(inventoryItemId, {
select: ["id"],
})
const variantInventoryRepo = manager.getRepository( const variantInventoryRepo = manager.getRepository(
ProductVariantInventoryItem ProductVariantInventoryItem
@@ -374,12 +378,14 @@ class ProductVariantInventoryService extends TransactionBaseService {
await Promise.all( await Promise.all(
variantInventory.map(async (inventoryPart) => { variantInventory.map(async (inventoryPart) => {
const itemQuantity = inventoryPart.required_quantity * quantity const itemQuantity = inventoryPart.required_quantity * quantity
return await this.inventoryService_.createReservationItem({ return await this.inventoryService_
...toReserve, .withTransaction(manager)
location_id: locationId as string, .createReservationItem({
inventory_item_id: inventoryPart.inventory_item_id, ...toReserve,
quantity: itemQuantity, 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, line_item_id: lineItemId,
}, },
@@ -442,11 +451,15 @@ class ProductVariantInventoryService extends TransactionBaseService {
quantity * productVariantInventory.required_quantity quantity * productVariantInventory.required_quantity
if (reservationQtyUpdate === 0) { if (reservationQtyUpdate === 0) {
await this.inventoryService_.deleteReservationItem(reservation.id) await this.inventoryService_
.withTransaction(manager)
.deleteReservationItem(reservation.id)
} else { } else {
await this.inventoryService_.updateReservationItem(reservation.id, { await this.inventoryService_
quantity: reservationQtyUpdate, .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 { } else {
const manager = this.transactionManager_ || this.manager_
const variantInventory = await this.listByVariant(variantId) const variantInventory = await this.listByVariant(variantId)
if (variantInventory.length === 0) { if (variantInventory.length === 0) {
@@ -563,11 +580,13 @@ class ProductVariantInventoryService extends TransactionBaseService {
await Promise.all( await Promise.all(
variantInventory.map(async (inventoryPart) => { variantInventory.map(async (inventoryPart) => {
const itemQuantity = inventoryPart.required_quantity * quantity const itemQuantity = inventoryPart.required_quantity * quantity
return await this.inventoryService_.adjustInventory( return await this.inventoryService_
inventoryPart.inventory_item_id, .withTransaction(manager)
locationId, .adjustInventory(
itemQuantity inventoryPart.inventory_item_id,
) locationId,
itemQuantity
)
}) })
) )
} }