From 3da01035f3808fb0b368747dc1d9dceb0c68e100 Mon Sep 17 00:00:00 2001 From: Adrien de Peretti Date: Fri, 30 Aug 2024 17:04:29 +0200 Subject: [PATCH] fix: Shipping profile deletion with options (#8910) * fix(fulfillment): Prevent deletion of profiles associated to options * add tests * typo * address feedback --- .../decorators/inject-transaction-manager.ts | 2 +- .../shipping-profile.spec.ts | 92 ++++++++++++++++++- .../services/fulfillment-module-service.ts | 58 ++++++++++++ 3 files changed, 150 insertions(+), 2 deletions(-) diff --git a/packages/core/utils/src/modules-sdk/decorators/inject-transaction-manager.ts b/packages/core/utils/src/modules-sdk/decorators/inject-transaction-manager.ts index 2926f13e21..4888389480 100644 --- a/packages/core/utils/src/modules-sdk/decorators/inject-transaction-manager.ts +++ b/packages/core/utils/src/modules-sdk/decorators/inject-transaction-manager.ts @@ -29,7 +29,7 @@ export function InjectTransactionManager( : () => false managerProperty = isString(shouldForceTransactionOrManagerProperty) ? shouldForceTransactionOrManagerProperty - : managerProperty + : managerProperty ?? "baseRepository_" const argIndex = target.MedusaContextIndex_[propertyKey] descriptor.value = async function (...args: any[]) { 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 5ec59c8949..0e65b79789 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 @@ -6,13 +6,37 @@ import { MockEventBusService, moduleIntegrationTestRunner, } from "medusa-test-utils" -import { buildExpectedEventMessageShape } from "../../__fixtures__" +import { + buildExpectedEventMessageShape, + generateCreateShippingOptionsData, +} from "../../__fixtures__" import { FulfillmentEvents, Modules } from "@medusajs/utils" +import { resolve } from "path" +import { FulfillmentProviderService } from "@services" +import { FulfillmentProviderServiceFixtures } from "../../__fixtures__/providers" jest.setTimeout(100000) +const moduleOptions = { + providers: [ + { + resolve: resolve( + process.cwd() + + "/integration-tests/__fixtures__/providers/default-provider" + ), + id: "test-provider", + }, + ], +} + +const providerId = FulfillmentProviderService.getRegistrationIdentifier( + FulfillmentProviderServiceFixtures, + "test-provider" +) + moduleIntegrationTestRunner({ moduleName: Modules.FULFILLMENT, + moduleOptions, testSuite: ({ service }) => { let eventBusEmitSpy @@ -121,6 +145,72 @@ moduleIntegrationTestRunner({ expect(err.message).toContain("exists") }) }) + + describe("on delete", () => { + it("should delete a shipping profile", async function () { + const createData: CreateShippingProfileDTO = { + name: "test-default-profile", + type: "default", + } + + const createdShippingProfile = await service.createShippingProfiles( + createData + ) + + await service.deleteShippingProfiles(createdShippingProfile.id) + + const [shippingProfile] = await service.listShippingProfiles({ + id: createdShippingProfile.id, + }) + + expect(shippingProfile).toBeUndefined() + }) + + it("should not allow to delete a shipping profile that is associated to any shipping options", async function () { + const createData: CreateShippingProfileDTO = { + name: "test-default-profile", + type: "default", + } + + const createdShippingProfile = await service.createShippingProfiles( + createData + ) + + const fulfillmentSet = await service.createFulfillmentSets({ + name: "test", + type: "test-type", + service_zones: [ + { + name: "test", + }, + ], + }) + + await service.createShippingOptions([ + generateCreateShippingOptionsData({ + service_zone_id: fulfillmentSet.service_zones[0].id, + shipping_profile_id: createdShippingProfile.id, + provider_id: providerId, + rules: [ + { + attribute: "test-attribute", + operator: "in", + value: ["test"], + }, + ], + }), + ]) + + const err = await service + .deleteShippingProfiles(createdShippingProfile.id) + .catch((e) => e) + + expect(err).toBeTruthy() + expect(err.message).toContain( + `Cannot delete Shipping Profiles ${createdShippingProfile.id} with associated Shipping Options. Delete Shipping Options first and try again.` + ) + }) + }) }) }) }, diff --git a/packages/modules/fulfillment/src/services/fulfillment-module-service.ts b/packages/modules/fulfillment/src/services/fulfillment-module-service.ts index c2b9aca2c2..fc982df015 100644 --- a/packages/modules/fulfillment/src/services/fulfillment-module-service.ts +++ b/packages/modules/fulfillment/src/services/fulfillment-module-service.ts @@ -10,6 +10,7 @@ import { ModuleJoinerConfig, ModulesSdkTypes, ShippingOptionDTO, + SoftDeleteReturn, UpdateFulfillmentSetDTO, UpdateServiceZoneDTO, } from "@medusajs/types" @@ -1953,6 +1954,63 @@ export default class FulfillmentModuleService return !!shippingOptions.length } + @InjectTransactionManager() + // @ts-expect-error + async deleteShippingProfiles( + ids: string | string[], + @MedusaContext() sharedContext: Context = {} + ) { + const shippingProfileIds = Array.isArray(ids) ? ids : [ids] + await this.validateShippingProfileDeletion( + shippingProfileIds, + sharedContext + ) + + return await super.deleteShippingProfiles(shippingProfileIds, sharedContext) + } + + @InjectTransactionManager() + // @ts-expect-error + async softDeleteShippingProfiles< + TReturnableLinkableKeys extends string = string + >( + ids: string[], + config?: SoftDeleteReturn, + @MedusaContext() sharedContext: Context = {} + ): Promise | void> { + await this.validateShippingProfileDeletion(ids, sharedContext) + + return await super.softDeleteShippingProfiles(ids, config, sharedContext) + } + + protected async validateShippingProfileDeletion( + ids: string[], + sharedContext: Context + ) { + const shippingProfileIds = Array.isArray(ids) ? ids : [ids] + const shippingProfiles = await this.shippingProfileService_.list( + { id: shippingProfileIds }, + { + relations: ["shipping_options.id"], + }, + sharedContext + ) + + const undeletableShippingProfiles = shippingProfiles.filter( + (profile) => profile.shipping_options.length > 0 + ) + if (undeletableShippingProfiles.length) { + const undeletableShippingProfileIds = undeletableShippingProfiles.map( + (profile) => profile.id + ) + + throw new MedusaError( + MedusaError.Types.INVALID_DATA, + `Cannot delete Shipping Profiles ${undeletableShippingProfileIds} with associated Shipping Options. Delete Shipping Options first and try again.` + ) + } + } + protected static canCancelFulfillmentOrThrow(fulfillment: Fulfillment) { if (fulfillment.shipped_at) { throw new MedusaError(