From 999aeb116c4742e5b5e0d80793af23f7727276f0 Mon Sep 17 00:00:00 2001 From: Philip Korsholm <88927411+pKorsholm@users.noreply.github.com> Date: Thu, 30 Mar 2023 09:29:44 +0200 Subject: [PATCH] fix(medusa): Fix hanging inventory item migration script (#3624) --- .changeset/serious-geckos-change.md | 5 ++ .../src/scripts/migrate-inventory-items.ts | 90 ++++++++++++------- .../src/services/product-variant-inventory.ts | 10 ++- packages/medusa/src/services/staged-job.ts | 11 ++- packages/types/src/inventory/inventory.ts | 86 ++++++++++++------ .../src/stock-location/stock-location.ts | 26 ++++-- 6 files changed, 156 insertions(+), 72 deletions(-) create mode 100644 .changeset/serious-geckos-change.md diff --git a/.changeset/serious-geckos-change.md b/.changeset/serious-geckos-change.md new file mode 100644 index 0000000000..a256527505 --- /dev/null +++ b/.changeset/serious-geckos-change.md @@ -0,0 +1,5 @@ +--- +"@medusajs/medusa": patch +--- + +fix(medusa): Fix hanging inventory item migration script diff --git a/packages/medusa/src/scripts/migrate-inventory-items.ts b/packages/medusa/src/scripts/migrate-inventory-items.ts index b1c11cc8c6..1feafff007 100644 --- a/packages/medusa/src/scripts/migrate-inventory-items.ts +++ b/packages/medusa/src/scripts/migrate-inventory-items.ts @@ -1,15 +1,16 @@ -import { AwilixContainer } from "awilix" -import dotenv from "dotenv" -import express from "express" - import { IInventoryService, IStockLocationService } from "@medusajs/types" -import loaders from "../loaders" -import { ProductVariant } from "../models" import { ProductVariantInventoryService, ProductVariantService, } from "../services" +import { AwilixContainer } from "awilix" +import { EntityManager } from "typeorm" +import { ProductVariant } from "../models" +import dotenv from "dotenv" +import express from "express" +import loaders from "../loaders" + dotenv.config() const BATCH_SIZE = 100 @@ -17,10 +18,24 @@ const BATCH_SIZE = 100 const migrateProductVariant = async ( variant: ProductVariant, locationId: string, - { container }: { container: AwilixContainer } + { + container, + transactionManager, + }: { container: AwilixContainer; transactionManager: EntityManager } ) => { const productVariantInventoryService: ProductVariantInventoryService = container.resolve("productVariantInventoryService") + + const productVariantInventoryServiceTx = + productVariantInventoryService.withTransaction(transactionManager) + + const existingVariantInventoryItems = + await productVariantInventoryServiceTx.listByVariant(variant.id) + + if (existingVariantInventoryItems.length) { + return + } + const inventoryService: IInventoryService = container.resolve("inventoryService") @@ -28,31 +43,38 @@ const migrateProductVariant = async ( return } - const inventoryItem = await inventoryService.createInventoryItem({ - sku: variant.sku, - material: variant.material, - width: variant.width, - length: variant.length, - height: variant.height, - weight: variant.weight, - origin_country: variant.origin_country, - hs_code: variant.hs_code, - mid_code: variant.mid_code, - requires_shipping: true, - }) + const context = { transactionManager } + const inventoryItem = await inventoryService.createInventoryItem( + { + sku: variant.sku, + material: variant.material, + width: variant.width, + length: variant.length, + height: variant.height, + weight: variant.weight, + origin_country: variant.origin_country, + hs_code: variant.hs_code, + mid_code: variant.mid_code, + requires_shipping: true, + }, + context + ) - await productVariantInventoryService.attachInventoryItem( + await productVariantInventoryServiceTx.attachInventoryItem( variant.id, inventoryItem.id, 1 ) - await inventoryService.createInventoryLevel({ - location_id: locationId, - inventory_item_id: inventoryItem.id, - stocked_quantity: variant.inventory_quantity, - incoming_quantity: 0, - }) + await inventoryService.createInventoryLevel( + { + location_id: locationId, + inventory_item_id: inventoryItem.id, + stocked_quantity: variant.inventory_quantity, + incoming_quantity: 0, + }, + context + ) } const migrateStockLocation = async (container: AwilixContainer) => { @@ -75,11 +97,17 @@ const processBatch = async ( locationId: string, container: AwilixContainer ) => { - await Promise.all( - variants.map(async (variant) => { - await migrateProductVariant(variant, locationId, { container }) - }) - ) + const manager = container.resolve("manager") + return await manager.transaction(async (transactionManager) => { + await Promise.all( + variants.map(async (variant) => { + await migrateProductVariant(variant, locationId, { + container, + transactionManager, + }) + }) + ) + }) } const migrate = async function ({ directory }) { diff --git a/packages/medusa/src/services/product-variant-inventory.ts b/packages/medusa/src/services/product-variant-inventory.ts index a175fda741..09396642b5 100644 --- a/packages/medusa/src/services/product-variant-inventory.ts +++ b/packages/medusa/src/services/product-variant-inventory.ts @@ -247,9 +247,13 @@ class ProductVariantInventoryService extends TransactionBaseService { }) // Verify that item exists - await this.inventoryService_.retrieveInventoryItem(inventoryItemId, { - select: ["id"], - }) + await this.inventoryService_.retrieveInventoryItem( + inventoryItemId, + { + select: ["id"], + }, + { transactionManager: this.activeManager_ } + ) const variantInventoryRepo = this.activeManager_.getRepository( ProductVariantInventoryItem diff --git a/packages/medusa/src/services/staged-job.ts b/packages/medusa/src/services/staged-job.ts index 5a4ae32adb..5a77c42e6b 100644 --- a/packages/medusa/src/services/staged-job.ts +++ b/packages/medusa/src/services/staged-job.ts @@ -1,10 +1,11 @@ -import { EventBusTypes } from "@medusajs/types" import { DeepPartial, EntityManager, In } from "typeorm" + +import { EventBusTypes } from "@medusajs/types" +import { FindConfig } from "../types/common" import { QueryDeepPartialEntity } from "typeorm/query-builder/QueryPartialEntity" -import { TransactionBaseService } from "../interfaces" import { StagedJob } from "../models" import { StagedJobRepository } from "../repositories/staged-job" -import { FindConfig } from "../types/common" +import { TransactionBaseService } from "../interfaces" import { isString } from "../utils" type StagedJobServiceProps = { @@ -42,8 +43,7 @@ class StagedJobService extends TransactionBaseService { } async create(data: EventBusTypes.EmitData[] | EventBusTypes.EmitData) { - return await this.atomicPhase_(async (manager) => { - const stagedJobRepo = manager.withRepository(this.stagedJobRepository_) + const stagedJobRepo = this.activeManager_.withRepository(this.stagedJobRepository_) const data_ = Array.isArray(data) ? data : [data] @@ -56,7 +56,6 @@ class StagedJobService extends TransactionBaseService { ) as QueryDeepPartialEntity[] return await stagedJobRepo.insertBulk(stagedJobs) - }) } } diff --git a/packages/types/src/inventory/inventory.ts b/packages/types/src/inventory/inventory.ts index aeda627801..bba326378e 100644 --- a/packages/types/src/inventory/inventory.ts +++ b/packages/types/src/inventory/inventory.ts @@ -1,5 +1,3 @@ -import { FindConfig } from "../common" - import { CreateInventoryItemInput, CreateInventoryLevelInput, @@ -14,101 +12,139 @@ import { UpdateReservationItemInput, } from "./common" +import { FindConfig } from "../common" +import { SharedContext } from ".." + export interface IInventoryService { listInventoryItems( selector: FilterableInventoryItemProps, - config?: FindConfig + config?: FindConfig, + context?: SharedContext ): Promise<[InventoryItemDTO[], number]> listReservationItems( selector: FilterableReservationItemProps, - config?: FindConfig + config?: FindConfig, + context?: SharedContext ): Promise<[ReservationItemDTO[], number]> listInventoryLevels( selector: FilterableInventoryLevelProps, - config?: FindConfig + config?: FindConfig, + context?: SharedContext ): Promise<[InventoryLevelDTO[], number]> retrieveInventoryItem( inventoryItemId: string, - config?: FindConfig + config?: FindConfig, + context?: SharedContext ): Promise retrieveInventoryLevel( inventoryItemId: string, - locationId: string + locationId: string, + context?: SharedContext ): Promise - retrieveReservationItem(reservationId: string): Promise + retrieveReservationItem( + reservationId: string, + context?: SharedContext + ): Promise createReservationItem( - input: CreateReservationItemInput + input: CreateReservationItemInput, + context?: SharedContext ): Promise createInventoryItem( - input: CreateInventoryItemInput + input: CreateInventoryItemInput, + context?: SharedContext ): Promise createInventoryLevel( - data: CreateInventoryLevelInput + data: CreateInventoryLevelInput, + context?: SharedContext ): Promise updateInventoryLevel( inventoryItemId: string, locationId: string, - update: UpdateInventoryLevelInput + update: UpdateInventoryLevelInput, + context?: SharedContext ): Promise updateInventoryItem( inventoryItemId: string, - input: CreateInventoryItemInput + input: CreateInventoryItemInput, + context?: SharedContext ): Promise updateReservationItem( reservationItemId: string, - input: UpdateReservationItemInput + input: UpdateReservationItemInput, + context?: SharedContext ): Promise - deleteReservationItemsByLineItem(lineItemId: string): Promise + deleteReservationItemsByLineItem( + lineItemId: string, + context?: SharedContext + ): Promise - deleteReservationItem(reservationItemId: string | string[]): Promise + deleteReservationItem( + reservationItemId: string | string[], + context?: SharedContext + ): Promise - deleteInventoryItem(inventoryItemId: string): Promise + deleteInventoryItem( + inventoryItemId: string, + context?: SharedContext + ): Promise - deleteInventoryItemLevelByLocationId(locationId: string): Promise + deleteInventoryItemLevelByLocationId( + locationId: string, + context?: SharedContext + ): Promise - deleteReservationItemByLocationId(locationId: string): Promise + deleteReservationItemByLocationId( + locationId: string, + context?: SharedContext + ): Promise deleteInventoryLevel( inventoryLevelId: string, - locationId: string + locationId: string, + context?: SharedContext ): Promise adjustInventory( inventoryItemId: string, locationId: string, - adjustment: number + adjustment: number, + context?: SharedContext ): Promise confirmInventory( inventoryItemId: string, locationIds: string[], - quantity: number + quantity: number, + context?: SharedContext ): Promise retrieveAvailableQuantity( inventoryItemId: string, - locationIds: string[] + locationIds: string[], + context?: SharedContext ): Promise retrieveStockedQuantity( inventoryItemId: string, - locationIds: string[] + locationIds: string[], + context?: SharedContext ): Promise retrieveReservedQuantity( inventoryItemId: string, - locationIds: string[] + locationIds: string[], + context?: SharedContext ): Promise } diff --git a/packages/types/src/stock-location/stock-location.ts b/packages/types/src/stock-location/stock-location.ts index 625ae879c0..502c5803f9 100644 --- a/packages/types/src/stock-location/stock-location.ts +++ b/packages/types/src/stock-location/stock-location.ts @@ -1,4 +1,3 @@ -import { FindConfig } from "../common/common" import { CreateStockLocationInput, FilterableStockLocationProps, @@ -6,25 +5,38 @@ import { UpdateStockLocationInput, } from "./common" +import { FindConfig } from "../common/common" +import { SharedContext } from "../shared-context" + export interface IStockLocationService { list( selector: FilterableStockLocationProps, - config?: FindConfig + config?: FindConfig, + context?: SharedContext ): Promise listAndCount( selector: FilterableStockLocationProps, - config?: FindConfig + config?: FindConfig, + context?: SharedContext ): Promise<[StockLocationDTO[], number]> retrieve( id: string, - config?: FindConfig + config?: FindConfig, + context?: SharedContext ): Promise - create(input: CreateStockLocationInput): Promise + create( + input: CreateStockLocationInput, + context?: SharedContext + ): Promise - update(id: string, input: UpdateStockLocationInput): Promise + update( + id: string, + input: UpdateStockLocationInput, + context?: SharedContext + ): Promise - delete(id: string): Promise + delete(id: string, context?: SharedContext): Promise }