feat(core-flows, types): calculated shipping in RMA flows (#11533)

* wip: calculated SO pricing in RMA flows

* fix: types

* chore: small refactor

* feat: caluclated shipping in return flow

* fix: module integrations

* fix: array containing

* feat: refresh shipping on update item quantity

* rm: log

* rm: log2

* feat: update interface, remove flag

* fix: revert change on OE for now

* fix: import

* feat: refactor flwos, cleanup cacluation cotext data model, wip exchanges

* feat: refreshing inbound/outbound shipping on items change

* feat: refresh exchange shipping on return item add, test

* feat: refresh shipping on exchange/return item remove

* fix: check optional

* feat: test recalculation on quantity update

* feat: calculated shipping on claims

* fix: comment

* wip: address comments

* fix: more remote query, fix build

* refactor: claim refresh workflow

* fix: remove throw option

* fix: deconstruct param

---------

Co-authored-by: Oli Juhl <59018053+olivermrbl@users.noreply.github.com>
This commit is contained in:
Frane Polić
2025-03-24 07:07:47 +01:00
committed by GitHub
parent 053326950d
commit de8c034d1b
33 changed files with 1997 additions and 219 deletions

View File

@@ -33,9 +33,15 @@ medusaIntegrationTestRunner({
)
expect(response.status).toEqual(200)
expect(response.data.fulfillment_providers).toEqual([
{ id: "manual_test-provider", is_enabled: true },
])
expect(response.data.fulfillment_providers).toEqual(
expect.arrayContaining([
{ id: "manual_test-provider", is_enabled: true },
{
id: "manual-calculated_test-provider-calculated",
is_enabled: true,
},
])
)
})
})
},

View File

@@ -13,14 +13,22 @@ import {
} from "@medusajs/utils"
const providerId = "manual_test-provider"
const providerIdCalculated = "manual-calculated_test-provider-calculated"
export async function prepareDataFixtures({ container }) {
const fulfillmentService = container.resolve(Modules.FULFILLMENT)
const salesChannelService = container.resolve(Modules.SALES_CHANNEL)
const stockLocationModule: IStockLocationService = container.resolve(
Modules.STOCK_LOCATION
)
const pricingModule = container.resolve(Modules.PRICING)
const productModule = container.resolve(Modules.PRODUCT)
const inventoryModule = container.resolve(Modules.INVENTORY)
const customerService = container.resolve(Modules.CUSTOMER)
const customer = await customerService.createCustomers({
email: "foo@bar.com",
})
const shippingProfile = await fulfillmentService.createShippingProfiles({
name: "test",
@@ -71,6 +79,18 @@ export async function prepareDataFixtures({ container }) {
},
})
const priceSets = await pricingModule.createPriceSets([
{
prices: [
{
amount: 10,
region_id: region.id,
currency_code: "usd",
},
],
},
])
const [product] = await productModule.createProducts([
{
title: "Test product",
@@ -91,7 +111,7 @@ export async function prepareDataFixtures({ container }) {
{
inventory_item_id: inventoryItem.id,
location_id: location.id,
stocked_quantity: 2,
stocked_quantity: 10,
reserved_quantity: 0,
},
])
@@ -123,6 +143,14 @@ export async function prepareDataFixtures({ container }) {
inventory_item_id: inventoryItem.id,
},
},
{
[Modules.PRODUCT]: {
variant_id: product.variants[0].id,
},
[Modules.PRICING]: {
price_set_id: priceSets[0].id,
},
},
])
await remoteLink.create([
@@ -131,7 +159,16 @@ export async function prepareDataFixtures({ container }) {
stock_location_id: location.id,
},
[Modules.FULFILLMENT]: {
fulfillment_provider_id: "manual_test-provider",
fulfillment_provider_id: providerId,
},
},
{
[Modules.STOCK_LOCATION]: {
stock_location_id: location.id,
},
[Modules.FULFILLMENT]: {
fulfillment_provider_id: providerIdCalculated,
},
},
])
@@ -160,14 +197,29 @@ export async function prepareDataFixtures({ container }) {
],
}
const shippingOptionCalculatedData: FulfillmentWorkflow.CreateShippingOptionsWorkflowInput =
{
name: "Calculated shipping option",
service_zone_id: serviceZone.id,
shipping_profile_id: shippingProfile.id,
provider_id: providerIdCalculated,
price_type: "calculated",
type: {
label: "Test type",
description: "Test description",
code: "test-code",
},
rules: [],
}
const { result } = await createShippingOptionsWorkflow(container).run({
input: [shippingOptionData],
input: [shippingOptionData, shippingOptionCalculatedData],
})
const remoteQueryObject = remoteQueryObjectFromString({
entryPoint: "shipping_option",
variables: {
id: result[0].id,
id: result.map((r) => r.id),
},
fields: [
"id",
@@ -189,13 +241,17 @@ export async function prepareDataFixtures({ container }) {
const remoteQuery = container.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const [createdShippingOption] = await remoteQuery(remoteQueryObject)
const shippingOptions = await remoteQuery(remoteQueryObject)
return {
shippingOption: createdShippingOption,
shippingOption: shippingOptions.find((s) => s.price_type === "flat"),
shippingOptionCalculated: shippingOptions.find(
(s) => s.price_type === "calculated"
),
region,
salesChannel,
location,
product,
customer,
inventoryItem,
}
}
@@ -205,18 +261,31 @@ export async function createOrderFixture({
product,
location,
inventoryItem,
region,
salesChannel,
customer,
overrides,
}: {
container: any
product: any
location: any
inventoryItem: any
salesChannel?: any
customer?: any
region?: any
overrides?: { quantity?: number }
}) {
const orderService: IOrderModuleService = container.resolve(Modules.ORDER)
let order = await orderService.createOrders({
region_id: "test_region_id",
email: "foo@bar.com",
region_id: region?.id || "test_region_id",
email: customer?.email || "foo@bar.com",
items: [
{
title: "Custom Item 2",
variant_sku: product.variants[0].sku,
variant_title: product.variants[0].title,
quantity: 1,
quantity: overrides?.quantity ?? 1,
unit_price: 50,
adjustments: [
{
@@ -235,7 +304,7 @@ export async function createOrderFixture({
currency_code: "usd",
},
],
sales_channel_id: "test",
sales_channel_id: salesChannel?.id || "test",
shipping_address: {
first_name: "Test",
last_name: "Test",
@@ -277,7 +346,7 @@ export async function createOrderFixture({
},
],
currency_code: "usd",
customer_id: "joe",
customer_id: customer?.id || "joe",
})
const inventoryModule = container.resolve(Modules.INVENTORY)

View File

@@ -0,0 +1,212 @@
import {
beginClaimOrderWorkflow,
createClaimShippingMethodWorkflow,
createOrderFulfillmentWorkflow,
orderClaimAddNewItemWorkflow,
orderClaimRequestItemReturnWorkflow,
updateClaimAddItemWorkflow,
} from "@medusajs/core-flows"
import { IFulfillmentModuleService, OrderDTO } from "@medusajs/types"
import {
ContainerRegistrationKeys,
Modules,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import { medusaIntegrationTestRunner } from "@medusajs/test-utils"
import { createOrderFixture, prepareDataFixtures } from "../__fixtures__"
jest.setTimeout(50000)
medusaIntegrationTestRunner({
env: { MEDUSA_FF_MEDUSA_V2: true },
testSuite: ({ getContainer }) => {
let container
beforeAll(() => {
container = getContainer()
})
describe("Order change: Claim shipping", () => {
let order: OrderDTO
let service: IFulfillmentModuleService
let fixtures
let claimOrder: OrderDTO
beforeEach(async () => {
fixtures = await prepareDataFixtures({ container })
order = await createOrderFixture({
container,
product: fixtures.product,
location: fixtures.location,
inventoryItem: fixtures.inventoryItem,
salesChannel: fixtures.salesChannel,
customer: fixtures.customer,
region: fixtures.region,
overrides: { quantity: 2 },
})
await createOrderFulfillmentWorkflow(container).run({
input: {
order_id: order.id,
items: [
{
quantity: 2,
id: order.items![0].id,
},
],
},
})
await beginClaimOrderWorkflow(container).run({
input: { order_id: order.id, type: "replace" },
throwOnError: true,
})
const remoteQuery = container.resolve(
ContainerRegistrationKeys.REMOTE_QUERY
)
const remoteQueryObject = remoteQueryObjectFromString({
entryPoint: "order_claim",
variables: { order_id: order.id },
fields: ["order_id", "id", "status", "order_change_id"],
})
service = container.resolve(Modules.FULFILLMENT)
;[claimOrder] = await remoteQuery(remoteQueryObject)
})
describe("createClaimShippingMethodWorkflow", () => {
it("should successfully add caluclated inbound and outbound shipping to order changes", async () => {
const { result } = await orderClaimAddNewItemWorkflow(container).run({
input: {
claim_id: claimOrder.id,
items: [
{
variant_id: fixtures.product.variants[0].id,
quantity: 1,
internal_note: "test",
},
],
},
})
const shippingOptionId = fixtures.shippingOptionCalculated.id
const { result: orderChangePreview } =
await createClaimShippingMethodWorkflow(container).run({
input: {
claim_id: claimOrder.id,
shipping_option_id: shippingOptionId,
},
})
// Original shipping + outbound
expect(orderChangePreview.shipping_methods).toHaveLength(2)
const outboundShippingMethod =
orderChangePreview.shipping_methods?.find(
(sm) => sm.shipping_option_id === shippingOptionId
)
expect((outboundShippingMethod as any).actions).toEqual([
expect.objectContaining({
id: expect.any(String),
reference: "order_shipping_method",
reference_id: expect.any(String),
raw_amount: { value: "2.5", precision: 20 },
return_id: null,
claim_id: claimOrder.id,
applied: false,
action: "SHIPPING_ADD",
amount: 2.5,
}),
])
const { result: orderChangePreview2 } =
await orderClaimRequestItemReturnWorkflow.run({
container,
input: {
claim_id: claimOrder.id,
items: [
{
id: result.items[0].id,
quantity: 1,
},
],
},
})
const associatedReturnId = orderChangePreview2.order_change.return_id
const { result: orderChangePreview3 } =
await createClaimShippingMethodWorkflow(container).run({
input: {
claim_id: claimOrder.id,
return_id: associatedReturnId,
shipping_option_id: shippingOptionId,
},
})
expect(orderChangePreview3.shipping_methods).toHaveLength(3)
const inboundShippingMethod =
orderChangePreview3.shipping_methods?.find(
(sm) =>
sm.shipping_option_id === shippingOptionId &&
sm.actions?.find(
(a) =>
a.action === "SHIPPING_ADD" &&
a.return_id === associatedReturnId
)
)
expect(inboundShippingMethod!.actions![0]).toEqual(
expect.objectContaining({
return_id: associatedReturnId,
claim_id: claimOrder.id,
applied: false,
action: "SHIPPING_ADD",
amount: 2,
})
)
// Update outbound quantity to test refresh caluclation
const { result: orderChangePreview4 } =
await updateClaimAddItemWorkflow(container).run({
input: {
claim_id: claimOrder.id,
action_id: result.items.find(
(i) => i.variant_id === fixtures.product.variants[0].id
)?.actions?.[0]?.id as string,
data: {
quantity: 2,
},
},
})
const outboundShippingMethod2 =
orderChangePreview4.shipping_methods?.find(
(sm) => sm.shipping_option_id === shippingOptionId
)
expect((outboundShippingMethod2 as any).actions).toEqual([
expect.objectContaining({
id: expect.any(String),
reference: "order_shipping_method",
reference_id: expect.any(String),
raw_amount: { value: "5", precision: 20 },
return_id: null,
claim_id: claimOrder.id,
applied: false,
action: "SHIPPING_ADD",
amount: 5,
}),
])
})
})
})
},
})

View File

@@ -0,0 +1,214 @@
import {
beginExchangeOrderWorkflow,
createExchangeShippingMethodWorkflow,
createOrderFulfillmentWorkflow,
orderExchangeAddNewItemWorkflow,
orderExchangeRequestItemReturnWorkflow,
updateExchangeAddItemWorkflow,
} from "@medusajs/core-flows"
import { IFulfillmentModuleService, OrderDTO } from "@medusajs/types"
import {
ContainerRegistrationKeys,
Modules,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import { medusaIntegrationTestRunner } from "@medusajs/test-utils"
import { createOrderFixture, prepareDataFixtures } from "../__fixtures__"
jest.setTimeout(50000)
medusaIntegrationTestRunner({
env: { MEDUSA_FF_MEDUSA_V2: true },
testSuite: ({ getContainer }) => {
let container
beforeAll(() => {
container = getContainer()
})
describe("Order change: Exchange shipping", () => {
let order: OrderDTO
let service: IFulfillmentModuleService
let fixtures
let exchangeOrder: OrderDTO
beforeEach(async () => {
fixtures = await prepareDataFixtures({ container })
order = await createOrderFixture({
container,
product: fixtures.product,
location: fixtures.location,
inventoryItem: fixtures.inventoryItem,
salesChannel: fixtures.salesChannel,
customer: fixtures.customer,
region: fixtures.region,
overrides: { quantity: 2 },
})
await createOrderFulfillmentWorkflow(container).run({
input: {
order_id: order.id,
items: [
{
quantity: 2,
id: order.items![0].id,
},
],
},
})
await beginExchangeOrderWorkflow(container).run({
input: { order_id: order.id },
throwOnError: true,
})
const remoteQuery = container.resolve(
ContainerRegistrationKeys.REMOTE_QUERY
)
const remoteQueryObject = remoteQueryObjectFromString({
entryPoint: "order_exchange",
variables: { order_id: order.id },
fields: ["order_id", "id", "status", "order_change_id"],
})
service = container.resolve(Modules.FULFILLMENT)
;[exchangeOrder] = await remoteQuery(remoteQueryObject)
})
describe("createExchangeShippingMethodWorkflow", () => {
it("should successfully add caluclated inbound and outbound shipping to order changes", async () => {
const { result } = await orderExchangeAddNewItemWorkflow(
container
).run({
input: {
exchange_id: exchangeOrder.id,
items: [
{
variant_id: fixtures.product.variants[0].id,
quantity: 1,
internal_note: "test",
},
],
},
})
const shippingOptionId = fixtures.shippingOptionCalculated.id
const { result: orderChangePreview } =
await createExchangeShippingMethodWorkflow(container).run({
input: {
exchange_id: exchangeOrder.id,
shipping_option_id: shippingOptionId,
},
})
// Original shipping + outbound
expect(orderChangePreview.shipping_methods).toHaveLength(2)
const outboundShippingMethod =
orderChangePreview.shipping_methods?.find(
(sm) => sm.shipping_option_id === shippingOptionId
)
expect((outboundShippingMethod as any).actions).toEqual([
expect.objectContaining({
id: expect.any(String),
reference: "order_shipping_method",
reference_id: expect.any(String),
raw_amount: { value: "2.5", precision: 20 },
return_id: null,
exchange_id: exchangeOrder.id,
applied: false,
action: "SHIPPING_ADD",
amount: 2.5,
}),
])
const { result: orderChangePreview2 } =
await orderExchangeRequestItemReturnWorkflow.run({
container,
input: {
exchange_id: exchangeOrder.id,
items: [
{
id: result.items[0].id,
quantity: 1,
},
],
},
})
const associatedReturnId = orderChangePreview2.order_change.return_id
const { result: orderChangePreview3 } =
await createExchangeShippingMethodWorkflow(container).run({
input: {
exchange_id: exchangeOrder.id,
return_id: associatedReturnId,
shipping_option_id: shippingOptionId,
},
})
expect(orderChangePreview3.shipping_methods).toHaveLength(3)
const inboundShippingMethod =
orderChangePreview3.shipping_methods?.find(
(sm) =>
sm.shipping_option_id === shippingOptionId &&
sm.actions?.find(
(a) =>
a.action === "SHIPPING_ADD" &&
a.return_id === associatedReturnId
)
)
expect(inboundShippingMethod!.actions![0]).toEqual(
expect.objectContaining({
return_id: associatedReturnId,
exchange_id: exchangeOrder.id,
applied: false,
action: "SHIPPING_ADD",
amount: 2,
})
)
// Update outbound quantity to test refresh caluclation
const { result: orderChangePreview4 } =
await updateExchangeAddItemWorkflow(container).run({
input: {
exchange_id: exchangeOrder.id,
action_id: result.items.find(
(i) => i.variant_id === fixtures.product.variants[0].id
)?.actions?.[0]?.id as string,
data: {
quantity: 2,
},
},
})
const outboundShippingMethod2 =
orderChangePreview4.shipping_methods?.find(
(sm) => sm.shipping_option_id === shippingOptionId
)
expect((outboundShippingMethod2 as any).actions).toEqual([
expect.objectContaining({
id: expect.any(String),
reference: "order_shipping_method",
reference_id: expect.any(String),
raw_amount: { value: "5", precision: 20 },
return_id: null,
exchange_id: exchangeOrder.id,
applied: false,
action: "SHIPPING_ADD",
amount: 5,
}),
])
})
})
})
},
})

View File

@@ -1,6 +1,9 @@
import {
beginReturnOrderWorkflow,
createOrderFulfillmentWorkflow,
createReturnShippingMethodWorkflow,
requestItemReturnWorkflow,
updateRequestItemReturnWorkflow,
} from "@medusajs/core-flows"
import { IFulfillmentModuleService, OrderDTO, ReturnDTO } from "@medusajs/types"
import {
@@ -10,7 +13,6 @@ import {
} from "@medusajs/utils"
import { medusaIntegrationTestRunner } from "@medusajs/test-utils"
import { createOrderFixture, prepareDataFixtures } from "../__fixtures__"
jest.setTimeout(50000)
medusaIntegrationTestRunner({
@@ -37,6 +39,19 @@ medusaIntegrationTestRunner({
product: fixtures.product,
location: fixtures.location,
inventoryItem: fixtures.inventoryItem,
overrides: { quantity: 2 },
})
await createOrderFulfillmentWorkflow(container).run({
input: {
order_id: order.id,
items: [
{
quantity: 2,
id: order.items![0].id,
},
],
},
})
await beginReturnOrderWorkflow(container).run({
@@ -115,6 +130,121 @@ medusaIntegrationTestRunner({
}),
])
})
it("should successfully add calculated return shipping to order changes", async () => {
const shippingOptionId = fixtures.shippingOptionCalculated.id
const { result: orderChangePreview } =
await createReturnShippingMethodWorkflow(container).run({
input: {
return_id: returnOrder.id,
shipping_option_id: shippingOptionId,
},
})
const shippingMethod = orderChangePreview.shipping_methods?.find(
(sm) => sm.shipping_option_id === shippingOptionId
)
/**
* Shipping is 0 because the shipping option is calculated based on the return items
* and currently there are no return items.
*/
expect((shippingMethod as any).actions).toEqual([
expect.objectContaining({
id: expect.any(String),
reference: "order_shipping_method",
reference_id: expect.any(String),
raw_amount: { value: "0", precision: 20 },
applied: false,
action: "SHIPPING_ADD",
amount: 0,
}),
])
const { result } = await requestItemReturnWorkflow(container).run({
input: {
return_id: returnOrder.id,
items: [
{
id: order.items![0].id,
quantity: 1,
internal_note: "test",
},
],
},
})
console.log(result.items[0].actions)
let updatedShippingMethod = result.shipping_methods?.find(
(sm) => sm.shipping_option_id === shippingOptionId
)
/**
* Caluclated shipping is 2$ per return item.
*/
expect(updatedShippingMethod).toEqual(
expect.objectContaining({
id: expect.any(String),
shipping_option_id: shippingOptionId,
amount: 2,
actions: expect.arrayContaining([
expect.objectContaining({
id: expect.any(String),
reference: "order_shipping_method",
reference_id: expect.any(String),
raw_amount: { value: "2", precision: 20 },
applied: false,
action: "SHIPPING_ADD",
amount: 2,
}),
]),
})
)
/**
* Update the return item quantity to 2.
*/
const { result: updatedResult } =
await updateRequestItemReturnWorkflow(container).run({
input: {
return_id: returnOrder.id,
action_id: result.items
.find((i) =>
i.actions?.find((a) => a.action === "RETURN_ITEM")
)
?.actions?.find((a) => a.action === "RETURN_ITEM")?.id!,
data: {
quantity: 2,
},
},
})
updatedShippingMethod = updatedResult.shipping_methods?.find(
(sm) => sm.shipping_option_id === shippingOptionId
)
expect(updatedShippingMethod).toEqual(
expect.objectContaining({
id: expect.any(String),
shipping_option_id: shippingOptionId,
amount: 4,
actions: expect.arrayContaining([
expect.objectContaining({
id: expect.any(String),
reference: "order_shipping_method",
reference_id: expect.any(String),
raw_amount: { value: "4", precision: 20 },
applied: false,
action: "SHIPPING_ADD",
amount: 4,
}),
]),
})
)
})
})
})
},

View File

@@ -22,6 +22,12 @@ const customFulfillmentProvider = {
id: "test-provider",
}
const customFulfillmentProviderCalculated = {
resolve: require("./dist/utils/providers/fulfillment-manual-calculated")
.default,
id: "test-provider-calculated",
}
module.exports = {
admin: {
disable: true,
@@ -96,7 +102,10 @@ module.exports = {
[Modules.FULFILLMENT]: {
/** @type {import('@medusajs/fulfillment').FulfillmentModuleOptions} */
options: {
providers: [customFulfillmentProvider],
providers: [
customFulfillmentProvider,
customFulfillmentProviderCalculated,
],
},
},
[Modules.NOTIFICATION]: {

View File

@@ -0,0 +1,8 @@
import { ModuleProvider, Modules } from "@medusajs/framework/utils"
import { ManualFulfillmentService } from "./services/manual-fulfillment"
const services = [ManualFulfillmentService]
export default ModuleProvider(Modules.FULFILLMENT, {
services,
})

View File

@@ -0,0 +1,80 @@
import { AbstractFulfillmentProviderService } from "@medusajs/framework/utils"
export class ManualFulfillmentService extends AbstractFulfillmentProviderService {
static identifier = "manual-calculated"
constructor() {
super()
}
async getFulfillmentOptions() {
return [
{
id: "manual-fulfillment-calculated",
},
{
id: "manual-fulfillment-return-calculated",
is_return: true,
},
]
}
async validateFulfillmentData(optionData, data, context) {
return data
}
async calculatePrice(optionData, data, context) {
if (context.exchange_id) {
return {
calculated_amount:
context.exchange_items.reduce((acc, i) => acc + i.quantity, 0) * 2.5, // mock return cost as 2 per item
is_calculated_price_tax_inclusive: false,
}
}
if (context.claim_id) {
return {
calculated_amount:
context.claim_items.reduce((acc, i) => acc + i.quantity, 0) * 2.5, // mock return cost as 2 per item
is_calculated_price_tax_inclusive: false,
}
}
if (context.return_id) {
return {
calculated_amount:
context.return_items.reduce((acc, i) => acc + i.quantity, 0) * 2, // mock return cost as 2 per item
is_calculated_price_tax_inclusive: false,
}
}
return {
calculated_amount:
context.items.reduce((acc, i) => acc + i.quantity, 0) * 1.5, // mock caluclation as 1.5 per item
is_calculated_price_tax_inclusive: false,
}
}
async canCalculate() {
return true
}
async validateOption(data) {
return true
}
async createFulfillment() {
// No data is being sent anywhere
return {
data: {},
labels: [],
}
}
async cancelFulfillment() {
return {}
}
async createReturnFulfillment() {
return { data: {}, labels: [] }
}
}

View File

@@ -8,13 +8,14 @@ import { StepResponse, createStep } from "@medusajs/framework/workflows-sdk"
/**
* The data to calculate the prices for one or more shipping options.
*/
export type CalculateShippingOptionsPriceStepInput = CalculateShippingOptionPriceDTO[]
export type CalculateShippingOptionsPriceStepInput =
CalculateShippingOptionPriceDTO[]
export const calculateShippingOptionsPricesStepId =
"calculate-shipping-options-prices"
/**
* This step calculates the prices for one or more shipping options.
*
*
* @example
* const data = calculateShippingOptionsPricesStep([{
* id: "so_123",

View File

@@ -22,9 +22,9 @@ export const validateShippingOptionPricesStepId =
*
* For flat rate prices, it validates that regions exist for the shipping option prices.
* For calculated prices, it validates with the fulfillment provider if the price can be calculated.
*
*
* If not valid, the step throws an error.
*
*
* @example
* const data = validateShippingOptionPricesStep([
* {

View File

@@ -0,0 +1,210 @@
import {
BigNumberInput,
CalculatedRMAShippingContext,
CalculateShippingOptionPriceDTO,
ShippingOptionDTO,
} from "@medusajs/framework/types"
import {
WorkflowResponse,
createWorkflow,
transform,
when,
} from "@medusajs/framework/workflows-sdk"
import { BigNumber, ShippingOptionPriceType } from "@medusajs/framework/utils"
import { calculateShippingOptionsPricesStep } from "../../fulfillment/steps"
import { useRemoteQueryStep } from "../../common"
const COMMON_OPTIONS_FIELDS = [
"id",
"name",
"price_type",
"service_zone_id",
"service_zone.fulfillment_set_id",
"service_zone.fulfillment_set.type",
"service_zone.fulfillment_set.location.*",
"service_zone.fulfillment_set.location.address.*",
"shipping_profile_id",
"provider_id",
"data",
"type.id",
"type.label",
"type.description",
"type.code",
"provider.id",
"provider.is_enabled",
"rules.attribute",
"rules.value",
"rules.operator",
]
/**
* The data to create a shipping method for an order edit.
*/
export type FetchShippingOptionForOrderWorkflowInput = {
/**
* The ID of the shipping option to create the shipping method from.
*/
shipping_option_id: string
/**
* The custom amount to create the shipping method with.
* If not provided, the shipping option's amount is used.
*/
custom_amount?: BigNumberInput | null
/**
* The currency code of the order.
*/
currency_code: string
/**
* The ID of the order.
*/
order_id: string
/**
* The context of the order.
*/
context: CalculatedRMAShippingContext
}
/**
* The output of the fetch shipping option for order workflow.
*/
export type FetchShippingOptionForOrderWorkflowOutput = ShippingOptionDTO & {
calculated_price: {
calculated_amount: BigNumber
is_calculated_price_tax_inclusive: boolean
}
}
export const createOrderEditShippingMethodWorkflowId = "fetch-shipping-option"
/**
* This workflows fetches a shipping option for an order (used in RMA flows).
*
* There can be 3 cases:
* 1. The shipping option is a flat rate shipping option.
* In this case, pricing calculation context is not used.
* 2. The shipping option is a calculated shipping option.
* In this case, calculate shipping price method from the provider is called.
* 3. The shipping option is a custom shipping option. -- TODO
* In this case, we don't need to do caluclations above and just return the shipping option with the custom amount.
*/
export const fetchShippingOptionForOrderWorkflow = createWorkflow(
createOrderEditShippingMethodWorkflowId,
function (
input: FetchShippingOptionForOrderWorkflowInput
): WorkflowResponse<FetchShippingOptionForOrderWorkflowOutput> {
const initialOption = useRemoteQueryStep({
entry_point: "shipping_option",
variables: { id: input.shipping_option_id },
fields: ["id", "price_type"],
list: false,
}).config({ name: "shipping-option-query" })
const isCalculatedPriceShippingOption = transform(
initialOption,
(option) => option.price_type === ShippingOptionPriceType.CALCULATED
)
const calculatedPriceShippingOption = when(
"option-calculated",
{ isCalculatedPriceShippingOption },
({ isCalculatedPriceShippingOption }) => isCalculatedPriceShippingOption
).then(() => {
const order = useRemoteQueryStep({
entry_point: "orders",
fields: ["id", "shipping_address", "items.*", "items.variant.*"],
variables: { id: input.order_id },
list: false,
throw_if_key_not_found: true,
}).config({ name: "order-query" })
const shippingOption = useRemoteQueryStep({
entry_point: "shipping_option",
fields: [...COMMON_OPTIONS_FIELDS],
variables: { id: input.shipping_option_id },
list: false,
}).config({ name: "calculated-option" })
const calculateShippingOptionsPricesData = transform(
{
shippingOption,
order,
input,
},
({ shippingOption, order, input }) => {
return [
{
id: shippingOption.id as string,
optionData: shippingOption.data,
context: {
...order,
...input.context,
from_location:
shippingOption.service_zone.fulfillment_set.location,
},
// data: {}, // TODO: add data
provider_id: shippingOption.provider_id,
} as CalculateShippingOptionPriceDTO,
]
}
)
const prices = calculateShippingOptionsPricesStep(
calculateShippingOptionsPricesData
)
const shippingOptionsWithPrice = transform(
{
shippingOption,
prices,
},
({ shippingOption, prices }) => {
return {
id: shippingOption.id,
name: shippingOption.name,
calculated_price: prices[0],
}
}
)
return shippingOptionsWithPrice
})
const flatRateShippingOption = when(
"option-flat",
{ isCalculatedPriceShippingOption },
({ isCalculatedPriceShippingOption }) => !isCalculatedPriceShippingOption
).then(() => {
const shippingOption = useRemoteQueryStep({
entry_point: "shipping_option",
fields: [
"id",
"name",
"calculated_price.calculated_amount",
"calculated_price.is_calculated_price_tax_inclusive",
],
variables: {
id: input.shipping_option_id,
calculated_price: {
context: { currency_code: input.currency_code },
},
},
list: false,
}).config({ name: "flat-reate-option" })
return shippingOption
})
const result = transform(
{
calculatedPriceShippingOption,
flatRateShippingOption,
},
({ calculatedPriceShippingOption, flatRateShippingOption }) => {
return calculatedPriceShippingOption ?? flatRateShippingOption
}
)
return new WorkflowResponse(result)
}
)

View File

@@ -0,0 +1,173 @@
import {
CalculatedRMAShippingContext,
CalculateShippingOptionPriceDTO,
} from "@medusajs/framework/types"
import {
WorkflowResponse,
createWorkflow,
parallelize,
transform,
when,
} from "@medusajs/framework/workflows-sdk"
import { ShippingOptionPriceType } from "@medusajs/framework/utils"
import { calculateShippingOptionsPricesStep } from "../../fulfillment/steps"
import {
updateOrderChangeActionsStep,
updateOrderShippingMethodsStep,
} from "../steps"
import { useQueryGraphStep } from "../../common"
const COMMON_OPTIONS_FIELDS = [
"id",
"name",
"price_type",
"service_zone_id",
"service_zone.fulfillment_set_id",
"service_zone.fulfillment_set.type",
"service_zone.fulfillment_set.location.*",
"service_zone.fulfillment_set.location.address.*",
"shipping_profile_id",
"provider_id",
"data",
"type.id",
"type.label",
"type.description",
"type.code",
"provider.id",
"provider.is_enabled",
"rules.attribute",
"rules.value",
"rules.operator",
]
/**
* The data to create a shipping method for an order edit.
*/
export type MaybeRefreshShippingMethodsWorkflowInput = {
/**
* The ID of the shipping method to refresh.
*/
shipping_method_id: string
/**
* The ID of the order.
*/
order_id: string
/**
* The ID of the ADD SHIPPING action to update.
*/
action_id: string
/**
* Data to pass for the shipping calculation.
*/
context: CalculatedRMAShippingContext
}
export const maybeRefreshShippingMethodsWorkflowId =
"maybe-refresh-shipping-methods"
/**
* This workflows refreshes shipping method an order (used in RMA flows).
* The shipping method and the action is updated if the shipping option is calculated.
*/
export const maybeRefreshShippingMethodsWorkflow = createWorkflow(
maybeRefreshShippingMethodsWorkflowId,
function (
input: MaybeRefreshShippingMethodsWorkflowInput
): WorkflowResponse<void> {
const shippingMethodQuery = useQueryGraphStep({
entity: "order_shipping_method",
fields: ["id", "shipping_option_id"],
filters: {
id: input.shipping_method_id,
},
}).config({ name: "fetch-shipping-method" })
const shippingMethod = transform(shippingMethodQuery, ({ data }) => data[0])
const shippingOptionQuery = useQueryGraphStep({
entity: "shipping_option",
fields: [...COMMON_OPTIONS_FIELDS],
filters: { id: shippingMethod.shipping_option_id },
}).config({ name: "calculated-option" })
const shippingOption = transform(shippingOptionQuery, ({ data }) => data[0])
const isCalculatedPriceShippingOption = transform(
shippingOption,
(option) => option?.price_type === ShippingOptionPriceType.CALCULATED
)
when(
{ isCalculatedPriceShippingOption, shippingOption },
({ isCalculatedPriceShippingOption, shippingOption }) =>
isCalculatedPriceShippingOption
).then(() => {
const orderQuery = useQueryGraphStep({
entity: "order",
fields: ["id", "shipping_address", "items.*", "items.variant.*"],
filters: { id: input.order_id },
options: { throwIfKeyNotFound: true },
}).config({ name: "order-query" })
const order = transform(orderQuery, (data) => data[0])
const calculateShippingOptionsPricesData = transform(
{
shippingOption,
order,
input,
},
({ shippingOption, order, input }) => {
return [
{
id: shippingOption.id as string,
optionData: shippingOption.data,
context: {
...order,
...input.context,
from_location:
shippingOption.service_zone.fulfillment_set.location,
},
// data: {}, // TODO: add data
provider_id: shippingOption.provider_id,
} as CalculateShippingOptionPriceDTO,
]
}
)
const prices = calculateShippingOptionsPricesStep(
calculateShippingOptionsPricesData
)
const updateData = transform(
{
shippingOption,
prices,
input,
},
({ prices, input }) => {
return [
{
id: input.action_id,
amount: prices[0].calculated_amount,
},
{
id: input.shipping_method_id,
amount: prices[0].calculated_amount,
is_custom_amount: false,
},
]
}
)
parallelize(
updateOrderChangeActionsStep([updateData[0]]),
updateOrderShippingMethodsStep([updateData[1]!])
)
})
return new WorkflowResponse(void 0)
}
)

View File

@@ -22,6 +22,7 @@ import {
import { addOrderLineItemsWorkflow } from "../add-line-items"
import { createOrderChangeActionsWorkflow } from "../create-order-change-actions"
import { updateOrderTaxLinesWorkflow } from "../update-tax-lines"
import { refreshClaimShippingWorkflow } from "./refresh-shipping"
/**
* The data to validate adding new items to a claim.
@@ -191,6 +192,21 @@ export const orderClaimAddNewItemWorkflow = createWorkflow(
input: orderChangeActionInput,
})
const refreshArgs = transform(
{ orderChange, orderClaim },
({ orderChange, orderClaim }) => {
return {
order_change_id: orderChange.id,
claim_id: orderClaim.id,
order_id: orderClaim.order_id,
}
}
)
refreshClaimShippingWorkflow.runAsStep({
input: refreshArgs,
})
return new WorkflowResponse(previewOrderChangeStep(orderClaim.order_id))
}
)

View File

@@ -26,6 +26,7 @@ import {
throwIfOrderChangeIsNotActive,
} from "../../utils/order-validation"
import { createOrderChangeActionsWorkflow } from "../create-order-change-actions"
import { refreshClaimShippingWorkflow } from "./refresh-shipping"
/**
* The data to validate that items can be requested to return as part of a claim.
@@ -56,14 +57,14 @@ export type OrderClaimRequestItemReturnValidationStepInput = {
/**
* This step validates that items can be requested to return as part of a claim.
* If the order, claim, or return is canceled, or the order change is not active, the step will throw an error.
*
*
* :::note
*
*
* You can retrieve an order, order claim, order return, and order change details using [Query](https://docs.medusajs.com/learn/fundamentals/module-links/query),
* or [useQueryGraphStep](https://docs.medusajs.com/resources/references/medusa-workflows/steps/useQueryGraphStep).
*
*
* :::
*
*
* @example
* const data = orderClaimRequestItemReturnValidationStep({
* order: {
@@ -112,10 +113,10 @@ export const orderClaimRequestItemReturnWorkflowId = "claim-request-item-return"
* This workflow requests one or more items to be returned as part of a claim. The
* items are added to the claim as inbound items. The workflow is used by the
* [Add Inbound Items to Claim Admin API Route](https://docs.medusajs.com/api/admin#claims_postclaimsidinbounditems).
*
*
* You can use this workflow within your customizations or your own custom workflows, allowing you to request items to be returned
* as part of a claim in your custom flows.
*
*
* @example
* const { result } = await orderClaimRequestItemReturnWorkflow(container)
* .run({
@@ -130,9 +131,9 @@ export const orderClaimRequestItemReturnWorkflowId = "claim-request-item-return"
* ]
* }
* })
*
*
* @summary
*
*
* Request one or more items to be returned as part of a claim.
*/
export const orderClaimRequestItemReturnWorkflow = createWorkflow(
@@ -258,6 +259,21 @@ export const orderClaimRequestItemReturnWorkflow = createWorkflow(
input: orderChangeActionInput,
})
const refreshArgs = transform(
{ orderChange, orderClaim },
({ orderChange, orderClaim }) => {
return {
order_change_id: orderChange.id,
claim_id: orderClaim.id,
order_id: orderClaim.order_id,
}
}
)
refreshClaimShippingWorkflow.runAsStep({
input: refreshArgs,
})
return new WorkflowResponse(previewOrderChangeStep(orderClaim.order_id))
}
)

View File

@@ -1,5 +1,6 @@
import {
BigNumberInput,
CalculatedRMAShippingContext,
OrderChangeDTO,
OrderClaimDTO,
OrderDTO,
@@ -22,6 +23,7 @@ import {
import { prepareShippingMethod } from "../../utils/prepare-shipping-method"
import { createOrderChangeActionsWorkflow } from "../create-order-change-actions"
import { updateOrderTaxLinesWorkflow } from "../update-tax-lines"
import { fetchShippingOptionForOrderWorkflow } from "../../utils/fetch-shipping-option"
/**
* The data to validate that a shipping method can be created for a claim.
@@ -44,14 +46,14 @@ export type CreateClaimShippingMethodValidationStepInput = {
/**
* This step confirms that a shipping method can be created for a claim.
* If the order or claim is canceled, or the order change is not active, the step will throw an error.
*
*
* :::note
*
*
* You can retrieve an order, order claim, and order change details using [Query](https://docs.medusajs.com/learn/fundamentals/module-links/query),
* or [useQueryGraphStep](https://docs.medusajs.com/resources/references/medusa-workflows/steps/useQueryGraphStep).
*
*
* :::
*
*
* @example
* const data = createClaimShippingMethodValidationStep({
* order: {
@@ -111,13 +113,13 @@ export const createClaimShippingMethodWorkflowId =
* This workflow creates an inbound (return) or outbound (delivering new items) shipping method for a claim.
* It's used by the [Add Inbound Shipping Admin API Route](https://docs.medusajs.com/api/admin#claims_postclaimsidinboundshippingmethod),
* and the [Add Outbound Shipping Admin API Route](https://docs.medusajs.com/api/admin#claims_postclaimsidoutboundshippingmethod).
*
*
* You can use this workflow within your customizations or your own custom workflows, allowing you to create a shipping method
* for a claim in your custom flows.
*
*
* @example
* To create an outbound shipping method for a claim:
*
*
* ```ts
* const { result } = await createClaimShippingMethodWorkflow(container)
* .run({
@@ -127,9 +129,9 @@ export const createClaimShippingMethodWorkflowId =
* }
* })
* ```
*
*
* To create an inbound shipping method for a claim, specify the ID of the return associated with the claim:
*
*
* ```ts
* const { result } = await createClaimShippingMethodWorkflow(container)
* .run({
@@ -140,14 +142,16 @@ export const createClaimShippingMethodWorkflowId =
* }
* })
* ```
*
*
* @summary
*
*
* Create an inbound or outbound shipping method for a claim.
*/
export const createClaimShippingMethodWorkflow = createWorkflow(
createClaimShippingMethodWorkflowId,
function (input: CreateClaimShippingMethodWorkflowInput): WorkflowResponse<OrderPreviewDTO> {
function (
input: CreateClaimShippingMethodWorkflowInput
): WorkflowResponse<OrderPreviewDTO> {
const orderClaim: OrderClaimDTO = useRemoteQueryStep({
entry_point: "order_claim",
fields: ["id", "status", "order_id", "canceled_at"],
@@ -164,25 +168,9 @@ export const createClaimShippingMethodWorkflow = createWorkflow(
throw_if_key_not_found: true,
}).config({ name: "order-query" })
const shippingOptions = useRemoteQueryStep({
entry_point: "shipping_option",
fields: [
"id",
"name",
"calculated_price.calculated_amount",
"calculated_price.is_calculated_price_tax_inclusive",
],
variables: {
id: input.shipping_option_id,
calculated_price: {
context: { currency_code: order.currency_code },
},
},
}).config({ name: "fetch-shipping-option" })
const orderChange: OrderChangeDTO = useRemoteQueryStep({
entry_point: "order_change",
fields: ["id", "status", "version"],
fields: ["id", "status", "version", "actions.*"],
variables: {
filters: {
order_id: orderClaim.order_id,
@@ -193,6 +181,52 @@ export const createClaimShippingMethodWorkflow = createWorkflow(
list: false,
}).config({ name: "order-change-query" })
const isReturn = transform(input, (data) => {
return !!data.return_id
})
const fetchShippingOptionInput = transform(
{ input, isReturn, orderChange, order },
(data) => {
const changeActionType = data.isReturn
? ChangeActionType.RETURN_ITEM
: ChangeActionType.ITEM_ADD
const items = data.orderChange.actions
.filter((action) => action.action === changeActionType)
.map((a) => ({
id: a.details?.reference_id,
quantity: a.details?.quantity,
}))
const context = data.isReturn
? {
return_id: data.input.return_id,
return_items: items,
}
: {
claim_id: data.input.claim_id,
claim_items: items,
}
return {
order_id: data.order.id,
currency_code: data.order.currency_code,
shipping_option_id: data.input.shipping_option_id,
custom_amount: data.input.custom_amount,
context: context as CalculatedRMAShippingContext,
}
}
)
const shippingOption = fetchShippingOptionForOrderWorkflow.runAsStep({
input: fetchShippingOptionInput,
})
const shippingOptions = transform(shippingOption, (shippingOption) => {
return [shippingOption]
})
createClaimShippingMethodValidationStep({ order, orderClaim, orderChange })
const shippingMethodInput = transform(
@@ -214,10 +248,6 @@ export const createClaimShippingMethodWorkflow = createWorkflow(
return createdMethods.map((item) => item.id)
})
const isReturn = transform(input, (data) => {
return !!data.return_id
})
updateOrderTaxLinesWorkflow.runAsStep({
input: {
order_id: order.id,

View File

@@ -0,0 +1,141 @@
import { ChangeActionType, OrderChangeStatus } from "@medusajs/framework/utils"
import {
WorkflowData,
WorkflowResponse,
createWorkflow,
transform,
when,
} from "@medusajs/framework/workflows-sdk"
import { maybeRefreshShippingMethodsWorkflow } from "../../utils/maybe-refresh-shipping-methods"
import { useQueryGraphStep } from "../../../common"
/**
* The data to refresh the shipping methods for an claim.
*/
export type RefreshClaimShippingWorkflowInput = {
/**
* The order change's ID.
*/
order_change_id: string
/**
* The claim's details.
*/
claim_id: string
/**
* The order's ID.
*/
order_id: string
}
export const refreshClaimShippingWorkflowId = "refresh-claim-shipping"
/**
* This workflow refreshes the shipping methods for an claim in case the shipping option is calculated.
* It refreshes both inbound and outbound shipping methods.
*
* @summary
*
* Refresh claim shipping.
*/
export const refreshClaimShippingWorkflow = createWorkflow(
refreshClaimShippingWorkflowId,
function (
input: WorkflowData<RefreshClaimShippingWorkflowInput>
): WorkflowResponse<void> {
const orderChangeQuery = useQueryGraphStep({
entity: "order_change",
fields: [
"id",
"status",
"order_id",
"claim_id",
"return_id",
"actions.*",
],
filters: {
order_id: input.order_id,
claim_id: input.claim_id,
status: [OrderChangeStatus.PENDING, OrderChangeStatus.REQUESTED],
},
}).config({ name: "order-change-query" })
const orderChange = transform(orderChangeQuery, ({ data }) => data[0])
const refreshArgs = transform(
{ input, orderChange, orderChangeQuery },
({ input, orderChange, orderChangeQuery }) => {
const shippingToRefresh = {} as Record<"inbound" | "outbound", any>
const inboundShippingAction = orderChange.actions.find(
(action) =>
action.action === ChangeActionType.SHIPPING_ADD &&
!!action.return_id
)
const outboundShippingAction = orderChange.actions.find(
(action) =>
action.action === ChangeActionType.SHIPPING_ADD && !action.return_id
)
if (inboundShippingAction) {
const items = orderChange.actions
.filter((action) => action.action === ChangeActionType.RETURN_ITEM)
.map((a) => ({
id: a.details?.reference_id as string,
quantity: a.details?.quantity as number,
}))
shippingToRefresh.inbound = {
shipping_method_id: inboundShippingAction.reference_id,
order_id: orderChange.order_id,
action_id: inboundShippingAction.id,
context: {
return_id: inboundShippingAction.return_id,
return_items: items,
},
}
}
if (outboundShippingAction) {
const items = orderChange.actions
.filter((action) => action.action === ChangeActionType.ITEM_ADD)
.map((a) => ({
id: a.details?.reference_id as string,
quantity: a.details?.quantity as number,
}))
shippingToRefresh.outbound = {
shipping_method_id: outboundShippingAction.reference_id,
order_id: orderChange.order_id,
action_id: outboundShippingAction.id,
context: {
claim_id: outboundShippingAction.claim_id,
claim_items: items,
},
}
}
return shippingToRefresh
}
)
when({ refreshArgs }, ({ refreshArgs }) => !!refreshArgs.inbound).then(() =>
maybeRefreshShippingMethodsWorkflow
.runAsStep({
input: refreshArgs.inbound,
})
.config({ name: "refresh-inbound-shipping-method" })
)
when({ refreshArgs }, ({ refreshArgs }) => !!refreshArgs.outbound).then(
() =>
maybeRefreshShippingMethodsWorkflow
.runAsStep({
input: refreshArgs.outbound,
})
.config({ name: "refresh-outbound-shipping-method" })
)
return new WorkflowResponse(void 0)
}
)

View File

@@ -12,6 +12,7 @@ import {
WorkflowResponse,
createStep,
createWorkflow,
transform,
} from "@medusajs/framework/workflows-sdk"
import { useRemoteQueryStep } from "../../../common"
import {
@@ -22,6 +23,7 @@ import {
throwIfIsCancelled,
throwIfOrderChangeIsNotActive,
} from "../../utils/order-validation"
import { refreshClaimShippingWorkflow } from "./refresh-shipping"
/**
* The data to validate that claim items can be removed.
@@ -48,14 +50,14 @@ export type RemoveClaimItemActionValidationStepInput = {
/**
* This step confirms that a claim's items, added as order items, can be removed.
* If the order, claim, or order change is canceled, or the action is not claiming an item, the step will throw an error.
*
*
* :::note
*
*
* You can retrieve an order, order claim, and order change details using [Query](https://docs.medusajs.com/learn/fundamentals/module-links/query),
* or [useQueryGraphStep](https://docs.medusajs.com/resources/references/medusa-workflows/steps/useQueryGraphStep).
*
*
* :::
*
*
* @example
* const data = removeClaimItemActionValidationStep({
* order: {
@@ -104,22 +106,23 @@ export const removeClaimItemActionValidationStep = createStep(
/**
* The data to remove order items from a claim.
*
*
* @property action_id - The ID of the action associated with the outbound items.
* Every item has an `actions` property, whose value is an array of actions.
* You can find the action name `WRITE_OFF_ITEM` using its `action` property,
* Every item has an `actions` property, whose value is an array of actions.
* You can find the action name `WRITE_OFF_ITEM` using its `action` property,
* and use the value of its `id` property.
*/
export type RemoveItemClaimActionWorkflowInput = OrderWorkflow.DeleteOrderClaimItemActionWorkflowInput
export type RemoveItemClaimActionWorkflowInput =
OrderWorkflow.DeleteOrderClaimItemActionWorkflowInput
export const removeItemClaimActionWorkflowId = "remove-item-claim-action"
/**
* This workflow removes order items from a claim. It's used by the
* [Remove Claim Item Admin API Route](https://docs.medusajs.com/api/admin#claims_deleteclaimsidclaimitemsaction_id).
*
*
* You can use this workflow within your customizations or your own custom workflows, allowing you to remove order items from a claim
* in your custom flows.
*
*
* @example
* const { result } = await removeItemClaimActionWorkflow(container)
* .run({
@@ -128,9 +131,9 @@ export const removeItemClaimActionWorkflowId = "remove-item-claim-action"
* action_id: "orchact_123",
* }
* })
*
*
* @summary
*
*
* Remove order items from a claim.
*/
export const removeItemClaimActionWorkflow = createWorkflow(
@@ -176,6 +179,21 @@ export const removeItemClaimActionWorkflow = createWorkflow(
deleteOrderChangeActionsStep({ ids: [input.action_id] })
const refreshArgs = transform(
{ orderChange, orderClaim },
({ orderChange, orderClaim }) => {
return {
order_change_id: orderChange.id,
claim_id: orderClaim.id,
order_id: orderClaim.order_id,
}
}
)
refreshClaimShippingWorkflow.runAsStep({
input: refreshArgs,
})
return new WorkflowResponse(previewOrderChangeStep(order.id))
}
)

View File

@@ -23,6 +23,7 @@ import {
throwIfIsCancelled,
throwIfOrderChangeIsNotActive,
} from "../../utils/order-validation"
import { refreshClaimShippingWorkflow } from "./refresh-shipping"
/**
* The data to validate that a claim's outbound item can be updated.
@@ -50,14 +51,14 @@ export type UpdateClaimAddNewItemValidationStepInput = {
* This step validates that a claim's new or outbound item can be updated.
* If the order, claim, or order change is canceled, no action is adding the item,
* or the action is not adding an outbound item, the step will throw an error.
*
*
* :::note
*
*
* You can retrieve an order, order claim, and order change details using [Query](https://docs.medusajs.com/learn/fundamentals/module-links/query),
* or [useQueryGraphStep](https://docs.medusajs.com/resources/references/medusa-workflows/steps/useQueryGraphStep).
*
*
* :::
*
*
* @example
* const data = updateClaimAddItemValidationStep({
* order: {
@@ -114,10 +115,10 @@ export const updateClaimAddItemWorkflowId = "update-claim-add-item"
/**
* This workflow updates a claim's new or outbound item. It's used by the
* [Update Outbound Item API Route](https://docs.medusajs.com/api/admin#claims_postclaimsidoutbounditemsaction_id).
*
*
* You can use this workflow within your customizations or your own custom workflows, allowing you to update a claim's new or outbound item
* in your custom flows.
*
*
* @example
* const { result } = await updateClaimAddItemWorkflow(container)
* .run({
@@ -129,9 +130,9 @@ export const updateClaimAddItemWorkflowId = "update-claim-add-item"
* }
* }
* })
*
*
* @summary
*
*
* Update a claim's new or outbound item.
*/
export const updateClaimAddItemWorkflow = createWorkflow(
@@ -190,6 +191,21 @@ export const updateClaimAddItemWorkflow = createWorkflow(
updateOrderChangeActionsStep([updateData])
const refreshArgs = transform(
{ orderChange, orderClaim },
({ orderChange, orderClaim }) => {
return {
order_change_id: orderChange.id,
claim_id: orderClaim.id,
order_id: orderClaim.order_id,
}
}
)
refreshClaimShippingWorkflow.runAsStep({
input: refreshArgs,
})
return new WorkflowResponse(previewOrderChangeStep(order.id))
}
)

View File

@@ -28,14 +28,14 @@ export type BeginOrderExchangeValidationStepInput = {
/**
* This step validates that an exchange can be requested for an order.
* If the order is canceled, the step will throw an error.
*
*
* :::note
*
*
* You can retrieve an order's details using [Query](https://docs.medusajs.com/learn/fundamentals/module-links/query),
* or [useQueryGraphStep](https://docs.medusajs.com/resources/references/medusa-workflows/steps/useQueryGraphStep).
*
*
* :::
*
*
* @example
* const data = beginOrderExchangeValidationStep({
* order: {
@@ -53,12 +53,12 @@ export const beginOrderExchangeValidationStep = createStep(
export const beginExchangeOrderWorkflowId = "begin-exchange-order"
/**
* This workflow requests an order exchange. It's used by the
* This workflow requests an order exchange. It's used by the
* [Create Exchange Admin API Route](https://docs.medusajs.com/api/admin#exchanges_postexchanges).
*
*
* You can use this workflow within your customizations or your own custom workflows, allowing you to request an exchange
* for an order in your custom flow.
*
*
* @example
* const { result } = await beginExchangeOrderWorkflow(container)
* .run({
@@ -66,9 +66,9 @@ export const beginExchangeOrderWorkflowId = "begin-exchange-order"
* order_id: "order_123",
* }
* })
*
*
* @summary
*
*
* Request an order exchange.
*/
export const beginExchangeOrderWorkflow = createWorkflow(

View File

@@ -1,5 +1,6 @@
import {
BigNumberInput,
CalculatedRMAShippingContext,
OrderChangeDTO,
OrderDTO,
OrderExchangeDTO,
@@ -22,6 +23,7 @@ import {
import { prepareShippingMethod } from "../../utils/prepare-shipping-method"
import { createOrderChangeActionsWorkflow } from "../create-order-change-actions"
import { updateOrderTaxLinesWorkflow } from "../update-tax-lines"
import { fetchShippingOptionForOrderWorkflow } from "../../utils/fetch-shipping-option"
/**
* The data to validate that a shipping method can be created for an exchange.
@@ -44,14 +46,14 @@ export type CreateExchangeShippingMethodValidationStepInput = {
/**
* This step validates that an inbound or outbound shipping method can be created for an exchange.
* If the order or exchange is canceled, or the order change is not active, the step will throw an error.
*
*
* :::note
*
*
* You can retrieve an order, order exchange, and order change details using [Query](https://docs.medusajs.com/learn/fundamentals/module-links/query),
* or [useQueryGraphStep](https://docs.medusajs.com/resources/references/medusa-workflows/steps/useQueryGraphStep).
*
*
* :::
*
*
* @example
* const data = createExchangeShippingMethodValidationStep({
* order: {
@@ -112,13 +114,13 @@ export const createExchangeShippingMethodWorkflowId =
* This workflow creates an inbound (return) or outbound (delivery of new items) shipping method for an exchange.
* It's used by the [Add Inbound Shipping Admin API Route](https://docs.medusajs.com/api/admin#exchanges_postexchangesidinboundshippingmethod)
* and the [Add Outbound Shipping Admin API Route](https://docs.medusajs.com/api/admin#exchanges_postexchangesidoutboundshippingmethod).
*
*
* You can use this workflow within your customizations or your own custom workflows, allowing you to create a shipping method
* for an exchange in your custom flow.
*
*
* @example
* To create an outbound shipping method for the exchange:
*
*
* ```ts
* const { result } = await createExchangeShippingMethodWorkflow(container)
* .run({
@@ -128,9 +130,9 @@ export const createExchangeShippingMethodWorkflowId =
* }
* })
* ```
*
*
* To create an inbound shipping method, pass the ID of the return associated with the exchange:
*
*
* ```ts
* const { result } = await createExchangeShippingMethodWorkflow(container)
* .run({
@@ -141,14 +143,16 @@ export const createExchangeShippingMethodWorkflowId =
* }
* })
* ```
*
*
* @summary
*
*
* Create an inbound or outbound shipping method for an exchange.
*/
export const createExchangeShippingMethodWorkflow = createWorkflow(
createExchangeShippingMethodWorkflowId,
function (input: CreateExchangeShippingMethodWorkflowInput): WorkflowResponse<OrderPreviewDTO> {
function (
input: CreateExchangeShippingMethodWorkflowInput
): WorkflowResponse<OrderPreviewDTO> {
const orderExchange: OrderExchangeDTO = useRemoteQueryStep({
entry_point: "order_exchange",
fields: ["id", "status", "order_id", "canceled_at"],
@@ -165,25 +169,13 @@ export const createExchangeShippingMethodWorkflow = createWorkflow(
throw_if_key_not_found: true,
}).config({ name: "order-query" })
const shippingOptions = useRemoteQueryStep({
entry_point: "shipping_option",
fields: [
"id",
"name",
"calculated_price.calculated_amount",
"calculated_price.is_calculated_price_tax_inclusive",
],
variables: {
id: input.shipping_option_id,
calculated_price: {
context: { currency_code: order.currency_code },
},
},
}).config({ name: "fetch-shipping-option" })
const isReturn = transform(input, (data) => {
return !!data.return_id
})
const orderChange: OrderChangeDTO = useRemoteQueryStep({
entry_point: "order_change",
fields: ["id", "status", "version"],
fields: ["id", "status", "version", "actions.*"],
variables: {
filters: {
order_id: orderExchange.order_id,
@@ -194,6 +186,48 @@ export const createExchangeShippingMethodWorkflow = createWorkflow(
list: false,
}).config({ name: "order-change-query" })
const fetchShippingOptionInput = transform(
{ input, isReturn, orderChange, order },
(data) => {
const changeActionType = data.isReturn
? ChangeActionType.RETURN_ITEM
: ChangeActionType.ITEM_ADD
const items = data.orderChange.actions
.filter((action) => action.action === changeActionType)
.map((a) => ({
id: a.details?.reference_id,
quantity: a.details?.quantity,
}))
const context = data.isReturn
? {
return_id: data.input.return_id,
return_items: items,
}
: {
exchange_id: data.input.exchange_id,
exchange_items: items,
}
return {
order_id: data.order.id,
currency_code: data.order.currency_code,
shipping_option_id: data.input.shipping_option_id,
custom_amount: data.input.custom_amount,
context: context as CalculatedRMAShippingContext,
}
}
)
const shippingOption = fetchShippingOptionForOrderWorkflow.runAsStep({
input: fetchShippingOptionInput,
})
const shippingOptions = transform(shippingOption, (shippingOption) => {
return [shippingOption]
})
createExchangeShippingMethodValidationStep({
order,
orderExchange,
@@ -219,10 +253,6 @@ export const createExchangeShippingMethodWorkflow = createWorkflow(
return createdMethods.map((item) => item.id)
})
const isReturn = transform(input, (data) => {
return !!data.return_id
})
updateOrderTaxLinesWorkflow.runAsStep({
input: {
order_id: order.id,

View File

@@ -22,6 +22,7 @@ import {
import { addOrderLineItemsWorkflow } from "../add-line-items"
import { createOrderChangeActionsWorkflow } from "../create-order-change-actions"
import { updateOrderTaxLinesWorkflow } from "../update-tax-lines"
import { refreshExchangeShippingWorkflow } from "./refresh-shipping"
/**
* The data to validate that new or outbound items can be added to an exchange.
@@ -44,14 +45,14 @@ export type ExchangeAddNewItemValidationStepInput = {
/**
* This step validates that new or outbound items can be added to an exchange.
* If the order or exchange is canceled, or the order change is not active, the step will throw an error.
*
*
* :::note
*
*
* You can retrieve an order, order exchange, and order change details using [Query](https://docs.medusajs.com/learn/fundamentals/module-links/query),
* or [useQueryGraphStep](https://docs.medusajs.com/resources/references/medusa-workflows/steps/useQueryGraphStep).
*
*
* :::
*
*
* @example
* const data = exchangeAddNewItemValidationStep({
* order: {
@@ -85,10 +86,10 @@ export const orderExchangeAddNewItemWorkflowId = "exchange-add-new-item"
/**
* This workflow adds new or outbound items to an exchange. It's used by the
* [Add Outbound Items Admin API Route](https://docs.medusajs.com/api/admin#exchanges_postexchangesidoutbounditems).
*
*
* You can use this workflow within your customizations or your own custom workflows, allowing you to add new or outbound items
* to an exchange in your custom flow.
*
*
* @example
* const { result } = await orderExchangeAddNewItemWorkflow(container)
* .run({
@@ -102,9 +103,9 @@ export const orderExchangeAddNewItemWorkflowId = "exchange-add-new-item"
* ]
* }
* })
*
*
* @summary
*
*
* Add new or outbound items to an exchange.
*/
export const orderExchangeAddNewItemWorkflow = createWorkflow(
@@ -191,6 +192,21 @@ export const orderExchangeAddNewItemWorkflow = createWorkflow(
input: orderChangeActionInput,
})
const refreshArgs = transform(
{ orderChange, orderExchange },
({ orderChange, orderExchange }) => {
return {
order_change_id: orderChange.id,
exchange_id: orderExchange.id,
order_id: orderExchange.order_id,
}
}
)
refreshExchangeShippingWorkflow.runAsStep({
input: refreshArgs,
})
return new WorkflowResponse(previewOrderChangeStep(orderExchange.order_id))
}
)

View File

@@ -26,6 +26,7 @@ import {
throwIfOrderChangeIsNotActive,
} from "../../utils/order-validation"
import { createOrderChangeActionsWorkflow } from "../create-order-change-actions"
import { refreshExchangeShippingWorkflow } from "./refresh-shipping"
/**
* The data to validate that items can be returned as part of an exchange.
@@ -55,16 +56,16 @@ export type ExchangeRequestItemReturnValidationStepInput = {
/**
* This step validates that items can be returned as part of an exchange.
* If the order, exchange, or return is canceled, the order change is not active,
* If the order, exchange, or return is canceled, the order change is not active,
* or the item doesn't exist in the order, the step will throw an error.
*
*
* :::note
*
*
* You can retrieve an order, order exchange, and order return details using [Query](https://docs.medusajs.com/learn/fundamentals/module-links/query),
* or [useQueryGraphStep](https://docs.medusajs.com/resources/references/medusa-workflows/steps/useQueryGraphStep).
*
*
* :::
*
*
* @example
* const data = exchangeRequestItemReturnValidationStep({
* order: {
@@ -113,10 +114,10 @@ export const orderExchangeRequestItemReturnWorkflowId =
/**
* This workflow adds inbound items to be retuned as part of the exchange. It's used
* by the [Add Inbound Items Admin API Route](https://docs.medusajs.com/api/admin#exchanges_postexchangesidinbounditems).
*
*
* You can use this workflow within your customizations or your own custom workflows, allowing you to add inbound items
* to be returned as part of an exchange in your custom flow.
*
*
* @example
* const { result } = await orderExchangeRequestItemReturnWorkflow(container)
* .run({
@@ -131,9 +132,9 @@ export const orderExchangeRequestItemReturnWorkflowId =
* ]
* }
* })
*
*
* @summary
*
*
* Add inbound items to be returned as part of the exchange.
*/
export const orderExchangeRequestItemReturnWorkflow = createWorkflow(
@@ -260,6 +261,21 @@ export const orderExchangeRequestItemReturnWorkflow = createWorkflow(
input: orderChangeActionInput,
})
const refreshArgs = transform(
{ orderChange, orderExchange },
({ orderChange, orderExchange }) => {
return {
order_change_id: orderChange.id,
exchange_id: orderExchange.id,
order_id: orderExchange.order_id,
}
}
)
refreshExchangeShippingWorkflow.runAsStep({
input: refreshArgs,
})
return new WorkflowResponse(previewOrderChangeStep(orderExchange.order_id))
}
)

View File

@@ -0,0 +1,150 @@
import { OrderChangeDTO } from "@medusajs/framework/types"
import { ChangeActionType, OrderChangeStatus } from "@medusajs/framework/utils"
import {
WorkflowData,
WorkflowResponse,
createWorkflow,
transform,
when,
} from "@medusajs/framework/workflows-sdk"
import { maybeRefreshShippingMethodsWorkflow } from "../../utils/maybe-refresh-shipping-methods"
import { useQueryGraphStep } from "../../../common"
/**
* The data to refresh the shipping methods for an exchange.
*/
export type RefreshExchangeShippingWorkflowInput = {
/**
* The order change's ID.
*/
order_change_id: string
/**
* The exchange's details.
*/
exchange_id: string
/**
* The order's ID.
*/
order_id: string
}
export const refreshExchangeShippingWorkflowId = "refresh-exchange-shipping"
/**
* This workflow refreshes the shipping methods for an exchange in case the shipping option is calculated.
* It refreshes both inbound and outbound shipping methods.
*
* @summary
*
* Refresh exchange shipping.
*/
export const refreshExchangeShippingWorkflow = createWorkflow(
refreshExchangeShippingWorkflowId,
function (
input: WorkflowData<RefreshExchangeShippingWorkflowInput>
): WorkflowResponse<void> {
const orderChangeQuery = useQueryGraphStep({
entity: "order_change",
fields: [
"id",
"status",
"order_id",
"exchange_id",
"return_id",
"actions.*",
],
filters: {
order_id: input.order_id,
exchange_id: input.exchange_id,
status: [OrderChangeStatus.PENDING, OrderChangeStatus.REQUESTED],
},
}).config({ name: "order-change-query" })
const orderChange: OrderChangeDTO = transform(
orderChangeQuery,
({ data }) => data[0]
)
const shippingToRefresh = transform(
{ input, orderChange },
({ input, orderChange }) => {
const shippingToRefresh = {} as Record<"inbound" | "outbound", any>
const inboundShippingAction = orderChange.actions.find(
(action) =>
action.action === ChangeActionType.SHIPPING_ADD &&
!!action.return_id
)
const outboundShippingAction = orderChange.actions.find(
(action) =>
action.action === ChangeActionType.SHIPPING_ADD && !action.return_id
)
if (inboundShippingAction) {
const items = orderChange.actions
.filter((action) => action.action === ChangeActionType.RETURN_ITEM)
.map((a) => ({
id: a.details?.reference_id as string,
quantity: a.details?.quantity as number,
}))
shippingToRefresh.inbound = {
shipping_method_id: inboundShippingAction.reference_id,
order_id: orderChange.order_id,
action_id: inboundShippingAction.id,
context: {
return_id: inboundShippingAction.return_id,
return_items: items,
},
}
}
if (outboundShippingAction) {
const items = orderChange.actions
.filter((action) => action.action === ChangeActionType.ITEM_ADD)
.map((a) => ({
id: a.details?.reference_id as string,
quantity: a.details?.quantity as number,
}))
shippingToRefresh.outbound = {
shipping_method_id: outboundShippingAction.reference_id,
order_id: orderChange.order_id,
action_id: outboundShippingAction.id,
context: {
exchange_id: outboundShippingAction.exchange_id,
exchange_items: items,
},
}
}
return shippingToRefresh
}
)
when(
{ shippingToRefresh },
({ shippingToRefresh }) => !!shippingToRefresh.inbound
).then(() =>
maybeRefreshShippingMethodsWorkflow
.runAsStep({
input: shippingToRefresh.inbound,
})
.config({ name: "refresh-inbound-shipping-method" })
)
when(
{ shippingToRefresh },
({ shippingToRefresh }) => !!shippingToRefresh.outbound
).then(() =>
maybeRefreshShippingMethodsWorkflow
.runAsStep({
input: shippingToRefresh.outbound,
})
.config({ name: "refresh-outbound-shipping-method" })
)
return new WorkflowResponse(void 0)
}
)

View File

@@ -25,6 +25,7 @@ import {
throwIfOrderChangeIsNotActive,
} from "../../utils/order-validation"
import { removeExchangeShippingMethodWorkflow } from "./remove-exchange-shipping-method"
import { refreshExchangeShippingWorkflow } from "./refresh-shipping"
/**
* The data to validate that an outbound item can be removed from an exchange.
@@ -52,14 +53,14 @@ export type RemoveExchangeItemActionValidationStepInput = {
* This step validates that an outbound item can be removed from an exchange.
* If the order or exchange is canceled, the item is not found,
* or the order change is not active, the step will throw an error.
*
*
* :::note
*
*
* You can retrieve an order, order exchange, and order change details using [Query](https://docs.medusajs.com/learn/fundamentals/module-links/query),
* or [useQueryGraphStep](https://docs.medusajs.com/resources/references/medusa-workflows/steps/useQueryGraphStep).
*
*
* :::
*
*
* @example
* const data = removeExchangeItemActionValidationStep({
* order: {
@@ -79,7 +80,7 @@ export type RemoveExchangeItemActionValidationStepInput = {
* action_id: "orchact_123",
* }
* })
*
*
*/
export const removeExchangeItemActionValidationStep = createStep(
"remove-item-exchange-action-validation",
@@ -111,10 +112,10 @@ export const removeItemExchangeActionWorkflowId = "remove-item-exchange-action"
/**
* This workflow removes an outbound or new item from an exchange. It's used by
* the [Remove Outbound Item API Route](https://docs.medusajs.com/api/admin#exchanges_deleteexchangesidoutbounditemsaction_id).
*
*
* You can use this workflow within your customizations or your own custom workflows, allowing you to remove an outbound or new item
* from an exchange in your custom flow.
*
*
* @example
* const { result } = await removeItemExchangeActionWorkflow(container)
* .run({
@@ -123,9 +124,9 @@ export const removeItemExchangeActionWorkflowId = "remove-item-exchange-action"
* action_id: "orchact_123",
* }
* })
*
*
* @summary
*
*
* Remove an outbound or new item from an exchange.
*/
export const removeItemExchangeActionWorkflow = createWorkflow(
@@ -227,6 +228,28 @@ export const removeItemExchangeActionWorkflow = createWorkflow(
})
})
when(
{ actionIdToDelete, orderExchange, orderChange },
({ actionIdToDelete }) => {
return !actionIdToDelete
}
).then(() => {
const refreshArgs = transform(
{ orderChange, orderExchange },
({ orderChange, orderExchange }) => {
return {
order_change_id: orderChange.id,
exchange_id: orderExchange.id,
order_id: orderExchange.order_id,
}
}
)
refreshExchangeShippingWorkflow.runAsStep({
input: refreshArgs,
})
})
return new WorkflowResponse(previewOrderChangeStep(order.id))
}
)

View File

@@ -23,6 +23,7 @@ import {
throwIfIsCancelled,
throwIfOrderChangeIsNotActive,
} from "../../utils/order-validation"
import { refreshExchangeShippingWorkflow } from "./refresh-shipping"
/**
* The data to validate that an outbound or new item in an exchange can be updated.
@@ -50,14 +51,14 @@ export type UpdateExchangeAddItemValidationStepInput = {
* This step validates that an outbound or new item can be removed from an exchange.
* If the order or exchange is canceled, the item is not found in the exchange,
* or the order change is not active, the step will throw an error.
*
*
* :::note
*
*
* You can retrieve an order, order exchange, and order change details using [Query](https://docs.medusajs.com/learn/fundamentals/module-links/query),
* or [useQueryGraphStep](https://docs.medusajs.com/resources/references/medusa-workflows/steps/useQueryGraphStep).
*
*
* :::
*
*
* @example
* const data = updateExchangeAddItemValidationStep({
* order: {
@@ -114,10 +115,10 @@ export const updateExchangeAddItemWorkflowId = "update-exchange-add-item"
/**
* This workflow updates an outbound or new item in the exchange. It's used by the
* [Update Outbound Item Admin API Route](https://docs.medusajs.com/api/admin#exchanges_postexchangesidoutbounditemsaction_id).
*
*
* You can use this workflow within your customizations or your own custom workflows, allowing you to update an outbound or new item
* in an exchange in your custom flow.
*
*
* @example
* const { result } = await updateExchangeAddItemWorkflow(container)
* .run({
@@ -129,9 +130,9 @@ export const updateExchangeAddItemWorkflowId = "update-exchange-add-item"
* }
* }
* })
*
*
* @summary
*
*
* Update an outbound or new item in an exchange.
*/
export const updateExchangeAddItemWorkflow = createWorkflow(
@@ -195,6 +196,21 @@ export const updateExchangeAddItemWorkflow = createWorkflow(
updateOrderChangeActionsStep([updateData])
const refreshArgs = transform(
{ orderChange, orderExchange },
({ orderChange, orderExchange }) => {
return {
order_change_id: orderChange.id,
exchange_id: orderExchange.id,
order_id: orderExchange.order_id,
}
}
)
refreshExchangeShippingWorkflow.runAsStep({
input: refreshArgs,
})
return new WorkflowResponse(previewOrderChangeStep(order.id))
}
)

View File

@@ -185,7 +185,6 @@ export const createOrderEditShippingMethodWorkflow = createWorkflow(
createdMethods,
customPrice: input.custom_amount,
orderChange,
input,
},
({
shippingOptions,
@@ -193,7 +192,6 @@ export const createOrderEditShippingMethodWorkflow = createWorkflow(
createdMethods,
customPrice,
orderChange,
input,
}) => {
const shippingOption = shippingOptions[0]
const createdMethod = createdMethods[0]

View File

@@ -22,6 +22,7 @@ import {
import { prepareShippingMethod } from "../../utils/prepare-shipping-method"
import { createOrderChangeActionsWorkflow } from "../create-order-change-actions"
import { updateOrderTaxLinesWorkflow } from "../update-tax-lines"
import { fetchShippingOptionForOrderWorkflow } from "../../utils/fetch-shipping-option"
/**
* The data to validate that a shipping method can be created for a return.
@@ -44,14 +45,14 @@ export type CreateReturnShippingMethodValidationStepInput = {
/**
* This step validates that a shipping method can be created for a return.
* If the order or return is canceled, or the order change is not active, the step will throw an error.
*
*
* :::note
*
*
* You can retrieve an order, return, and order change details using [Query](https://docs.medusajs.com/learn/fundamentals/module-links/query),
* or [useQueryGraphStep](https://docs.medusajs.com/resources/references/medusa-workflows/steps/useQueryGraphStep).
*
*
* :::
*
*
* @example
* const data = createReturnShippingMethodValidationStep({
* order: {
@@ -113,10 +114,10 @@ export const createReturnShippingMethodWorkflowId =
/**
* This workflow creates a shipping method for a return. It's used by the
* [Add Shipping Method Store API Route](https://docs.medusajs.com/api/admin#returns_postreturnsidshippingmethod).
*
*
* You can use this workflow within your customizations or your own custom workflows, allowing you
* to create a shipping method for a return in your custom flows.
*
*
* @example
* const { result } = await createReturnShippingMethodWorkflow(container)
* .run({
@@ -125,14 +126,16 @@ export const createReturnShippingMethodWorkflowId =
* shipping_option_id: "so_123",
* }
* })
*
*
* @summary
*
*
* Create a shipping method for a return.
*/
export const createReturnShippingMethodWorkflow = createWorkflow(
createReturnShippingMethodWorkflowId,
function (input: CreateReturnShippingMethodWorkflowInput): WorkflowResponse<OrderPreviewDTO> {
function (
input: CreateReturnShippingMethodWorkflowInput
): WorkflowResponse<OrderPreviewDTO> {
const orderReturn: ReturnDTO = useRemoteQueryStep({
entry_point: "return",
fields: [
@@ -156,25 +159,9 @@ export const createReturnShippingMethodWorkflow = createWorkflow(
throw_if_key_not_found: true,
}).config({ name: "order-query" })
const shippingOptions = useRemoteQueryStep({
entry_point: "shipping_option",
fields: [
"id",
"name",
"calculated_price.calculated_amount",
"calculated_price.is_calculated_price_tax_inclusive",
],
variables: {
id: input.shipping_option_id,
calculated_price: {
context: { currency_code: order.currency_code },
},
},
}).config({ name: "fetch-shipping-option" })
const orderChange: OrderChangeDTO = useRemoteQueryStep({
entry_point: "order_change",
fields: ["id", "status", "version"],
fields: ["id", "status", "version", "actions.*"],
variables: {
filters: {
order_id: orderReturn.order_id,
@@ -185,6 +172,36 @@ export const createReturnShippingMethodWorkflow = createWorkflow(
list: false,
}).config({ name: "order-change-query" })
const shippingOptionFetchInput = transform(
{ orderChange, input, order, orderReturn },
({ orderChange, input, order, orderReturn }) => {
return {
order_id: order.id,
shipping_option_id: input.shipping_option_id,
currency_code: order.currency_code,
context: {
return_id: orderReturn.id,
return_items: orderChange.actions
.filter(
(action) => action.action === ChangeActionType.RETURN_ITEM
)
.map((a) => ({
id: a.details?.reference_id as string,
quantity: a.details?.quantity as number,
})),
},
}
}
)
const shippingOption = fetchShippingOptionForOrderWorkflow.runAsStep({
input: shippingOptionFetchInput,
})
const shippingOptions = transform(shippingOption, (shippingOption) => {
return [shippingOption]
})
createReturnShippingMethodValidationStep({
order,
orderReturn,

View File

@@ -0,0 +1,98 @@
import { OrderChangeDTO } from "@medusajs/framework/types"
import { ChangeActionType, OrderChangeStatus } from "@medusajs/framework/utils"
import {
WorkflowData,
WorkflowResponse,
createWorkflow,
transform,
when,
} from "@medusajs/framework/workflows-sdk"
import { maybeRefreshShippingMethodsWorkflow } from "../../utils/maybe-refresh-shipping-methods"
import { useQueryGraphStep } from "../../../common"
/**
* The data to validate that items can be added to a return.
*/
export type RequestItemReturnValidationStepInput = {
/**
* The order change's ID.
*/
order_change_id: string
/**
* The return's details.
*/
return_id: string
/**
* The order's ID.
*/
order_id: string
}
export const refreshReturnShippingWorkflowId = "refresh-return-shipping"
/**
* This workflow refreshes the shipping method for a return in case the shipping option is calculated.
*
* @summary
*
* Refresh return shipping.
*/
export const refreshReturnShippingWorkflow = createWorkflow(
refreshReturnShippingWorkflowId,
function (
input: WorkflowData<RequestItemReturnValidationStepInput>
): WorkflowResponse<void> {
const orderChangeQuery = useQueryGraphStep({
entity: "order_change",
fields: ["id", "status", "order_id", "return_id", "actions.*"],
filters: {
order_id: input.order_id,
return_id: input.return_id,
status: [OrderChangeStatus.PENDING, OrderChangeStatus.REQUESTED],
},
}).config({ name: "order-change-query" })
const orderChange: OrderChangeDTO = transform(
orderChangeQuery,
({ data }) => data[0]
)
const refreshArgs = transform(
{ input, orderChange },
({ input, orderChange }) => {
const shippingAction = orderChange.actions.find(
(action) => action.action === ChangeActionType.SHIPPING_ADD
)
const items = orderChange.actions
.filter((action) => action.action === ChangeActionType.RETURN_ITEM)
.map((a) => ({
id: a.details?.reference_id as string,
quantity: a.details?.quantity as number,
}))
if (shippingAction) {
return {
shipping_method_id: shippingAction.reference_id,
order_id: orderChange.order_id,
action_id: shippingAction.id,
context: {
return_id: input.return_id,
return_items: items,
},
}
}
return null
}
)
when({ refreshArgs }, ({ refreshArgs }) => refreshArgs !== null).then(() =>
maybeRefreshShippingMethodsWorkflow.runAsStep({
input: refreshArgs,
})
)
return new WorkflowResponse(void 0)
}
)

View File

@@ -26,6 +26,7 @@ import {
} from "../../utils/order-validation"
import { removeReturnShippingMethodWorkflow } from "./remove-return-shipping-method"
import { updateReturnWorkflow } from "./update-return"
import { refreshReturnShippingWorkflow } from "./refresh-shipping"
/**
* The data to validate that a return item can be removed.
@@ -54,14 +55,14 @@ export type RemoveItemReturnActionValidationStepInput = {
* If the order or return is canceled, the order change is not active,
* the return request is not found,
* or the action is not a request return action, the step will throw an error.
*
*
* :::note
*
*
* You can retrieve an order, return, and order change details using [Query](https://docs.medusajs.com/learn/fundamentals/module-links/query),
* or [useQueryGraphStep](https://docs.medusajs.com/resources/references/medusa-workflows/steps/useQueryGraphStep).
*
*
* :::
*
*
* @example
* const data = removeReturnItemActionValidationStep({
* order: {
@@ -114,10 +115,10 @@ export const removeItemReturnActionWorkflowId = "remove-item-return-action"
/**
* This workflow removes a return item. It's used by the
* [Remove Item from Return Admin API Route](https://docs.medusajs.com/api/admin#returns_deletereturnsidrequestitemsaction_id).
*
*
* You can use this workflow within your customizations or your own custom workflows, allowing you
* to remove an item from a return request in your custom flows.
*
*
* @example
* const { result } = await removeItemReturnActionWorkflow(container)
* .run({
@@ -126,9 +127,9 @@ export const removeItemReturnActionWorkflowId = "remove-item-return-action"
* action_id: "orchac_123",
* }
* })
*
*
* @summary
*
*
* Remove an item from a return.
*/
export const removeItemReturnActionWorkflow = createWorkflow(
@@ -249,6 +250,25 @@ export const removeItemReturnActionWorkflow = createWorkflow(
})
})
when({ actionIdToDelete }, ({ actionIdToDelete }) => {
return !actionIdToDelete
}).then(() => {
const refreshArgs = transform(
{ orderChange, orderReturn },
({ orderChange, orderReturn }) => {
return {
order_change_id: orderChange.id,
return_id: orderReturn.id,
order_id: orderReturn.order_id,
}
}
)
refreshReturnShippingWorkflow.runAsStep({
input: refreshArgs,
})
})
return new WorkflowResponse(previewOrderChangeStep(order.id))
}
)

View File

@@ -22,7 +22,7 @@ import {
} from "../../utils/order-validation"
import { validateReturnReasons } from "../../utils/validate-return-reason"
import { createOrderChangeActionsWorkflow } from "../create-order-change-actions"
import { refreshReturnShippingWorkflow } from "./refresh-shipping"
/**
* The data to validate that items can be added to a return.
*/
@@ -50,14 +50,14 @@ export type RequestItemReturnValidationStepInput = {
* If the order or return is canceled, the order change is not active,
* the items do not exist in the order, or the return reasons are invalid,
* the step will throw an error.
*
*
* :::note
*
*
* You can retrieve an order, return, and order change details using [Query](https://docs.medusajs.com/learn/fundamentals/module-links/query),
* or [useQueryGraphStep](https://docs.medusajs.com/resources/references/medusa-workflows/steps/useQueryGraphStep).
*
*
* :::
*
*
* @example
* const data = requestItemReturnValidationStep({
* order: {
@@ -110,12 +110,12 @@ export const requestItemReturnValidationStep = createStep(
export const requestItemReturnWorkflowId = "request-item-return"
/**
* This workflow adds items to a return. It's used by the
* This workflow adds items to a return. It's used by the
* [Add Requested Items to Return Admin API Route](https://docs.medusajs.com/api/admin#returns_postreturnsidrequestitems).
*
*
* You can use this workflow within your customizations or your own custom workflows, allowing you to add items to a return
* in your custom flows.
*
*
* @example
* const { result } = await requestItemReturnWorkflow(container)
* .run({
@@ -129,9 +129,9 @@ export const requestItemReturnWorkflowId = "request-item-return"
* ]
* }
* })
*
*
* @summary
*
*
* Add items to a return.
*/
export const requestItemReturnWorkflow = createWorkflow(
@@ -201,6 +201,21 @@ export const requestItemReturnWorkflow = createWorkflow(
input: orderChangeActionInput,
})
const refreshArgs = transform(
{ orderChange, orderReturn },
({ orderChange, orderReturn }) => {
return {
order_change_id: orderChange.id,
return_id: orderReturn.id,
order_id: orderReturn.order_id,
}
}
)
refreshReturnShippingWorkflow.runAsStep({
input: refreshArgs,
})
return new WorkflowResponse(previewOrderChangeStep(order.id))
}
)

View File

@@ -28,6 +28,7 @@ import {
throwIfOrderChangeIsNotActive,
} from "../../utils/order-validation"
import { validateReturnReasons } from "../../utils/validate-return-reason"
import { refreshReturnShippingWorkflow } from "./refresh-shipping"
/**
* The data to validate that an item in a return can be updated.
@@ -56,14 +57,14 @@ export type UpdateRequestItemReturnValidationStepInput = {
* If the order or return is canceled, the order change is not active,
* the return request is not found, or the action is not requesting an item return,
* the step will throw an error.
*
*
* :::note
*
*
* You can retrieve an order, return, and order change details using [Query](https://docs.medusajs.com/learn/fundamentals/module-links/query),
* or [useQueryGraphStep](https://docs.medusajs.com/resources/references/medusa-workflows/steps/useQueryGraphStep).
*
*
* :::
*
*
* @example
* const data = updateRequestItemReturnValidationStep({
* order: {
@@ -130,12 +131,12 @@ export const updateRequestItemReturnValidationStep = createStep(
export const updateRequestItemReturnWorkflowId = "update-request-item-return"
/**
* This workflow updates a requested item in a return. It's used by the
* This workflow updates a requested item in a return. It's used by the
* [Update Requested Item in Return Admin API Route](https://docs.medusajs.com/api/admin#returns_postreturnsidrequestitemsaction_id).
*
* You can use this workflow within your customizations or your own custom workflows, allowing you to update an
*
* You can use this workflow within your customizations or your own custom workflows, allowing you to update an
* item in a return in your custom flows.
*
*
* @example
* const { result } = await updateRequestItemReturnWorkflow(container)
* .run({
@@ -147,9 +148,9 @@ export const updateRequestItemReturnWorkflowId = "update-request-item-return"
* }
* }
* })
*
*
* @summary
*
*
* Update a requested item in a return.
*/
export const updateRequestItemReturnWorkflow = createWorkflow(
@@ -216,6 +217,21 @@ export const updateRequestItemReturnWorkflow = createWorkflow(
updateOrderChangeActionsStep([updateData])
const refreshArgs = transform(
{ orderChange, orderReturn },
({ orderChange, orderReturn }) => {
return {
order_change_id: orderChange.id,
return_id: orderReturn.id,
order_id: orderReturn.order_id,
}
}
)
refreshReturnShippingWorkflow.runAsStep({
input: refreshArgs,
})
return new WorkflowResponse(previewOrderChangeStep(order.id))
}
)

View File

@@ -21,7 +21,7 @@ export type RefundPaymentsStepInput = {
*/
payment_id: string
/**
* The amount to refund.
* The amount to refund.
*/
amount: BigNumberInput
/**
@@ -36,10 +36,7 @@ export const refundPaymentsStepId = "refund-payments-step"
*/
export const refundPaymentsStep = createStep(
refundPaymentsStepId,
async (
input: RefundPaymentsStepInput,
{ container }
) => {
async (input: RefundPaymentsStepInput, { container }) => {
const logger = container.resolve<Logger>(ContainerRegistrationKeys.LOGGER)
const paymentModule = container.resolve<IPaymentModuleService>(
Modules.PAYMENT

View File

@@ -120,6 +120,22 @@ export interface UpdateShippingOptionDTO {
*/
export interface UpsertShippingOptionDTO extends UpdateShippingOptionDTO {}
type CalculateShippingItems = {
/**
* The ID of the order item. Lookup in context.items for the details about variant / product.
*/
id: string
/**
* The quantity to ship.
*/
quantity: number
}
export type CalculatedRMAShippingContext =
| { return_id: string; return_items: CalculateShippingItems[] }
| { exchange_id: string; exchange_items: CalculateShippingItems[] }
| { claim_id: string; claim_items: CalculateShippingItems[] }
/**
* The data needed for the associated fulfillment provider to calculate the price of a shipping option.
*/
@@ -149,11 +165,12 @@ export interface CalculateShippingOptionPriceDTO {
*/
context: CartPropsForFulfillment & {
/**
* The location that the items will be shipped from.
* The location that the items will be shipped from (or to if it is a return).
*/
from_location?: StockLocationDTO
[k: string]: unknown
}
} & CalculatedRMAShippingContext
}
/**