implement events to the pricing module (#7584)

This commit is contained in:
Harminder Virk
2024-06-05 17:33:49 +05:30
committed by GitHub
parent b2f2c366ec
commit 2b62686ec6
8 changed files with 435 additions and 17 deletions

View File

@@ -159,7 +159,7 @@ export const ModulesDefinition: { [key: string | Modules]: ModuleDefinition } =
label: upperCaseFirst(ModuleRegistrationName.PRICING),
isRequired: false,
isQueryable: true,
dependencies: ["logger"],
dependencies: [ModuleRegistrationName.EVENT_BUS, "logger"],
defaultModuleDeclaration: {
scope: MODULE_SCOPE.INTERNAL,
resources: MODULE_RESOURCE_TYPE.SHARED,

View File

@@ -0,0 +1,27 @@
import { buildEventNamesFromEntityName } from "../event-bus"
import { Modules } from "../modules-sdk"
const eventBaseNames: [
"priceListRuleValue",
"priceListRule",
"priceList",
"priceRule",
"priceSetRuleType",
"priceSet",
"price",
"ruleType"
] = [
"priceListRuleValue",
"priceListRule",
"priceList",
"priceRule",
"priceSetRuleType",
"priceSet",
"price",
"ruleType",
]
export const PricingEvents = buildEventNamesFromEntityName(
eventBaseNames,
Modules.PRICING
)

View File

@@ -1,3 +1,4 @@
export * from "./builders"
export * from "./price-list"
export * from "./rule-type"
export * from "./events"

View File

@@ -1,8 +1,13 @@
import { Modules } from "@medusajs/modules-sdk"
import { IPricingModuleService } from "@medusajs/types"
import { moduleIntegrationTestRunner, SuiteOptions } from "medusa-test-utils"
import {
MockEventBusService,
moduleIntegrationTestRunner,
SuiteOptions,
} from "medusa-test-utils"
import { createPriceLists } from "../../../__fixtures__/price-list"
import { createPriceSets } from "../../../__fixtures__/price-set"
import { CommonEvents, composeMessage, PricingEvents } from "@medusajs/utils"
jest.setTimeout(30000)
@@ -12,6 +17,16 @@ moduleIntegrationTestRunner({
MikroOrmWrapper,
service,
}: SuiteOptions<IPricingModuleService>) => {
let eventBusEmitSpy
beforeEach(() => {
eventBusEmitSpy = jest.spyOn(MockEventBusService.prototype, "emit")
})
afterEach(() => {
jest.clearAllMocks()
})
describe("PriceList Service", () => {
beforeEach(async () => {
const testManager = await MikroOrmWrapper.forkManager()
@@ -400,8 +415,8 @@ moduleIntegrationTestRunner({
{
title: "test",
description: "test",
starts_at: new Date("10/01/2023"),
ends_at: new Date("10/30/2023"),
starts_at: new Date("10/01/2023").toISOString(),
ends_at: new Date("10/30/2023").toISOString(),
rules: {
customer_group_id: [
"vip-customer-group-id",
@@ -488,6 +503,41 @@ moduleIntegrationTestRunner({
]),
})
)
const events = eventBusEmitSpy.mock.calls[0][0]
expect(events).toHaveLength(4)
expect(events[0]).toEqual(
composeMessage(PricingEvents.price_list_created, {
service: Modules.PRICING,
action: CommonEvents.CREATED,
object: "price_list",
data: { id: priceList.id },
})
)
expect(events[1]).toEqual(
composeMessage(PricingEvents.price_list_rule_created, {
service: Modules.PRICING,
action: CommonEvents.CREATED,
object: "price_list_rule",
data: { id: priceList.price_list_rules?.[0].id },
})
)
expect(events[2]).toEqual(
composeMessage(PricingEvents.price_list_rule_created, {
service: Modules.PRICING,
action: CommonEvents.CREATED,
object: "price_list_rule",
data: { id: priceList.price_list_rules?.[1].id },
})
)
expect(events[3]).toEqual(
composeMessage(PricingEvents.price_created, {
service: Modules.PRICING,
action: CommonEvents.CREATED,
object: "price",
data: { id: priceList.prices![0].id },
})
)
})
it("should create a price list with granular rules within prices", async () => {
@@ -745,6 +795,8 @@ moduleIntegrationTestRunner({
},
])
jest.clearAllMocks()
await service.addPriceListPrices([
{
price_list_id: "price-list-1",
@@ -809,6 +861,26 @@ moduleIntegrationTestRunner({
price_list_rules: [],
})
)
const events = eventBusEmitSpy.mock.calls[0][0]
expect(events).toHaveLength(2)
expect(events[0]).toEqual(
composeMessage(PricingEvents.price_created, {
service: Modules.PRICING,
action: CommonEvents.CREATED,
object: "price",
data: { id: priceList.prices![0].id },
})
)
expect(events[1]).toEqual(
composeMessage(PricingEvents.price_rule_created, {
service: Modules.PRICING,
action: CommonEvents.CREATED,
object: "price_rule",
data: { id: priceList.prices![0].price_rules![0].id },
})
)
})
})

View File

@@ -5,9 +5,14 @@ import {
IPricingModuleService,
} from "@medusajs/types"
import { SqlEntityManager } from "@mikro-orm/postgresql"
import { moduleIntegrationTestRunner, SuiteOptions } from "medusa-test-utils"
import {
MockEventBusService,
moduleIntegrationTestRunner,
SuiteOptions,
} from "medusa-test-utils"
import { PriceSetRuleType } from "../../../../src"
import { seedPriceData } from "../../../__fixtures__/seed-price-data"
import { CommonEvents, PricingEvents, composeMessage } from "@medusajs/utils"
jest.setTimeout(30000)
@@ -32,6 +37,16 @@ moduleIntegrationTestRunner({
MikroOrmWrapper,
service,
}: SuiteOptions<IPricingModuleService>) => {
let eventBusEmitSpy
beforeEach(() => {
eventBusEmitSpy = jest.spyOn(MockEventBusService.prototype, "emit")
})
afterEach(() => {
jest.clearAllMocks()
})
describe("PricingModule Service - PriceSet", () => {
beforeEach(async () => {
const testManager = await MikroOrmWrapper.forkManager()
@@ -422,6 +437,41 @@ moduleIntegrationTestRunner({
],
})
)
const [priceRules] = await service.listPriceRules({
price_set_id: [priceSet.id],
})
const events = eventBusEmitSpy.mock.calls[0][0]
expect(events).toHaveLength(3)
expect(events[0]).toEqual(
composeMessage(PricingEvents.price_set_created, {
service: Modules.PRICING,
action: CommonEvents.CREATED,
object: "price_set",
data: { id: priceSet.id },
})
)
expect(events[1]).toEqual(
composeMessage(PricingEvents.price_created, {
service: Modules.PRICING,
action: CommonEvents.CREATED,
object: "price",
data: { id: priceSet.prices![0].id },
})
)
expect(events[2]).toEqual(
composeMessage(PricingEvents.price_rule_created, {
service: Modules.PRICING,
action: CommonEvents.CREATED,
object: "price_rule",
data: {
id: priceRules.id,
},
})
)
})
it("should create a price set with money amounts with and without rules", async () => {
@@ -625,7 +675,7 @@ moduleIntegrationTestRunner({
const [priceSet] = await service.list(
{ id: ["price-set-1"] },
{ relations: ["prices"] }
{ relations: ["prices", "prices.price_rules"] }
)
expect(priceSet).toEqual(
@@ -639,6 +689,25 @@ moduleIntegrationTestRunner({
]),
})
)
const events = eventBusEmitSpy.mock.calls[0][0]
expect(events).toHaveLength(2)
expect(events[0]).toEqual(
composeMessage(PricingEvents.price_created, {
service: Modules.PRICING,
action: CommonEvents.CREATED,
object: "price",
data: { id: priceSet.prices![1].id },
})
)
expect(events[1]).toEqual(
composeMessage(PricingEvents.price_rule_created, {
service: Modules.PRICING,
action: CommonEvents.CREATED,
object: "price_rule",
data: { id: priceSet.prices![1].price_rules[0].id },
})
)
})
it("should add prices to multiple existing price set", async () => {

View File

@@ -21,6 +21,7 @@ import {
import {
arrayDifference,
deduplicate,
EmitEvents,
generateEntityId,
groupBy,
InjectManager,
@@ -47,11 +48,11 @@ import {
} from "@models"
import { PriceListService, RuleTypeService } from "@services"
import { validatePriceListDates } from "@utils"
import { UpdatePriceSetInput } from "src/types/services"
import { validatePriceListDates, eventBuilders } from "@utils"
import { entityNameToLinkableKeysMap, joinerConfig } from "../joiner-config"
import { PriceListIdPrefix } from "../models/price-list"
import { PriceSetIdPrefix } from "../models/price-set"
import { ServiceTypes } from "@types"
type InjectedDependencies = {
baseRepository: DAL.RepositoryService
@@ -326,6 +327,7 @@ export default class PricingModuleService<
): Promise<PriceSetDTO[]>
@InjectManager("baseRepository_")
@EmitEvents()
async create(
data: PricingTypes.CreatePriceSetDTO | PricingTypes.CreatePriceSetDTO[],
@MedusaContext() sharedContext: Context = {}
@@ -336,7 +338,14 @@ export default class PricingModuleService<
// TODO: Remove the need to refetch the data here
const dbPriceSets = await this.list(
{ id: priceSets.map((p) => p.id) },
{ relations: ["rule_types", "prices", "price_rules"] },
{
relations: [
"rule_types",
"prices",
"price_rules",
"prices.price_rules",
],
},
sharedContext
)
@@ -366,7 +375,7 @@ export default class PricingModuleService<
): Promise<PriceSetDTO | PriceSetDTO[]> {
const input = Array.isArray(data) ? data : [data]
const forUpdate = input.filter(
(priceSet): priceSet is UpdatePriceSetInput => !!priceSet.id
(priceSet): priceSet is ServiceTypes.UpdatePriceSetInput => !!priceSet.id
)
const forCreate = input.filter(
(priceSet): priceSet is CreatePriceSetDTO => !priceSet.id
@@ -404,7 +413,7 @@ export default class PricingModuleService<
data: PricingTypes.UpdatePriceSetDTO,
@MedusaContext() sharedContext: Context = {}
): Promise<PriceSetDTO | PriceSetDTO[]> {
let normalizedInput: UpdatePriceSetInput[] = []
let normalizedInput: ServiceTypes.UpdatePriceSetInput[] = []
if (isString(idOrSelector)) {
// Check if the ID exists, it will throw if not.
await this.priceSetService_.retrieve(idOrSelector, {}, sharedContext)
@@ -431,7 +440,7 @@ export default class PricingModuleService<
}
private async normalizeUpdateData(
data: UpdatePriceSetInput[],
data: ServiceTypes.UpdatePriceSetInput[],
sharedContext
) {
const ruleAttributes = data
@@ -480,7 +489,7 @@ export default class PricingModuleService<
@InjectTransactionManager("baseRepository_")
protected async update_(
data: UpdatePriceSetInput[],
data: ServiceTypes.UpdatePriceSetInput[],
@MedusaContext() sharedContext: Context = {}
): Promise<PriceSet[]> {
// TODO: We are not handling rule types, rules, etc. here, add support after data models are finalized
@@ -560,6 +569,7 @@ export default class PricingModuleService<
): Promise<PricingTypes.PriceSetDTO[]>
@InjectManager("baseRepository_")
@EmitEvents()
async addPrices(
data: AddPricesDTO | AddPricesDTO[],
@MedusaContext() sharedContext: Context = {}
@@ -626,6 +636,7 @@ export default class PricingModuleService<
}
@InjectManager("baseRepository_")
@EmitEvents()
async createPriceLists(
data: PricingTypes.CreatePriceListDTO[],
@MedusaContext() sharedContext: Context = {}
@@ -668,6 +679,7 @@ export default class PricingModuleService<
}
@InjectManager("baseRepository_")
@EmitEvents()
async addPriceListPrices(
data: PricingTypes.AddPriceListPricesDTO[],
@MedusaContext() sharedContext: Context = {}
@@ -823,6 +835,49 @@ export default class PricingModuleService<
sharedContext
)
const eventsData = createdPriceSets.reduce(
(eventsData, priceSet) => {
eventsData.priceSets.push({
id: priceSet.id,
})
priceSet.prices.map((price) => {
eventsData.prices.push({
id: price.id,
})
price.price_rules.map((priceRule) => {
eventsData.priceRules.push({
id: priceRule.id,
})
})
})
return eventsData
},
{
priceSets: [],
priceRules: [],
prices: [],
} as {
priceSets: { id: string }[]
priceRules: { id: string }[]
prices: { id: string }[]
}
)
eventBuilders.createdPriceSet({
data: eventsData.priceSets,
sharedContext,
})
eventBuilders.createdPrice({
data: eventsData.prices,
sharedContext,
})
eventBuilders.createdPriceRule({
data: eventsData.priceRules,
sharedContext,
})
if (ruleSetRuleTypeToCreateMap.size) {
await this.priceSetRuleTypeService_.create(
Array.from(ruleSetRuleTypeToCreateMap.values()),
@@ -982,12 +1037,53 @@ export default class PricingModuleService<
price_set_id: priceSetId,
title: "test", // TODO: accept title
rules_count: numberOfRules,
priceRules,
price_rules: priceRules,
}
})
)
await this.priceService_.create(pricesToCreate, sharedContext)
const prices = await this.priceService_.create(
pricesToCreate,
sharedContext
)
/**
* Preparing data for emitting events
*/
const eventsData = prices.reduce(
(eventsData, price) => {
eventsData.prices.push({
id: price.id,
})
price.price_rules.map((priceRule) => {
eventsData.priceRules.push({
id: priceRule.id,
})
})
return eventsData
},
{
priceRules: [],
prices: [],
} as {
priceRules: { id: string }[]
prices: { id: string }[]
}
)
/**
* Emitting events for all created entities
*/
eventBuilders.createdPrice({
data: eventsData.prices,
sharedContext,
})
eventBuilders.createdPriceRule({
data: eventsData.priceRules,
sharedContext,
})
return prices
}
@InjectTransactionManager("baseRepository_")
@@ -1085,10 +1181,73 @@ export default class PricingModuleService<
}
)
return await this.priceListService_.create(
const priceLists = await this.priceListService_.create(
priceListsToCreate,
sharedContext
)
/**
* Preparing data for emitting events
*/
const eventsData = priceLists.reduce(
(eventsData, priceList) => {
eventsData.priceList.push({
id: priceList.id,
})
priceList.price_list_rules.map((listRule) => {
eventsData.priceListRules.push({
id: listRule.id,
})
})
priceList.prices.map((price) => {
eventsData.prices.push({
id: price.id,
})
price.price_rules.map((priceRule) => {
eventsData.priceRules.push({
id: priceRule.id,
})
})
})
return eventsData
},
{
priceList: [],
priceListRules: [],
priceRules: [],
prices: [],
} as {
priceList: { id: string }[]
priceListRules: { id: string }[]
priceRules: { id: string }[]
prices: { id: string }[]
}
)
/**
* Emitting events for all created entities
*/
eventBuilders.createdPriceList({
data: eventsData.priceList,
sharedContext,
})
eventBuilders.createdPriceListRule({
data: eventsData.priceListRules,
sharedContext,
})
eventBuilders.createdPrice({
data: eventsData.prices,
sharedContext,
})
eventBuilders.createdPriceRule({
data: eventsData.priceRules,
sharedContext,
})
return priceLists
}
@InjectTransactionManager("baseRepository_")
@@ -1468,7 +1627,44 @@ export default class PricingModuleService<
pricesToCreate.push(...priceListPricesToCreate)
}
return await this.priceService_.create(pricesToCreate, sharedContext)
const createdPrices = await this.priceService_.create(
pricesToCreate,
sharedContext
)
const eventsData = createdPrices.reduce(
(eventsData, price) => {
eventsData.prices.push({
id: price.id,
})
price.price_rules.map((priceRule) => {
eventsData.priceRules.push({
id: priceRule.id,
})
})
return eventsData
},
{
priceRules: [],
prices: [],
} as {
priceRules: { id: string }[]
prices: { id: string }[]
}
)
eventBuilders.createdPrice({
data: eventsData.prices,
sharedContext,
})
eventBuilders.createdPriceRule({
data: eventsData.priceRules,
sharedContext,
})
return createdPrices
}
@InjectTransactionManager("baseRepository_")

View File

@@ -0,0 +1,52 @@
import {} from "@models"
import {
Modules,
CommonEvents,
PricingEvents,
eventBuilderFactory,
} from "@medusajs/utils"
export const eventBuilders = {
createdPriceSet: eventBuilderFactory({
service: Modules.PRICING,
action: CommonEvents.CREATED,
object: "price_set",
eventsEnum: PricingEvents,
}),
createdPriceSetRuleType: eventBuilderFactory({
service: Modules.PRICING,
action: CommonEvents.CREATED,
object: "price_set_rule_type",
eventsEnum: PricingEvents,
}),
createdPrice: eventBuilderFactory({
service: Modules.PRICING,
action: CommonEvents.CREATED,
object: "price",
eventsEnum: PricingEvents,
}),
createdPriceRule: eventBuilderFactory({
service: Modules.PRICING,
action: CommonEvents.CREATED,
object: "price_rule",
eventsEnum: PricingEvents,
}),
createdPriceList: eventBuilderFactory({
service: Modules.PRICING,
action: CommonEvents.CREATED,
object: "price_list",
eventsEnum: PricingEvents,
}),
createdPriceListRule: eventBuilderFactory({
service: Modules.PRICING,
action: CommonEvents.CREATED,
object: "price_list_rule",
eventsEnum: PricingEvents,
}),
attachedPriceListRule: eventBuilderFactory({
service: Modules.PRICING,
action: CommonEvents.ATTACHED,
object: "price_list_rule",
eventsEnum: PricingEvents,
}),
}

View File

@@ -1 +1,2 @@
export * from "./validate-price-list-dates"
export * from "./events"