fix: Shipping profile deletion with options (#8910)

* fix(fulfillment): Prevent deletion of profiles associated to options

* add tests

* typo

* address feedback
This commit is contained in:
Adrien de Peretti
2024-08-30 17:04:29 +02:00
committed by GitHub
parent dea5af085a
commit 3da01035f3
3 changed files with 150 additions and 2 deletions

View File

@@ -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[]) {

View File

@@ -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<IFulfillmentModuleService>({
moduleName: Modules.FULFILLMENT,
moduleOptions,
testSuite: ({ service }) => {
let eventBusEmitSpy
@@ -121,6 +145,72 @@ moduleIntegrationTestRunner<IFulfillmentModuleService>({
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.`
)
})
})
})
})
},

View File

@@ -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<TReturnableLinkableKeys>,
@MedusaContext() sharedContext: Context = {}
): Promise<Record<string, string[]> | 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(