diff --git a/.changeset/rude-garlics-hang.md b/.changeset/rude-garlics-hang.md new file mode 100644 index 0000000000000..9105da8814ecd --- /dev/null +++ b/.changeset/rude-garlics-hang.md @@ -0,0 +1,10 @@ +--- +"@medusajs/medusa": patch +"@medusajs/core-flows": patch +"@medusajs/link-modules": patch +"medusa-test-utils": patch +"@medusajs/orchestration": patch +"@medusajs/types": patch +--- + +feat: Create shipping options API diff --git a/integration-tests/modules/__tests__/fulfillment/workflows/create-shipping-options.ts b/integration-tests/modules/__tests__/fulfillment/workflows/create-shipping-options.ts new file mode 100644 index 0000000000000..d38d1a78b3fdd --- /dev/null +++ b/integration-tests/modules/__tests__/fulfillment/workflows/create-shipping-options.ts @@ -0,0 +1,270 @@ +import { ModuleRegistrationName } from "@medusajs/modules-sdk" +import { + FulfillmentSetDTO, + FulfillmentWorkflow, + IFulfillmentModuleService, + IRegionModuleService, + ServiceZoneDTO, + ShippingProfileDTO, +} from "@medusajs/types" +import { medusaIntegrationTestRunner } from "medusa-test-utils/dist" +import { createShippingOptionsWorkflow } 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 create shipping options and prices", 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 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: 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, + }), + 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 and prices", 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 workflow = createShippingOptionsWorkflow(container) + + workflow.addAction( + "throw", + { + invoke: async function failStep() { + throw new Error(`Failed to create shipping options`) + }, + }, + { + noCompensation: true, + } + ) + + const { errors } = await workflow.run({ + input: [shippingOptionData], + throwOnError: false, + }) + + expect(errors).toHaveLength(1) + expect(errors[0].error.message).toEqual( + `Failed to create shipping options` + ) + + const remoteQuery = container.resolve( + ContainerRegistrationKeys.REMOTE_QUERY + ) + + const remoteQueryObject = remoteQueryObjectFromString({ + entryPoint: "shipping_option", + fields: ["id"], + }) + + const createdShippingOptions = await remoteQuery(remoteQueryObject) + + expect(createdShippingOptions).toHaveLength(0) + + const priceSetsRemoteQueryObject = remoteQueryObjectFromString({ + entryPoint: "price_sets", + fields: ["id"], + }) + + const createdPriceSets = await remoteQuery(priceSetsRemoteQueryObject) + + expect(createdPriceSets).toHaveLength(0) + }) + }) + }, +}) diff --git a/integration-tests/modules/__tests__/link-modules/shipping-option-price-set.spec.ts b/integration-tests/modules/__tests__/link-modules/shipping-option-price-set.spec.ts index f868bcb174449..c3a5223a3437a 100644 --- a/integration-tests/modules/__tests__/link-modules/shipping-option-price-set.spec.ts +++ b/integration-tests/modules/__tests__/link-modules/shipping-option-price-set.spec.ts @@ -88,7 +88,7 @@ medusaIntegrationTestRunner({ const link = await remoteQuery({ shipping_option: { fields: ["id"], - price: { + price_set_link: { fields: ["id", "price_set_id", "shipping_option_id"], }, }, @@ -99,7 +99,7 @@ medusaIntegrationTestRunner({ expect.arrayContaining([ expect.objectContaining({ id: shippingOption.id, - price: expect.objectContaining({ + price_set_link: expect.objectContaining({ price_set_id: priceSet.id, shipping_option_id: shippingOption.id, }), diff --git a/integration-tests/modules/medusa-config.js b/integration-tests/modules/medusa-config.js index 21391f9801550..39f6c25d0cef4 100644 --- a/integration-tests/modules/medusa-config.js +++ b/integration-tests/modules/medusa-config.js @@ -21,6 +21,15 @@ const customPaymentProvider = { }, } +const customFulfillmentProvider = { + resolve: "@medusajs/fulfillment-manual", + options: { + config: { + "test-provider": {}, + }, + }, +} + module.exports = { plugins: [], projectConfig: { @@ -91,16 +100,7 @@ module.exports = { [Modules.FULFILLMENT]: { /** @type {import('@medusajs/fulfillment').FulfillmentModuleOptions} */ options: { - providers: [ - { - resolve: "@medusajs/fulfillment-manual", - options: { - config: { - "test-provider": {}, - }, - }, - }, - ], + providers: [customFulfillmentProvider], }, }, }, diff --git a/integration-tests/modules/package.json b/integration-tests/modules/package.json index 4a05d23f1342c..f4d46fe22fffc 100644 --- a/integration-tests/modules/package.json +++ b/integration-tests/modules/package.json @@ -15,6 +15,8 @@ "@medusajs/cache-inmemory": "workspace:*", "@medusajs/customer": "workspace:^", "@medusajs/event-bus-local": "workspace:*", + "@medusajs/fulfillment": "workspace:^", + "@medusajs/fulfillment-manual": "workspace:^", "@medusajs/inventory-next": "workspace:^", "@medusajs/medusa": "workspace:*", "@medusajs/modules-sdk": "workspace:^", diff --git a/packages/core-flows/src/fulfillment/steps/add-shipping-options-prices.ts b/packages/core-flows/src/fulfillment/steps/add-shipping-options-prices.ts new file mode 100644 index 0000000000000..0bb3ba95bb28f --- /dev/null +++ b/packages/core-flows/src/fulfillment/steps/add-shipping-options-prices.ts @@ -0,0 +1,124 @@ +import { createStep, StepResponse } from "@medusajs/workflows-sdk" +import { + CreatePriceSetDTO, + IPricingModuleService, + IRegionModuleService, +} from "@medusajs/types" +import { ModuleRegistrationName } from "@medusajs/modules-sdk" + +interface PriceCurrencyCode { + currency_code: string + amount: number +} + +interface PriceRegionId { + region_id: string + amount: number +} + +type StepInput = { + id: string + prices: (PriceCurrencyCode | PriceRegionId)[] +}[] + +function buildPriceSet( + prices: StepInput[0]["prices"], + regionToCurrencyMap: Map +): CreatePriceSetDTO { + const rules: CreatePriceSetDTO["rules"] = [] + + const shippingOptionPrices = prices.map((price) => { + if ("currency_code" in price) { + return { + currency_code: price.currency_code, + amount: price.amount, + } + } + + rules.push({ + rule_attribute: "region_id", + }) + + return { + currency_code: regionToCurrencyMap.get(price.region_id)!, + amount: price.amount, + rules: { + region_id: price.region_id, + }, + } + }) + + return { rules, prices: shippingOptionPrices } +} + +export const createShippingOptionsPriceSetsStepId = + "add-shipping-options-prices-step" +export const createShippingOptionsPriceSetsStep = createStep( + createShippingOptionsPriceSetsStepId, + async (data: StepInput, { container }) => { + if (!data?.length) { + return new StepResponse([], []) + } + + const regionIds = data + .map((input) => input.prices) + .flat() + .filter((price): price is PriceRegionId => { + return "region_id" in price + }) + .map((price) => price.region_id) + + let regionToCurrencyMap: Map = new Map() + + if (regionIds.length) { + const regionService = container.resolve( + ModuleRegistrationName.REGION + ) + const regions = await regionService.list( + { + id: [...new Set(regionIds)], + }, + { + select: ["id", "currency_code"], + } + ) + + regionToCurrencyMap = new Map( + regions.map((region) => [region.id, region.currency_code]) + ) + } + + const priceSetsData = data.map((input) => + buildPriceSet(input.prices, regionToCurrencyMap) + ) + + const pricingService = container.resolve( + ModuleRegistrationName.PRICING + ) + + const priceSets = await pricingService.create(priceSetsData) + + const shippingOptionPriceSetLinData = data.map((input, index) => { + return { + id: input.id, + priceSetId: priceSets[index].id, + } + }) + + return new StepResponse( + shippingOptionPriceSetLinData, + priceSets.map((priceSet) => priceSet.id) + ) + }, + async (priceSetIds, { container }) => { + if (!priceSetIds?.length) { + return + } + + const pricingService = container.resolve( + ModuleRegistrationName.PRICING + ) + + await pricingService.delete(priceSetIds) + } +) diff --git a/packages/core-flows/src/fulfillment/steps/create-shipping-options.ts b/packages/core-flows/src/fulfillment/steps/create-shipping-options.ts new file mode 100644 index 0000000000000..c2a05f4b4938a --- /dev/null +++ b/packages/core-flows/src/fulfillment/steps/create-shipping-options.ts @@ -0,0 +1,43 @@ +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( + 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( + ModuleRegistrationName.FULFILLMENT + ) + + await fulfillmentService.deleteShippingOptions(shippingOptionIds) + } +) diff --git a/packages/core-flows/src/fulfillment/steps/index.ts b/packages/core-flows/src/fulfillment/steps/index.ts index e00c0be1ae583..2a07f2c99824e 100644 --- a/packages/core-flows/src/fulfillment/steps/index.ts +++ b/packages/core-flows/src/fulfillment/steps/index.ts @@ -1,3 +1,5 @@ export * from "./add-rules-to-fulfillment-shipping-option" export * from "./create-fulfillment-set" export * from "./remove-rules-from-fulfillment-shipping-option" +export * from "./create-shipping-options" +export * from "./add-shipping-options-prices" diff --git a/packages/core-flows/src/fulfillment/steps/set-shipping-options-price-sets.ts b/packages/core-flows/src/fulfillment/steps/set-shipping-options-price-sets.ts new file mode 100644 index 0000000000000..8c8f90888b5d4 --- /dev/null +++ b/packages/core-flows/src/fulfillment/steps/set-shipping-options-price-sets.ts @@ -0,0 +1,172 @@ +import { RemoteLink } from "@medusajs/modules-sdk" +import { RemoteQueryFunction } from "@medusajs/types" +import { createStep, StepResponse } from "@medusajs/workflows-sdk" +import { + ContainerRegistrationKeys, + LINKS, + Modules, + promiseAll, + remoteQueryObjectFromString, +} from "@medusajs/utils" + +type SetShippingOptionsPriceSetsStepInput = { + id: string + price_sets?: string[] +}[] + +interface FilteredSetShippingOptionsPriceSetsStepInput { + id: string + price_sets: string[] +} + +type LinkItems = { + [Modules.FULFILLMENT]: { shipping_option_id: string } + [Modules.PRICING]: { price_set_id: string } +}[] + +async function getCurrentShippingOptionPriceSetsLinks( + shippingOptionIds: string[], + { remoteQuery }: { remoteQuery: RemoteQueryFunction } +): Promise { + const query = remoteQueryObjectFromString({ + service: LINKS.ShippingOptionPriceSet, + variables: { + filters: { shipping_option_id: shippingOptionIds }, + take: null, + }, + fields: ["shipping_option_id", "price_set_id"], + }) + + const shippingOptionPriceSetLinks = (await remoteQuery(query)) as { + shipping_option_id: string + price_set_id: string + }[] + + return shippingOptionPriceSetLinks.map((shippingOption) => { + return { + [Modules.FULFILLMENT]: { + shipping_option_id: shippingOption.shipping_option_id, + }, + [Modules.PRICING]: { + price_set_id: shippingOption.price_set_id, + }, + } + }) +} + +export const setShippingOptionsPriceSetsStepId = + "set-shipping-options-price-sets-step" +export const setShippingOptionsPriceSetsStep = createStep( + setShippingOptionsPriceSetsStepId, + async (data: SetShippingOptionsPriceSetsStepInput, { container }) => { + if (!data.length) { + return + } + + const dataInputToProcess = data.filter((inputData) => { + return inputData.price_sets?.length + }) as FilteredSetShippingOptionsPriceSetsStepInput[] + + if (!dataInputToProcess.length) { + return + } + + const remoteLink = container.resolve( + ContainerRegistrationKeys.REMOTE_LINK + ) + const remoteQuery = container.resolve( + ContainerRegistrationKeys.REMOTE_QUERY + ) + + const shippingOptionIds = dataInputToProcess.map( + (inputData) => inputData.id + ) + const currentExistingLinks = await getCurrentShippingOptionPriceSetsLinks( + shippingOptionIds, + { remoteQuery } + ) + + const linksToRemove: LinkItems = currentExistingLinks + .filter((existingLink) => { + return !dataInputToProcess.some((input) => { + return ( + input.id === existingLink[Modules.FULFILLMENT].shipping_option_id && + input.price_sets.includes( + existingLink[Modules.PRICING].price_set_id + ) + ) + }) + }) + .map((link) => { + return { + [Modules.FULFILLMENT]: { + shipping_option_id: link[Modules.FULFILLMENT].shipping_option_id, + }, + [Modules.PRICING]: { + price_set_id: link[Modules.PRICING].price_set_id, + }, + } + }) + + const linksToCreate = dataInputToProcess + .map((inputData) => { + return inputData.price_sets.map((priceSet) => { + const alreadyExists = currentExistingLinks.some((link) => { + return ( + link[Modules.FULFILLMENT].shipping_option_id === inputData.id && + link[Modules.PRICING].price_set_id === priceSet + ) + }) + + if (alreadyExists) { + return + } + + return { + [Modules.FULFILLMENT]: { shipping_option_id: inputData.id }, + [Modules.PRICING]: { price_set_id: priceSet }, + } + }) + }) + .flat() + .filter((d): d is LinkItems[0] => !!d) + + const promises: Promise[] = [] + + if (linksToRemove.length) { + promises.push(remoteLink.dismiss(linksToRemove)) + } + + if (linksToCreate.length) { + promises.push(remoteLink.create(linksToCreate)) + } + + await promiseAll(promises) + + return new StepResponse(void 0, { + linksToCreate: linksToRemove, + linksToRemove: linksToCreate, + }) + }, + async (rollbackData, { container }) => { + if (!rollbackData) { + return + } + + const remoteLink = container.resolve( + ContainerRegistrationKeys.REMOTE_LINK + ) + + const promises: Promise[] = [] + + if (rollbackData.linksToRemove.length) { + promises.push(remoteLink.dismiss(rollbackData.linksToRemove)) + } + + if (rollbackData.linksToCreate.length) { + promises.push(remoteLink.create(rollbackData.linksToCreate)) + } + + await promiseAll(promises) + } +) diff --git a/packages/core-flows/src/fulfillment/workflows/create-shipping-options.ts b/packages/core-flows/src/fulfillment/workflows/create-shipping-options.ts new file mode 100644 index 0000000000000..ffa143f104940 --- /dev/null +++ b/packages/core-flows/src/fulfillment/workflows/create-shipping-options.ts @@ -0,0 +1,97 @@ +import { CreateRuleTypeDTO, FulfillmentWorkflow } from "@medusajs/types" +import { + createWorkflow, + transform, + WorkflowData, +} from "@medusajs/workflows-sdk" +import { + createShippingOptionsPriceSetsStep, + createShippingOptionsStep, +} from "../steps" +import { setShippingOptionsPriceSetsStep } from "../steps/set-shipping-options-price-sets" +import { createPricingRuleTypesStep } from "../../pricing" + +export const createShippingOptionsWorkflowId = + "create-shipping-options-workflow" +export const createShippingOptionsWorkflow = createWorkflow( + createShippingOptionsWorkflowId, + ( + input: WorkflowData< + FulfillmentWorkflow.CreateShippingOptionsWorkflowInput[] + > + ): WorkflowData => { + const data = transform(input, (data) => { + const shippingOptionsIndexToPrices = data.map((option, index) => { + return { + shipping_option_index: index, + prices: option.prices, + } + }) + + return { + shippingOptions: data, + shippingOptionsIndexToPrices, + } + }) + + const createdShippingOptions = createShippingOptionsStep( + data.shippingOptions + ) + + const normalizedShippingOptionsPrices = transform( + { + shippingOptions: createdShippingOptions, + shippingOptionsIndexToPrices: data.shippingOptionsIndexToPrices, + }, + (data) => { + const ruleTypes = new Set() + 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 CreateRuleTypeDTO[], + } + } + ) + + createPricingRuleTypesStep(normalizedShippingOptionsPrices.ruleTypes) + + const shippingOptionsPriceSetsLinkData = createShippingOptionsPriceSetsStep( + normalizedShippingOptionsPrices.shippingOptionsPrices + ) + + const normalizedLinkData = transform( + { + shippingOptionsPriceSetsLinkData, + }, + (data) => { + return data.shippingOptionsPriceSetsLinkData.map((item) => { + return { + id: item.id, + price_sets: [item.priceSetId], + } + }) + } + ) + + setShippingOptionsPriceSetsStep(normalizedLinkData) + + return createdShippingOptions + } +) diff --git a/packages/core-flows/src/fulfillment/workflows/index.ts b/packages/core-flows/src/fulfillment/workflows/index.ts index 957da3d401131..c6cf4d5b8d5e6 100644 --- a/packages/core-flows/src/fulfillment/workflows/index.ts +++ b/packages/core-flows/src/fulfillment/workflows/index.ts @@ -1,2 +1,3 @@ export * from "./add-rules-to-fulfillment-shipping-option" export * from "./remove-rules-from-fulfillment-shipping-option" +export * from "./create-shipping-options" diff --git a/packages/core-flows/src/pricing/steps/create-pricing-rule-types.ts b/packages/core-flows/src/pricing/steps/create-pricing-rule-types.ts index f3b48c80c22d7..c14f7cbf2f8ee 100644 --- a/packages/core-flows/src/pricing/steps/create-pricing-rule-types.ts +++ b/packages/core-flows/src/pricing/steps/create-pricing-rule-types.ts @@ -1,16 +1,32 @@ import { ModuleRegistrationName } from "@medusajs/modules-sdk" import { CreateRuleTypeDTO, IPricingModuleService } from "@medusajs/types" -import { StepResponse, createStep } from "@medusajs/workflows-sdk" +import { createStep, StepResponse } from "@medusajs/workflows-sdk" export const createPricingRuleTypesStepId = "create-pricing-rule-types" export const createPricingRuleTypesStep = createStep( createPricingRuleTypesStepId, async (data: CreateRuleTypeDTO[], { container }) => { + if (!data?.length) { + return + } + const pricingModule = container.resolve( ModuleRegistrationName.PRICING ) - const ruleTypes = await pricingModule.createRuleTypes(data) + const existingRuleTypes = await pricingModule.listRuleTypes({ + rule_attribute: data.map((d) => d.rule_attribute), + }) + + const existingRuleTypeAttributes = new Set( + existingRuleTypes.map((ruleType) => ruleType.rule_attribute) + ) + + const ruleTypesToCreate = data.filter( + (dataItem) => !existingRuleTypeAttributes.has(dataItem.rule_attribute) + ) + + const ruleTypes = await pricingModule.createRuleTypes(ruleTypesToCreate) return new StepResponse( ruleTypes, diff --git a/packages/link-modules/src/definitions/shipping-option-price-set.ts b/packages/link-modules/src/definitions/shipping-option-price-set.ts index 90d3d56193bac..b586d9a0b0ec8 100644 --- a/packages/link-modules/src/definitions/shipping-option-price-set.ts +++ b/packages/link-modules/src/definitions/shipping-option-price-set.ts @@ -39,11 +39,17 @@ export const ShippingOptionPriceSet: ModuleJoinerConfig = { extends: [ { serviceName: Modules.FULFILLMENT, + fieldAlias: { + prices: { + path: "price_set_link.price_set.prices", + isList: true, + }, + }, relationship: { serviceName: LINKS.ShippingOptionPriceSet, primaryKey: "shipping_option_id", foreignKey: "id", - alias: "price", + alias: "price_set_link", }, }, { diff --git a/packages/medusa-test-utils/src/medusa-test-runner.ts b/packages/medusa-test-utils/src/medusa-test-runner.ts index 0d4aa3223bdcc..bb8a4c5c8c6fb 100644 --- a/packages/medusa-test-utils/src/medusa-test-runner.ts +++ b/packages/medusa-test-utils/src/medusa-test-runner.ts @@ -89,7 +89,7 @@ export function medusaIntegrationTestRunner({ testSuite, }: { moduleName?: string - env?: Record + env?: Record dbName?: string schema?: string debug?: boolean diff --git a/packages/orchestration/src/joiner/remote-joiner.ts b/packages/orchestration/src/joiner/remote-joiner.ts index 178644bf693d2..39f6ca2f9590c 100644 --- a/packages/orchestration/src/joiner/remote-joiner.ts +++ b/packages/orchestration/src/joiner/remote-joiner.ts @@ -841,6 +841,7 @@ export class RemoteJoiner { const alias = fieldAlias[property] as any const path = isString(alias) ? alias : alias.path + const fieldAliasIsList = isString(alias) ? false : !!alias.isList const fullPath = [...currentPath.concat(path.split("."))] if (aliasRealPathMap.has(aliasPath)) { @@ -868,7 +869,7 @@ export class RemoteJoiner { location: [...currentPath], property, path: fullPath, - isList: !!serviceConfig.relationships?.find( + isList: fieldAliasIsList || !!serviceConfig.relationships?.find( (relationship) => relationship.alias === parentFieldAlias )?.isList, }) diff --git a/packages/types/src/modules-sdk/index.ts b/packages/types/src/modules-sdk/index.ts index af5d1fab07e51..17e1e1b461d48 100644 --- a/packages/types/src/modules-sdk/index.ts +++ b/packages/types/src/modules-sdk/index.ts @@ -147,7 +147,8 @@ export type ModuleJoinerConfig = Omit< | string | { path: string - forwardArgumentsOnPath: string[] + forwardArgumentsOnPath?: string[] + isList?: boolean } > // alias for deeper nested relationships (e.g. { 'price': 'prices.calculated_price_set.amount' }) relationship: ModuleJoinerRelationship diff --git a/packages/types/src/workflow/fulfillment/create-shipping-options.ts b/packages/types/src/workflow/fulfillment/create-shipping-options.ts new file mode 100644 index 0000000000000..8afb89d9c213e --- /dev/null +++ b/packages/types/src/workflow/fulfillment/create-shipping-options.ts @@ -0,0 +1,35 @@ +import { ShippingOptionPriceType } from "../../fulfillment" +import { RuleOperatorType } from "../../common" + +export interface CreateShippingOptionsWorkflowInput { + name: string + service_zone_id: string + shipping_profile_id: string + data?: Record + 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 CreateShippingOptionsWorkflowOutput = { + id: string +}[] diff --git a/packages/types/src/workflow/fulfillment/index.ts b/packages/types/src/workflow/fulfillment/index.ts new file mode 100644 index 0000000000000..ba97ae27d7899 --- /dev/null +++ b/packages/types/src/workflow/fulfillment/index.ts @@ -0,0 +1 @@ +export * from "./create-shipping-options" diff --git a/packages/types/src/workflow/index.ts b/packages/types/src/workflow/index.ts index a9550b43261cc..560860cfecc75 100644 --- a/packages/types/src/workflow/index.ts +++ b/packages/types/src/workflow/index.ts @@ -6,3 +6,4 @@ export * as PriceListWorkflow from "./price-list" export * as UserWorkflow from "./user" export * as RegionWorkflow from "./region" export * as InviteWorkflow from "./invite" +export * as FulfillmentWorkflow from "./fulfillment" diff --git a/yarn.lock b/yarn.lock index 0654779957e97..329147b488b17 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8274,7 +8274,7 @@ __metadata: languageName: unknown linkType: soft -"@medusajs/fulfillment-manual@workspace:*, @medusajs/fulfillment-manual@workspace:packages/fulfillment-manual": +"@medusajs/fulfillment-manual@workspace:*, @medusajs/fulfillment-manual@workspace:^, @medusajs/fulfillment-manual@workspace:packages/fulfillment-manual": version: 0.0.0-use.local resolution: "@medusajs/fulfillment-manual@workspace:packages/fulfillment-manual" dependencies: @@ -8288,7 +8288,7 @@ __metadata: languageName: unknown linkType: soft -"@medusajs/fulfillment@workspace:packages/fulfillment": +"@medusajs/fulfillment@workspace:^, @medusajs/fulfillment@workspace:packages/fulfillment": version: 0.0.0-use.local resolution: "@medusajs/fulfillment@workspace:packages/fulfillment" dependencies: @@ -32134,6 +32134,8 @@ __metadata: "@medusajs/cache-inmemory": "workspace:*" "@medusajs/customer": "workspace:^" "@medusajs/event-bus-local": "workspace:*" + "@medusajs/fulfillment": "workspace:^" + "@medusajs/fulfillment-manual": "workspace:^" "@medusajs/inventory-next": "workspace:^" "@medusajs/medusa": "workspace:*" "@medusajs/modules-sdk": "workspace:^"