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:
@@ -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,
|
||||
},
|
||||
])
|
||||
)
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
})
|
||||
},
|
||||
})
|
||||
@@ -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,
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
})
|
||||
},
|
||||
})
|
||||
@@ -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,
|
||||
}),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
@@ -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]: {
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
@@ -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: [] }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user