feat(*): shipping options update API (#6987)

**What**
- Update the `updateShippingOptions` module service API to follow the newest convention
- Add upsert support for shipping options
- update shipping options workflow
- update shipping options api end point and validation
- update shipping options all integration tests

FIXES CORE-1926
This commit is contained in:
Adrien de Peretti
2024-04-07 18:28:59 +02:00
committed by GitHub
parent f65fbff535
commit f132929c7e
20 changed files with 916 additions and 74 deletions

View File

@@ -202,6 +202,141 @@ medusaIntegrationTestRunner({
)
})
})
describe("POST /admin/shipping-options/:id", () => {
it("should throw error when required params are missing", async () => {
const shippingOptionPayload = {
name: "Test shipping option",
service_zone_id: fulfillmentSet.service_zones[0].id,
shipping_profile_id: shippingProfile.id,
provider_id: "manual_test-provider",
price_type: "flat",
type: {
label: "Test type",
description: "Test description",
code: "test-code",
},
prices: [
{
currency_code: "usd",
amount: 1000,
},
{
region_id: region.id,
amount: 1000,
},
],
rules: [shippingOptionRule],
}
const response = await api.post(
`/admin/shipping-options`,
shippingOptionPayload,
adminHeaders
)
const shippingOptionId = response.data.shipping_option.id
const updateShippingOptionPayload = {
}
let err = await api
.post(
`/admin/shipping-options/${shippingOptionId}`,
updateShippingOptionPayload,
adminHeaders
)
.catch((e) => e.response)
const errorsFields = [
{
code: "invalid_type",
expected: "string",
received: "undefined",
path: ["id"],
message: "Required",
},
]
expect(err.status).toEqual(400)
expect(err.data).toEqual({
type: "invalid_data",
message: `Invalid request body: ${JSON.stringify(errorsFields)}`,
})
})
it("should create a shipping option successfully", async () => {
const shippingOptionPayload = {
name: "Test shipping option",
service_zone_id: fulfillmentSet.service_zones[0].id,
shipping_profile_id: shippingProfile.id,
provider_id: "manual_test-provider",
price_type: "flat",
type: {
label: "Test type",
description: "Test description",
code: "test-code",
},
prices: [
{
currency_code: "usd",
amount: 1000,
},
{
region_id: region.id,
amount: 1000,
},
],
rules: [shippingOptionRule],
}
const response = await api.post(
`/admin/shipping-options`,
shippingOptionPayload,
adminHeaders
)
expect(response.status).toEqual(200)
expect(response.data.shipping_option).toEqual(
expect.objectContaining({
id: expect.any(String),
name: shippingOptionPayload.name,
provider: expect.objectContaining({
id: shippingOptionPayload.provider_id,
}),
price_type: shippingOptionPayload.price_type,
type: expect.objectContaining({
id: expect.any(String),
label: shippingOptionPayload.type.label,
description: shippingOptionPayload.type.description,
code: shippingOptionPayload.type.code,
}),
service_zone_id: fulfillmentSet.service_zones[0].id,
shipping_profile_id: shippingProfile.id,
prices: expect.arrayContaining([
expect.objectContaining({
id: expect.any(String),
currency_code: "usd",
amount: 1000,
}),
expect.objectContaining({
id: expect.any(String),
currency_code: "eur",
amount: 1000,
}),
]),
rules: expect.arrayContaining([
expect.objectContaining({
id: expect.any(String),
operator: "eq",
attribute: "old_attr",
value: "old value",
}),
]),
})
)
})
})
})
},
})

View File

@@ -0,0 +1,326 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import {
FulfillmentSetDTO,
FulfillmentWorkflow,
IFulfillmentModuleService,
IRegionModuleService,
ServiceZoneDTO,
ShippingProfileDTO,
UpdateShippingOptionsWorkflowInput,
} from "@medusajs/types"
import { medusaIntegrationTestRunner } from "medusa-test-utils/dist"
import {
createShippingOptionsWorkflow,
updateShippingOptionsWorkflow,
} from "@medusajs/core-flows"
import {
ContainerRegistrationKeys,
remoteQueryObjectFromString,
RuleOperator,
} from "@medusajs/utils"
jest.setTimeout(100000)
const env = { MEDUSA_FF_MEDUSA_V2: true }
const provider_id = "manual_test-provider"
medusaIntegrationTestRunner({
env,
testSuite: ({ getContainer }) => {
let service: IFulfillmentModuleService
let container
beforeAll(() => {
container = getContainer()
service = container.resolve(ModuleRegistrationName.FULFILLMENT)
})
describe("Fulfillment workflows", () => {
let fulfillmentSet: FulfillmentSetDTO
let serviceZone: ServiceZoneDTO
let shippingProfile: ShippingProfileDTO
beforeEach(async () => {
shippingProfile = await service.createShippingProfiles({
name: "test",
type: "default",
})
fulfillmentSet = await service.create({
name: "Test fulfillment set",
type: "manual_test",
})
serviceZone = await service.createServiceZones({
name: "Test service zone",
fulfillment_set_id: fulfillmentSet.id,
geo_zones: [
{
type: "country",
country_code: "US",
},
],
})
})
it("should update shipping options", async () => {
const regionService = container.resolve(
ModuleRegistrationName.REGION
) as IRegionModuleService
const [region] = await regionService.create([
{
name: "Test region",
currency_code: "eur",
countries: ["fr"],
},
])
const shippingOptionData: FulfillmentWorkflow.CreateShippingOptionsWorkflowInput =
{
name: "Test shipping option",
price_type: "flat",
service_zone_id: serviceZone.id,
shipping_profile_id: shippingProfile.id,
provider_id,
type: {
code: "manual-type",
label: "Manual Type",
description: "Manual Type Description",
},
prices: [
{
currency_code: "usd",
amount: 10,
},
{
region_id: region.id,
amount: 100,
},
],
rules: [
{
attribute: "total",
operator: RuleOperator.EQ,
value: "100",
},
],
}
const { result } = await createShippingOptionsWorkflow(container).run({
input: [shippingOptionData],
})
const updateData: UpdateShippingOptionsWorkflowInput = {
id: result[0].id,
name: "Test shipping option",
price_type: "flat",
type: {
code: "manual-type",
label: "Manual Type",
description: "Manual Type Description",
},
}
await updateShippingOptionsWorkflow(container).run({
input: [updateData],
})
const remoteQuery = container.resolve(
ContainerRegistrationKeys.REMOTE_QUERY
)
const remoteQueryObject = remoteQueryObjectFromString({
entryPoint: "shipping_option",
variables: {
id: result[0].id,
},
fields: [
"id",
"name",
"price_type",
"service_zone_id",
"shipping_profile_id",
"provider_id",
"data",
"metadata",
"type.*",
"created_at",
"updated_at",
"deleted_at",
"shipping_option_type_id",
"prices.*",
],
})
const [createdShippingOption] = await remoteQuery(remoteQueryObject)
const prices = createdShippingOption.prices
delete createdShippingOption.prices
expect(createdShippingOption).toEqual(
expect.objectContaining({
id: result[0].id,
name: updateData.name,
price_type: updateData.price_type,
service_zone_id: serviceZone.id,
shipping_profile_id: shippingProfile.id,
provider_id: provider_id,
data: null,
metadata: null,
type: expect.objectContaining({
id: expect.any(String),
code: updateData.type.code,
label: updateData.type.label,
description: updateData.type.description,
}),
shipping_option_type_id: expect.any(String),
})
)
expect(prices).toHaveLength(2)
expect(prices).toContainEqual(
expect.objectContaining({
currency_code: "usd",
amount: 10,
})
)
expect(prices).toContainEqual(
expect.objectContaining({
currency_code: "eur",
amount: 100,
rules_count: 1,
})
)
})
it("should revert the shipping options", async () => {
const regionService = container.resolve(
ModuleRegistrationName.REGION
) as IRegionModuleService
const [region] = await regionService.create([
{
name: "Test region",
currency_code: "eur",
countries: ["fr"],
},
])
const shippingOptionData: FulfillmentWorkflow.CreateShippingOptionsWorkflowInput =
{
name: "Test shipping option",
price_type: "flat",
service_zone_id: serviceZone.id,
shipping_profile_id: shippingProfile.id,
provider_id,
type: {
code: "manual-type",
label: "Manual Type",
description: "Manual Type Description",
},
prices: [
{
currency_code: "usd",
amount: 10,
},
{
region_id: region.id,
amount: 100,
},
],
rules: [
{
attribute: "total",
operator: RuleOperator.EQ,
value: "100",
},
],
}
const updateWorkflow = await updateShippingOptionsWorkflow(container)
updateWorkflow.addAction(
"throw",
{
invoke: async function failStep() {
throw new Error(`Failed to update shipping options`)
},
},
{
noCompensation: true,
}
)
const { result } = await createShippingOptionsWorkflow(container).run({
input: [shippingOptionData],
})
const updateData: UpdateShippingOptionsWorkflowInput = {
id: result[0].id,
name: "Test shipping option",
price_type: "flat",
type: {
code: "manual-type",
label: "Manual Type",
description: "Manual Type Description",
},
}
const { errors } = await updateWorkflow.run({
input: [updateData],
throwOnError: false,
})
expect(errors).toHaveLength(1)
expect(errors[0].error.message).toEqual(
`Failed to update shipping options`
)
const remoteQuery = container.resolve(
ContainerRegistrationKeys.REMOTE_QUERY
)
const remoteQueryObject = remoteQueryObjectFromString({
entryPoint: "shipping_option",
fields: [
"id",
"name",
"price_type",
"service_zone_id",
"shipping_profile_id",
"provider_id",
"data",
"metadata",
"type.*",
"created_at",
"updated_at",
"deleted_at",
"shipping_option_type_id",
],
})
const createdShippingOptions = await remoteQuery(remoteQueryObject)
expect(createdShippingOptions).toHaveLength(1)
expect(createdShippingOptions[0]).toEqual(
expect.objectContaining({
name: shippingOptionData.name,
price_type: shippingOptionData.price_type,
service_zone_id: serviceZone.id,
shipping_profile_id: shippingProfile.id,
provider_id: provider_id,
data: null,
metadata: null,
type: expect.objectContaining({
id: expect.any(String),
code: shippingOptionData.type.code,
label: shippingOptionData.type.label,
description: shippingOptionData.type.description,
}),
})
)
})
})
},
})

View File

@@ -1,43 +0,0 @@
import { createStep, StepResponse } from "@medusajs/workflows-sdk"
import {
FulfillmentWorkflow,
IFulfillmentModuleService,
ShippingOptionDTO,
} from "@medusajs/types"
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
type StepInput = Omit<
FulfillmentWorkflow.CreateShippingOptionsWorkflowInput,
"prices"
>[]
export const createShippingOptionsStepId = "create-shipping-options-step"
export const createShippingOptionsStep = createStep(
createShippingOptionsStepId,
async (input: StepInput, { container }) => {
if (!input?.length) {
return new StepResponse([], [])
}
const fulfillmentService = container.resolve<IFulfillmentModuleService>(
ModuleRegistrationName.FULFILLMENT
)
const createdShippingOptions: ShippingOptionDTO[] =
await fulfillmentService.createShippingOptions(input)
const shippingOptionIds = createdShippingOptions.map((s) => s.id)
return new StepResponse(createdShippingOptions, shippingOptionIds)
},
async (shippingOptionIds, { container }) => {
if (!shippingOptionIds?.length) {
return
}
const fulfillmentService = container.resolve<IFulfillmentModuleService>(
ModuleRegistrationName.FULFILLMENT
)
await fulfillmentService.deleteShippingOptions(shippingOptionIds)
}
)

View File

@@ -2,6 +2,6 @@ export * from "./add-rules-to-fulfillment-shipping-option"
export * from "./add-shipping-options-prices"
export * from "./create-fulfillment-set"
export * from "./create-service-zones"
export * from "./create-shipping-options"
export * from "./upsert-shipping-options"
export * from "./delete-service-zones"
export * from "./remove-rules-from-fulfillment-shipping-option"

View File

@@ -0,0 +1,100 @@
import { createStep, StepResponse } from "@medusajs/workflows-sdk"
import {
FulfillmentWorkflow,
IFulfillmentModuleService,
ShippingOptionDTO,
} from "@medusajs/types"
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import {
arrayDifference,
getSelectsAndRelationsFromObjectArray,
} from "@medusajs/utils"
import { UpsertShippingOptionDTO } from "@medusajs/types/src"
type StepInput = Omit<
| FulfillmentWorkflow.CreateShippingOptionsWorkflowInput
| FulfillmentWorkflow.UpdateShippingOptionsWorkflowInput,
"prices"
>[]
export const upsertShippingOptionsStepId = "create-shipping-options-step"
export const upsertShippingOptionsStep = createStep(
upsertShippingOptionsStepId,
async (input: StepInput, { container }) => {
if (!input?.length) {
return new StepResponse([], {})
}
const fulfillmentService = container.resolve<IFulfillmentModuleService>(
ModuleRegistrationName.FULFILLMENT
)
const toUpdate: FulfillmentWorkflow.UpdateShippingOptionsWorkflowInput[] =
[]
;(
input as FulfillmentWorkflow.UpdateShippingOptionsWorkflowInput[]
).forEach((inputItem) => {
if (!!inputItem.id) {
return toUpdate.push(inputItem)
}
return
})
let toUpdatePreviousData: ShippingOptionDTO[] = []
if (toUpdate.length) {
const { selects, relations } =
getSelectsAndRelationsFromObjectArray(toUpdate)
toUpdatePreviousData = await fulfillmentService.listShippingOptions(
{
id: toUpdate.map((s) => s.id),
},
{
select: selects,
relations,
}
)
}
const upsertedShippingOptions: ShippingOptionDTO[] =
await fulfillmentService.upsertShippingOptions(
input as UpsertShippingOptionDTO[]
)
const upsertedShippingOptionIds = upsertedShippingOptions.map((s) => s.id)
const updatedIds = toUpdate.map((s) => s.id)
return new StepResponse(upsertedShippingOptions, {
updatedPreviousData: toUpdatePreviousData,
createdIds: arrayDifference(
upsertedShippingOptionIds,
updatedIds
) as string[],
})
},
async (shippingOptionIds: any, { container }) => {
if (
!shippingOptionIds?.updatedPreviousData?.length &&
!shippingOptionIds?.createdIds?.length
) {
return
}
const fulfillmentService = container.resolve<IFulfillmentModuleService>(
ModuleRegistrationName.FULFILLMENT
)
if (shippingOptionIds.updatedPreviousData.length) {
await fulfillmentService.upsertShippingOptions(
shippingOptionIds.updatedPreviousData
)
}
if (shippingOptionIds.createdIds.length) {
await fulfillmentService.deleteShippingOptions(
shippingOptionIds.createdIds
)
}
}
)

View File

@@ -6,7 +6,7 @@ import {
} from "@medusajs/workflows-sdk"
import {
createShippingOptionsPriceSetsStep,
createShippingOptionsStep,
upsertShippingOptionsStep,
} from "../steps"
import { setShippingOptionsPriceSetsStep } from "../steps/set-shipping-options-price-sets"
import { createPricingRuleTypesStep } from "../../pricing"
@@ -34,7 +34,7 @@ export const createShippingOptionsWorkflow = createWorkflow(
}
})
const createdShippingOptions = createShippingOptionsStep(
const createdShippingOptions = upsertShippingOptionsStep(
data.shippingOptions
)

View File

@@ -1,6 +1,7 @@
export * from "./add-rules-to-fulfillment-shipping-option"
export * from "./create-service-zones"
export * from "./create-shipping-options"
export * from "./update-shipping-options"
export * from "./delete-service-zones"
export * from "./remove-rules-from-fulfillment-shipping-option"
export * from "./update-service-zones"

View File

@@ -0,0 +1,92 @@
import { FulfillmentWorkflow } from "@medusajs/types"
import {
createWorkflow,
transform,
WorkflowData,
} from "@medusajs/workflows-sdk"
import { upsertShippingOptionsStep } from "../steps"
export const updateShippingOptionsWorkflowId =
"update-shipping-options-workflow"
export const updateShippingOptionsWorkflow = createWorkflow(
updateShippingOptionsWorkflowId,
(
input: WorkflowData<
FulfillmentWorkflow.UpdateShippingOptionsWorkflowInput[]
>
): WorkflowData<FulfillmentWorkflow.UpdateShippingOptionsWorkflowOutput> => {
const data = transform(input, (data) => {
const shippingOptionsIndexToPrices = data.map((option, index) => {
return {
shipping_option_index: index,
prices: option.prices,
}
})
return {
shippingOptions: data,
shippingOptionsIndexToPrices,
}
})
const updatedShippingOptions = upsertShippingOptionsStep(
data.shippingOptions
)
/*const normalizedShippingOptionsPrices = transform(
{
shippingOptions: updatedShippingOptions,
shippingOptionsIndexToPrices: data.shippingOptionsIndexToPrices,
},
(data) => {
const ruleTypes = new Set<Partial<RuleTypeDTO>>()
const shippingOptionsPrices = data.shippingOptionsIndexToPrices.map(
({ shipping_option_index, prices }) => {
prices?.forEach((price) => {
if ("region_id" in price) {
ruleTypes.add({
name: "region_id",
rule_attribute: "region_id",
})
}
})
return {
id: data.shippingOptions[shipping_option_index].id,
prices,
}
}
)
return {
shippingOptionsPrices,
ruleTypes: Array.from(ruleTypes) as UpdateRuleTypeDTO[],
}
}
)*/
/*updatePricingRuleTypesStep(normalizedShippingOptionsPrices.ruleTypes)*/
/*const shippingOptionsPriceSetsLinkData = updateShippingOptionsPriceSetsStep(
normalizedShippingOptionsPrices.shippingOptionsPrices
)
const normalizedLinkData = transform(
{
shippingOptionsPriceSetsLinkData,
},
(data) => {
return data.shippingOptionsPriceSetsLinkData.map((item) => {
return {
id: item.id,
price_sets: [item.priceSetId],
}
})
}
)*/
/*setShippingOptionsPriceSetsStep(normalizedLinkData)*/
return updatedShippingOptions
}
)

View File

@@ -9,6 +9,7 @@ import { resolve } from "path"
import { FulfillmentProviderService } from "@services"
import { FulfillmentProviderServiceFixtures } from "../../__fixtures__/providers"
import { GeoZoneType } from "@medusajs/utils"
import { UpdateShippingOptionDTO } from "@medusajs/types/src"
jest.setTimeout(100000)
@@ -592,7 +593,7 @@ moduleIntegrationTestRunner({
shippingOptionData
)
const updateData = {
const updateData: UpdateShippingOptionDTO = {
id: shippingOption.id,
name: "updated-test",
price_type: "calculated",
@@ -617,6 +618,7 @@ moduleIntegrationTestRunner({
}
const updatedShippingOption = await service.updateShippingOptions(
updateData.id!,
updateData
)
@@ -690,7 +692,7 @@ moduleIntegrationTestRunner({
shippingOptionData
)
const updateData = {
const updateData: Partial<UpdateShippingOptionDTO> = {
id: shippingOption.id,
name: "updated-test",
price_type: "calculated",
@@ -702,7 +704,7 @@ moduleIntegrationTestRunner({
},
}
await service.updateShippingOptions(updateData)
await service.updateShippingOptions(updateData.id!, updateData)
const updatedShippingOption = await service.retrieveShippingOption(
shippingOption.id,
@@ -837,7 +839,7 @@ moduleIntegrationTestRunner({
},
]
const updatedShippingOption = await service.updateShippingOptions(
const updatedShippingOption = await service.upsertShippingOptions(
updateData
)
@@ -943,7 +945,7 @@ moduleIntegrationTestRunner({
}
const err = await service
.updateShippingOptions(shippingOptionData)
.updateShippingOptions(shippingOptionData.id!, shippingOptionData)
.catch((e) => e)
expect(err).toBeDefined()
@@ -988,7 +990,7 @@ moduleIntegrationTestRunner({
]
const err = await service
.updateShippingOptions(updateData)
.updateShippingOptions(updateData[0].id!, updateData[0])
.catch((e) => e)
expect(err).toBeDefined()
@@ -1035,7 +1037,7 @@ moduleIntegrationTestRunner({
]
const err = await service
.updateShippingOptions(updateData)
.updateShippingOptions(updateData[0].id!, updateData[0])
.catch((e) => e)
expect(err).toBeDefined()

View File

@@ -1,4 +1,3 @@
import { Modules } from "@medusajs/modules-sdk"
import {
Context,
DAL,
@@ -26,6 +25,7 @@ import {
getSetDifference,
isString,
promiseAll,
Modules
} from "@medusajs/utils"
import {
Fulfillment,
@@ -40,6 +40,7 @@ import {
import { isContextValid, validateRules } from "@utils"
import { entityNameToLinkableKeysMap, joinerConfig } from "../joiner-config"
import FulfillmentProviderService from "./fulfillment-provider"
import { UpdateShippingOptionsInput } from "../types/service"
const generateMethodForModels = [
ServiceZone,
@@ -1046,40 +1047,56 @@ export default class FulfillmentModuleService<
}
updateShippingOptions(
data: FulfillmentTypes.UpdateShippingOptionDTO[],
sharedContext?: Context
): Promise<FulfillmentTypes.ShippingOptionDTO[]>
updateShippingOptions(
id: string,
data: FulfillmentTypes.UpdateShippingOptionDTO,
sharedContext?: Context
): Promise<FulfillmentTypes.ShippingOptionDTO>
updateShippingOptions(
selector: FulfillmentTypes.FilterableShippingOptionProps,
data: FulfillmentTypes.UpdateShippingOptionDTO,
sharedContext?: Context
): Promise<FulfillmentTypes.ShippingOptionDTO[]>
@InjectManager("baseRepository_")
async updateShippingOptions(
data:
| FulfillmentTypes.UpdateShippingOptionDTO[]
| FulfillmentTypes.UpdateShippingOptionDTO,
idOrSelector: string | FulfillmentTypes.FilterableShippingOptionProps,
data: FulfillmentTypes.UpdateShippingOptionDTO,
@MedusaContext() sharedContext: Context = {}
): Promise<
FulfillmentTypes.ShippingOptionDTO[] | FulfillmentTypes.ShippingOptionDTO
> {
const normalizedInput: UpdateShippingOptionsInput[] = []
if (isString(idOrSelector)) {
normalizedInput.push({ id: idOrSelector, ...data })
} else {
const shippingOptions = await this.shippingOptionService_.list(
idOrSelector,
{},
sharedContext
)
shippingOptions.forEach((shippingOption) => {
normalizedInput.push({ id: shippingOption.id, ...data })
})
}
const updatedShippingOptions = await this.updateShippingOptions_(
data,
normalizedInput,
sharedContext
)
return await this.baseRepository_.serialize<
const serialized = await this.baseRepository_.serialize<
FulfillmentTypes.ShippingOptionDTO | FulfillmentTypes.ShippingOptionDTO[]
>(updatedShippingOptions, {
populate: true,
})
return isString(idOrSelector) ? serialized[0] : serialized
}
@InjectTransactionManager("baseRepository_")
async updateShippingOptions_(
data:
| FulfillmentTypes.UpdateShippingOptionDTO[]
| FulfillmentTypes.UpdateShippingOptionDTO,
data: UpdateShippingOptionsInput[] | UpdateShippingOptionsInput,
@MedusaContext() sharedContext: Context = {}
): Promise<TShippingOptionEntity | TShippingOptionEntity[]> {
const dataArray = Array.isArray(data) ? data : [data]
@@ -1182,6 +1199,82 @@ export default class FulfillmentModuleService<
: updatedShippingOptions[0]
}
async upsertShippingOptions(
data: FulfillmentTypes.UpsertShippingOptionDTO[],
sharedContext?: Context
): Promise<FulfillmentTypes.ShippingOptionDTO[]>
async upsertShippingOptions(
data: FulfillmentTypes.UpsertShippingOptionDTO,
sharedContext?: Context
): Promise<FulfillmentTypes.ShippingOptionDTO>
@InjectManager("baseRepository_")
async upsertShippingOptions(
data:
| FulfillmentTypes.UpsertShippingOptionDTO[]
| FulfillmentTypes.UpsertShippingOptionDTO,
@MedusaContext() sharedContext: Context = {}
): Promise<
FulfillmentTypes.ShippingOptionDTO[] | FulfillmentTypes.ShippingOptionDTO
> {
const upsertedShippingOptions = await this.upsertShippingOptions_(
data,
sharedContext
)
const allShippingOptions = await this.baseRepository_.serialize<
FulfillmentTypes.ShippingOptionDTO[] | FulfillmentTypes.ShippingOptionDTO
>(upsertedShippingOptions)
return Array.isArray(data) ? allShippingOptions : allShippingOptions[0]
}
@InjectTransactionManager("baseRepository_")
async upsertShippingOptions_(
data:
| FulfillmentTypes.UpsertShippingOptionDTO[]
| FulfillmentTypes.UpsertShippingOptionDTO,
@MedusaContext() sharedContext: Context = {}
): Promise<TShippingOptionEntity[] | TShippingOptionEntity> {
const input = Array.isArray(data) ? data : [data]
const forUpdate = input.filter(
(shippingOption): shippingOption is UpdateShippingOptionsInput =>
!!shippingOption.id
)
const forCreate = input.filter(
(
shippingOption
): shippingOption is FulfillmentTypes.CreateShippingOptionDTO =>
!shippingOption.id
)
let created: TShippingOptionEntity[] = []
let updated: TShippingOptionEntity[] = []
if (forCreate.length) {
const createdShippingOptions = await this.createShippingOptions_(
forCreate,
sharedContext
)
const toPush = Array.isArray(createdShippingOptions)
? createdShippingOptions
: [createdShippingOptions]
created.push(...toPush)
}
if (forUpdate.length) {
const updatedShippingOptions = await this.updateShippingOptions_(
forUpdate,
sharedContext
)
const toPush = Array.isArray(updatedShippingOptions)
? updatedShippingOptions
: [updatedShippingOptions]
updated.push(...toPush)
}
return [...created, ...updated]
}
updateShippingProfiles(
data: FulfillmentTypes.UpdateShippingProfileDTO[],
sharedContext?: Context
@@ -1415,7 +1508,7 @@ export default class FulfillmentModuleService<
protected static validateMissingShippingOptions_(
shippingOptions: ShippingOption[],
shippingOptionsData: FulfillmentTypes.UpdateShippingOptionDTO[]
shippingOptionsData: UpdateShippingOptionsInput[]
) {
const missingShippingOptionIds = arrayDifference(
shippingOptionsData.map((s) => s.id),

View File

@@ -0,0 +1,6 @@
import { FulfillmentTypes } from "@medusajs/types"
export type UpdateShippingOptionsInput = Required<
Pick<FulfillmentTypes.UpdateShippingOptionDTO, "id">
> &
FulfillmentTypes.UpdateShippingOptionDTO

View File

@@ -0,0 +1,44 @@
import {
ContainerRegistrationKeys,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import { AdminShippingOptionRetrieveResponse } from "@medusajs/types"
import { AdminUpdateShippingOptionType } from "../validators"
import { updateShippingOptionsWorkflow } from "@medusajs/core-flows"
import {
AuthenticatedMedusaRequest,
MedusaResponse,
} from "../../../../types/routing"
export const POST = async (
req: AuthenticatedMedusaRequest<AdminUpdateShippingOptionType>,
res: MedusaResponse<AdminShippingOptionRetrieveResponse>
) => {
const shippingOptionPayload = req.validatedBody
const workflow = updateShippingOptionsWorkflow(req.scope)
const { result, errors } = await workflow.run({
input: [shippingOptionPayload],
throwOnError: false,
})
if (Array.isArray(errors) && errors[0]) {
throw errors[0].error
}
const shippingOptionId = result[0].id
const query = remoteQueryObjectFromString({
entryPoint: "shipping_options",
variables: {
id: shippingOptionId,
},
fields: req.remoteQueryConfig.fields,
})
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const [shippingOption] = await remoteQuery(query)
res.status(200).json({ shipping_option: shippingOption })
}

View File

@@ -5,6 +5,7 @@ import {
AdminGetShippingOptionParams,
AdminShippingOptionRulesBatchAdd,
AdminShippingOptionRulesBatchRemove,
AdminUpdateShippingOption,
} from "./validators"
import { retrieveTransformQueryConfig } from "./query-config"
import { validateAndTransformBody } from "../../utils/validate-body"
@@ -28,6 +29,18 @@ export const adminShippingOptionRoutesMiddlewares: MiddlewareRoute[] = [
],
},
{
method: ["POST"],
matcher: "/admin/shipping-options/:id",
middlewares: [
validateAndTransformQuery(
AdminGetShippingOptionParams,
retrieveTransformQueryConfig
),
validateAndTransformBody(AdminUpdateShippingOption),
],
},
{
method: ["POST"],
matcher: "/admin/shipping-options/:id/rules/batch/add",

View File

@@ -43,7 +43,7 @@ export type AdminShippingOptionRulesBatchRemoveType = z.infer<
* SHIPPING OPTIONS
*/
export const AdminCreateShippingOptionType = z
export const AdminCreateShippingOptionTypeObject = z
.object({
label: z.string(),
description: z.string(),
@@ -74,7 +74,7 @@ export const AdminCreateShippingOption = z
data: z.record(z.unknown()).optional(),
price_type: z.nativeEnum(ShippingOptionPriceTypeEnum),
provider_id: z.string(),
type: AdminCreateShippingOptionType,
type: AdminCreateShippingOptionTypeObject,
prices: AdminCreateShippingOptionPriceWithCurrency.or(
AdminCreateShippingOptionPriceWithRegion
).array(),
@@ -85,3 +85,18 @@ export const AdminCreateShippingOption = z
export type AdminCreateShippingOptionType = z.infer<
typeof AdminCreateShippingOption
>
export const AdminUpdateShippingOption = z
.object({
id: z.string(),
name: z.string().optional(),
data: z.record(z.unknown()).optional(),
price_type: z.nativeEnum(ShippingOptionPriceTypeEnum).optional(),
provider_id: z.string().optional(),
type: AdminCreateShippingOptionTypeObject.optional(),
})
.strict()
export type AdminUpdateShippingOptionType = z.infer<
typeof AdminUpdateShippingOption
>

View File

@@ -14,7 +14,7 @@ export interface CreateShippingOptionDTO {
}
export interface UpdateShippingOptionDTO {
id: string
id?: string
name?: string
price_type?: ShippingOptionPriceType
service_zone_id?: string
@@ -27,3 +27,7 @@ export interface UpdateShippingOptionDTO {
| { id: string }
)[]
}
export interface UpsertShippingOptionDTO extends UpdateShippingOptionDTO {
}

View File

@@ -35,6 +35,7 @@ import {
UpdateShippingOptionRuleDTO,
UpdateShippingProfileDTO,
UpsertServiceZoneDTO,
UpsertShippingOptionDTO,
} from "./mutations"
import { CreateFulfillmentDTO } from "./mutations/fulfillment"
import { CreateShippingProfileDTO } from "./mutations/shipping-profile"
@@ -376,19 +377,35 @@ export interface IFulfillmentModuleService extends IModuleService {
data: CreateShippingOptionDTO,
sharedContext?: Context
): Promise<ShippingOptionDTO>
/**
* Update a shipping option
* @param id
* @param data
* @param sharedContext
*/
updateShippingOptions(
data: UpdateShippingOptionDTO[],
sharedContext?: Context
): Promise<ShippingOptionDTO[]>
updateShippingOptions(
id: string,
data: UpdateShippingOptionDTO,
sharedContext?: Context
): Promise<ShippingOptionDTO>
updateShippingOptions(
selector: FilterableShippingOptionProps,
data: UpdateShippingOptionDTO,
sharedContext?: Context
): Promise<ShippingOptionDTO[]>
/**
* Upsert a shipping option
*/
upsertShippingOptions(
data: UpsertShippingOptionDTO,
sharedContext?: Context
): Promise<ShippingOptionDTO>
upsertShippingOptions(
data: UpsertShippingOptionDTO[],
sharedContext?: Context
): Promise<ShippingOptionDTO[]>
/**
* Delete a shippingOption
* @param ids

View File

@@ -1,2 +1,3 @@
export * from "./create-shipping-options"
export * from "./update-shipping-options"
export * from "./service-zones"

View File

@@ -0,0 +1,36 @@
import { ShippingOptionPriceType } from "../../fulfillment"
import { RuleOperatorType } from "../../common"
export interface UpdateShippingOptionsWorkflowInput {
id: string
name?: string
service_zone_id?: string
shipping_profile_id?: string
data?: Record<string, unknown>
price_type?: ShippingOptionPriceType
provider_id?: string
type?: {
label: string
description: string
code: string
}
prices?: (
| {
currency_code: string
amount: number
}
| {
region_id: string
amount: number
}
)[]
rules?: {
attribute: string
operator: RuleOperatorType
value: string | string[]
}[]
}
export type UpdateShippingOptionsWorkflowOutput = {
id: string
}[]