feat: Create shipping options workflow (#6962)
**What** - Create main workflow - Update create pricing rule types step to be idempotent - update remote joiner to support granular isList confguration on field aliases - Add full workflow integration tests
This commit is contained in:
committed by
GitHub
parent
5e30b8cce6
commit
65794f4bb5
10
.changeset/rude-garlics-hang.md
Normal file
10
.changeset/rude-garlics-hang.md
Normal file
@@ -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
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
},
|
||||
})
|
||||
@@ -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,
|
||||
}),
|
||||
|
||||
@@ -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],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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:^",
|
||||
|
||||
@@ -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<string, string>
|
||||
): 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<string, string> = new Map()
|
||||
|
||||
if (regionIds.length) {
|
||||
const regionService = container.resolve<IRegionModuleService>(
|
||||
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<IPricingModuleService>(
|
||||
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<IPricingModuleService>(
|
||||
ModuleRegistrationName.PRICING
|
||||
)
|
||||
|
||||
await pricingService.delete(priceSetIds)
|
||||
}
|
||||
)
|
||||
@@ -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<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)
|
||||
}
|
||||
)
|
||||
@@ -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"
|
||||
|
||||
@@ -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<LinkItems> {
|
||||
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<RemoteLink>(
|
||||
ContainerRegistrationKeys.REMOTE_LINK
|
||||
)
|
||||
const remoteQuery = container.resolve<RemoteQueryFunction>(
|
||||
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<unknown[]>[] = []
|
||||
|
||||
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<RemoteLink>(
|
||||
ContainerRegistrationKeys.REMOTE_LINK
|
||||
)
|
||||
|
||||
const promises: Promise<unknown[]>[] = []
|
||||
|
||||
if (rollbackData.linksToRemove.length) {
|
||||
promises.push(remoteLink.dismiss(rollbackData.linksToRemove))
|
||||
}
|
||||
|
||||
if (rollbackData.linksToCreate.length) {
|
||||
promises.push(remoteLink.create(rollbackData.linksToCreate))
|
||||
}
|
||||
|
||||
await promiseAll(promises)
|
||||
}
|
||||
)
|
||||
@@ -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<FulfillmentWorkflow.CreateShippingOptionsWorkflowOutput> => {
|
||||
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<CreateRuleTypeDTO>()
|
||||
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
|
||||
}
|
||||
)
|
||||
@@ -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"
|
||||
|
||||
@@ -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<IPricingModuleService>(
|
||||
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,
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -89,7 +89,7 @@ export function medusaIntegrationTestRunner({
|
||||
testSuite,
|
||||
}: {
|
||||
moduleName?: string
|
||||
env?: Record<string, string | any>
|
||||
env?: Record<string, any>
|
||||
dbName?: string
|
||||
schema?: string
|
||||
debug?: boolean
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<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 CreateShippingOptionsWorkflowOutput = {
|
||||
id: string
|
||||
}[]
|
||||
1
packages/types/src/workflow/fulfillment/index.ts
Normal file
1
packages/types/src/workflow/fulfillment/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./create-shipping-options"
|
||||
@@ -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"
|
||||
|
||||
@@ -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:^"
|
||||
|
||||
Reference in New Issue
Block a user