chore(): Module Internal Events (#13296)
* chore(): Ensure the product module emits all necessary events * chore(): Ensure the product module emits all necessary events * Update events tests * more events and fixes * more tests and category fixes * more tests and category fixes * Add todo * update updateProduct_ event emitting and adjust test * Adjust update products implementation to rely on already computed events * rm unnecessary update variants events * Fix formatting in changeset for product events * refactor: Manage event emitting automatically (WIP) * refactor: Manage event emitting automatically (WIP) * chore(api-key): Add missing emit events and refactoring * chore(cart): Add missing emit events and refactoring * chore(customer): Add missing emit events and refactoring * chore(fufillment, utils): Add missing emit events and refactoring * chore(fufillment, utils): Add missing emit events and refactoring * chore(inventory): Add missing emit events and refactoring * chore(notification): Add missing emit events and refactoring * chore(utils): Remove medusa service event handling legacy * chore(product): Add missing emit events and refactoring * chore(order): Add missing emit events and refactoring * chore(payment): Add missing emit events and refactoring * chore(pricing, util): Add missing emit events and refactoring, fix internal service upsertWithReplace event dispatching * chore(promotions): Add missing emit events and refactoring * chore(region): Add missing emit events and refactoring * chore(sales-channel): Add missing emit events and refactoring * chore(settings): Add missing emit events and refactoring * chore(stock-location): Add missing emit events and refactoring * chore(store): Add missing emit events and refactoring * chore(taxes): Add missing emit events and refactoring * chore(user): Add missing emit events and refactoring * fix unit tests * rm changeset for regeneration * Create changeset for Medusa.js patch updates Add a changeset for patch updates to multiple Medusa.js modules. * rm unused product event builders * address feedback * remove old changeset * fix event action for token generated * fix user module events * fix import * fix promotion events * add new module integration tests shard * fix medusa service * revert shard * fix event action * fix pipeline * fix pipeline --------- Co-authored-by: Oli Juhl <59018053+olivermrbl@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
afe21741c4
commit
e8822f3e69
@@ -79,7 +79,10 @@ export const buildProductAndRelationsData = ({
|
||||
title: faker.commerce.productName(),
|
||||
sku: faker.commerce.productName(),
|
||||
options: options
|
||||
? { [options[0].title]: options[0].values[0] }
|
||||
? options.reduce((acc, option) => {
|
||||
acc[option.title] = option.values[0]
|
||||
return acc
|
||||
}, {} as Record<string, string>)
|
||||
: {
|
||||
[defaultOptionTitle]: defaultOptionValue,
|
||||
},
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,15 +1,11 @@
|
||||
import { IProductModuleService } from "@medusajs/framework/types"
|
||||
import {
|
||||
CommonEvents,
|
||||
composeMessage,
|
||||
Modules,
|
||||
ProductEvents,
|
||||
ProductStatus,
|
||||
toMikroORMEntity,
|
||||
} from "@medusajs/framework/utils"
|
||||
import { Product, ProductCategory } from "@models"
|
||||
import {
|
||||
MockEventBusService,
|
||||
moduleIntegrationTestRunner,
|
||||
} from "@medusajs/test-utils"
|
||||
import { productCategoriesRankData } from "../../__fixtures__/product-category/data"
|
||||
@@ -18,9 +14,6 @@ jest.setTimeout(30000)
|
||||
|
||||
moduleIntegrationTestRunner<IProductModuleService>({
|
||||
moduleName: Modules.PRODUCT,
|
||||
injectedDependencies: {
|
||||
[Modules.EVENT_BUS]: new MockEventBusService(),
|
||||
},
|
||||
testSuite: ({ MikroOrmWrapper, service }) => {
|
||||
describe("ProductModuleService product categories", () => {
|
||||
let productOne: Product
|
||||
@@ -404,29 +397,6 @@ moduleIntegrationTestRunner<IProductModuleService>({
|
||||
)
|
||||
})
|
||||
|
||||
it("should emit events through event bus", async () => {
|
||||
const eventBusSpy = jest.spyOn(MockEventBusService.prototype, "emit")
|
||||
|
||||
const category = await service.createProductCategories({
|
||||
name: "New Category",
|
||||
parent_category_id: productCategoryOne.id,
|
||||
})
|
||||
|
||||
expect(eventBusSpy.mock.calls[0][0]).toHaveLength(1)
|
||||
expect(eventBusSpy).toHaveBeenCalledWith(
|
||||
[
|
||||
composeMessage(ProductEvents.PRODUCT_CATEGORY_CREATED, {
|
||||
data: { id: category.id },
|
||||
object: "product_category",
|
||||
source: Modules.PRODUCT,
|
||||
action: CommonEvents.CREATED,
|
||||
}),
|
||||
],
|
||||
{
|
||||
internal: true,
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("should append rank from an existing category depending on parent", async () => {
|
||||
await service.createProductCategories({
|
||||
@@ -504,29 +474,6 @@ moduleIntegrationTestRunner<IProductModuleService>({
|
||||
productCategoryZeroTwo = categories[5]
|
||||
})
|
||||
|
||||
it("should emit events through event bus", async () => {
|
||||
const eventBusSpy = jest.spyOn(MockEventBusService.prototype, "emit")
|
||||
eventBusSpy.mockClear()
|
||||
|
||||
await service.updateProductCategories(productCategoryZero.id, {
|
||||
name: "New Category",
|
||||
})
|
||||
|
||||
expect(eventBusSpy.mock.calls[0][0]).toHaveLength(1)
|
||||
expect(eventBusSpy).toHaveBeenCalledWith(
|
||||
[
|
||||
composeMessage(ProductEvents.PRODUCT_CATEGORY_UPDATED, {
|
||||
data: { id: productCategoryZero.id },
|
||||
object: "product_category",
|
||||
source: Modules.PRODUCT,
|
||||
action: CommonEvents.UPDATED,
|
||||
}),
|
||||
],
|
||||
{
|
||||
internal: true,
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("should update the name of the category successfully", async () => {
|
||||
await service.updateProductCategories(productCategoryZero.id, {
|
||||
@@ -683,30 +630,6 @@ moduleIntegrationTestRunner<IProductModuleService>({
|
||||
productCategoryTwo = categories[2]
|
||||
})
|
||||
|
||||
it("should emit events through event bus", async () => {
|
||||
const eventBusSpy = jest.spyOn(MockEventBusService.prototype, "emit")
|
||||
eventBusSpy.mockClear()
|
||||
|
||||
await service.deleteProductCategories([productCategoryOne.id])
|
||||
|
||||
expect(eventBusSpy).toHaveBeenCalledTimes(1)
|
||||
expect(eventBusSpy).toHaveBeenCalledWith(
|
||||
[
|
||||
expect.objectContaining({
|
||||
data: { id: productCategoryOne.id },
|
||||
name: "product.product-category.deleted",
|
||||
metadata: {
|
||||
action: CommonEvents.DELETED,
|
||||
object: "product_category",
|
||||
source: Modules.PRODUCT,
|
||||
},
|
||||
}),
|
||||
],
|
||||
{
|
||||
internal: true,
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("should throw an error when an id does not exist", async () => {
|
||||
let error
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
import { IProductModuleService } from "@medusajs/framework/types"
|
||||
import {
|
||||
CommonEvents,
|
||||
composeMessage,
|
||||
Modules,
|
||||
ProductEvents,
|
||||
ProductStatus,
|
||||
toMikroORMEntity,
|
||||
} from "@medusajs/framework/utils"
|
||||
import {
|
||||
MockEventBusService,
|
||||
moduleIntegrationTestRunner,
|
||||
} from "@medusajs/test-utils"
|
||||
import { Product, ProductCollection } from "@models"
|
||||
@@ -18,9 +14,6 @@ jest.setTimeout(30000)
|
||||
|
||||
moduleIntegrationTestRunner<IProductModuleService>({
|
||||
moduleName: Modules.PRODUCT,
|
||||
injectedDependencies: {
|
||||
[Modules.EVENT_BUS]: new MockEventBusService(),
|
||||
},
|
||||
testSuite: ({ MikroOrmWrapper, service }) => {
|
||||
describe("ProductModuleService product collections", () => {
|
||||
let productOne: Product
|
||||
@@ -284,59 +277,11 @@ moduleIntegrationTestRunner<IProductModuleService>({
|
||||
expect(collections).toHaveLength(0)
|
||||
})
|
||||
|
||||
it("should emit events through event bus", async () => {
|
||||
const eventBusSpy = jest.spyOn(MockEventBusService.prototype, "emit")
|
||||
await service.deleteProductCollections([collectionId])
|
||||
|
||||
expect(eventBusSpy).toHaveBeenCalledTimes(1)
|
||||
expect(eventBusSpy).toHaveBeenCalledWith(
|
||||
[
|
||||
{
|
||||
name: "product.product-collection.deleted",
|
||||
data: { id: collectionId },
|
||||
metadata: {
|
||||
action: CommonEvents.DELETED,
|
||||
object: "product_collection",
|
||||
source: Modules.PRODUCT,
|
||||
},
|
||||
},
|
||||
],
|
||||
{
|
||||
internal: true,
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("updateCollections", () => {
|
||||
const collectionId = "test-1"
|
||||
|
||||
it("should emit events through event bus", async () => {
|
||||
const eventBusSpy = jest.spyOn(MockEventBusService.prototype, "emit")
|
||||
|
||||
await service.upsertProductCollections([
|
||||
{
|
||||
id: collectionId,
|
||||
title: "New Collection",
|
||||
product_ids: ["product_id"],
|
||||
},
|
||||
])
|
||||
|
||||
expect(eventBusSpy).toHaveBeenCalledTimes(1)
|
||||
expect(eventBusSpy).toHaveBeenCalledWith(
|
||||
[
|
||||
composeMessage(ProductEvents.PRODUCT_COLLECTION_UPDATED, {
|
||||
data: { id: collectionId },
|
||||
object: "product_collection",
|
||||
source: Modules.PRODUCT,
|
||||
action: CommonEvents.UPDATED,
|
||||
}),
|
||||
],
|
||||
{
|
||||
internal: true,
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("should update the value of the collection successfully", async () => {
|
||||
await service.upsertProductCollections([
|
||||
@@ -534,28 +479,6 @@ moduleIntegrationTestRunner<IProductModuleService>({
|
||||
)
|
||||
})
|
||||
|
||||
it("should emit events through event bus", async () => {
|
||||
const eventBusSpy = jest.spyOn(MockEventBusService.prototype, "emit")
|
||||
|
||||
const collections = await service.createProductCollections([
|
||||
{ title: "New Collection" },
|
||||
])
|
||||
|
||||
expect(eventBusSpy).toHaveBeenCalledTimes(1)
|
||||
expect(eventBusSpy).toHaveBeenCalledWith(
|
||||
[
|
||||
composeMessage(ProductEvents.PRODUCT_COLLECTION_CREATED, {
|
||||
data: { id: collections[0].id },
|
||||
object: "product_collection",
|
||||
source: Modules.PRODUCT,
|
||||
action: CommonEvents.CREATED,
|
||||
}),
|
||||
],
|
||||
{
|
||||
internal: true,
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
import { IProductModuleService } from "@medusajs/framework/types"
|
||||
import {
|
||||
CommonEvents,
|
||||
composeMessage,
|
||||
Modules,
|
||||
ProductEvents,
|
||||
ProductStatus,
|
||||
toMikroORMEntity,
|
||||
} from "@medusajs/framework/utils"
|
||||
import { Product, ProductTag } from "@models"
|
||||
import {
|
||||
MockEventBusService,
|
||||
moduleIntegrationTestRunner,
|
||||
} from "@medusajs/test-utils"
|
||||
|
||||
@@ -18,15 +14,6 @@ jest.setTimeout(30000)
|
||||
moduleIntegrationTestRunner<IProductModuleService>({
|
||||
moduleName: Modules.PRODUCT,
|
||||
testSuite: ({ MikroOrmWrapper, service }) => {
|
||||
let eventBusEmitSpy
|
||||
|
||||
beforeEach(() => {
|
||||
eventBusEmitSpy = jest.spyOn(MockEventBusService.prototype, "emit")
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
describe("ProductModuleService product tags", () => {
|
||||
let tagOne: ProductTag
|
||||
@@ -303,20 +290,6 @@ moduleIntegrationTestRunner<IProductModuleService>({
|
||||
|
||||
expect(productTag.value).toEqual("UK")
|
||||
|
||||
expect(eventBusEmitSpy.mock.calls[0][0]).toHaveLength(1)
|
||||
expect(eventBusEmitSpy).toHaveBeenCalledWith(
|
||||
[
|
||||
composeMessage(ProductEvents.PRODUCT_TAG_UPDATED, {
|
||||
data: { id: productTag.id },
|
||||
object: "product_tag",
|
||||
source: Modules.PRODUCT,
|
||||
action: CommonEvents.UPDATED,
|
||||
}),
|
||||
],
|
||||
{
|
||||
internal: true,
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("should throw an error when an id does not exist", async () => {
|
||||
@@ -350,20 +323,6 @@ moduleIntegrationTestRunner<IProductModuleService>({
|
||||
|
||||
expect(productTag[0]?.value).toEqual("UK")
|
||||
|
||||
expect(eventBusEmitSpy.mock.calls[0][0]).toHaveLength(1)
|
||||
expect(eventBusEmitSpy).toHaveBeenCalledWith(
|
||||
[
|
||||
composeMessage(ProductEvents.PRODUCT_TAG_CREATED, {
|
||||
data: { id: productTag[0].id },
|
||||
object: "product_tag",
|
||||
source: Modules.PRODUCT,
|
||||
action: CommonEvents.CREATED,
|
||||
}),
|
||||
],
|
||||
{
|
||||
internal: true,
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -409,26 +368,6 @@ moduleIntegrationTestRunner<IProductModuleService>({
|
||||
const newTag = productTags.find((t) => t.value === "new")!
|
||||
const updatedTag = productTags.find((t) => t.value === "updated")!
|
||||
|
||||
expect(eventBusEmitSpy.mock.calls[0][0]).toHaveLength(2)
|
||||
expect(eventBusEmitSpy).toHaveBeenCalledWith(
|
||||
[
|
||||
composeMessage(ProductEvents.PRODUCT_TAG_CREATED, {
|
||||
data: { id: newTag.id },
|
||||
object: "product_tag",
|
||||
source: Modules.PRODUCT,
|
||||
action: CommonEvents.CREATED,
|
||||
}),
|
||||
composeMessage(ProductEvents.PRODUCT_TAG_UPDATED, {
|
||||
data: { id: updatedTag.id },
|
||||
object: "product_tag",
|
||||
source: Modules.PRODUCT,
|
||||
action: CommonEvents.UPDATED,
|
||||
}),
|
||||
],
|
||||
{
|
||||
internal: true,
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -7,15 +7,11 @@ import {
|
||||
UpdateProductVariantDTO,
|
||||
} from "@medusajs/framework/types"
|
||||
import {
|
||||
CommonEvents,
|
||||
composeMessage,
|
||||
Modules,
|
||||
ProductEvents,
|
||||
ProductStatus,
|
||||
} from "@medusajs/framework/utils"
|
||||
|
||||
import {
|
||||
MockEventBusService,
|
||||
moduleIntegrationTestRunner,
|
||||
} from "@medusajs/test-utils"
|
||||
|
||||
@@ -24,15 +20,6 @@ jest.setTimeout(30000)
|
||||
moduleIntegrationTestRunner<IProductModuleService>({
|
||||
moduleName: Modules.PRODUCT,
|
||||
testSuite: ({ service }) => {
|
||||
let eventBusEmitSpy
|
||||
|
||||
beforeEach(() => {
|
||||
eventBusEmitSpy = jest.spyOn(MockEventBusService.prototype, "emit")
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
describe("ProductModuleService product variants", () => {
|
||||
let variantOne: ProductVariantDTO
|
||||
@@ -212,20 +199,6 @@ moduleIntegrationTestRunner<IProductModuleService>({
|
||||
)
|
||||
expect(productVariant.title).toEqual("new test")
|
||||
|
||||
expect(eventBusEmitSpy.mock.calls[0][0]).toHaveLength(1)
|
||||
expect(eventBusEmitSpy).toHaveBeenCalledWith(
|
||||
[
|
||||
composeMessage(ProductEvents.PRODUCT_VARIANT_UPDATED, {
|
||||
data: { id: variantOne.id },
|
||||
object: "product_variant",
|
||||
source: Modules.PRODUCT,
|
||||
action: CommonEvents.UPDATED,
|
||||
}),
|
||||
],
|
||||
{
|
||||
internal: true,
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("should do a partial update on the options of a variant successfully", async () => {
|
||||
@@ -294,20 +267,6 @@ moduleIntegrationTestRunner<IProductModuleService>({
|
||||
})
|
||||
)
|
||||
|
||||
expect(eventBusEmitSpy.mock.calls[0][0]).toHaveLength(1)
|
||||
expect(eventBusEmitSpy).toHaveBeenCalledWith(
|
||||
[
|
||||
composeMessage(ProductEvents.PRODUCT_VARIANT_CREATED, {
|
||||
data: { id: variant.id },
|
||||
object: "product_variant",
|
||||
source: Modules.PRODUCT,
|
||||
action: CommonEvents.CREATED,
|
||||
}),
|
||||
],
|
||||
{
|
||||
internal: true,
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("should correctly associate variants with own product options", async () => {
|
||||
|
||||
@@ -4,11 +4,8 @@ import {
|
||||
ProductTagDTO,
|
||||
} from "@medusajs/framework/types"
|
||||
import {
|
||||
CommonEvents,
|
||||
composeMessage,
|
||||
kebabCase,
|
||||
Modules,
|
||||
ProductEvents,
|
||||
ProductStatus,
|
||||
} from "@medusajs/framework/utils"
|
||||
import {
|
||||
@@ -20,7 +17,6 @@ import {
|
||||
} from "@models"
|
||||
|
||||
import {
|
||||
MockEventBusService,
|
||||
moduleIntegrationTestRunner,
|
||||
} from "@medusajs/test-utils"
|
||||
import { UpdateProductInput } from "@types"
|
||||
@@ -34,9 +30,6 @@ jest.setTimeout(300000)
|
||||
|
||||
moduleIntegrationTestRunner<IProductModuleService>({
|
||||
moduleName: Modules.PRODUCT,
|
||||
injectedDependencies: {
|
||||
[Modules.EVENT_BUS]: new MockEventBusService(),
|
||||
},
|
||||
testSuite: ({ MikroOrmWrapper, service }) => {
|
||||
describe("ProductModuleService products", function () {
|
||||
let productCollectionOne: ProductCollection
|
||||
@@ -573,37 +566,6 @@ moduleIntegrationTestRunner<IProductModuleService>({
|
||||
)
|
||||
})
|
||||
|
||||
it("should emit events through event bus", async () => {
|
||||
const eventBusSpy = jest.spyOn(MockEventBusService.prototype, "emit")
|
||||
const data = buildProductAndRelationsData({
|
||||
images,
|
||||
thumbnail: images[0].url,
|
||||
})
|
||||
|
||||
const updateData = {
|
||||
...data,
|
||||
options: data.options,
|
||||
id: productOne.id,
|
||||
title: "updated title",
|
||||
}
|
||||
|
||||
await service.upsertProducts([updateData])
|
||||
|
||||
expect(eventBusSpy).toHaveBeenCalledTimes(1)
|
||||
expect(eventBusSpy).toHaveBeenCalledWith(
|
||||
[
|
||||
composeMessage(ProductEvents.PRODUCT_UPDATED, {
|
||||
data: { id: productOne.id },
|
||||
object: "product",
|
||||
source: Modules.PRODUCT,
|
||||
action: CommonEvents.UPDATED,
|
||||
}),
|
||||
],
|
||||
{
|
||||
internal: true,
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("should add relationships to a product", async () => {
|
||||
const updateData = {
|
||||
@@ -1086,29 +1048,6 @@ moduleIntegrationTestRunner<IProductModuleService>({
|
||||
)
|
||||
})
|
||||
|
||||
it("should emit events through eventBus", async () => {
|
||||
const eventBusSpy = jest.spyOn(MockEventBusService.prototype, "emit")
|
||||
const data = buildProductAndRelationsData({
|
||||
images,
|
||||
thumbnail: images[0].url,
|
||||
})
|
||||
|
||||
const products = await service.createProducts([data])
|
||||
expect(eventBusSpy).toHaveBeenCalledTimes(1)
|
||||
expect(eventBusSpy).toHaveBeenCalledWith(
|
||||
[
|
||||
composeMessage(ProductEvents.PRODUCT_CREATED, {
|
||||
data: { id: products[0].id },
|
||||
object: "product",
|
||||
source: Modules.PRODUCT,
|
||||
action: CommonEvents.CREATED,
|
||||
}),
|
||||
],
|
||||
{
|
||||
internal: true,
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("should throw because variant doesn't have all options set", async () => {
|
||||
const error = await service
|
||||
@@ -1289,75 +1228,6 @@ moduleIntegrationTestRunner<IProductModuleService>({
|
||||
expect(softDeleted).toHaveLength(1)
|
||||
})
|
||||
|
||||
it("should emit events through eventBus", async () => {
|
||||
const eventBusSpy = jest.spyOn(MockEventBusService.prototype, "emit")
|
||||
const data = buildProductAndRelationsData({
|
||||
images,
|
||||
thumbnail: images[0].url,
|
||||
})
|
||||
|
||||
const products = await service.createProducts([data])
|
||||
|
||||
await service.softDeleteProducts([products[0].id])
|
||||
|
||||
expect(eventBusSpy).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
[
|
||||
composeMessage(ProductEvents.PRODUCT_CREATED, {
|
||||
data: { id: products[0].id },
|
||||
object: "product",
|
||||
source: Modules.PRODUCT,
|
||||
action: CommonEvents.CREATED,
|
||||
}),
|
||||
],
|
||||
{
|
||||
internal: true,
|
||||
}
|
||||
)
|
||||
|
||||
expect(eventBusSpy).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
[
|
||||
composeMessage(ProductEvents.PRODUCT_DELETED, {
|
||||
data: { id: [products[0].id] },
|
||||
object: "product",
|
||||
source: Modules.PRODUCT,
|
||||
action: CommonEvents.DELETED,
|
||||
}),
|
||||
composeMessage(ProductEvents.PRODUCT_VARIANT_DELETED, {
|
||||
data: { id: [products[0].variants[0].id] },
|
||||
object: "product_variant",
|
||||
source: Modules.PRODUCT,
|
||||
action: CommonEvents.DELETED,
|
||||
}),
|
||||
composeMessage(ProductEvents.PRODUCT_OPTION_DELETED, {
|
||||
data: { id: [products[0].options[0].id] },
|
||||
object: "product_option",
|
||||
source: Modules.PRODUCT,
|
||||
action: CommonEvents.DELETED,
|
||||
}),
|
||||
composeMessage(ProductEvents.PRODUCT_IMAGE_DELETED, {
|
||||
data: {
|
||||
id: [products[0].images[0].id],
|
||||
},
|
||||
object: "product_image",
|
||||
source: Modules.PRODUCT,
|
||||
action: CommonEvents.DELETED,
|
||||
}),
|
||||
composeMessage(ProductEvents.PRODUCT_OPTION_VALUE_DELETED, {
|
||||
data: {
|
||||
id: [products[0].options[0].values[0].id],
|
||||
},
|
||||
object: "product_option_value",
|
||||
source: Modules.PRODUCT,
|
||||
action: CommonEvents.DELETED,
|
||||
}),
|
||||
],
|
||||
{
|
||||
internal: true,
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("restore", function () {
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
"resolve:aliases": "tsc --showConfig -p tsconfig.json > tsconfig.resolved.json && tsc-alias -p tsconfig.resolved.json && rimraf tsconfig.resolved.json",
|
||||
"build": "rimraf dist && tsc --build && npm run resolve:aliases",
|
||||
"test": "jest --runInBand --bail --forceExit -- src/**/__tests__/**/*.ts",
|
||||
"test:integration": "jest --bail --forceExit -- integration-tests/__tests__/**/*.ts",
|
||||
"test:integration": "jest --bail --forceExit -- integration-tests/__tests__/**/*.spec.ts",
|
||||
"migration:initial": "MIKRO_ORM_CLI_CONFIG=./mikro-orm.config.dev.ts medusa-mikro-orm migration:create --initial",
|
||||
"migration:create": "MIKRO_ORM_CLI_CONFIG=./mikro-orm.config.dev.ts medusa-mikro-orm migration:create",
|
||||
"migration:up": "MIKRO_ORM_CLI_CONFIG=./mikro-orm.config.dev.ts medusa-mikro-orm migration:up",
|
||||
|
||||
@@ -452,9 +452,7 @@ export class ProductCategoryRepository extends DALUtils.MikroOrmBaseTreeReposito
|
||||
}
|
||||
let productCategory = await manager.findOne<
|
||||
InferEntityType<typeof ProductCategory>
|
||||
>(ProductCategory.name, {
|
||||
id: categoryData.id,
|
||||
})
|
||||
>(ProductCategory.name, categoryData.id!)
|
||||
|
||||
if (!productCategory) {
|
||||
throw new MedusaError(
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
isPresent,
|
||||
mergeMetadata,
|
||||
isDefined,
|
||||
deepCopy,
|
||||
} from "@medusajs/framework/utils"
|
||||
import { SqlEntityManager, wrap } from "@mikro-orm/postgresql"
|
||||
|
||||
@@ -80,21 +81,22 @@ export class ProductRepository extends DALUtils.mikroOrmBaseRepositoryFactory(
|
||||
) => void,
|
||||
context: Context = {}
|
||||
): Promise<InferEntityType<typeof Product>[]> {
|
||||
const productsToUpdate_ = deepCopy(productsToUpdate)
|
||||
const productIdsToUpdate: string[] = []
|
||||
|
||||
productsToUpdate.forEach((productToUpdate) => {
|
||||
productsToUpdate_.forEach((productToUpdate) => {
|
||||
ProductRepository.#correctUpdateDTOTypes(productToUpdate)
|
||||
productIdsToUpdate.push(productToUpdate.id)
|
||||
})
|
||||
|
||||
const relationsToLoad =
|
||||
ProductRepository.#getProductDeepUpdateRelationsToLoad(productsToUpdate)
|
||||
ProductRepository.#getProductDeepUpdateRelationsToLoad(productsToUpdate_)
|
||||
|
||||
const findOptions = buildQuery(
|
||||
{ id: productIdsToUpdate },
|
||||
{
|
||||
relations: relationsToLoad,
|
||||
take: productsToUpdate.length,
|
||||
take: productsToUpdate_.length,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -111,7 +113,7 @@ export class ProductRepository extends DALUtils.mikroOrmBaseRepositoryFactory(
|
||||
)
|
||||
}
|
||||
|
||||
for (const productToUpdate of productsToUpdate) {
|
||||
for (const productToUpdate of productsToUpdate_) {
|
||||
const product = productsMap.get(productToUpdate.id)!
|
||||
const wrappedProduct = wrap(product)
|
||||
|
||||
@@ -173,7 +175,7 @@ export class ProductRepository extends DALUtils.mikroOrmBaseRepositoryFactory(
|
||||
// Doing this to ensure updates are returned in the same order they were provided,
|
||||
// since some core flows rely on this.
|
||||
// This is a high level of coupling though.
|
||||
return productsToUpdate.map(
|
||||
return productsToUpdate_.map(
|
||||
(productToUpdate) => productsMap.get(productToUpdate.id)!
|
||||
)
|
||||
}
|
||||
|
||||
@@ -6,30 +6,45 @@ import {
|
||||
ProductTypes,
|
||||
} from "@medusajs/framework/types"
|
||||
import {
|
||||
createMedusaMikroOrmEventSubscriber,
|
||||
FreeTextSearchFilterKeyPrefix,
|
||||
InjectManager,
|
||||
InjectTransactionManager,
|
||||
isDefined,
|
||||
MedusaContext,
|
||||
MedusaError,
|
||||
MedusaInternalService,
|
||||
MedusaService,
|
||||
ModulesSdkUtils,
|
||||
registerInternalServiceEventSubscriber,
|
||||
} from "@medusajs/framework/utils"
|
||||
import { EntityManager, EventType } from "@mikro-orm/core"
|
||||
import { ProductCategory } from "@models"
|
||||
import { ProductCategoryRepository } from "@repositories"
|
||||
import { UpdateCategoryInput } from "@types"
|
||||
|
||||
type InjectedDependencies = {
|
||||
productCategoryRepository: DAL.TreeRepositoryService
|
||||
productModuleService: ReturnType<typeof MedusaService>
|
||||
}
|
||||
export default class ProductCategoryService {
|
||||
protected readonly productCategoryRepository_: DAL.TreeRepositoryService
|
||||
|
||||
constructor({ productCategoryRepository }: InjectedDependencies) {
|
||||
this.productCategoryRepository_ = productCategoryRepository
|
||||
export default class ProductCategoryService extends MedusaInternalService<
|
||||
InjectedDependencies,
|
||||
typeof ProductCategory
|
||||
>(ProductCategory) {
|
||||
protected readonly productCategoryRepository_: DAL.TreeRepositoryService
|
||||
protected readonly container: InjectedDependencies
|
||||
|
||||
constructor(container: InjectedDependencies) {
|
||||
// @ts-expect-error
|
||||
super(...arguments)
|
||||
this.container = container
|
||||
this.productCategoryRepository_ = container.productCategoryRepository
|
||||
}
|
||||
|
||||
// TODO: Add support for object filter
|
||||
@InjectManager("productCategoryRepository_")
|
||||
// @ts-expect-error
|
||||
async retrieve(
|
||||
productCategoryId: string,
|
||||
config: FindConfig<ProductTypes.ProductCategoryDTO> = {},
|
||||
@@ -150,6 +165,7 @@ export default class ProductCategoryService {
|
||||
}
|
||||
|
||||
@InjectTransactionManager("productCategoryRepository_")
|
||||
// @ts-expect-error
|
||||
async update(
|
||||
data: UpdateCategoryInput[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
@@ -160,14 +176,45 @@ export default class ProductCategoryService {
|
||||
}
|
||||
|
||||
@InjectTransactionManager("productCategoryRepository_")
|
||||
// @ts-expect-error
|
||||
async delete(
|
||||
ids: string[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<string[]> {
|
||||
return await this.productCategoryRepository_.delete(ids, sharedContext)
|
||||
const subscriber = createMedusaMikroOrmEventSubscriber(
|
||||
[ProductCategory.name],
|
||||
this.container["productModuleService"]
|
||||
)
|
||||
|
||||
registerInternalServiceEventSubscriber(sharedContext, subscriber)
|
||||
|
||||
const deletedIds = await this.productCategoryRepository_.delete(
|
||||
ids,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
// Delete are handled a bit differently since we are going to the DB directly, therefore
|
||||
// just like upsert with replace, we need to dispatch the events manually.
|
||||
if (deletedIds.length) {
|
||||
const manager = (sharedContext.transactionManager ??
|
||||
sharedContext.manager) as EntityManager
|
||||
const eventManager = manager.getEventManager()
|
||||
|
||||
deletedIds.forEach((id) => {
|
||||
eventManager.dispatchEvent(EventType.afterDelete, {
|
||||
entity: { id },
|
||||
meta: {
|
||||
className: ProductCategory.name,
|
||||
} as Parameters<typeof eventManager.dispatchEvent>[2],
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
return deletedIds
|
||||
}
|
||||
|
||||
@InjectTransactionManager("productCategoryRepository_")
|
||||
// @ts-expect-error
|
||||
async softDelete(
|
||||
ids: string[],
|
||||
@MedusaContext() sharedContext?: Context
|
||||
@@ -178,6 +225,7 @@ export default class ProductCategoryService {
|
||||
}
|
||||
|
||||
@InjectTransactionManager("productCategoryRepository_")
|
||||
// @ts-expect-error
|
||||
async restore(
|
||||
ids: string[],
|
||||
@MedusaContext() sharedContext?: Context
|
||||
|
||||
@@ -25,6 +25,7 @@ import { ProductCategoryService } from "@services"
|
||||
|
||||
import {
|
||||
arrayDifference,
|
||||
createMedusaMikroOrmEventSubscriber,
|
||||
EmitEvents,
|
||||
generateEntityId,
|
||||
InjectManager,
|
||||
@@ -37,11 +38,13 @@ import {
|
||||
MedusaContext,
|
||||
MedusaError,
|
||||
MedusaService,
|
||||
MessageAggregator,
|
||||
Modules,
|
||||
ProductStatus,
|
||||
removeUndefined,
|
||||
toHandle,
|
||||
} from "@medusajs/framework/utils"
|
||||
import { EntityManager } from "@mikro-orm/core"
|
||||
import { ProductRepository } from "../repositories"
|
||||
import {
|
||||
UpdateCategoryInput,
|
||||
@@ -52,8 +55,8 @@ import {
|
||||
UpdateTagInput,
|
||||
UpdateTypeInput,
|
||||
} from "../types"
|
||||
import { eventBuilders } from "../utils"
|
||||
import { joinerConfig } from "./../joiner-config"
|
||||
import { eventBuilders } from "../utils/events"
|
||||
|
||||
type InjectedDependencies = {
|
||||
baseRepository: DAL.RepositoryService
|
||||
@@ -331,11 +334,6 @@ export default class ProductModuleService
|
||||
sharedContext
|
||||
)
|
||||
|
||||
eventBuilders.createdProductVariant({
|
||||
data: createdVariants,
|
||||
sharedContext,
|
||||
})
|
||||
|
||||
return createdVariants
|
||||
}
|
||||
|
||||
@@ -366,11 +364,11 @@ export default class ProductModuleService
|
||||
(variant): variant is ProductTypes.CreateProductVariantDTO => !variant.id
|
||||
)
|
||||
|
||||
let created: InferEntityType<typeof ProductVariant>[] = []
|
||||
let created: ProductTypes.ProductVariantDTO[] = []
|
||||
let updated: InferEntityType<typeof ProductVariant>[] = []
|
||||
|
||||
if (forCreate.length) {
|
||||
created = await this.createVariants_(forCreate, sharedContext)
|
||||
created = await this.createProductVariants(forCreate, sharedContext)
|
||||
}
|
||||
if (forUpdate.length) {
|
||||
updated = await this.updateVariants_(forUpdate, sharedContext)
|
||||
@@ -493,7 +491,7 @@ export default class ProductModuleService
|
||||
)
|
||||
}
|
||||
|
||||
const { entities: productVariants, performedActions } =
|
||||
const { entities: productVariants } =
|
||||
await this.productVariantService_.upsertWithReplace(
|
||||
productVariantsWithOptions,
|
||||
{
|
||||
@@ -502,19 +500,6 @@ export default class ProductModuleService
|
||||
sharedContext
|
||||
)
|
||||
|
||||
eventBuilders.createdProductVariant({
|
||||
data: performedActions.created[ProductVariant.name] ?? [],
|
||||
sharedContext,
|
||||
})
|
||||
eventBuilders.updatedProductVariant({
|
||||
data: performedActions.updated[ProductVariant.name] ?? [],
|
||||
sharedContext,
|
||||
})
|
||||
eventBuilders.deletedProductVariant({
|
||||
data: performedActions.deleted[ProductVariant.name] ?? [],
|
||||
sharedContext,
|
||||
})
|
||||
|
||||
return productVariants
|
||||
}
|
||||
|
||||
@@ -544,11 +529,6 @@ export default class ProductModuleService
|
||||
ProductTypes.ProductTagDTO[]
|
||||
>(tags)
|
||||
|
||||
eventBuilders.createdProductTag({
|
||||
data: createdTags,
|
||||
sharedContext,
|
||||
})
|
||||
|
||||
return Array.isArray(data) ? createdTags : createdTags[0]
|
||||
}
|
||||
|
||||
@@ -561,12 +541,26 @@ export default class ProductModuleService
|
||||
sharedContext?: Context
|
||||
): Promise<ProductTypes.ProductTagDTO>
|
||||
|
||||
@InjectTransactionManager()
|
||||
@InjectManager()
|
||||
@EmitEvents()
|
||||
async upsertProductTags(
|
||||
data: ProductTypes.UpsertProductTagDTO[] | ProductTypes.UpsertProductTagDTO,
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<ProductTypes.ProductTagDTO[] | ProductTypes.ProductTagDTO> {
|
||||
const tags = await this.upsertProductTags_(data, sharedContext)
|
||||
|
||||
const allTags = await this.baseRepository_.serialize<
|
||||
ProductTypes.ProductTagDTO[] | ProductTypes.ProductTagDTO
|
||||
>(Array.isArray(data) ? tags : tags[0])
|
||||
|
||||
return allTags
|
||||
}
|
||||
|
||||
@InjectTransactionManager()
|
||||
protected async upsertProductTags_(
|
||||
data: ProductTypes.UpsertProductTagDTO[] | ProductTypes.UpsertProductTagDTO,
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<InferEntityType<typeof ProductTag>[]> {
|
||||
const input = Array.isArray(data) ? data : [data]
|
||||
const forUpdate = input.filter((tag): tag is UpdateTagInput => !!tag.id)
|
||||
const forCreate = input.filter(
|
||||
@@ -578,25 +572,12 @@ export default class ProductModuleService
|
||||
|
||||
if (forCreate.length) {
|
||||
created = await this.productTagService_.create(forCreate, sharedContext)
|
||||
eventBuilders.createdProductTag({
|
||||
data: created,
|
||||
sharedContext,
|
||||
})
|
||||
}
|
||||
if (forUpdate.length) {
|
||||
updated = await this.productTagService_.update(forUpdate, sharedContext)
|
||||
eventBuilders.updatedProductTag({
|
||||
data: updated,
|
||||
sharedContext,
|
||||
})
|
||||
}
|
||||
|
||||
const result = [...created, ...updated]
|
||||
const allTags = await this.baseRepository_.serialize<
|
||||
ProductTypes.ProductTagDTO[] | ProductTypes.ProductTagDTO
|
||||
>(result)
|
||||
|
||||
return Array.isArray(data) ? allTags : allTags[0]
|
||||
return [...created, ...updated]
|
||||
}
|
||||
|
||||
// @ts-expect-error
|
||||
@@ -647,11 +628,6 @@ export default class ProductModuleService
|
||||
ProductTypes.ProductTagDTO[]
|
||||
>(tags)
|
||||
|
||||
eventBuilders.updatedProductTag({
|
||||
data: updatedTags,
|
||||
sharedContext,
|
||||
})
|
||||
|
||||
return isString(idOrSelector) ? updatedTags[0] : updatedTags
|
||||
}
|
||||
|
||||
@@ -667,6 +643,7 @@ export default class ProductModuleService
|
||||
): Promise<ProductTypes.ProductTypeDTO>
|
||||
|
||||
@InjectManager()
|
||||
@EmitEvents()
|
||||
// @ts-expect-error
|
||||
async createProductTypes(
|
||||
data:
|
||||
@@ -694,13 +671,30 @@ export default class ProductModuleService
|
||||
sharedContext?: Context
|
||||
): Promise<ProductTypes.ProductTypeDTO>
|
||||
|
||||
@InjectTransactionManager()
|
||||
@InjectManager()
|
||||
@EmitEvents()
|
||||
async upsertProductTypes(
|
||||
data:
|
||||
| ProductTypes.UpsertProductTypeDTO[]
|
||||
| ProductTypes.UpsertProductTypeDTO,
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<ProductTypes.ProductTypeDTO[] | ProductTypes.ProductTypeDTO> {
|
||||
const types = await this.upsertProductTypes_(data, sharedContext)
|
||||
|
||||
const result = await this.baseRepository_.serialize<
|
||||
ProductTypes.ProductTypeDTO[] | ProductTypes.ProductTypeDTO
|
||||
>(types)
|
||||
|
||||
return Array.isArray(data) ? result : result[0]
|
||||
}
|
||||
|
||||
@InjectTransactionManager()
|
||||
protected async upsertProductTypes_(
|
||||
data:
|
||||
| ProductTypes.UpsertProductTypeDTO
|
||||
| ProductTypes.UpsertProductTypeDTO[],
|
||||
sharedContext?: Context
|
||||
): Promise<InferEntityType<typeof ProductType>[]> {
|
||||
const input = Array.isArray(data) ? data : [data]
|
||||
const forUpdate = input.filter((type): type is UpdateTypeInput => !!type.id)
|
||||
const forCreate = input.filter(
|
||||
@@ -717,12 +711,7 @@ export default class ProductModuleService
|
||||
updated = await this.productTypeService_.update(forUpdate, sharedContext)
|
||||
}
|
||||
|
||||
const result = [...created, ...updated]
|
||||
const allTypes = await this.baseRepository_.serialize<
|
||||
ProductTypes.ProductTypeDTO[] | ProductTypes.ProductTypeDTO
|
||||
>(result)
|
||||
|
||||
return Array.isArray(data) ? allTypes : allTypes[0]
|
||||
return [...created, ...updated]
|
||||
}
|
||||
|
||||
// @ts-expect-error
|
||||
@@ -739,6 +728,7 @@ export default class ProductModuleService
|
||||
): Promise<ProductTypes.ProductTypeDTO[]>
|
||||
|
||||
@InjectManager()
|
||||
@EmitEvents()
|
||||
// @ts-expect-error
|
||||
async updateProductTypes(
|
||||
idOrSelector: string | ProductTypes.FilterableProductTypeProps,
|
||||
@@ -787,6 +777,7 @@ export default class ProductModuleService
|
||||
): Promise<ProductTypes.ProductOptionDTO>
|
||||
|
||||
@InjectManager()
|
||||
@EmitEvents()
|
||||
// @ts-expect-error
|
||||
async createProductOptions(
|
||||
data:
|
||||
@@ -842,6 +833,7 @@ export default class ProductModuleService
|
||||
): Promise<ProductTypes.ProductOptionDTO>
|
||||
|
||||
@InjectTransactionManager()
|
||||
@EmitEvents()
|
||||
async upsertProductOptions(
|
||||
data:
|
||||
| ProductTypes.UpsertProductOptionDTO[]
|
||||
@@ -888,6 +880,7 @@ export default class ProductModuleService
|
||||
): Promise<ProductTypes.ProductOptionDTO[]>
|
||||
|
||||
@InjectManager()
|
||||
@EmitEvents()
|
||||
// @ts-expect-error
|
||||
async updateProductOptions(
|
||||
idOrSelector: string | ProductTypes.FilterableProductOptionProps,
|
||||
@@ -1023,11 +1016,6 @@ export default class ProductModuleService
|
||||
ProductTypes.ProductCollectionDTO[]
|
||||
>(collections)
|
||||
|
||||
eventBuilders.createdProductCollection({
|
||||
data: collections,
|
||||
sharedContext,
|
||||
})
|
||||
|
||||
return Array.isArray(data) ? createdCollections : createdCollections[0]
|
||||
}
|
||||
|
||||
@@ -1061,7 +1049,7 @@ export default class ProductModuleService
|
||||
sharedContext?: Context
|
||||
): Promise<ProductTypes.ProductCollectionDTO>
|
||||
|
||||
@InjectTransactionManager()
|
||||
@InjectManager()
|
||||
@EmitEvents()
|
||||
async upsertProductCollections(
|
||||
data:
|
||||
@@ -1071,6 +1059,24 @@ export default class ProductModuleService
|
||||
): Promise<
|
||||
ProductTypes.ProductCollectionDTO[] | ProductTypes.ProductCollectionDTO
|
||||
> {
|
||||
const collections = await this.upsertCollections_(data, sharedContext)
|
||||
|
||||
const serializedCollections = await this.baseRepository_.serialize<
|
||||
ProductTypes.ProductCollectionDTO[]
|
||||
>(collections)
|
||||
|
||||
return Array.isArray(data)
|
||||
? serializedCollections
|
||||
: serializedCollections[0]
|
||||
}
|
||||
|
||||
@InjectTransactionManager()
|
||||
protected async upsertCollections_(
|
||||
data:
|
||||
| ProductTypes.UpsertProductCollectionDTO[]
|
||||
| ProductTypes.UpsertProductCollectionDTO,
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<InferEntityType<typeof ProductCollection>[]> {
|
||||
const input = Array.isArray(data) ? data : [data]
|
||||
const forUpdate = input.filter(
|
||||
(collection): collection is UpdateCollectionInput => !!collection.id
|
||||
@@ -1091,26 +1097,7 @@ export default class ProductModuleService
|
||||
updated = await this.updateCollections_(forUpdate, sharedContext)
|
||||
}
|
||||
|
||||
const result = [...created, ...updated]
|
||||
const allCollections = await this.baseRepository_.serialize<
|
||||
ProductTypes.ProductCollectionDTO[] | ProductTypes.ProductCollectionDTO
|
||||
>(result)
|
||||
|
||||
if (created.length) {
|
||||
eventBuilders.createdProductCollection({
|
||||
data: created,
|
||||
sharedContext,
|
||||
})
|
||||
}
|
||||
|
||||
if (updated.length) {
|
||||
eventBuilders.updatedProductCollection({
|
||||
data: updated,
|
||||
sharedContext,
|
||||
})
|
||||
}
|
||||
|
||||
return Array.isArray(data) ? allCollections : allCollections[0]
|
||||
return [...created, ...updated]
|
||||
}
|
||||
|
||||
// @ts-expect-error
|
||||
@@ -1166,11 +1153,6 @@ export default class ProductModuleService
|
||||
ProductTypes.ProductCollectionDTO[]
|
||||
>(collections)
|
||||
|
||||
eventBuilders.updatedProductCollection({
|
||||
data: updatedCollections,
|
||||
sharedContext,
|
||||
})
|
||||
|
||||
return isString(idOrSelector) ? updatedCollections[0] : updatedCollections
|
||||
}
|
||||
|
||||
@@ -1283,6 +1265,7 @@ export default class ProductModuleService
|
||||
ProductTypes.ProductCategoryDTO[]
|
||||
>(categories)
|
||||
|
||||
// TODO: Same as the update categories, for some reason I cant get the tree repository update
|
||||
eventBuilders.createdProductCategory({
|
||||
data: createdCategories,
|
||||
sharedContext,
|
||||
@@ -1300,7 +1283,7 @@ export default class ProductModuleService
|
||||
sharedContext?: Context
|
||||
): Promise<ProductTypes.ProductCategoryDTO>
|
||||
|
||||
@InjectTransactionManager()
|
||||
@InjectManager()
|
||||
@EmitEvents()
|
||||
async upsertProductCategories(
|
||||
data:
|
||||
@@ -1310,11 +1293,27 @@ export default class ProductModuleService
|
||||
): Promise<
|
||||
ProductTypes.ProductCategoryDTO[] | ProductTypes.ProductCategoryDTO
|
||||
> {
|
||||
const categories = await this.upsertProductCategories_(data, sharedContext)
|
||||
|
||||
const serializedCategories = await this.baseRepository_.serialize<
|
||||
ProductTypes.ProductCategoryDTO[]
|
||||
>(categories)
|
||||
|
||||
return Array.isArray(data) ? serializedCategories : serializedCategories[0]
|
||||
}
|
||||
|
||||
@InjectTransactionManager()
|
||||
protected async upsertProductCategories_(
|
||||
data:
|
||||
| ProductTypes.UpsertProductCategoryDTO[]
|
||||
| ProductTypes.UpsertProductCategoryDTO,
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<InferEntityType<typeof ProductCategory>[]> {
|
||||
const input = Array.isArray(data) ? data : [data]
|
||||
const forUpdate = input.filter(
|
||||
(category): category is UpdateCategoryInput => !!category.id
|
||||
)
|
||||
const forCreate = input.filter(
|
||||
let forCreate = input.filter(
|
||||
(category): category is ProductTypes.CreateProductCategoryDTO =>
|
||||
!category.id
|
||||
)
|
||||
@@ -1323,6 +1322,11 @@ export default class ProductModuleService
|
||||
let updated: InferEntityType<typeof ProductCategory>[] = []
|
||||
|
||||
if (forCreate.length) {
|
||||
forCreate = forCreate.map((productCategory) => {
|
||||
productCategory.handle ??= kebabCase(productCategory.name)
|
||||
return productCategory
|
||||
})
|
||||
|
||||
created = await this.productCategoryService_.create(
|
||||
forCreate,
|
||||
sharedContext
|
||||
@@ -1335,25 +1339,22 @@ export default class ProductModuleService
|
||||
)
|
||||
}
|
||||
|
||||
const createdCategories = await this.baseRepository_.serialize<
|
||||
ProductTypes.ProductCategoryDTO[]
|
||||
>(created)
|
||||
const updatedCategories = await this.baseRepository_.serialize<
|
||||
ProductTypes.ProductCategoryDTO[]
|
||||
>(updated)
|
||||
// TODO: Same as the update categories, for some reason I cant get the tree repository update
|
||||
// event. I ll need to investigate this
|
||||
if (created.length) {
|
||||
eventBuilders.createdProductCategory({
|
||||
data: created,
|
||||
sharedContext,
|
||||
})
|
||||
}
|
||||
if (updated.length) {
|
||||
eventBuilders.updatedProductCategory({
|
||||
data: updated,
|
||||
sharedContext,
|
||||
})
|
||||
}
|
||||
|
||||
eventBuilders.createdProductCategory({
|
||||
data: createdCategories,
|
||||
sharedContext,
|
||||
})
|
||||
|
||||
eventBuilders.updatedProductCategory({
|
||||
data: updatedCategories,
|
||||
sharedContext,
|
||||
})
|
||||
|
||||
const result = [...createdCategories, ...updatedCategories]
|
||||
return Array.isArray(data) ? result : result[0]
|
||||
return [...created, ...updated]
|
||||
}
|
||||
|
||||
// @ts-expect-error
|
||||
@@ -1377,8 +1378,36 @@ export default class ProductModuleService
|
||||
data: ProductTypes.UpdateProductCategoryDTO,
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<
|
||||
ProductTypes.ProductCategoryDTO[] | ProductTypes.ProductCategoryDTO
|
||||
ProductTypes.ProductCategoryDTO | ProductTypes.ProductCategoryDTO[]
|
||||
> {
|
||||
const categories = await this.updateProductCategories_(
|
||||
idOrSelector,
|
||||
data,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
const serializedCategories = await this.baseRepository_.serialize<
|
||||
ProductTypes.ProductCategoryDTO[]
|
||||
>(categories)
|
||||
|
||||
// TODO: for some reason I cant get the tree repository update
|
||||
// event. I ll need to investigate this
|
||||
eventBuilders.updatedProductCategory({
|
||||
data: serializedCategories,
|
||||
sharedContext,
|
||||
})
|
||||
|
||||
return isString(idOrSelector)
|
||||
? serializedCategories[0]
|
||||
: serializedCategories
|
||||
}
|
||||
|
||||
@InjectTransactionManager()
|
||||
protected async updateProductCategories_(
|
||||
idOrSelector: string | ProductTypes.FilterableProductTypeProps,
|
||||
data: ProductTypes.UpdateProductCategoryDTO,
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<InferEntityType<typeof ProductCategory>[]> {
|
||||
let normalizedInput: UpdateCategoryInput[] = []
|
||||
if (isString(idOrSelector)) {
|
||||
// Check if the type exists in the first place
|
||||
@@ -1406,16 +1435,7 @@ export default class ProductModuleService
|
||||
sharedContext
|
||||
)
|
||||
|
||||
const updatedCategories = await this.baseRepository_.serialize<
|
||||
ProductTypes.ProductCategoryDTO[]
|
||||
>(categories)
|
||||
|
||||
eventBuilders.updatedProductCategory({
|
||||
data: updatedCategories,
|
||||
sharedContext,
|
||||
})
|
||||
|
||||
return isString(idOrSelector) ? updatedCategories[0] : updatedCategories
|
||||
return categories
|
||||
}
|
||||
|
||||
//@ts-expect-error
|
||||
@@ -1443,11 +1463,6 @@ export default class ProductModuleService
|
||||
ProductTypes.ProductDTO[]
|
||||
>(products)
|
||||
|
||||
eventBuilders.createdProduct({
|
||||
data: createdProducts,
|
||||
sharedContext,
|
||||
})
|
||||
|
||||
return Array.isArray(data) ? createdProducts : createdProducts[0]
|
||||
}
|
||||
|
||||
@@ -1474,11 +1489,11 @@ export default class ProductModuleService
|
||||
(product): product is ProductTypes.CreateProductDTO => !product.id
|
||||
)
|
||||
|
||||
let created: InferEntityType<typeof Product>[] = []
|
||||
let created: ProductTypes.ProductDTO[] = []
|
||||
let updated: InferEntityType<typeof Product>[] = []
|
||||
|
||||
if (forCreate.length) {
|
||||
created = await this.createProducts_(forCreate, sharedContext)
|
||||
created = await this.createProducts(forCreate, sharedContext)
|
||||
}
|
||||
if (forUpdate.length) {
|
||||
updated = await this.updateProducts_(forUpdate, sharedContext)
|
||||
@@ -1489,20 +1504,6 @@ export default class ProductModuleService
|
||||
ProductTypes.ProductDTO[] | ProductTypes.ProductDTO
|
||||
>(result)
|
||||
|
||||
if (created.length) {
|
||||
eventBuilders.createdProduct({
|
||||
data: created,
|
||||
sharedContext,
|
||||
})
|
||||
}
|
||||
|
||||
if (updated.length) {
|
||||
eventBuilders.updatedProduct({
|
||||
data: updated,
|
||||
sharedContext,
|
||||
})
|
||||
}
|
||||
|
||||
return Array.isArray(data) ? allProducts : allProducts[0]
|
||||
}
|
||||
|
||||
@@ -1552,11 +1553,6 @@ export default class ProductModuleService
|
||||
ProductTypes.ProductDTO[]
|
||||
>(products)
|
||||
|
||||
eventBuilders.updatedProduct({
|
||||
data: updatedProducts,
|
||||
sharedContext,
|
||||
})
|
||||
|
||||
return isString(idOrSelector) ? updatedProducts[0] : updatedProducts
|
||||
}
|
||||
|
||||
@@ -1657,20 +1653,47 @@ export default class ProductModuleService
|
||||
data: UpdateProductInput[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<InferEntityType<typeof Product>[]> {
|
||||
// We have to do that manually because this method is bypassing the product service and goes
|
||||
// directly to the custom product repository
|
||||
const manager = (sharedContext.transactionManager ??
|
||||
sharedContext.manager) as EntityManager
|
||||
const subscriber = createMedusaMikroOrmEventSubscriber(
|
||||
["updateProducts_"],
|
||||
this as unknown as ReturnType<typeof MedusaService<any>>
|
||||
)
|
||||
|
||||
if (manager && subscriber) {
|
||||
manager
|
||||
.getEventManager()
|
||||
.registerSubscriber(new subscriber(sharedContext))
|
||||
}
|
||||
|
||||
const originalProducts = await this.productService_.list(
|
||||
{
|
||||
id: data.map((d) => d.id),
|
||||
},
|
||||
{
|
||||
relations: ["options", "options.values", "variants", "images", "tags"],
|
||||
},
|
||||
sharedContext
|
||||
)
|
||||
|
||||
const normalizedProducts = await this.normalizeUpdateProductInput(
|
||||
data,
|
||||
sharedContext
|
||||
originalProducts
|
||||
)
|
||||
|
||||
for (const product of normalizedProducts) {
|
||||
this.validateProductUpdatePayload(product)
|
||||
}
|
||||
|
||||
return this.productRepository_.deepUpdate(
|
||||
const updatedProducts = await this.productRepository_.deepUpdate(
|
||||
normalizedProducts,
|
||||
ProductModuleService.validateVariantOptions,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
return updatedProducts
|
||||
}
|
||||
|
||||
// @ts-expect-error
|
||||
@@ -1685,6 +1708,7 @@ export default class ProductModuleService
|
||||
data: ProductTypes.UpdateProductOptionValueDTO,
|
||||
sharedContext?: Context
|
||||
): Promise<ProductTypes.ProductOptionValueDTO[]>
|
||||
|
||||
// @ts-expect-error
|
||||
async updateProductOptionValues(
|
||||
idOrSelector: string | FilterableProductOptionValueProps,
|
||||
@@ -1693,6 +1717,11 @@ export default class ProductModuleService
|
||||
): Promise<
|
||||
ProductTypes.ProductOptionValueDTO | ProductTypes.ProductOptionValueDTO[]
|
||||
> {
|
||||
// TODO: There is a missmatch in the API which lead to function with different number of
|
||||
// arguments. Therefore, applying the MedusaContext() decorator to the function will not work
|
||||
// because the context arg index will differ from method to method.
|
||||
sharedContext.messageAggregator ??= new MessageAggregator()
|
||||
|
||||
let normalizedInput: ({
|
||||
id: string
|
||||
} & ProductTypes.UpdateProductOptionValueDTO)[] = []
|
||||
@@ -1718,7 +1747,7 @@ export default class ProductModuleService
|
||||
}))
|
||||
}
|
||||
|
||||
const productOptionValues = await super.updateProductOptionValues(
|
||||
const productOptionValues = await this.updateProductOptionValues_(
|
||||
normalizedInput,
|
||||
sharedContext
|
||||
)
|
||||
@@ -1727,16 +1756,45 @@ export default class ProductModuleService
|
||||
ProductTypes.ProductOptionValueDTO[]
|
||||
>(productOptionValues)
|
||||
|
||||
eventBuilders.updatedProductOptionValue({
|
||||
data: updatedProductOptionValues,
|
||||
sharedContext: sharedContext,
|
||||
})
|
||||
// TODO: Because of the wrong method override, we have to compensate to prevent breaking
|
||||
// changes right now
|
||||
const groupedEvents = sharedContext.messageAggregator!.getMessages()
|
||||
if (
|
||||
Object.values(groupedEvents).flat().length > 0 &&
|
||||
this.eventBusModuleService_
|
||||
) {
|
||||
const promises: Promise<void>[] = []
|
||||
for (const group of Object.keys(groupedEvents)) {
|
||||
promises.push(
|
||||
this.eventBusModuleService_!.emit(groupedEvents[group], {
|
||||
internal: true,
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
await Promise.all(promises)
|
||||
|
||||
sharedContext.messageAggregator.clearMessages()
|
||||
}
|
||||
|
||||
return isString(idOrSelector)
|
||||
? updatedProductOptionValues[0]
|
||||
: updatedProductOptionValues
|
||||
}
|
||||
|
||||
@InjectTransactionManager()
|
||||
protected async updateProductOptionValues_(
|
||||
normalizedInput: ({
|
||||
id: string
|
||||
} & ProductTypes.UpdateProductOptionValueDTO)[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<InferEntityType<typeof ProductOptionValue>[]> {
|
||||
return await this.productOptionValueService_.update(
|
||||
normalizedInput,
|
||||
sharedContext
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the manually provided handle value of the product
|
||||
* to be URL-safe
|
||||
@@ -1805,8 +1863,7 @@ export default class ProductModuleService
|
||||
const products_ = Array.isArray(products) ? products : [products]
|
||||
|
||||
const normalizedProducts = (await this.normalizeUpdateProductInput(
|
||||
products_ as UpdateProductInput[],
|
||||
sharedContext
|
||||
products_ as UpdateProductInput[]
|
||||
)) as ProductTypes.CreateProductDTO[]
|
||||
|
||||
for (const productData of normalizedProducts) {
|
||||
@@ -1839,6 +1896,12 @@ export default class ProductModuleService
|
||||
) as TOutput
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes the input for the update product input
|
||||
* @param products - The products to normalize
|
||||
* @param originalProducts - The original products to use for the normalization (must include options and option values relations)
|
||||
* @returns The normalized products
|
||||
*/
|
||||
protected async normalizeUpdateProductInput<
|
||||
T extends UpdateProductInput | UpdateProductInput[],
|
||||
TOutput = T extends UpdateProductInput[]
|
||||
@@ -1846,7 +1909,7 @@ export default class ProductModuleService
|
||||
: UpdateProductInput
|
||||
>(
|
||||
products: T,
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
originalProducts?: InferEntityType<typeof Product>[]
|
||||
): Promise<TOutput> {
|
||||
const products_ = Array.isArray(products) ? products : [products]
|
||||
const productsIds = products_.map((p) => p.id).filter(Boolean)
|
||||
@@ -1854,11 +1917,14 @@ export default class ProductModuleService
|
||||
let dbOptions: InferEntityType<typeof ProductOption>[] = []
|
||||
|
||||
if (productsIds.length) {
|
||||
dbOptions = await this.productOptionService_.list(
|
||||
{ product_id: productsIds },
|
||||
{ relations: ["values"] },
|
||||
sharedContext
|
||||
)
|
||||
// Re map options to handle non serialized data as well
|
||||
dbOptions =
|
||||
originalProducts
|
||||
?.map((originalProduct) =>
|
||||
originalProduct.options.map((option) => option)
|
||||
)
|
||||
.flat()
|
||||
.filter(Boolean) ?? []
|
||||
}
|
||||
|
||||
const normalizedProducts: UpdateProductInput[] = []
|
||||
@@ -1872,7 +1938,9 @@ export default class ProductModuleService
|
||||
if (productData.options?.length) {
|
||||
;(productData as any).options = productData.options?.map((option) => {
|
||||
const dbOption = dbOptions.find(
|
||||
(o) => o.title === option.title && o.product_id === productData.id
|
||||
(o) =>
|
||||
(o.title === option.title || o.id === option.id) &&
|
||||
o.product_id === productData.id
|
||||
)
|
||||
return {
|
||||
title: option.title,
|
||||
|
||||
@@ -6,96 +6,6 @@ import {
|
||||
} from "@medusajs/framework/utils"
|
||||
|
||||
export const eventBuilders = {
|
||||
createdProduct: moduleEventBuilderFactory({
|
||||
source: Modules.PRODUCT,
|
||||
action: CommonEvents.CREATED,
|
||||
object: "product",
|
||||
eventName: ProductEvents.PRODUCT_CREATED,
|
||||
}),
|
||||
updatedProduct: moduleEventBuilderFactory({
|
||||
source: Modules.PRODUCT,
|
||||
action: CommonEvents.UPDATED,
|
||||
object: "product",
|
||||
eventName: ProductEvents.PRODUCT_UPDATED,
|
||||
}),
|
||||
deletedProduct: moduleEventBuilderFactory({
|
||||
source: Modules.PRODUCT,
|
||||
action: CommonEvents.DELETED,
|
||||
object: "product",
|
||||
eventName: ProductEvents.PRODUCT_DELETED,
|
||||
}),
|
||||
createdProductVariant: moduleEventBuilderFactory({
|
||||
source: Modules.PRODUCT,
|
||||
action: CommonEvents.CREATED,
|
||||
object: "product_variant",
|
||||
eventName: ProductEvents.PRODUCT_VARIANT_CREATED,
|
||||
}),
|
||||
updatedProductVariant: moduleEventBuilderFactory({
|
||||
source: Modules.PRODUCT,
|
||||
action: CommonEvents.UPDATED,
|
||||
object: "product_variant",
|
||||
eventName: ProductEvents.PRODUCT_VARIANT_UPDATED,
|
||||
}),
|
||||
deletedProductVariant: moduleEventBuilderFactory({
|
||||
source: Modules.PRODUCT,
|
||||
action: CommonEvents.DELETED,
|
||||
object: "product_variant",
|
||||
eventName: ProductEvents.PRODUCT_VARIANT_DELETED,
|
||||
}),
|
||||
createdProductOption: moduleEventBuilderFactory({
|
||||
source: Modules.PRODUCT,
|
||||
action: CommonEvents.CREATED,
|
||||
object: "product_option",
|
||||
eventName: ProductEvents.PRODUCT_OPTION_CREATED,
|
||||
}),
|
||||
updatedProductOption: moduleEventBuilderFactory({
|
||||
source: Modules.PRODUCT,
|
||||
action: CommonEvents.UPDATED,
|
||||
object: "product_option",
|
||||
eventName: ProductEvents.PRODUCT_OPTION_UPDATED,
|
||||
}),
|
||||
deletedProductOption: moduleEventBuilderFactory({
|
||||
source: Modules.PRODUCT,
|
||||
action: CommonEvents.DELETED,
|
||||
object: "product_option",
|
||||
eventName: ProductEvents.PRODUCT_OPTION_DELETED,
|
||||
}),
|
||||
createdProductType: moduleEventBuilderFactory({
|
||||
source: Modules.PRODUCT,
|
||||
action: CommonEvents.CREATED,
|
||||
object: "product_type",
|
||||
eventName: ProductEvents.PRODUCT_TYPE_CREATED,
|
||||
}),
|
||||
updatedProductType: moduleEventBuilderFactory({
|
||||
source: Modules.PRODUCT,
|
||||
action: CommonEvents.UPDATED,
|
||||
object: "product_type",
|
||||
eventName: ProductEvents.PRODUCT_TYPE_UPDATED,
|
||||
}),
|
||||
deletedProductType: moduleEventBuilderFactory({
|
||||
source: Modules.PRODUCT,
|
||||
action: CommonEvents.DELETED,
|
||||
object: "product_type",
|
||||
eventName: ProductEvents.PRODUCT_TYPE_DELETED,
|
||||
}),
|
||||
createdProductTag: moduleEventBuilderFactory({
|
||||
source: Modules.PRODUCT,
|
||||
action: CommonEvents.CREATED,
|
||||
object: "product_tag",
|
||||
eventName: ProductEvents.PRODUCT_TAG_CREATED,
|
||||
}),
|
||||
updatedProductTag: moduleEventBuilderFactory({
|
||||
source: Modules.PRODUCT,
|
||||
action: CommonEvents.UPDATED,
|
||||
object: "product_tag",
|
||||
eventName: ProductEvents.PRODUCT_TAG_UPDATED,
|
||||
}),
|
||||
deletedProductTag: moduleEventBuilderFactory({
|
||||
source: Modules.PRODUCT,
|
||||
action: CommonEvents.DELETED,
|
||||
object: "product_tag",
|
||||
eventName: ProductEvents.PRODUCT_TAG_DELETED,
|
||||
}),
|
||||
createdProductCategory: moduleEventBuilderFactory({
|
||||
source: Modules.PRODUCT,
|
||||
action: CommonEvents.CREATED,
|
||||
@@ -114,40 +24,4 @@ export const eventBuilders = {
|
||||
object: "product_category",
|
||||
eventName: ProductEvents.PRODUCT_CATEGORY_DELETED,
|
||||
}),
|
||||
createdProductCollection: moduleEventBuilderFactory({
|
||||
source: Modules.PRODUCT,
|
||||
action: CommonEvents.CREATED,
|
||||
object: "product_collection",
|
||||
eventName: ProductEvents.PRODUCT_COLLECTION_CREATED,
|
||||
}),
|
||||
updatedProductCollection: moduleEventBuilderFactory({
|
||||
source: Modules.PRODUCT,
|
||||
action: CommonEvents.UPDATED,
|
||||
object: "product_collection",
|
||||
eventName: ProductEvents.PRODUCT_COLLECTION_UPDATED,
|
||||
}),
|
||||
deletedProductCollection: moduleEventBuilderFactory({
|
||||
source: Modules.PRODUCT,
|
||||
action: CommonEvents.DELETED,
|
||||
object: "product_collection",
|
||||
eventName: ProductEvents.PRODUCT_COLLECTION_DELETED,
|
||||
}),
|
||||
createdProductOptionValue: moduleEventBuilderFactory({
|
||||
source: Modules.PRODUCT,
|
||||
action: CommonEvents.CREATED,
|
||||
object: "product_option_value",
|
||||
eventName: ProductEvents.PRODUCT_OPTION_VALUE_CREATED,
|
||||
}),
|
||||
updatedProductOptionValue: moduleEventBuilderFactory({
|
||||
source: Modules.PRODUCT,
|
||||
action: CommonEvents.UPDATED,
|
||||
object: "product_option_value",
|
||||
eventName: ProductEvents.PRODUCT_OPTION_VALUE_UPDATED,
|
||||
}),
|
||||
deletedProductOptionValue: moduleEventBuilderFactory({
|
||||
source: Modules.PRODUCT,
|
||||
action: CommonEvents.DELETED,
|
||||
object: "product_option_value",
|
||||
eventName: ProductEvents.PRODUCT_OPTION_VALUE_DELETED,
|
||||
}),
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user