diff --git a/packages/admin-next/dashboard/src/types/api-payloads.ts b/packages/admin-next/dashboard/src/types/api-payloads.ts index 471027fc36..c690daf3b0 100644 --- a/packages/admin-next/dashboard/src/types/api-payloads.ts +++ b/packages/admin-next/dashboard/src/types/api-payloads.ts @@ -60,11 +60,29 @@ export type UpdateStockLocationSalesChannelsReq = { } export type CreateFulfillmentSetReq = CreateFulfillmentSetDTO export type CreateServiceZoneReq = CreateServiceZoneDTO -export type UpdateServiceZoneReq = UpdateServiceZoneDTO +export type UpdateServiceZoneReq = + | UpdateServiceZoneDTO -// Shipping Options -export type CreateShippingOptionReq = CreateShippingOptionDTO -export type UpdateShippingOptionReq = UpdateShippingOptionDTO + // Shipping Options + | { region_id: string; amount: number } +export type CreateShippingOptionReq = CreateShippingOptionDTO & { + /** + * The shipping option pricing + */ + prices: ( + | { currency_code: string; amount: number } + | { region_id: string; amount: number } + )[] +} +export type UpdateShippingOptionReq = UpdateShippingOptionDTO & { + /** + * The shipping option pricing + */ + prices: ( + | { currency_code: string; amount: number; id?: string } + | { region_id: string; amount: number; id?: string } + )[] +} // Shipping Profile export type CreateShippingProfileReq = CreateShippingProfileDTO diff --git a/packages/core/medusa-test-utils/src/module-test-runner.ts b/packages/core/medusa-test-utils/src/module-test-runner.ts index a28275b040..0712250e8d 100644 --- a/packages/core/medusa-test-utils/src/module-test-runner.ts +++ b/packages/core/medusa-test-utils/src/module-test-runner.ts @@ -14,7 +14,7 @@ export interface SuiteOptions { } } -export function moduleIntegrationTestRunner({ +export function moduleIntegrationTestRunner({ moduleName, moduleModels, moduleOptions = {}, @@ -34,7 +34,7 @@ export function moduleIntegrationTestRunner({ injectedDependencies?: Record resolve?: string debug?: boolean - testSuite: (options: SuiteOptions) => void + testSuite: (options: SuiteOptions) => void }) { const moduleSdkImports = require("@medusajs/modules-sdk") @@ -111,7 +111,7 @@ export function moduleIntegrationTestRunner({ schema, clientUrl: dbConfig.clientUrl, }, - } as SuiteOptions + } as SuiteOptions const beforeEach_ = async () => { await MikroOrmWrapper.setupDatabase() diff --git a/packages/core/types/src/event-bus/common.ts b/packages/core/types/src/event-bus/common.ts index a589e83c55..28da5ebb1a 100644 --- a/packages/core/types/src/event-bus/common.ts +++ b/packages/core/types/src/event-bus/common.ts @@ -1,3 +1,5 @@ +import { Context } from "../shared-context" + export type Subscriber = ( data: T, eventName: string @@ -39,13 +41,12 @@ export type Message = { options?: Record } -export type MessageFormat = { +export type RawMessageFormat = { eventName: string - metadata: { - service: string - action: string - object: string - eventGroupId?: string - } - data: T | T[] + data: T + service: string + object: string + action?: string + context?: Pick + options?: Record } diff --git a/packages/core/types/src/fulfillment/mutations/fulfillment.ts b/packages/core/types/src/fulfillment/mutations/fulfillment.ts index 350e6693b7..74df0dfd2c 100644 --- a/packages/core/types/src/fulfillment/mutations/fulfillment.ts +++ b/packages/core/types/src/fulfillment/mutations/fulfillment.ts @@ -110,5 +110,8 @@ export interface UpdateFulfillmentDTO { /** * The labels associated with the fulfillment. */ - labels?: Omit[] + labels?: ( + | Omit + | { id: string } + )[] } diff --git a/packages/core/types/src/fulfillment/mutations/service-zone.ts b/packages/core/types/src/fulfillment/mutations/service-zone.ts index a986369239..9c28d4a7bf 100644 --- a/packages/core/types/src/fulfillment/mutations/service-zone.ts +++ b/packages/core/types/src/fulfillment/mutations/service-zone.ts @@ -3,6 +3,10 @@ import { CreateCountryGeoZoneDTO, CreateProvinceGeoZoneDTO, CreateZipGeoZoneDTO, + UpdateCityGeoZoneDTO, + UpdateCountryGeoZoneDTO, + UpdateProvinceGeoZoneDTO, + UpdateZipGeoZoneDTO, } from "./geo-zone" /** @@ -52,6 +56,10 @@ export interface UpdateServiceZoneDTO { | Omit | Omit | Omit + | UpdateCountryGeoZoneDTO + | UpdateProvinceGeoZoneDTO + | UpdateCityGeoZoneDTO + | UpdateZipGeoZoneDTO | { /** * The ID of the geo zone. diff --git a/packages/core/types/src/fulfillment/mutations/shipping-option.ts b/packages/core/types/src/fulfillment/mutations/shipping-option.ts index 06ab9c209c..b7432a1d0d 100644 --- a/packages/core/types/src/fulfillment/mutations/shipping-option.ts +++ b/packages/core/types/src/fulfillment/mutations/shipping-option.ts @@ -46,14 +46,6 @@ export interface CreateShippingOptionDTO { * The shipping option rules associated with the shipping option. */ rules?: Omit[] - - /** - * The shipping option pricing - */ - prices: ( - | { currency_code: string; amount: number } - | { region_id: string; amount: number } - )[] } /** @@ -120,14 +112,6 @@ export interface UpdateShippingOptionDTO { id: string } )[] - - /** - * The shipping option pricing - */ - prices: ( - | { currency_code: string; amount: number; id?: string } - | { region_id: string; amount: number; id?: string } - )[] } /** diff --git a/packages/core/types/src/shared-context.ts b/packages/core/types/src/shared-context.ts index ce9302645d..75e30e636c 100644 --- a/packages/core/types/src/shared-context.ts +++ b/packages/core/types/src/shared-context.ts @@ -30,8 +30,8 @@ export interface IMessageAggregator { clearMessages(): void saveRawMessageData( messageData: - | EventBusTypes.MessageFormat - | EventBusTypes.MessageFormat[], + | EventBusTypes.RawMessageFormat + | EventBusTypes.RawMessageFormat[], options?: Record ): void } diff --git a/packages/core/utils/src/event-bus/build-event-messages.ts b/packages/core/utils/src/event-bus/build-event-messages.ts index e3ba782488..e6a2410ba1 100644 --- a/packages/core/utils/src/event-bus/build-event-messages.ts +++ b/packages/core/utils/src/event-bus/build-event-messages.ts @@ -1,39 +1,4 @@ import { Context, EventBusTypes } from "@medusajs/types" -import { CommonEvents } from "./common-events" - -/** - * Build messages from message data to be consumed by the event bus and emitted to the consumer - * @param MessageFormat - * @param options - */ -export function buildEventMessages( - messageData: - | EventBusTypes.MessageFormat - | EventBusTypes.MessageFormat[], - options?: Record -): EventBusTypes.Message[] { - const messageData_ = Array.isArray(messageData) ? messageData : [messageData] - const messages: EventBusTypes.Message[] = [] - - messageData_.map((data) => { - const data_ = Array.isArray(data.data) ? data.data : [data.data] - data_.forEach((bodyData) => { - const message = composeMessage(data.eventName, { - data: bodyData, - service: data.metadata.service, - entity: data.metadata.object, - action: data.metadata.action, - context: { - eventGroupId: data.metadata.eventGroupId, - } as Context, - options, - }) - messages.push(message) - }) - }) - - return messages -} /** * Helper function to compose and normalize a Message to be emitted by EventBus Module @@ -48,27 +13,29 @@ export function composeMessage( { data, service, - entity, + object, action, context = {}, options, }: { - data: unknown + data: any service: string - entity: string + object: string action?: string context?: Context - options?: Record + options?: Record } ): EventBusTypes.Message { const act = action || eventName.split(".").pop() - if (!action && !Object.values(CommonEvents).includes(act as CommonEvents)) { + if ( + !action /* && !Object.values(CommonEvents).includes(act as CommonEvents)*/ + ) { throw new Error("Action is required if eventName is not a CommonEvent") } const metadata: EventBusTypes.MessageBody["metadata"] = { service, - object: entity, + object, action: act!, } diff --git a/packages/core/utils/src/event-bus/message-aggregator.ts b/packages/core/utils/src/event-bus/message-aggregator.ts index 4d36ee6644..656145829e 100644 --- a/packages/core/utils/src/event-bus/message-aggregator.ts +++ b/packages/core/utils/src/event-bus/message-aggregator.ts @@ -1,11 +1,12 @@ import { + Context, EventBusTypes, IMessageAggregator, Message, MessageAggregatorFormat, } from "@medusajs/types" -import { buildEventMessages } from "./build-event-messages" +import { composeMessage } from "./build-event-messages" export class MessageAggregator implements IMessageAggregator { private messages: Message[] @@ -28,11 +29,25 @@ export class MessageAggregator implements IMessageAggregator { saveRawMessageData( messageData: - | EventBusTypes.MessageFormat - | EventBusTypes.MessageFormat[], - options?: Record + | EventBusTypes.RawMessageFormat + | EventBusTypes.RawMessageFormat[], + { + options, + sharedContext, + }: { options?: Record; sharedContext?: Context } = {} ): void { - this.save(buildEventMessages(messageData, options)) + const messages = Array.isArray(messageData) ? messageData : [messageData] + const composedMessages = messages.map((message) => { + return composeMessage(message.eventName, { + data: message.data, + service: message.service, + object: message.object, + action: message.action, + options, + context: sharedContext, + }) + }) + this.save(composedMessages) } getMessages(format?: MessageAggregatorFormat): { diff --git a/packages/core/utils/src/event-bus/utils.ts b/packages/core/utils/src/event-bus/utils.ts index 8c0798f334..144d03d0be 100644 --- a/packages/core/utils/src/event-bus/utils.ts +++ b/packages/core/utils/src/event-bus/utils.ts @@ -58,7 +58,8 @@ type ReturnType = TNames extends [ * @param names */ export function buildEventNamesFromEntityName( - names: TNames + names: TNames, + prefix?: string ): ReturnType { const events = {} @@ -69,16 +70,15 @@ export function buildEventNamesFromEntityName( if (i === 0) { for (const event of Object.values(CommonEvents) as string[]) { - events[event] = `${kebabCaseName}.${event}` + events[event] = `${prefix ? prefix + "." : ""}${kebabCaseName}.${event}` } continue } for (const event of Object.values(CommonEvents) as string[]) { - events[`${snakedCaseName}_${event}`] = - `${kebabCaseName}.${event}` as `${KebabCase< - typeof name - >}.${typeof event}` + events[`${snakedCaseName}_${event}`] = `${ + prefix ? prefix + "." : "" + }${kebabCaseName}.${event}` as `${KebabCase}.${typeof event}` } } diff --git a/packages/core/utils/src/fulfillment/events.ts b/packages/core/utils/src/fulfillment/events.ts index 3e4c8c2973..04933fc580 100644 --- a/packages/core/utils/src/fulfillment/events.ts +++ b/packages/core/utils/src/fulfillment/events.ts @@ -1,21 +1,33 @@ import { buildEventNamesFromEntityName } from "../event-bus" +import { Modules } from "../modules-sdk" const eventBaseNames: [ "fulfillmentSet", "serviceZone", "geoZone", "shippingOption", + "shippingOptionType", "shippingProfile", "shippingOptionRule", - "fulfillment" + "fulfillment", + "fulfillmentAddress", + "fulfillmentItem", + "fulfillmentLabel" ] = [ "fulfillmentSet", "serviceZone", "geoZone", "shippingOption", + "shippingOptionType", "shippingProfile", "shippingOptionRule", "fulfillment", + "fulfillmentAddress", + "fulfillmentItem", + "fulfillmentLabel", ] -export const FulfillmentEvents = buildEventNamesFromEntityName(eventBaseNames) +export const FulfillmentEvents = buildEventNamesFromEntityName( + eventBaseNames, + Modules.FULFILLMENT +) diff --git a/packages/core/utils/src/inventory/events.ts b/packages/core/utils/src/inventory/events.ts index b693c46c32..d20a8fe586 100644 --- a/packages/core/utils/src/inventory/events.ts +++ b/packages/core/utils/src/inventory/events.ts @@ -1,14 +1,13 @@ -import { CommonEvents } from "../event-bus" +import { buildEventNamesFromEntityName } from "../event-bus" +import { Modules } from "../modules-sdk" -export const InventoryEvents = { - created: "inventory-item." + CommonEvents.CREATED, - updated: "inventory-item." + CommonEvents.UPDATED, - deleted: "inventory-item." + CommonEvents.DELETED, - restored: "inventory-item." + CommonEvents.RESTORED, - reservation_item_created: "reservation-item." + CommonEvents.CREATED, - reservation_item_updated: "reservation-item." + CommonEvents.UPDATED, - reservation_item_deleted: "reservation-item." + CommonEvents.DELETED, - inventory_level_deleted: "inventory-level." + CommonEvents.DELETED, - inventory_level_created: "inventory-level." + CommonEvents.CREATED, - inventory_level_updated: "inventory-level." + CommonEvents.UPDATED, -} +const eventBaseNames: ["inventoryItem", "reservationItem", "inventoryLevel"] = [ + "inventoryItem", + "reservationItem", + "inventoryLevel", +] + +export const InventoryEvents = buildEventNamesFromEntityName( + eventBaseNames, + Modules.INVENTORY +) diff --git a/packages/core/utils/src/modules-sdk/decorators/emit-events.ts b/packages/core/utils/src/modules-sdk/decorators/emit-events.ts index eb8a38665f..b8003c9f6d 100644 --- a/packages/core/utils/src/modules-sdk/decorators/emit-events.ts +++ b/packages/core/utils/src/modules-sdk/decorators/emit-events.ts @@ -1,16 +1,17 @@ import { MessageAggregator } from "../../event-bus" import { InjectIntoContext } from "./inject-into-context" -import {MessageAggregatorFormat} from "@medusajs/types"; +import { MessageAggregatorFormat } from "@medusajs/types" -export function EmitEvents(options: MessageAggregatorFormat = {} as MessageAggregatorFormat) { +export function EmitEvents( + options: MessageAggregatorFormat = {} as MessageAggregatorFormat +) { return function ( target: any, propertyKey: string | symbol, descriptor: any ): void { - const aggregator = new MessageAggregator() InjectIntoContext({ - messageAggregator: () => aggregator, + messageAggregator: () => new MessageAggregator(), })(target, propertyKey, descriptor) const original = descriptor.value @@ -18,6 +19,17 @@ export function EmitEvents(options: MessageAggregatorFormat = {} as MessageAggre descriptor.value = async function (...args: any[]) { const result = await original.apply(this, args) + if (!target.emitEvents_) { + const logger = Object.keys(this.__container__ ?? {}).includes("logger") + ? this.__container__.logger + : console + logger.warn( + `No emitEvents_ method found on ${target.constructor.name}. No events emitted. To be able to use the @EmitEvents() you need to have the emitEvents_ method implemented in the class.` + ) + } + + const argIndex = target.MedusaContextIndex_[propertyKey] + const aggregator = args[argIndex].messageAggregator as MessageAggregator await target.emitEvents_.apply(this, [aggregator.getMessages(options)]) aggregator.clearMessages() diff --git a/packages/core/utils/src/modules-sdk/decorators/inject-into-context.ts b/packages/core/utils/src/modules-sdk/decorators/inject-into-context.ts index 08f4e9dfec..07f8c4644d 100644 --- a/packages/core/utils/src/modules-sdk/decorators/inject-into-context.ts +++ b/packages/core/utils/src/modules-sdk/decorators/inject-into-context.ts @@ -1,5 +1,3 @@ -import { MessageAggregator } from "../../event-bus" - export function InjectIntoContext( properties: Record ): MethodDecorator { diff --git a/packages/core/utils/src/modules-sdk/event-builder-factory.ts b/packages/core/utils/src/modules-sdk/event-builder-factory.ts new file mode 100644 index 0000000000..d688411464 --- /dev/null +++ b/packages/core/utils/src/modules-sdk/event-builder-factory.ts @@ -0,0 +1,67 @@ +import { Context, EventBusTypes } from "@medusajs/types" + +/** + * Factory function to create event builders for different entities + * + * @example + * const createdFulfillment = eventBuilderFactory({ + * service: Modules.FULFILLMENT, + * action: CommonEvents.CREATED, + * object: "fulfillment", + * eventsEnum: FulfillmentEvents, + * }) + * + * createdFulfillment({ + * data, + * sharedContext, + * }) + * + * @param isMainEntity + * @param action + * @param object + * @param eventsEnum + * @param service + */ +export function eventBuilderFactory({ + isMainEntity, + action, + object, + eventsEnum, + service, +}: { + isMainEntity?: boolean + action: string + object: string + eventsEnum: Record + service: string +}) { + return function ({ + data, + sharedContext, + }: { + data: { id: string }[] + sharedContext: Context + }) { + if (!data.length) { + return + } + + const aggregator = sharedContext.messageAggregator! + const messages: EventBusTypes.RawMessageFormat[] = [] + + data.forEach((dataItem) => { + messages.push({ + service, + action, + context: sharedContext, + data: { id: dataItem.id }, + eventName: isMainEntity + ? eventsEnum[action] + : eventsEnum[`${object}_${action}`], + object, + }) + }) + + aggregator.saveRawMessageData(messages) + } +} diff --git a/packages/core/utils/src/modules-sdk/index.ts b/packages/core/utils/src/modules-sdk/index.ts index ecd006c03f..a8430c6d61 100644 --- a/packages/core/utils/src/modules-sdk/index.ts +++ b/packages/core/utils/src/modules-sdk/index.ts @@ -9,3 +9,4 @@ export * from "./migration-scripts" export * from "./internal-module-service-factory" export * from "./abstract-module-service-factory" export * from "./definition" +export * from "./event-builder-factory" diff --git a/packages/core/utils/src/user/events.ts b/packages/core/utils/src/user/events.ts index 467ad769de..26760233bc 100644 --- a/packages/core/utils/src/user/events.ts +++ b/packages/core/utils/src/user/events.ts @@ -1,9 +1,9 @@ -import { CommonEvents } from "../event-bus" +import { buildEventNamesFromEntityName } from "../event-bus" +import { Modules } from "../modules-sdk" + +const eventBaseNames: ["user", "invite"] = ["user", "invite"] export const UserEvents = { - created: "user." + CommonEvents.CREATED, - updated: "user." + CommonEvents.UPDATED, - invite_created: "invite." + CommonEvents.CREATED, - invite_updated: "invite." + CommonEvents.UPDATED, - invite_token_generated: "invite.token_generated", + ...buildEventNamesFromEntityName(eventBaseNames, Modules.USER), + invite_token_generated: `${Modules.USER}.user.invite.token_generated`, } diff --git a/packages/modules/fulfillment/integration-tests/__tests__/fulfillment-module-service/fulfillment-set.spec.ts b/packages/modules/fulfillment/integration-tests/__tests__/fulfillment-module-service/fulfillment-set.spec.ts index 95bb0eae24..25059b5125 100644 --- a/packages/modules/fulfillment/integration-tests/__tests__/fulfillment-module-service/fulfillment-set.spec.ts +++ b/packages/modules/fulfillment/integration-tests/__tests__/fulfillment-module-service/fulfillment-set.spec.ts @@ -7,15 +7,15 @@ import { UpdateFulfillmentSetDTO, } from "@medusajs/types" import { FulfillmentEvents, GeoZoneType } from "@medusajs/utils" -import { moduleIntegrationTestRunner, SuiteOptions } from "medusa-test-utils" +import { moduleIntegrationTestRunner } from "medusa-test-utils" import { MockEventBusService } from "medusa-test-utils/dist" import { buildExpectedEventMessageShape } from "../../__fixtures__" jest.setTimeout(100000) -moduleIntegrationTestRunner({ +moduleIntegrationTestRunner({ moduleName: Modules.FULFILLMENT, - testSuite: ({ service }: SuiteOptions) => { + testSuite: ({ service }) => { let eventBusEmitSpy beforeEach(() => { @@ -625,6 +625,15 @@ moduleIntegrationTestRunner({ type: updateData.type, }) ) + + expect(eventBusEmitSpy).toHaveBeenCalledWith([ + buildExpectedEventMessageShape({ + eventName: FulfillmentEvents.updated, + action: "updated", + object: "fulfillment_set", + data: { id: updatedFulfillmentSets.id }, + }), + ]) }) it("should update a collection of fulfillment sets", async function () { @@ -655,6 +664,7 @@ moduleIntegrationTestRunner({ }) expect(updatedFulfillmentSets).toHaveLength(2) + expect(eventBusEmitSpy.mock.calls[1][0]).toHaveLength(2) for (const data_ of updateData) { const currentFullfillmentSet = fullfillmentSets.find( @@ -668,6 +678,17 @@ moduleIntegrationTestRunner({ type: data_.type, }) ) + + expect(eventBusEmitSpy).toHaveBeenLastCalledWith( + expect.arrayContaining([ + buildExpectedEventMessageShape({ + eventName: FulfillmentEvents.updated, + action: "updated", + object: "fulfillment_set", + data: { id: currentFullfillmentSet.id }, + }), + ]) + ) } }) @@ -742,6 +763,46 @@ moduleIntegrationTestRunner({ id: updatedFulfillmentSet.service_zones[0].id, }) ) + + expect(eventBusEmitSpy.mock.calls[1][0]).toHaveLength(5) + expect(eventBusEmitSpy).toHaveBeenLastCalledWith( + expect.arrayContaining([ + buildExpectedEventMessageShape({ + eventName: FulfillmentEvents.updated, + action: "updated", + object: "fulfillment_set", + data: { id: updatedFulfillmentSet.id }, + }), + buildExpectedEventMessageShape({ + eventName: FulfillmentEvents.service_zone_created, + action: "created", + object: "service_zone", + data: { id: updatedFulfillmentSet.service_zones[0].id }, + }), + buildExpectedEventMessageShape({ + eventName: FulfillmentEvents.geo_zone_created, + action: "created", + object: "geo_zone", + data: { + id: updatedFulfillmentSet.service_zones[0].geo_zones[0].id, + }, + }), + buildExpectedEventMessageShape({ + eventName: FulfillmentEvents.service_zone_deleted, + action: "deleted", + object: "service_zone", + data: { id: createdFulfillmentSet.service_zones[0].id }, + }), + buildExpectedEventMessageShape({ + eventName: FulfillmentEvents.geo_zone_deleted, + action: "deleted", + object: "geo_zone", + data: { + id: createdFulfillmentSet.service_zones[0].geo_zones[0].id, + }, + }), + ]) + ) }) it("should update an existing fulfillment set and add a new service zone", async function () { @@ -812,6 +873,36 @@ moduleIntegrationTestRunner({ ]), }) ) + + const createdServiceZone = updatedFulfillmentSet.service_zones.find( + (s) => s.name === "service-zone-test2" + ) + + expect(eventBusEmitSpy.mock.calls[1][0]).toHaveLength(3) + expect(eventBusEmitSpy).toHaveBeenLastCalledWith( + expect.arrayContaining([ + buildExpectedEventMessageShape({ + eventName: FulfillmentEvents.updated, + action: "updated", + object: "fulfillment_set", + data: { id: updatedFulfillmentSet.id }, + }), + buildExpectedEventMessageShape({ + eventName: FulfillmentEvents.service_zone_created, + action: "created", + object: "service_zone", + data: { id: createdServiceZone.id }, + }), + buildExpectedEventMessageShape({ + eventName: FulfillmentEvents.geo_zone_created, + action: "created", + object: "geo_zone", + data: { + id: createdServiceZone.geo_zones[0].id, + }, + }), + ]) + ) }) it("should fail on duplicated fulfillment set name", async function () { @@ -897,11 +988,16 @@ moduleIntegrationTestRunner({ const updatedFulfillmentSets = await service.update(updateData) expect(updatedFulfillmentSets).toHaveLength(2) + expect(eventBusEmitSpy.mock.calls[1][0]).toHaveLength(10) for (const data_ of updateData) { const expectedFulfillmentSet = updatedFulfillmentSets.find( (f) => f.id === data_.id ) + const originalFulfillmentSet = createdFulfillmentSets.find( + (f) => f.id === data_.id + ) + expect(expectedFulfillmentSet).toEqual( expect.objectContaining({ id: data_.id, @@ -925,6 +1021,47 @@ moduleIntegrationTestRunner({ ]), }) ) + + expect(eventBusEmitSpy).toHaveBeenLastCalledWith( + expect.arrayContaining([ + buildExpectedEventMessageShape({ + eventName: FulfillmentEvents.updated, + action: "updated", + object: "fulfillment_set", + data: { id: expectedFulfillmentSet.id }, + }), + buildExpectedEventMessageShape({ + eventName: FulfillmentEvents.service_zone_created, + action: "created", + object: "service_zone", + data: { id: expectedFulfillmentSet.service_zones[0].id }, + }), + buildExpectedEventMessageShape({ + eventName: FulfillmentEvents.geo_zone_created, + action: "created", + object: "geo_zone", + data: { + id: expectedFulfillmentSet.service_zones[0].geo_zones[0] + .id, + }, + }), + buildExpectedEventMessageShape({ + eventName: FulfillmentEvents.service_zone_deleted, + action: "deleted", + object: "service_zone", + data: { id: originalFulfillmentSet.service_zones[0].id }, + }), + buildExpectedEventMessageShape({ + eventName: FulfillmentEvents.geo_zone_deleted, + action: "deleted", + object: "geo_zone", + data: { + id: originalFulfillmentSet.service_zones[0].geo_zones[0] + .id, + }, + }), + ]) + ) } const serviceZones = await service.listServiceZones() @@ -1002,6 +1139,7 @@ moduleIntegrationTestRunner({ const updatedFulfillmentSets = await service.update(updateData) expect(updatedFulfillmentSets).toHaveLength(2) + expect(eventBusEmitSpy.mock.calls[1][0]).toHaveLength(6) for (const data_ of updateData) { const expectedFulfillmentSet = updatedFulfillmentSets.find( @@ -1033,6 +1171,36 @@ moduleIntegrationTestRunner({ ]), }) ) + + const createdServiceZone = + expectedFulfillmentSet.service_zones.find((s) => + s.name.includes(`added-service-zone-test`) + ) + + expect(eventBusEmitSpy).toHaveBeenLastCalledWith( + expect.arrayContaining([ + buildExpectedEventMessageShape({ + eventName: FulfillmentEvents.updated, + action: "updated", + object: "fulfillment_set", + data: { id: expectedFulfillmentSet.id }, + }), + buildExpectedEventMessageShape({ + eventName: FulfillmentEvents.service_zone_created, + action: "created", + object: "service_zone", + data: { id: createdServiceZone.id }, + }), + buildExpectedEventMessageShape({ + eventName: FulfillmentEvents.geo_zone_created, + action: "created", + object: "geo_zone", + data: { + id: createdServiceZone.geo_zones[0].id, + }, + }), + ]) + ) } const serviceZones = await service.listServiceZones() diff --git a/packages/modules/fulfillment/integration-tests/__tests__/fulfillment-module-service/fulfillment.spec.ts b/packages/modules/fulfillment/integration-tests/__tests__/fulfillment-module-service/fulfillment.spec.ts index 696caaddad..d8553899e0 100644 --- a/packages/modules/fulfillment/integration-tests/__tests__/fulfillment-module-service/fulfillment.spec.ts +++ b/packages/modules/fulfillment/integration-tests/__tests__/fulfillment-module-service/fulfillment.spec.ts @@ -1,11 +1,19 @@ import { resolve } from "path" import { Modules } from "@medusajs/modules-sdk" -import { IFulfillmentModuleService } from "@medusajs/types" -import { moduleIntegrationTestRunner, SuiteOptions } from "medusa-test-utils" import { + IFulfillmentModuleService, + UpdateFulfillmentDTO, +} from "@medusajs/types" +import { + MockEventBusService, + moduleIntegrationTestRunner, +} from "medusa-test-utils" +import { + buildExpectedEventMessageShape, generateCreateFulfillmentData, generateCreateShippingOptionsData, } from "../../__fixtures__" +import { FulfillmentEvents } from "@medusajs/utils" jest.setTimeout(100000) @@ -27,10 +35,20 @@ const moduleOptions = { const providerId = "fixtures-fulfillment-provider_test-provider" -moduleIntegrationTestRunner({ +moduleIntegrationTestRunner({ moduleName: Modules.FULFILLMENT, moduleOptions: moduleOptions, - testSuite: ({ service }: SuiteOptions) => { + testSuite: ({ service }) => { + let eventBusEmitSpy + + beforeEach(() => { + eventBusEmitSpy = jest.spyOn(MockEventBusService.prototype, "emit") + }) + + afterEach(() => { + jest.clearAllMocks() + }) + describe("Fulfillment Module Service", () => { describe("read", () => { it("should list fulfillment", async () => { @@ -103,6 +121,8 @@ moduleIntegrationTestRunner({ }) ) + jest.clearAllMocks() + const fulfillment = await service.createFulfillment( generateCreateFulfillmentData({ provider_id: providerId, @@ -136,6 +156,34 @@ moduleIntegrationTestRunner({ ], }) ) + + expect(eventBusEmitSpy.mock.calls[0][0]).toHaveLength(4) + expect(eventBusEmitSpy).toHaveBeenCalledWith([ + buildExpectedEventMessageShape({ + eventName: FulfillmentEvents.fulfillment_created, + action: "created", + object: "fulfillment", + data: { id: fulfillment.id }, + }), + buildExpectedEventMessageShape({ + eventName: FulfillmentEvents.fulfillment_address_created, + action: "created", + object: "fulfillment_address", + data: { id: fulfillment.delivery_address.id }, + }), + buildExpectedEventMessageShape({ + eventName: FulfillmentEvents.fulfillment_item_created, + action: "created", + object: "fulfillment_item", + data: { id: fulfillment.items[0].id }, + }), + buildExpectedEventMessageShape({ + eventName: FulfillmentEvents.fulfillment_label_created, + action: "created", + object: "fulfillment_label", + data: { id: fulfillment.labels[0].id }, + }), + ]) }) it("should create a return fulfillment", async () => { @@ -160,6 +208,8 @@ moduleIntegrationTestRunner({ }) ) + jest.clearAllMocks() + const fulfillment = await service.createReturnFulfillment( generateCreateFulfillmentData({ provider_id: providerId, @@ -193,6 +243,169 @@ moduleIntegrationTestRunner({ ], }) ) + + expect(eventBusEmitSpy.mock.calls[0][0]).toHaveLength(4) + expect(eventBusEmitSpy).toHaveBeenCalledWith([ + buildExpectedEventMessageShape({ + eventName: FulfillmentEvents.fulfillment_created, + action: "created", + object: "fulfillment", + data: { id: fulfillment.id }, + }), + buildExpectedEventMessageShape({ + eventName: FulfillmentEvents.fulfillment_address_created, + action: "created", + object: "fulfillment_address", + data: { id: fulfillment.delivery_address.id }, + }), + buildExpectedEventMessageShape({ + eventName: FulfillmentEvents.fulfillment_item_created, + action: "created", + object: "fulfillment_item", + data: { id: fulfillment.items[0].id }, + }), + buildExpectedEventMessageShape({ + eventName: FulfillmentEvents.fulfillment_label_created, + action: "created", + object: "fulfillment_label", + data: { id: fulfillment.labels[0].id }, + }), + ]) + }) + }) + + describe("on update", () => { + it("should update a fulfillment", async () => { + const shippingProfile = await service.createShippingProfiles({ + name: "test", + type: "default", + }) + const fulfillmentSet = await service.create({ + name: "test", + type: "test-type", + }) + const serviceZone = await service.createServiceZones({ + name: "test", + fulfillment_set_id: fulfillmentSet.id, + }) + + const shippingOption = await service.createShippingOptions( + generateCreateShippingOptionsData({ + provider_id: providerId, + service_zone_id: serviceZone.id, + shipping_profile_id: shippingProfile.id, + }) + ) + + const fulfillment = await service.createFulfillment( + generateCreateFulfillmentData({ + provider_id: providerId, + shipping_option_id: shippingOption.id, + labels: [ + { + tracking_number: "test-tracking-number-1", + tracking_url: "test-tracking-url-1", + label_url: "test-label-url-1", + }, + { + tracking_number: "test-tracking-number-2", + tracking_url: "test-tracking-url-2", + label_url: "test-label-url-2", + }, + { + tracking_number: "test-tracking-number-3", + tracking_url: "test-tracking-url-3", + label_url: "test-label-url-3", + }, + ], + }) + ) + + const label1 = fulfillment.labels.find( + (l) => l.tracking_number === "test-tracking-number-1" + )! + const label2 = fulfillment.labels.find( + (l) => l.tracking_number === "test-tracking-number-2" + )! + const label3 = fulfillment.labels.find( + (l) => l.tracking_number === "test-tracking-number-3" + )! + + const updateData: UpdateFulfillmentDTO = { + id: fulfillment.id, + labels: [ + { id: label1.id }, + { ...label2, label_url: "updated-test-label-url-2" }, + { + tracking_number: "test-tracking-number-4", + tracking_url: "test-tracking-url-4", + label_url: "test-label-url-4", + }, + ], + } + + jest.clearAllMocks() + + const updatedFulfillment = await service.updateFulfillment( + fulfillment.id, + updateData + ) + + const label4 = updatedFulfillment.labels.find( + (l) => l.tracking_number === "test-tracking-number-4" + )! + + expect(updatedFulfillment.labels).toHaveLength(3) + expect(updatedFulfillment).toEqual( + expect.objectContaining({ + id: expect.any(String), + labels: expect.arrayContaining([ + expect.objectContaining({ + tracking_number: "test-tracking-number-1", + tracking_url: "test-tracking-url-1", + label_url: "test-label-url-1", + }), + expect.objectContaining({ + tracking_number: "test-tracking-number-2", + tracking_url: "test-tracking-url-2", + label_url: "updated-test-label-url-2", + }), + expect.objectContaining({ + tracking_number: "test-tracking-number-4", + tracking_url: "test-tracking-url-4", + label_url: "test-label-url-4", + }), + ]), + }) + ) + + expect(eventBusEmitSpy.mock.calls[0][0]).toHaveLength(4) + expect(eventBusEmitSpy).toHaveBeenCalledWith([ + buildExpectedEventMessageShape({ + eventName: FulfillmentEvents.fulfillment_updated, + action: "updated", + object: "fulfillment", + data: { id: updatedFulfillment.id }, + }), + buildExpectedEventMessageShape({ + eventName: FulfillmentEvents.fulfillment_label_deleted, + action: "deleted", + object: "fulfillment_label", + data: { id: label3.id }, + }), + buildExpectedEventMessageShape({ + eventName: FulfillmentEvents.fulfillment_label_updated, + action: "updated", + object: "fulfillment_label", + data: { id: label2.id }, + }), + buildExpectedEventMessageShape({ + eventName: FulfillmentEvents.fulfillment_label_created, + action: "created", + object: "fulfillment_label", + data: { id: label4.id }, + }), + ]) }) }) @@ -227,6 +440,8 @@ moduleIntegrationTestRunner({ shipping_option_id: shippingOption.id, }) ) + + jest.clearAllMocks() }) it("should cancel a fulfillment successfully", async () => { @@ -239,6 +454,16 @@ moduleIntegrationTestRunner({ expect(result.canceled_at).not.toBeNull() expect(idempotentResult.canceled_at).not.toBeNull() expect(idempotentResult.canceled_at).toEqual(result.canceled_at) + + expect(eventBusEmitSpy.mock.calls[0][0]).toHaveLength(1) + expect(eventBusEmitSpy).toHaveBeenNthCalledWith(1, [ + buildExpectedEventMessageShape({ + eventName: FulfillmentEvents.fulfillment_updated, + action: "updated", + object: "fulfillment", + data: { id: fulfillment.id }, + }), + ]) }) it("should fail to cancel a fulfillment that is already shipped", async () => { diff --git a/packages/modules/fulfillment/integration-tests/__tests__/fulfillment-module-service/geo-zone.spec.ts b/packages/modules/fulfillment/integration-tests/__tests__/fulfillment-module-service/geo-zone.spec.ts index 7e29ac3140..81dd5da57d 100644 --- a/packages/modules/fulfillment/integration-tests/__tests__/fulfillment-module-service/geo-zone.spec.ts +++ b/packages/modules/fulfillment/integration-tests/__tests__/fulfillment-module-service/geo-zone.spec.ts @@ -4,14 +4,28 @@ import { IFulfillmentModuleService, UpdateGeoZoneDTO, } from "@medusajs/types" -import { GeoZoneType } from "@medusajs/utils" -import { moduleIntegrationTestRunner, SuiteOptions } from "medusa-test-utils" +import { FulfillmentEvents, GeoZoneType } from "@medusajs/utils" +import { + MockEventBusService, + moduleIntegrationTestRunner, +} from "medusa-test-utils" +import { buildExpectedEventMessageShape } from "../../__fixtures__" jest.setTimeout(100000) -moduleIntegrationTestRunner({ +moduleIntegrationTestRunner({ moduleName: Modules.FULFILLMENT, - testSuite: ({ service }: SuiteOptions) => { + testSuite: ({ service }) => { + let eventBusEmitSpy + + beforeEach(() => { + eventBusEmitSpy = jest.spyOn(MockEventBusService.prototype, "emit") + }) + + afterEach(() => { + jest.clearAllMocks() + }) + describe("Fulfillment Module Service", () => { describe("read", () => { it("should list geo zones with a filter", async function () { @@ -81,6 +95,8 @@ moduleIntegrationTestRunner({ country_code: "fr", } + jest.clearAllMocks() + const geoZone = await service.createGeoZones(data) expect(geoZone).toEqual( @@ -90,6 +106,16 @@ moduleIntegrationTestRunner({ country_code: data.country_code, }) ) + + expect(eventBusEmitSpy.mock.calls[0][0]).toHaveLength(1) + expect(eventBusEmitSpy).toHaveBeenCalledWith([ + buildExpectedEventMessageShape({ + eventName: FulfillmentEvents.geo_zone_created, + action: "created", + object: "geo_zone", + data: { id: geoZone.id }, + }), + ]) }) it("should create a collection of geo zones", async function () { @@ -115,9 +141,12 @@ moduleIntegrationTestRunner({ }, ] + jest.clearAllMocks() + const geoZones = await service.createGeoZones(data) expect(geoZones).toHaveLength(2) + expect(eventBusEmitSpy.mock.calls[0][0]).toHaveLength(2) let i = 0 for (const data_ of data) { @@ -128,6 +157,18 @@ moduleIntegrationTestRunner({ country_code: data_.country_code, }) ) + + expect(eventBusEmitSpy).toHaveBeenCalledWith( + expect.arrayContaining([ + buildExpectedEventMessageShape({ + eventName: FulfillmentEvents.geo_zone_created, + action: "created", + object: "geo_zone", + data: { id: geoZones[i].id }, + }), + ]) + ) + ++i } }) @@ -213,6 +254,8 @@ moduleIntegrationTestRunner({ country_code: "us", } + jest.clearAllMocks() + const updatedGeoZone = await service.updateGeoZones(updateData) expect(updatedGeoZone).toEqual( @@ -222,6 +265,15 @@ moduleIntegrationTestRunner({ country_code: updateData.country_code, }) ) + + expect(eventBusEmitSpy).toHaveBeenCalledWith([ + buildExpectedEventMessageShape({ + eventName: FulfillmentEvents.geo_zone_updated, + action: "updated", + object: "geo_zone", + data: { id: updatedGeoZone.id }, + }), + ]) }) it("should update a collection of geo zones", async function () { @@ -258,14 +310,18 @@ moduleIntegrationTestRunner({ }) ) + jest.clearAllMocks() + const updatedGeoZones = await service.updateGeoZones(updateData) expect(updatedGeoZones).toHaveLength(2) + expect(eventBusEmitSpy.mock.calls[0][0]).toHaveLength(2) for (const data_ of updateData) { const expectedGeoZone = updatedGeoZones.find( (geoZone) => geoZone.id === data_.id - ) + )! + expect(expectedGeoZone).toEqual( expect.objectContaining({ id: data_.id, @@ -273,6 +329,17 @@ moduleIntegrationTestRunner({ country_code: data_.country_code, }) ) + + expect(eventBusEmitSpy).toHaveBeenCalledWith( + expect.arrayContaining([ + buildExpectedEventMessageShape({ + eventName: FulfillmentEvents.geo_zone_updated, + action: "updated", + object: "geo_zone", + data: { id: expectedGeoZone.id }, + }), + ]) + ) } }) }) diff --git a/packages/modules/fulfillment/integration-tests/__tests__/fulfillment-module-service/service-zone.spec.ts b/packages/modules/fulfillment/integration-tests/__tests__/fulfillment-module-service/service-zone.spec.ts index fd7b1eb3f0..2748a09b0f 100644 --- a/packages/modules/fulfillment/integration-tests/__tests__/fulfillment-module-service/service-zone.spec.ts +++ b/packages/modules/fulfillment/integration-tests/__tests__/fulfillment-module-service/service-zone.spec.ts @@ -5,14 +5,28 @@ import { IFulfillmentModuleService, UpdateServiceZoneDTO, } from "@medusajs/types" -import { GeoZoneType } from "@medusajs/utils" -import { SuiteOptions, moduleIntegrationTestRunner } from "medusa-test-utils" +import { FulfillmentEvents, GeoZoneType } from "@medusajs/utils" +import { + MockEventBusService, + moduleIntegrationTestRunner, +} from "medusa-test-utils" +import { buildExpectedEventMessageShape } from "../../__fixtures__" jest.setTimeout(100000) -moduleIntegrationTestRunner({ +moduleIntegrationTestRunner({ moduleName: Modules.FULFILLMENT, - testSuite: ({ service }: SuiteOptions) => { + testSuite: ({ service }) => { + let eventBusEmitSpy + + beforeEach(() => { + eventBusEmitSpy = jest.spyOn(MockEventBusService.prototype, "emit") + }) + + afterEach(() => { + jest.clearAllMocks() + }) + describe("Fulfillment Module Service", () => { describe("read", () => { it("should list service zones with a filter", async function () { @@ -276,6 +290,14 @@ moduleIntegrationTestRunner({ type: GeoZoneType.COUNTRY, country_code: "fr", }, + { + type: GeoZoneType.COUNTRY, + country_code: "us", + }, + { + type: GeoZoneType.COUNTRY, + country_code: "uk", + }, ], } @@ -283,35 +305,94 @@ moduleIntegrationTestRunner({ createData ) - const updateData = { + const usGeoZone = createdServiceZone.geo_zones.find( + (geoZone) => geoZone.country_code === "us" + )! + const frGeoZone = createdServiceZone.geo_zones.find( + (geoZone) => geoZone.country_code === "fr" + )! + const ukGeoZone = createdServiceZone.geo_zones.find( + (geoZone) => geoZone.country_code === "uk" + )! + + const updateData: UpdateServiceZoneDTO = { name: "updated-service-zone-test", geo_zones: [ { - id: createdServiceZone.geo_zones[0].id, + id: usGeoZone.id, type: GeoZoneType.COUNTRY, - country_code: "us", + country_code: "es", }, + { + type: GeoZoneType.COUNTRY, + country_code: "ch", + }, + { id: frGeoZone.id }, ], } + jest.clearAllMocks() + const updatedServiceZone = await service.updateServiceZones( createdServiceZone.id, updateData ) + expect(updatedServiceZone.geo_zones).toHaveLength(3) + expect(updatedServiceZone).toEqual( expect.objectContaining({ id: createdServiceZone.id, name: updateData.name, geo_zones: expect.arrayContaining([ expect.objectContaining({ - id: updateData.geo_zones[0].id, - type: updateData.geo_zones[0].type, - country_code: updateData.geo_zones[0].country_code, + id: frGeoZone.id, + }), + expect.objectContaining({ + id: usGeoZone.id, + type: GeoZoneType.COUNTRY, + country_code: "es", + }), + expect.objectContaining({ + id: expect.any(String), + type: GeoZoneType.COUNTRY, + country_code: "ch", }), ]), }) ) + + const chGeoZone = updatedServiceZone.geo_zones.find( + (geoZone) => geoZone.country_code === "ch" + )! + + expect(eventBusEmitSpy.mock.calls[0][0]).toHaveLength(4) + expect(eventBusEmitSpy).toHaveBeenCalledWith([ + buildExpectedEventMessageShape({ + eventName: FulfillmentEvents.geo_zone_deleted, + action: "deleted", + object: "geo_zone", + data: { id: ukGeoZone.id }, + }), + buildExpectedEventMessageShape({ + eventName: FulfillmentEvents.service_zone_updated, + action: "updated", + object: "service_zone", + data: { id: updatedServiceZone.id }, + }), + buildExpectedEventMessageShape({ + eventName: FulfillmentEvents.geo_zone_created, + action: "created", + object: "geo_zone", + data: { id: chGeoZone.id }, + }), + buildExpectedEventMessageShape({ + eventName: FulfillmentEvents.geo_zone_updated, + action: "updated", + object: "geo_zone", + data: { id: usGeoZone.id }, + }), + ]) }) it("should fail on duplicated service zone name", async function () { @@ -413,12 +494,16 @@ moduleIntegrationTestRunner({ }) ) + jest.clearAllMocks() + const updatedServiceZones = await service.upsertServiceZones( updateData ) expect(updatedServiceZones).toHaveLength(2) + expect(eventBusEmitSpy.mock.calls[0][0]).toHaveLength(6) // Since the update only calls create and update which are already tested, only check the length + for (const data_ of updateData) { const expectedServiceZone = updatedServiceZones.find( (serviceZone) => serviceZone.id === data_.id diff --git a/packages/modules/fulfillment/integration-tests/__tests__/fulfillment-module-service/shipping-option.spec.ts b/packages/modules/fulfillment/integration-tests/__tests__/fulfillment-module-service/shipping-option.spec.ts index fd8275d7d8..6a53dea52f 100644 --- a/packages/modules/fulfillment/integration-tests/__tests__/fulfillment-module-service/shipping-option.spec.ts +++ b/packages/modules/fulfillment/integration-tests/__tests__/fulfillment-module-service/shipping-option.spec.ts @@ -3,12 +3,18 @@ import { CreateShippingOptionDTO, IFulfillmentModuleService, } from "@medusajs/types" -import { moduleIntegrationTestRunner, SuiteOptions } from "medusa-test-utils" -import { generateCreateShippingOptionsData } from "../../__fixtures__" +import { + MockEventBusService, + moduleIntegrationTestRunner, +} from "medusa-test-utils" +import { + buildExpectedEventMessageShape, + generateCreateShippingOptionsData, +} from "../../__fixtures__" import { resolve } from "path" import { FulfillmentProviderService } from "@services" import { FulfillmentProviderServiceFixtures } from "../../__fixtures__/providers" -import { GeoZoneType } from "@medusajs/utils" +import { FulfillmentEvents, GeoZoneType } from "@medusajs/utils" import { UpdateShippingOptionDTO } from "@medusajs/types/src" jest.setTimeout(100000) @@ -34,10 +40,20 @@ const providerId = FulfillmentProviderService.getRegistrationIdentifier( "test-provider" ) -moduleIntegrationTestRunner({ +moduleIntegrationTestRunner({ moduleName: Modules.FULFILLMENT, moduleOptions, - testSuite: ({ service }: SuiteOptions) => { + testSuite: ({ service }) => { + let eventBusEmitSpy + + beforeEach(() => { + eventBusEmitSpy = jest.spyOn(MockEventBusService.prototype, "emit") + }) + + afterEach(() => { + jest.clearAllMocks() + }) + describe("Fulfillment Module Service", () => { describe("read", () => { it("should list shipping options with a filter", async function () { @@ -432,6 +448,8 @@ moduleIntegrationTestRunner({ provider_id: providerId, }) + jest.clearAllMocks() + const createdShippingOption = await service.createShippingOptions( createData ) @@ -462,6 +480,28 @@ moduleIntegrationTestRunner({ ]), }) ) + + expect(eventBusEmitSpy.mock.calls[0][0]).toHaveLength(3) + expect(eventBusEmitSpy).toHaveBeenCalledWith([ + buildExpectedEventMessageShape({ + eventName: FulfillmentEvents.shipping_option_created, + action: "created", + object: "shipping_option", + data: { id: createdShippingOption.id }, + }), + buildExpectedEventMessageShape({ + eventName: FulfillmentEvents.shipping_option_type_created, + action: "created", + object: "shipping_option_type", + data: { id: createdShippingOption.type.id }, + }), + buildExpectedEventMessageShape({ + eventName: FulfillmentEvents.shipping_option_rule_created, + action: "created", + object: "shipping_option_rule", + data: { id: createdShippingOption.rules[0].id }, + }), + ]) }) it("should create multiple new shipping options", async function () { @@ -491,11 +531,14 @@ moduleIntegrationTestRunner({ }), ] + jest.clearAllMocks() + const createdShippingOptions = await service.createShippingOptions( createData ) expect(createdShippingOptions).toHaveLength(2) + expect(eventBusEmitSpy.mock.calls[0][0]).toHaveLength(6) let i = 0 for (const data_ of createData) { @@ -525,6 +568,30 @@ moduleIntegrationTestRunner({ ]), }) ) + + expect(eventBusEmitSpy).toHaveBeenCalledWith( + expect.arrayContaining([ + buildExpectedEventMessageShape({ + eventName: FulfillmentEvents.shipping_option_created, + action: "created", + object: "shipping_option", + data: { id: createdShippingOptions[i].id }, + }), + buildExpectedEventMessageShape({ + eventName: FulfillmentEvents.shipping_option_type_created, + action: "created", + object: "shipping_option_type", + data: { id: createdShippingOptions[i].type.id }, + }), + buildExpectedEventMessageShape({ + eventName: FulfillmentEvents.shipping_option_rule_created, + action: "created", + object: "shipping_option_rule", + data: { id: createdShippingOptions[i].rules[0].id }, + }), + ]) + ) + ++i } }) @@ -623,6 +690,8 @@ moduleIntegrationTestRunner({ ], } + jest.clearAllMocks() + const updatedShippingOption = await service.updateShippingOptions( updateData.id!, updateData @@ -681,6 +750,42 @@ moduleIntegrationTestRunner({ label: updateData.type.label, }) ) + + expect(eventBusEmitSpy.mock.calls[0][0]).toHaveLength(5) + expect(eventBusEmitSpy).toHaveBeenCalledWith( + expect.arrayContaining([ + buildExpectedEventMessageShape({ + eventName: FulfillmentEvents.shipping_option_updated, + action: "updated", + object: "shipping_option", + data: { id: updatedShippingOption.id }, + }), + buildExpectedEventMessageShape({ + eventName: FulfillmentEvents.shipping_option_type_deleted, + action: "deleted", + object: "shipping_option_type", + data: { id: shippingOption.type.id }, + }), + buildExpectedEventMessageShape({ + eventName: FulfillmentEvents.shipping_option_type_created, + action: "created", + object: "shipping_option_type", + data: { id: updatedShippingOption.type.id }, + }), + buildExpectedEventMessageShape({ + eventName: FulfillmentEvents.shipping_option_rule_created, + action: "created", + object: "shipping_option_rule", + data: { id: updatedShippingOption.rules[1].id }, + }), + buildExpectedEventMessageShape({ + eventName: FulfillmentEvents.shipping_option_rule_updated, + action: "updated", + object: "shipping_option_rule", + data: { id: updatedShippingOption.rules[0].id }, + }), + ]) + ) }) it("should update a shipping option without updating the rules or the type", async () => { @@ -1092,6 +1197,8 @@ moduleIntegrationTestRunner({ shipping_option_id: shippingOption.id, } + jest.clearAllMocks() + const rule = await service.createShippingOptionRules(ruleData) expect(rule).toEqual( @@ -1104,6 +1211,16 @@ moduleIntegrationTestRunner({ }) ) + expect(eventBusEmitSpy.mock.calls[0][0]).toHaveLength(1) + expect(eventBusEmitSpy).toHaveBeenCalledWith([ + buildExpectedEventMessageShape({ + eventName: FulfillmentEvents.shipping_option_rule_created, + action: "created", + object: "shipping_option_rule", + data: { id: rule.id }, + }), + ]) + const rules = await service.listShippingOptionRules() expect(rules).toHaveLength(2) expect(rules).toEqual( @@ -1157,6 +1274,8 @@ moduleIntegrationTestRunner({ value: "updated-test", } + jest.clearAllMocks() + const updatedRule = await service.updateShippingOptionRules( updateData ) @@ -1169,6 +1288,15 @@ moduleIntegrationTestRunner({ value: updateData.value, }) ) + + expect(eventBusEmitSpy).toHaveBeenCalledWith([ + buildExpectedEventMessageShape({ + eventName: FulfillmentEvents.shipping_option_rule_updated, + action: "updated", + object: "shipping_option_rule", + data: { id: updatedRule.id }, + }), + ]) }) it("should fail to update a non-existent shipping option rule", async () => { diff --git a/packages/modules/fulfillment/integration-tests/__tests__/fulfillment-module-service/shipping-profile.spec.ts b/packages/modules/fulfillment/integration-tests/__tests__/fulfillment-module-service/shipping-profile.spec.ts index 0eb331b0f9..e74b390396 100644 --- a/packages/modules/fulfillment/integration-tests/__tests__/fulfillment-module-service/shipping-profile.spec.ts +++ b/packages/modules/fulfillment/integration-tests/__tests__/fulfillment-module-service/shipping-profile.spec.ts @@ -3,13 +3,28 @@ import { CreateShippingProfileDTO, IFulfillmentModuleService, } from "@medusajs/types" -import { moduleIntegrationTestRunner, SuiteOptions } from "medusa-test-utils" +import { + MockEventBusService, + moduleIntegrationTestRunner, +} from "medusa-test-utils" +import { buildExpectedEventMessageShape } from "../../__fixtures__" +import { FulfillmentEvents } from "@medusajs/utils" jest.setTimeout(100000) -moduleIntegrationTestRunner({ +moduleIntegrationTestRunner({ moduleName: Modules.FULFILLMENT, - testSuite: ({ service }: SuiteOptions) => { + testSuite: ({ service }) => { + let eventBusEmitSpy + + beforeEach(() => { + eventBusEmitSpy = jest.spyOn(MockEventBusService.prototype, "emit") + }) + + afterEach(() => { + jest.clearAllMocks() + }) + describe("Fulfillment Module Service", () => { describe("mutations", () => { describe("on create", () => { @@ -29,6 +44,16 @@ moduleIntegrationTestRunner({ type: createData.type, }) ) + + expect(eventBusEmitSpy.mock.calls[0][0]).toHaveLength(1) + expect(eventBusEmitSpy).toHaveBeenCalledWith([ + buildExpectedEventMessageShape({ + eventName: FulfillmentEvents.shipping_profile_created, + action: "created", + object: "shipping_profile", + data: { id: createdShippingProfile.id }, + }), + ]) }) it("should create multiple new shipping profiles", async function () { @@ -47,6 +72,7 @@ moduleIntegrationTestRunner({ await service.createShippingProfiles(createData) expect(createdShippingProfiles).toHaveLength(2) + expect(eventBusEmitSpy.mock.calls[0][0]).toHaveLength(2) let i = 0 for (const data_ of createData) { @@ -56,6 +82,18 @@ moduleIntegrationTestRunner({ type: data_.type, }) ) + + expect(eventBusEmitSpy).toHaveBeenCalledWith( + expect.arrayContaining([ + buildExpectedEventMessageShape({ + eventName: FulfillmentEvents.shipping_profile_created, + action: "created", + object: "shipping_profile", + data: { id: createdShippingProfiles[i].id }, + }), + ]) + ) + ++i } }) diff --git a/packages/modules/fulfillment/src/models/geo-zone.ts b/packages/modules/fulfillment/src/models/geo-zone.ts index 1483fb363d..57c2ca0382 100644 --- a/packages/modules/fulfillment/src/models/geo-zone.ts +++ b/packages/modules/fulfillment/src/models/geo-zone.ts @@ -115,7 +115,7 @@ export default class GeoZone { @BeforeCreate() onCreate() { - this.id = generateEntityId(this.id, " fgz") + this.id = generateEntityId(this.id, "fgz") this.service_zone_id ??= this.service_zone?.id } diff --git a/packages/modules/fulfillment/src/services/fulfillment-module-service.ts b/packages/modules/fulfillment/src/services/fulfillment-module-service.ts index 09367f7072..d79caee46f 100644 --- a/packages/modules/fulfillment/src/services/fulfillment-module-service.ts +++ b/packages/modules/fulfillment/src/services/fulfillment-module-service.ts @@ -15,15 +15,16 @@ import { } from "@medusajs/types" import { arrayDifference, + deepEqualObj, EmitEvents, - FulfillmentUtils, getSetDifference, InjectManager, InjectTransactionManager, + isDefined, + isPresent, isString, MedusaContext, MedusaError, - Modules, ModulesSdkUtils, promiseAll, } from "@medusajs/utils" @@ -38,10 +39,18 @@ import { ShippingOptionType, ShippingProfile, } from "@models" -import { isContextValid, validateAndNormalizeRules } from "@utils" +import { + buildCreatedFulfillmentEvents, + buildCreatedFulfillmentSetEvents, + buildCreatedServiceZoneEvents, + eventBuilders, + isContextValid, + validateAndNormalizeRules, +} from "@utils" import { entityNameToLinkableKeysMap, joinerConfig } from "../joiner-config" import { UpdateShippingOptionsInput } from "../types/service" import FulfillmentProviderService from "./fulfillment-provider" +import { buildCreatedShippingOptionEvents } from "../utils/events" const generateMethodForModels = [ ServiceZone, @@ -271,9 +280,13 @@ export default class FulfillmentModuleService< > { const createdFulfillmentSets = await this.create_(data, sharedContext) + const returnedFulfillmentSets = Array.isArray(data) + ? createdFulfillmentSets + : createdFulfillmentSets[0] + return await this.baseRepository_.serialize< FulfillmentTypes.FulfillmentSetDTO | FulfillmentTypes.FulfillmentSetDTO[] - >(createdFulfillmentSets) + >(returnedFulfillmentSets) } @InjectTransactionManager("baseRepository_") @@ -282,7 +295,7 @@ export default class FulfillmentModuleService< | FulfillmentTypes.CreateFulfillmentSetDTO | FulfillmentTypes.CreateFulfillmentSetDTO[], @MedusaContext() sharedContext: Context = {} - ): Promise { + ): Promise { const data_ = Array.isArray(data) ? data : [data] if (!data_.length) { @@ -304,14 +317,12 @@ export default class FulfillmentModuleService< sharedContext ) - this.aggregateFulfillmentSetCreatedEvents( - createdFulfillmentSets, - sharedContext - ) + buildCreatedFulfillmentSetEvents({ + fulfillmentSets: createdFulfillmentSets, + sharedContext, + }) - return Array.isArray(data) - ? createdFulfillmentSets - : createdFulfillmentSets[0] + return createdFulfillmentSets } createServiceZones( @@ -324,6 +335,7 @@ export default class FulfillmentModuleService< ): Promise @InjectManager("baseRepository_") + @EmitEvents() async createServiceZones( data: | FulfillmentTypes.CreateServiceZoneDTO[] @@ -339,7 +351,7 @@ export default class FulfillmentModuleService< return await this.baseRepository_.serialize< FulfillmentTypes.ServiceZoneDTO | FulfillmentTypes.ServiceZoneDTO[] - >(createdServiceZones) + >(Array.isArray(data) ? createdServiceZones : createdServiceZones[0]) } @InjectTransactionManager("baseRepository_") @@ -348,7 +360,7 @@ export default class FulfillmentModuleService< | FulfillmentTypes.CreateServiceZoneDTO[] | FulfillmentTypes.CreateServiceZoneDTO, @MedusaContext() sharedContext: Context = {} - ): Promise { + ): Promise { const data_ = Array.isArray(data) ? data : [data] if (!data_.length) { @@ -368,7 +380,12 @@ export default class FulfillmentModuleService< sharedContext ) - return Array.isArray(data) ? createdServiceZones : createdServiceZones[0] + buildCreatedServiceZoneEvents({ + serviceZones: createdServiceZones, + sharedContext, + }) + + return createdServiceZones } createShippingOptions( @@ -381,6 +398,7 @@ export default class FulfillmentModuleService< ): Promise @InjectManager("baseRepository_") + @EmitEvents() async createShippingOptions( data: | FulfillmentTypes.CreateShippingOptionDTO[] @@ -396,7 +414,7 @@ export default class FulfillmentModuleService< return await this.baseRepository_.serialize< FulfillmentTypes.ShippingOptionDTO | FulfillmentTypes.ShippingOptionDTO[] - >(createdShippingOptions) + >(Array.isArray(data) ? createdShippingOptions : createdShippingOptions[0]) } @InjectTransactionManager("baseRepository_") @@ -405,7 +423,7 @@ export default class FulfillmentModuleService< | FulfillmentTypes.CreateShippingOptionDTO[] | FulfillmentTypes.CreateShippingOptionDTO, @MedusaContext() sharedContext: Context = {} - ): Promise { + ): Promise { const data_ = Array.isArray(data) ? data : [data] if (!data_.length) { @@ -417,14 +435,17 @@ export default class FulfillmentModuleService< validateAndNormalizeRules(rules as Record[]) } - const createdShippingOptions = await this.shippingOptionService_.create( + const createdSO = await this.shippingOptionService_.create( data_, sharedContext ) - return Array.isArray(data) - ? createdShippingOptions - : createdShippingOptions[0] + buildCreatedShippingOptionEvents({ + shippingOptions: createdSO, + sharedContext, + }) + + return createdSO } createShippingProfiles( @@ -437,6 +458,7 @@ export default class FulfillmentModuleService< ): Promise @InjectTransactionManager("baseRepository_") + @EmitEvents() async createShippingProfiles( data: | FulfillmentTypes.CreateShippingProfileDTO[] @@ -450,10 +472,17 @@ export default class FulfillmentModuleService< sharedContext ) + eventBuilders.createdShippingProfile({ + data: createdShippingProfiles, + sharedContext, + }) + return await this.baseRepository_.serialize< | FulfillmentTypes.ShippingProfileDTO | FulfillmentTypes.ShippingProfileDTO[] - >(createdShippingProfiles) + >( + Array.isArray(data) ? createdShippingProfiles : createdShippingProfiles[0] + ) } @InjectTransactionManager("baseRepository_") @@ -462,21 +491,14 @@ export default class FulfillmentModuleService< | FulfillmentTypes.CreateShippingProfileDTO[] | FulfillmentTypes.CreateShippingProfileDTO, @MedusaContext() sharedContext: Context = {} - ): Promise { + ): Promise { const data_ = Array.isArray(data) ? data : [data] if (!data_.length) { return [] } - const createdShippingProfiles = await this.shippingProfileService_.create( - data_, - sharedContext - ) - - return Array.isArray(data) - ? createdShippingProfiles - : createdShippingProfiles[0] + return await this.shippingProfileService_.create(data_, sharedContext) } createGeoZones( @@ -489,6 +511,7 @@ export default class FulfillmentModuleService< ): Promise @InjectManager("baseRepository_") + @EmitEvents() async createGeoZones( data: | FulfillmentTypes.CreateGeoZoneDTO @@ -504,6 +527,11 @@ export default class FulfillmentModuleService< sharedContext ) + eventBuilders.createdGeoZone({ + data: createdGeoZones, + sharedContext, + }) + return await this.baseRepository_.serialize( Array.isArray(data) ? createdGeoZones : createdGeoZones[0] ) @@ -519,6 +547,7 @@ export default class FulfillmentModuleService< ): Promise @InjectManager("baseRepository_") + @EmitEvents() async createShippingOptionRules( data: | FulfillmentTypes.CreateShippingOptionRuleDTO[] @@ -536,7 +565,11 @@ export default class FulfillmentModuleService< return await this.baseRepository_.serialize< | FulfillmentTypes.ShippingOptionRuleDTO | FulfillmentTypes.ShippingOptionRuleDTO[] - >(createdShippingOptionRules) + >( + Array.isArray(data) + ? createdShippingOptionRules + : createdShippingOptionRules[0] + ) } @InjectTransactionManager("baseRepository_") @@ -545,7 +578,7 @@ export default class FulfillmentModuleService< | FulfillmentTypes.CreateShippingOptionRuleDTO[] | FulfillmentTypes.CreateShippingOptionRuleDTO, @MedusaContext() sharedContext: Context = {} - ): Promise { + ): Promise { const data_ = Array.isArray(data) ? data : [data] if (!data_.length) { @@ -554,15 +587,21 @@ export default class FulfillmentModuleService< validateAndNormalizeRules(data_ as unknown as Record[]) - const createdShippingOptionRules = - await this.shippingOptionRuleService_.create(data_, sharedContext) + const createdSORules = await this.shippingOptionRuleService_.create( + data_, + sharedContext + ) - return Array.isArray(data) - ? createdShippingOptionRules - : createdShippingOptionRules[0] + eventBuilders.createdShippingOptionRule({ + data: createdSORules.map((sor) => ({ id: sor.id })), + sharedContext, + }) + + return createdSORules } @InjectManager("baseRepository_") + @EmitEvents() async createFulfillment( data: FulfillmentTypes.CreateFulfillmentDTO, @MedusaContext() sharedContext: Context = {} @@ -603,12 +642,18 @@ export default class FulfillmentModuleService< throw error } + buildCreatedFulfillmentEvents({ + fulfillments: [fulfillment], + sharedContext, + }) + return await this.baseRepository_.serialize( fulfillment ) } @InjectManager("baseRepository_") + @EmitEvents() async createReturnFulfillment( data: FulfillmentTypes.CreateFulfillmentDTO, @MedusaContext() sharedContext: Context = {} @@ -639,6 +684,11 @@ export default class FulfillmentModuleService< throw error } + buildCreatedFulfillmentEvents({ + fulfillments: [fulfillment], + sharedContext, + }) + return await this.baseRepository_.serialize( fulfillment ) @@ -654,6 +704,7 @@ export default class FulfillmentModuleService< ): Promise @InjectManager("baseRepository_") + @EmitEvents() async update( data: UpdateFulfillmentSetDTO[] | UpdateFulfillmentSetDTO, @MedusaContext() sharedContext: Context = {} @@ -714,9 +765,11 @@ export default class FulfillmentModuleService< fulfillmentSets.map((f) => [f.id, f]) ) - // find service zones to delete const serviceZoneIdsToDelete: string[] = [] const geoZoneIdsToDelete: string[] = [] + const existingServiceZoneIds: string[] = [] + const existingGeoZoneIds: string[] = [] + data_.forEach((fulfillmentSet) => { if (fulfillmentSet.service_zones) { /** @@ -734,6 +787,7 @@ export default class FulfillmentModuleService< .filter((id): id is string => !!id) ) ) + if (toDeleteServiceZoneIds.size) { serviceZoneIdsToDelete.push(...Array.from(toDeleteServiceZoneIds)) geoZoneIdsToDelete.push( @@ -758,11 +812,13 @@ export default class FulfillmentModuleService< .map((s) => "id" in s && s.id) .filter((id): id is string => !!id) ) + const expectedServiceZoneSet = new Set( fulfillmentSet.service_zones .map((s) => "id" in s && s.id) .filter((id): id is string => !!id) ) + const missingServiceZoneIds = getSetDifference( expectedServiceZoneSet, serviceZonesSet @@ -789,6 +845,16 @@ export default class FulfillmentModuleService< } return serviceZone } + + const existingServiceZone = serviceZonesMap.get(serviceZone.id)! + existingServiceZoneIds.push(existingServiceZone.id) + + if (existingServiceZone.geo_zones.length) { + existingGeoZoneIds.push( + ...existingServiceZone.geo_zones.map((g) => g.id) + ) + } + return serviceZonesMap.get(serviceZone.id)! } ) @@ -797,6 +863,15 @@ export default class FulfillmentModuleService< }) if (serviceZoneIdsToDelete.length) { + eventBuilders.deletedServiceZone({ + data: serviceZoneIdsToDelete.map((id) => ({ id })), + sharedContext, + }) + eventBuilders.deletedGeoZone({ + data: geoZoneIdsToDelete.map((id) => ({ id })), + sharedContext, + }) + await promiseAll([ this.geoZoneService_.delete( { @@ -818,6 +893,32 @@ export default class FulfillmentModuleService< sharedContext ) + eventBuilders.updatedFulfillmentSet({ + data: updatedFulfillmentSets, + sharedContext, + }) + + const createdServiceZoneIds: string[] = [] + const createdGeoZoneIds = updatedFulfillmentSets + .flatMap((f) => + [...f.service_zones].flatMap((serviceZone) => { + if (!existingServiceZoneIds.includes(serviceZone.id)) { + createdServiceZoneIds.push(serviceZone.id) + } + return serviceZone.geo_zones.map((g) => g.id) + }) + ) + .filter((id) => !existingGeoZoneIds.includes(id)) + + eventBuilders.createdServiceZone({ + data: createdServiceZoneIds.map((id) => ({ id })), + sharedContext, + }) + eventBuilders.createdGeoZone({ + data: createdGeoZoneIds.map((id) => ({ id })), + sharedContext, + }) + return Array.isArray(data) ? updatedFulfillmentSets : updatedFulfillmentSets[0] @@ -835,6 +936,7 @@ export default class FulfillmentModuleService< ): Promise @InjectManager("baseRepository_") + @EmitEvents() async updateServiceZones( idOrSelector: string | FulfillmentTypes.FilterableServiceZoneProps, data: FulfillmentTypes.UpdateServiceZoneDTO, @@ -926,20 +1028,28 @@ export default class FulfillmentModuleService< ) const geoZoneIdsToDelete: string[] = [] + const existingGeoZoneIds: string[] = [] + const updatedGeoZoneIds: string[] = [] data_.forEach((serviceZone) => { if (serviceZone.geo_zones) { const existingServiceZone = serviceZoneMap.get(serviceZone.id!)! const existingGeoZones = existingServiceZone.geo_zones const updatedGeoZones = serviceZone.geo_zones + const existingGeoZoneIdsForServiceZone = existingGeoZones.map( + (g) => g.id + ) const toDeleteGeoZoneIds = getSetDifference( - new Set(existingGeoZones.map((g) => g.id)), + new Set(existingGeoZoneIdsForServiceZone), new Set( updatedGeoZones .map((g) => "id" in g && g.id) .filter((id): id is string => !!id) ) ) + + existingGeoZoneIds.push(...existingGeoZoneIdsForServiceZone) + if (toDeleteGeoZoneIds.size) { geoZoneIdsToDelete.push(...Array.from(toDeleteGeoZoneIds)) } @@ -978,12 +1088,25 @@ export default class FulfillmentModuleService< } const existing = geoZonesMap.get(geoZone.id)! + // If only the id is provided we dont consider it as an update + if ( + Object.keys(geoZone).length > 1 && + !deepEqualObj(existing, geoZone) + ) { + updatedGeoZoneIds.push(geoZone.id) + } + return { ...existing, ...geoZone } }) } }) if (geoZoneIdsToDelete.length) { + eventBuilders.deletedGeoZone({ + data: geoZoneIdsToDelete.map((id) => ({ id })), + sharedContext, + }) + await this.geoZoneService_.delete( { id: geoZoneIdsToDelete, @@ -997,6 +1120,26 @@ export default class FulfillmentModuleService< sharedContext ) + eventBuilders.updatedServiceZone({ + data: updatedServiceZones, + sharedContext, + }) + + const createdGeoZoneIds = updatedServiceZones + .flatMap((serviceZone) => { + return serviceZone.geo_zones.map((g) => g.id) + }) + .filter((id) => !existingGeoZoneIds.includes(id)) + + eventBuilders.createdGeoZone({ + data: createdGeoZoneIds.map((id) => ({ id })), + sharedContext, + }) + eventBuilders.updatedGeoZone({ + data: updatedGeoZoneIds.map((id) => ({ id })), + sharedContext, + }) + return Array.isArray(data) ? updatedServiceZones : updatedServiceZones[0] } @@ -1010,11 +1153,12 @@ export default class FulfillmentModuleService< ): Promise @InjectManager("baseRepository_") + @EmitEvents() async upsertServiceZones( data: | FulfillmentTypes.UpsertServiceZoneDTO | FulfillmentTypes.UpsertServiceZoneDTO[], - sharedContext?: Context + @MedusaContext() sharedContext: Context = {} ): Promise< FulfillmentTypes.ServiceZoneDTO | FulfillmentTypes.ServiceZoneDTO[] > { @@ -1055,9 +1199,11 @@ export default class FulfillmentModuleService< forCreate, sharedContext ) + const toPush = Array.isArray(createdServiceZones) ? createdServiceZones : [createdServiceZones] + created.push(...toPush) } @@ -1066,9 +1212,11 @@ export default class FulfillmentModuleService< forUpdate, sharedContext ) + const toPush = Array.isArray(updatedServiceZones) ? updatedServiceZones : [updatedServiceZones] + updated.push(...toPush) } @@ -1087,6 +1235,7 @@ export default class FulfillmentModuleService< ): Promise @InjectManager("baseRepository_") + @EmitEvents() async updateShippingOptions( idOrSelector: string | FulfillmentTypes.FilterableShippingOptionProps, data: FulfillmentTypes.UpdateShippingOptionDTO, @@ -1142,7 +1291,7 @@ export default class FulfillmentModuleService< id: shippingOptionIds, }, { - relations: ["rules"], + relations: ["rules", "type"], take: shippingOptionIds.length, }, sharedContext @@ -1157,16 +1306,28 @@ export default class FulfillmentModuleService< ) const ruleIdsToDelete: string[] = [] + const updatedRuleIds: string[] = [] + const existingRuleIds: string[] = [] + + const optionTypeDeletedIds: string[] = [] + dataArray.forEach((shippingOption) => { + const existingShippingOption = existingShippingOptions.get( + shippingOption.id + )! // Garuantueed to exist since the validation above have been performed + + if (shippingOption.type && !("id" in shippingOption.type)) { + optionTypeDeletedIds.push(existingShippingOption.type.id) + } + if (!shippingOption.rules) { return } - const existingShippingOption = existingShippingOptions.get( - shippingOption.id - )! // Garuantueed to exist since the validation above have been performed const existingRules = existingShippingOption.rules + existingRuleIds.push(...existingRules.map((r) => r.id)) + FulfillmentModuleService.validateMissingShippingOptionRules( existingShippingOption, shippingOption @@ -1183,6 +1344,10 @@ export default class FulfillmentModuleService< const existingRule = (existingRulesMap.get(rule.id) ?? {}) as FulfillmentTypes.UpdateShippingOptionRuleDTO + if (existingRulesMap.get(rule.id)) { + updatedRuleIds.push(rule.id) + } + const ruleData: FulfillmentTypes.UpdateShippingOptionRuleDTO = { ...existingRule, ...rule, @@ -1198,8 +1363,6 @@ export default class FulfillmentModuleService< validateAndNormalizeRules(updatedRules) - const updatedRuleIds = updatedRules.map((r) => "id" in r && r.id) - const toDeleteRuleIds = arrayDifference( updatedRuleIds, Array.from(existingRulesMap.keys()) @@ -1219,6 +1382,11 @@ export default class FulfillmentModuleService< }) if (ruleIdsToDelete.length) { + eventBuilders.deletedShippingOptionRule({ + data: ruleIdsToDelete.map((id) => ({ id })), + sharedContext, + }) + await this.shippingOptionRuleService_.delete( ruleIdsToDelete, sharedContext @@ -1230,11 +1398,71 @@ export default class FulfillmentModuleService< sharedContext ) + this.handleShippingOptionUpdateEvents({ + shippingOptionsData: dataArray, + updatedShippingOptions, + optionTypeDeletedIds, + updatedRuleIds, + existingRuleIds, + sharedContext, + }) + return Array.isArray(data) ? updatedShippingOptions : updatedShippingOptions[0] } + private handleShippingOptionUpdateEvents({ + shippingOptionsData, + updatedShippingOptions, + optionTypeDeletedIds, + updatedRuleIds, + existingRuleIds, + sharedContext, + }) { + eventBuilders.updatedShippingOption({ + data: updatedShippingOptions, + sharedContext, + }) + eventBuilders.deletedShippingOptionType({ + data: optionTypeDeletedIds.map((id) => ({ id })), + sharedContext, + }) + + const createdOptionTypeIds = updatedShippingOptions + .filter((so) => { + const updateData = shippingOptionsData.find((sod) => sod.id === so.id) + return updateData?.type && !("id" in updateData.type) + }) + .map((so) => so.type.id) + + eventBuilders.createdShippingOptionType({ + data: createdOptionTypeIds.map((id) => ({ id })), + sharedContext, + }) + + const createdRuleIds = updatedShippingOptions + .flatMap((so) => + [...so.rules].map((rule) => { + if (existingRuleIds.includes(rule.id)) { + return + } + + return rule.id + }) + ) + .filter((id): id is string => !!id) + + eventBuilders.createdShippingOptionRule({ + data: createdRuleIds.map((id) => ({ id })), + sharedContext, + }) + eventBuilders.updatedShippingOptionRule({ + data: updatedRuleIds.map((id) => ({ id })), + sharedContext, + }) + } + async upsertShippingOptions( data: FulfillmentTypes.UpsertShippingOptionDTO[], sharedContext?: Context @@ -1245,6 +1473,7 @@ export default class FulfillmentModuleService< ): Promise @InjectManager("baseRepository_") + @EmitEvents() async upsertShippingOptions( data: | FulfillmentTypes.UpsertShippingOptionDTO[] @@ -1329,6 +1558,7 @@ export default class FulfillmentModuleService< ): Promise< FulfillmentTypes.ShippingProfileDTO | FulfillmentTypes.ShippingProfileDTO[] > { + // TODO: should we implement that or can we get rid of the profiles concept entirely and link to the so instead? return [] } @@ -1342,6 +1572,7 @@ export default class FulfillmentModuleService< ): Promise @InjectManager("baseRepository_") + @EmitEvents() async updateGeoZones( data: | FulfillmentTypes.UpdateGeoZoneDTO @@ -1357,10 +1588,15 @@ export default class FulfillmentModuleService< FulfillmentModuleService.validateGeoZones(data_) const updatedGeoZones = await this.geoZoneService_.update( - data, + data_, sharedContext ) + eventBuilders.updatedGeoZone({ + data: updatedGeoZones, + sharedContext, + }) + const serialized = await this.baseRepository_.serialize< FulfillmentTypes.GeoZoneDTO[] >(updatedGeoZones) @@ -1378,6 +1614,7 @@ export default class FulfillmentModuleService< ): Promise @InjectManager("baseRepository_") + @EmitEvents() async updateShippingOptionRules( data: | FulfillmentTypes.UpdateShippingOptionRuleDTO[] @@ -1416,31 +1653,140 @@ export default class FulfillmentModuleService< const updatedShippingOptionRules = await this.shippingOptionRuleService_.update(data_, sharedContext) + eventBuilders.updatedShippingOptionRule({ + data: updatedShippingOptionRules.map((rule) => ({ id: rule.id })), + sharedContext, + }) + return Array.isArray(data) ? updatedShippingOptionRules : updatedShippingOptionRules[0] } - @InjectTransactionManager("baseRepository_") + @InjectManager("baseRepository_") + @EmitEvents() async updateFulfillment( id: string, data: FulfillmentTypes.UpdateFulfillmentDTO, @MedusaContext() sharedContext: Context = {} ): Promise { - const fulfillment = await this.fulfillmentService_.update( - { id, ...data }, + const fulfillment = await this.updateFulfillment_(id, data, sharedContext) + + return await this.baseRepository_.serialize( + fulfillment + ) + } + + @InjectTransactionManager("baseRepository_") + protected async updateFulfillment_( + id: string, + data: FulfillmentTypes.UpdateFulfillmentDTO, + @MedusaContext() sharedContext: Context + ): Promise { + const existingFulfillment: TFulfillmentEntity = + await this.fulfillmentService_.retrieve( + id, + { + relations: ["items", "labels"], + }, + sharedContext + ) + + const updatedLabelIds: string[] = [] + let deletedLabelIds: string[] = [] + + const existingLabelIds = existingFulfillment.labels.map((label) => label.id) + + /** + * @note + * Since the relation is a one to many, the deletion, update and creation of labels + * is handled b the orm. That means that we dont have to perform any manual deletions or update. + * For some reason we use to have upsert and replace handled manually but we could simplify all that just like + * we do below which will create the label, update some and delete the one that does not exists in the new data. + * + * There is a bit of logic as we need to reassign the data of those we want to keep + * and we also need to emit the events later on. + */ + if (isDefined(data.labels) && isPresent(data.labels)) { + const dataLabelIds: string[] = data.labels + .filter((label): label is { id: string } => "id" in label) + .map((label) => label.id) + + deletedLabelIds = arrayDifference(existingLabelIds, dataLabelIds) + + for (let label of data.labels) { + if (!("id" in label)) { + continue + } + + const existingLabel = existingFulfillment.labels.find( + ({ id }) => id === label.id + )! + + if ( + !existingLabel || + Object.keys(label).length === 1 || + deepEqualObj(existingLabel, label) + ) { + continue + } + + updatedLabelIds.push(label.id) + const labelData = { ...label } + Object.assign(label, existingLabel, labelData) + } + } + + const [fulfillment] = await this.fulfillmentService_.update( + [{ id, ...data }], sharedContext ) - const serialized = - await this.baseRepository_.serialize( - fulfillment - ) + this.handleFulfillmentUpdateEvents( + fulfillment, + existingLabelIds, + updatedLabelIds, + deletedLabelIds, + sharedContext + ) - return Array.isArray(serialized) ? serialized[0] : serialized + return fulfillment + } + + private handleFulfillmentUpdateEvents( + fulfillment: Fulfillment, + existingLabelIds: string[], + updatedLabelIds: string[], + deletedLabelIds: string[], + sharedContext: Context + ) { + eventBuilders.updatedFulfillment({ + data: [{ id: fulfillment.id }], + sharedContext, + }) + + eventBuilders.deletedFulfillmentLabel({ + data: deletedLabelIds.map((id) => ({ id })), + sharedContext, + }) + + eventBuilders.updatedFulfillmentLabel({ + data: updatedLabelIds.map((id) => ({ id })), + sharedContext, + }) + + const createdLabels = fulfillment.labels.filter((label) => { + return !existingLabelIds.includes(label.id) + }) + + eventBuilders.createdFulfillmentLabel({ + data: createdLabels.map((label) => ({ id: label.id })), + sharedContext, + }) } @InjectManager("baseRepository_") + @EmitEvents() async cancelFulfillment( id: string, @MedusaContext() sharedContext: Context = {} @@ -1473,6 +1819,11 @@ export default class FulfillmentModuleService< }, sharedContext ) + + eventBuilders.updatedFulfillment({ + data: [{ id }], + sharedContext, + }) } const result = await this.baseRepository_.serialize(fulfillment) @@ -1791,60 +2142,4 @@ export default class FulfillmentModuleService< return geoZoneConstraints } - - protected aggregateFulfillmentSetCreatedEvents( - createdFulfillmentSets: TEntity[], - sharedContext: Context - ): void { - const buildMessage = ({ - eventName, - id, - object, - }: { - eventName: string - id: string - object: string - }) => { - return { - eventName, - metadata: { - object, - service: Modules.FULFILLMENT, - action: "created", - eventGroupId: sharedContext.eventGroupId, - }, - data: { id }, - } - } - - for (const fulfillmentSet of createdFulfillmentSets) { - sharedContext.messageAggregator!.saveRawMessageData( - buildMessage({ - eventName: FulfillmentUtils.FulfillmentEvents.created, - id: fulfillmentSet.id, - object: "fulfillment_set", - }) - ) - - for (const serviceZone of fulfillmentSet.service_zones ?? []) { - sharedContext.messageAggregator!.saveRawMessageData( - buildMessage({ - eventName: FulfillmentUtils.FulfillmentEvents.service_zone_created, - id: serviceZone.id, - object: "service_zone", - }) - ) - - for (const geoZone of serviceZone.geo_zones ?? []) { - sharedContext.messageAggregator!.saveRawMessageData( - buildMessage({ - eventName: FulfillmentUtils.FulfillmentEvents.geo_zone_created, - id: geoZone.id, - object: "geo_zone", - }) - ) - } - } - } - } } diff --git a/packages/modules/fulfillment/src/utils/events.ts b/packages/modules/fulfillment/src/utils/events.ts new file mode 100644 index 0000000000..94aca06311 --- /dev/null +++ b/packages/modules/fulfillment/src/utils/events.ts @@ -0,0 +1,293 @@ +import { + Fulfillment, + FulfillmentSet, + GeoZone, + ServiceZone, + ShippingOption, + ShippingOptionRule, + ShippingOptionType, +} from "@models" +import { Context } from "@medusajs/types" +import { + CommonEvents, + eventBuilderFactory, + FulfillmentEvents, + Modules, +} from "@medusajs/utils" + +export const eventBuilders = { + createdFulfillment: eventBuilderFactory({ + service: Modules.FULFILLMENT, + action: CommonEvents.CREATED, + object: "fulfillment", + eventsEnum: FulfillmentEvents, + }), + updatedFulfillment: eventBuilderFactory({ + service: Modules.FULFILLMENT, + action: CommonEvents.UPDATED, + object: "fulfillment", + eventsEnum: FulfillmentEvents, + }), + createdFulfillmentAddress: eventBuilderFactory({ + service: Modules.FULFILLMENT, + action: CommonEvents.CREATED, + object: "fulfillment_address", + eventsEnum: FulfillmentEvents, + }), + createdFulfillmentItem: eventBuilderFactory({ + service: Modules.FULFILLMENT, + action: CommonEvents.CREATED, + object: "fulfillment_item", + eventsEnum: FulfillmentEvents, + }), + createdFulfillmentLabel: eventBuilderFactory({ + service: Modules.FULFILLMENT, + action: CommonEvents.CREATED, + object: "fulfillment_label", + eventsEnum: FulfillmentEvents, + }), + updatedFulfillmentLabel: eventBuilderFactory({ + service: Modules.FULFILLMENT, + action: CommonEvents.UPDATED, + object: "fulfillment_label", + eventsEnum: FulfillmentEvents, + }), + deletedFulfillmentLabel: eventBuilderFactory({ + service: Modules.FULFILLMENT, + action: CommonEvents.DELETED, + object: "fulfillment_label", + eventsEnum: FulfillmentEvents, + }), + createdShippingProfile: eventBuilderFactory({ + service: Modules.FULFILLMENT, + action: CommonEvents.CREATED, + object: "shipping_profile", + eventsEnum: FulfillmentEvents, + }), + createdShippingOptionType: eventBuilderFactory({ + service: Modules.FULFILLMENT, + action: CommonEvents.CREATED, + object: "shipping_option_type", + eventsEnum: FulfillmentEvents, + }), + updatedShippingOptionType: eventBuilderFactory({ + service: Modules.FULFILLMENT, + action: CommonEvents.UPDATED, + object: "shipping_option_type", + eventsEnum: FulfillmentEvents, + }), + deletedShippingOptionType: eventBuilderFactory({ + service: Modules.FULFILLMENT, + action: CommonEvents.DELETED, + object: "shipping_option_type", + eventsEnum: FulfillmentEvents, + }), + createdShippingOptionRule: eventBuilderFactory({ + service: Modules.FULFILLMENT, + action: CommonEvents.CREATED, + object: "shipping_option_rule", + eventsEnum: FulfillmentEvents, + }), + updatedShippingOptionRule: eventBuilderFactory({ + service: Modules.FULFILLMENT, + action: CommonEvents.UPDATED, + object: "shipping_option_rule", + eventsEnum: FulfillmentEvents, + }), + deletedShippingOptionRule: eventBuilderFactory({ + service: Modules.FULFILLMENT, + action: CommonEvents.DELETED, + object: "shipping_option_rule", + eventsEnum: FulfillmentEvents, + }), + createdShippingOption: eventBuilderFactory({ + service: Modules.FULFILLMENT, + action: CommonEvents.CREATED, + object: "shipping_option", + eventsEnum: FulfillmentEvents, + }), + updatedShippingOption: eventBuilderFactory({ + service: Modules.FULFILLMENT, + action: CommonEvents.UPDATED, + object: "shipping_option", + eventsEnum: FulfillmentEvents, + }), + createdFulfillmentSet: eventBuilderFactory({ + service: Modules.FULFILLMENT, + action: CommonEvents.CREATED, + object: "fulfillment_set", + isMainEntity: true, + eventsEnum: FulfillmentEvents, + }), + updatedFulfillmentSet: eventBuilderFactory({ + service: Modules.FULFILLMENT, + action: CommonEvents.UPDATED, + object: "fulfillment_set", + isMainEntity: true, + eventsEnum: FulfillmentEvents, + }), + deletedFulfillmentSet: eventBuilderFactory({ + service: Modules.FULFILLMENT, + action: CommonEvents.DELETED, + object: "fulfillment_set", + isMainEntity: true, + eventsEnum: FulfillmentEvents, + }), + createdServiceZone: eventBuilderFactory({ + service: Modules.FULFILLMENT, + action: CommonEvents.CREATED, + object: "service_zone", + eventsEnum: FulfillmentEvents, + }), + updatedServiceZone: eventBuilderFactory({ + service: Modules.FULFILLMENT, + action: CommonEvents.UPDATED, + object: "service_zone", + eventsEnum: FulfillmentEvents, + }), + deletedServiceZone: eventBuilderFactory({ + service: Modules.FULFILLMENT, + action: CommonEvents.DELETED, + object: "service_zone", + eventsEnum: FulfillmentEvents, + }), + createdGeoZone: eventBuilderFactory({ + service: Modules.FULFILLMENT, + action: CommonEvents.CREATED, + object: "geo_zone", + eventsEnum: FulfillmentEvents, + }), + updatedGeoZone: eventBuilderFactory({ + service: Modules.FULFILLMENT, + action: CommonEvents.UPDATED, + object: "geo_zone", + eventsEnum: FulfillmentEvents, + }), + deletedGeoZone: eventBuilderFactory({ + service: Modules.FULFILLMENT, + action: CommonEvents.DELETED, + object: "geo_zone", + eventsEnum: FulfillmentEvents, + }), +} + +export function buildCreatedFulfillmentEvents({ + fulfillments, + sharedContext, +}: { + fulfillments: Fulfillment[] + sharedContext: Context +}) { + if (!fulfillments.length) { + return + } + + const fulfillments_: { id: string }[] = [] + const addresses: { id: string }[] = [] + const items: { id: string }[] = [] + const labels: { id: string }[] = [] + + fulfillments.forEach((fulfillment) => { + fulfillments_.push({ id: fulfillment.id }) + + if (fulfillment.delivery_address) { + addresses.push({ id: fulfillment.delivery_address.id }) + } + + if (fulfillment.items) { + items.push(...fulfillment.items) + } + + if (fulfillment.labels) { + labels.push(...fulfillment.labels) + } + }) + + eventBuilders.createdFulfillment({ data: fulfillments_, sharedContext }) + eventBuilders.createdFulfillmentAddress({ data: addresses, sharedContext }) + eventBuilders.createdFulfillmentItem({ data: items, sharedContext }) + eventBuilders.createdFulfillmentLabel({ data: labels, sharedContext }) +} + +export function buildCreatedShippingOptionEvents({ + shippingOptions, + sharedContext, +}: { + shippingOptions: ShippingOption[] + sharedContext: Context +}) { + if (!shippingOptions.length) { + return + } + + const options: { id: string }[] = [] + const types: ShippingOptionType[] = [] + const rules: ShippingOptionRule[] = [] + + shippingOptions.forEach((shippingOption) => { + options.push({ id: shippingOption.id }) + + if (shippingOption.type) { + types.push(shippingOption.type) + } + + if (shippingOption.rules) { + rules.push(...shippingOption.rules) + } + }) + + eventBuilders.createdShippingOption({ data: options, sharedContext }) + eventBuilders.createdShippingOptionType({ data: types, sharedContext }) + eventBuilders.createdShippingOptionRule({ data: rules, sharedContext }) +} + +export function buildCreatedFulfillmentSetEvents({ + fulfillmentSets, + sharedContext, +}: { + fulfillmentSets: FulfillmentSet[] + sharedContext: Context +}): void { + if (!fulfillmentSets.length) { + return + } + + const serviceZones: ServiceZone[] = [] + + fulfillmentSets.forEach((fulfillmentSet) => { + if (!fulfillmentSet.service_zones?.length) { + return + } + + serviceZones.push(...fulfillmentSet.service_zones) + }) + + eventBuilders.createdFulfillmentSet({ data: fulfillmentSets, sharedContext }) + + buildCreatedServiceZoneEvents({ serviceZones, sharedContext }) +} + +export function buildCreatedServiceZoneEvents({ + serviceZones, + sharedContext, +}: { + serviceZones: ServiceZone[] + sharedContext: Context +}): void { + if (!serviceZones.length) { + return + } + + const geoZones: GeoZone[] = [] + + serviceZones.forEach((serviceZone) => { + if (!serviceZone.geo_zones.length) { + return + } + + geoZones.push(...serviceZone.geo_zones) + }) + + eventBuilders.createdServiceZone({ data: serviceZones, sharedContext }) + eventBuilders.createdGeoZone({ data: geoZones, sharedContext }) +} diff --git a/packages/modules/fulfillment/src/utils/index.ts b/packages/modules/fulfillment/src/utils/index.ts index cfa334d6ac..8a259a7eb5 100644 --- a/packages/modules/fulfillment/src/utils/index.ts +++ b/packages/modules/fulfillment/src/utils/index.ts @@ -1 +1,2 @@ -export * from './utils' \ No newline at end of file +export * from "./utils" +export * from "./events" diff --git a/packages/modules/inventory-next/src/services/inventory.ts b/packages/modules/inventory-next/src/services/inventory.ts index 7d04d3f2c9..8a09fd1bfa 100644 --- a/packages/modules/inventory-next/src/services/inventory.ts +++ b/packages/modules/inventory-next/src/services/inventory.ts @@ -15,11 +15,11 @@ import { InjectManager, InjectTransactionManager, InventoryEvents, + isDefined, + isString, MedusaContext, MedusaError, ModulesSdkUtils, - isDefined, - isString, partitionArray, promiseAll, } from "@medusajs/utils" @@ -198,11 +198,10 @@ export default class InventoryModuleService< context.messageAggregator?.saveRawMessageData( created.map((reservationItem) => ({ eventName: InventoryEvents.reservation_item_created, - metadata: { - service: this.constructor.name, - action: CommonEvents.CREATED, - object: "reservation-item", - }, + service: this.constructor.name, + action: CommonEvents.CREATED, + object: "reservation-item", + context, data: { id: reservationItem.id }, })) ) @@ -297,11 +296,10 @@ export default class InventoryModuleService< context.messageAggregator?.saveRawMessageData( result.map((inventoryItem) => ({ eventName: InventoryEvents.created, - metadata: { - service: this.constructor.name, - action: CommonEvents.CREATED, - object: "inventory-item", - }, + service: this.constructor.name, + action: CommonEvents.CREATED, + object: "inventory-item", + context, data: { id: inventoryItem.id }, })) ) @@ -349,11 +347,10 @@ export default class InventoryModuleService< context.messageAggregator?.saveRawMessageData( created.map((inventoryLevel) => ({ eventName: InventoryEvents.inventory_level_created, - metadata: { - service: this.constructor.name, - action: CommonEvents.CREATED, - object: "inventory-level", - }, + service: this.constructor.name, + action: CommonEvents.CREATED, + object: "inventory-level", + context, data: { id: inventoryLevel.id }, })) ) @@ -408,11 +405,10 @@ export default class InventoryModuleService< context.messageAggregator?.saveRawMessageData( result.map((inventoryItem) => ({ eventName: InventoryEvents.updated, - metadata: { - service: this.constructor.name, - action: CommonEvents.UPDATED, - object: "inventory-item", - }, + service: this.constructor.name, + action: CommonEvents.UPDATED, + object: "inventory-item", + context, data: { id: inventoryItem.id }, })) ) @@ -448,11 +444,10 @@ export default class InventoryModuleService< context.messageAggregator?.saveRawMessageData( result[0].map((inventoryLevel) => ({ eventName: InventoryEvents.inventory_level_deleted, - metadata: { - service: this.constructor.name, - action: CommonEvents.DELETED, - object: "inventory-level", - }, + service: this.constructor.name, + action: CommonEvents.DELETED, + object: "inventory-level", + context, data: { id: inventoryLevel.id }, })) ) @@ -480,11 +475,10 @@ export default class InventoryModuleService< context.messageAggregator?.saveRawMessageData({ eventName: InventoryEvents.inventory_level_deleted, - metadata: { - service: this.constructor.name, - action: CommonEvents.DELETED, - object: "inventory-level", - }, + service: this.constructor.name, + action: CommonEvents.DELETED, + object: "inventory-level", + context, data: { id: inventoryLevel.id }, }) @@ -521,11 +515,10 @@ export default class InventoryModuleService< context.messageAggregator?.saveRawMessageData( levels.map((inventoryLevel) => ({ eventName: InventoryEvents.inventory_level_updated, - metadata: { - service: this.constructor.name, - action: CommonEvents.UPDATED, - object: "inventory-level", - }, + service: this.constructor.name, + action: CommonEvents.UPDATED, + object: "inventory-level", + context, data: { id: inventoryLevel.id }, })) ) @@ -606,11 +599,10 @@ export default class InventoryModuleService< context.messageAggregator?.saveRawMessageData( result.map((reservationItem) => ({ eventName: InventoryEvents.inventory_level_updated, - metadata: { - service: this.constructor.name, - action: CommonEvents.UPDATED, - object: "reservation-item", - }, + service: this.constructor.name, + action: CommonEvents.UPDATED, + object: "reservation-item", + context, data: { id: reservationItem.id }, })) ) @@ -738,11 +730,10 @@ export default class InventoryModuleService< context.messageAggregator?.saveRawMessageData( reservations.map((reservationItem) => ({ eventName: InventoryEvents.reservation_item_deleted, - metadata: { - service: this.constructor.name, - action: CommonEvents.DELETED, - object: "reservation-item", - }, + service: this.constructor.name, + action: CommonEvents.DELETED, + object: "reservation-item", + context, data: { id: reservationItem.id }, })) ) @@ -781,11 +772,10 @@ export default class InventoryModuleService< context.messageAggregator?.saveRawMessageData( reservations.map((reservationItem) => ({ eventName: InventoryEvents.reservation_item_deleted, - metadata: { - service: this.constructor.name, - action: CommonEvents.DELETED, - object: "reservation-item", - }, + service: this.constructor.name, + action: CommonEvents.DELETED, + object: "reservation-item", + context, data: { id: reservationItem.id }, })) ) @@ -851,11 +841,10 @@ export default class InventoryModuleService< context.messageAggregator?.saveRawMessageData({ eventName: InventoryEvents.inventory_level_updated, - metadata: { - service: this.constructor.name, - action: CommonEvents.UPDATED, - object: "inventory-level", - }, + service: this.constructor.name, + action: CommonEvents.UPDATED, + object: "inventory-level", + context, data: { id: result.id }, }) } diff --git a/packages/modules/user/src/services/user-module.ts b/packages/modules/user/src/services/user-module.ts index bfd2349375..c4fe264be2 100644 --- a/packages/modules/user/src/services/user-module.ts +++ b/packages/modules/user/src/services/user-module.ts @@ -1,19 +1,19 @@ import { Context, DAL, + IEventBusModuleService, InternalModuleDeclaration, ModuleJoinerConfig, - UserTypes, ModulesSdkTypes, - IEventBusModuleService, + UserTypes, } from "@medusajs/types" import { + CommonEvents, EmitEvents, + InjectManager, InjectTransactionManager, MedusaContext, ModulesSdkUtils, - InjectManager, - CommonEvents, UserEvents, } from "@medusajs/utils" import { entityNameToLinkableKeysMap, joinerConfig } from "../joiner-config" @@ -94,11 +94,10 @@ export default class UserModuleService< sharedContext.messageAggregator?.saveRawMessageData( invites.map((invite) => ({ eventName: UserEvents.invite_token_generated, - metadata: { - service: this.constructor.name, - action: "token_generated", - object: "invite", - }, + service: this.constructor.name, + action: "token_generated", + object: "invite", + context: sharedContext, data: { id: invite.id }, })) ) @@ -150,11 +149,10 @@ export default class UserModuleService< sharedContext.messageAggregator?.saveRawMessageData( users.map((user) => ({ eventName: UserEvents.created, - metadata: { - service: this.constructor.name, - action: CommonEvents.CREATED, - object: "user", - }, + service: this.constructor.name, + action: CommonEvents.CREATED, + object: "user", + context: sharedContext, data: { id: user.id }, })) ) @@ -190,11 +188,10 @@ export default class UserModuleService< sharedContext.messageAggregator?.saveRawMessageData( updatedUsers.map((user) => ({ eventName: UserEvents.updated, - metadata: { - service: this.constructor.name, - action: CommonEvents.UPDATED, - object: "user", - }, + service: this.constructor.name, + action: CommonEvents.UPDATED, + object: "user", + context: sharedContext, data: { id: user.id }, })) ) @@ -230,11 +227,10 @@ export default class UserModuleService< sharedContext.messageAggregator?.saveRawMessageData( invites.map((invite) => ({ eventName: UserEvents.invite_created, - metadata: { - service: this.constructor.name, - action: CommonEvents.CREATED, - object: "invite", - }, + service: this.constructor.name, + action: CommonEvents.CREATED, + object: "invite", + context: sharedContext, data: { id: invite.id }, })) ) @@ -242,11 +238,10 @@ export default class UserModuleService< sharedContext.messageAggregator?.saveRawMessageData( invites.map((invite) => ({ eventName: UserEvents.invite_token_generated, - metadata: { - service: this.constructor.name, - action: "token_generated", - object: "invite", - }, + service: this.constructor.name, + action: "token_generated", + object: "invite", + context: sharedContext, data: { id: invite.id }, })) ) @@ -301,11 +296,10 @@ export default class UserModuleService< sharedContext.messageAggregator?.saveRawMessageData( serializedInvites.map((invite) => ({ eventName: UserEvents.invite_updated, - metadata: { - service: this.constructor.name, - action: CommonEvents.UPDATED, - object: "invite", - }, + service: this.constructor.name, + action: CommonEvents.UPDATED, + object: "invite", + context: sharedContext, data: { id: invite.id }, })) )